diff --git a/Gopkg.lock b/Gopkg.lock index 476511f319..8ef54696b8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -182,7 +182,7 @@ [[projects]] branch = "master" - digest = "1:bada18f8cc9cf347266c81ee2ab8956b7c20ff6bf798b3d848e92f63a5c57330" + digest = "1:97f41aee849f8b913f2da5983e44be080c904161a3331701b6e1e112424c9db0" name = "github.com/openshift/api" packages = [ "config/v1", @@ -192,7 +192,7 @@ "security/v1", ] pruneopts = "NUT" - revision = "8dce450af78098c96d68dc2a0c2fb8e917993fb4" + revision = "aab033bae2a129607f4fb277c3777b2eabb08601" [[projects]] branch = "master" @@ -213,7 +213,7 @@ "security/clientset/versioned/typed/security/v1", ] pruneopts = "NUT" - revision = "960f72aa32a8e9b4dd769b90ff1cb5bd4c898eec" + revision = "2f38d042c79762d5bf2ba3c795d4ad72101289e9" [[projects]] branch = "master" @@ -754,6 +754,7 @@ "k8s.io/apimachinery/pkg/util/runtime", "k8s.io/apimachinery/pkg/util/sets", "k8s.io/apimachinery/pkg/util/strategicpatch", + "k8s.io/apimachinery/pkg/util/validation/field", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/util/yaml", "k8s.io/apimachinery/pkg/watch", diff --git a/install/0000_00_cluster-version-operator_01_clusterversion.crd.yaml b/install/0000_00_cluster-version-operator_01_clusterversion.crd.yaml index 79f19cf800..6dbd7da830 100644 --- a/install/0000_00_cluster-version-operator_01_clusterversion.crd.yaml +++ b/install/0000_00_cluster-version-operator_01_clusterversion.crd.yaml @@ -28,7 +28,7 @@ spec: additionalPrinterColumns: - name: Version type: string - JSONPath: .status.current.version + JSONPath: .status.desired.version - name: Available type: string JSONPath: .status.conditions[?(@.type=="Available")].status diff --git a/lib/validation/validation.go b/lib/validation/validation.go index 0ed9b64d5e..a074b21030 100644 --- a/lib/validation/validation.go +++ b/lib/validation/validation.go @@ -36,11 +36,53 @@ func ValidateClusterVersion(config *configv1.ClusterVersion) field.ErrorList { errs = append(errs, field.Required(field.NewPath("spec", "desiredUpdate", "version"), "must specify version or payload")) case len(u.Version) > 0 && !validSemVer(u.Version): errs = append(errs, field.Invalid(field.NewPath("spec", "desiredUpdate", "version"), u.Version, "must be a semantic version (1.2.3[-...])")) + case len(u.Version) > 0 && len(u.Payload) == 0: + switch countPayloadsForVersion(config, u.Version) { + case 0: + errs = append(errs, field.Invalid(field.NewPath("spec", "desiredUpdate", "version"), u.Version, "when payload is empty the update must be a previous version or an available update")) + case 1: + default: + errs = append(errs, field.Invalid(field.NewPath("spec", "desiredUpdate", "version"), u.Version, "there are multiple possible payloads for this version, specify the exact payload")) + } } } return errs } +func countPayloadsForVersion(config *configv1.ClusterVersion, version string) int { + count := 0 + for _, update := range config.Status.AvailableUpdates { + if update.Version == version && len(update.Payload) > 0 { + count++ + } + } + if count > 0 { + return count + } + for _, history := range config.Status.History { + if history.Version == version { + if len(history.Payload) > 0 { + return 1 + } + } + } + return 0 +} + +func hasAmbiguousPayloadForVersion(config *configv1.ClusterVersion, version string) bool { + for _, update := range config.Status.AvailableUpdates { + if update.Version == version { + return len(update.Payload) > 0 + } + } + for _, history := range config.Status.History { + if history.Version == version { + return len(history.Payload) > 0 + } + } + return false +} + func ClearInvalidFields(config *configv1.ClusterVersion, errs field.ErrorList) *configv1.ClusterVersion { if len(errs) == 0 { return config diff --git a/pkg/cvo/cvo.go b/pkg/cvo/cvo.go index ce3261bd52..8330c8a6d5 100644 --- a/pkg/cvo/cvo.go +++ b/pkg/cvo/cvo.go @@ -324,7 +324,7 @@ func (optr *Operator) sync(key string) error { glog.V(2).Infof("Reconciling cluster to version %s and image %s (hash=%s)", update.Version, update.Payload, payload.ManifestHash) } else { glog.V(2).Infof("Updating the cluster to version %s and image %s (hash=%s)", update.Version, update.Payload, payload.ManifestHash) - if err := optr.syncProgressingStatus(original); err != nil { + if err := optr.syncProgressingStatus(original, update); err != nil { return err } } @@ -429,7 +429,6 @@ func (optr *Operator) getOrCreateClusterVersion() (*configv1.ClusterVersion, boo // for fields that have meaning that are incomplete, clear them // prevents us from loading clearly malformed payloads obj = validation.ClearInvalidFields(obj, errs) - return obj, changed, nil } @@ -465,11 +464,14 @@ func (optr *Operator) getOrCreateClusterVersion() (*configv1.ClusterVersion, boo // versionString returns a string describing the current version. func (optr *Operator) currentVersionString(config *configv1.ClusterVersion) string { - if s := config.Status.Current.Version; len(s) > 0 { - return s - } - if s := config.Status.Current.Payload; len(s) > 0 { - return s + if len(config.Status.History) > 0 { + last := config.Status.History[0] + if s := last.Version; len(s) > 0 { + return s + } + if s := last.Payload; len(s) > 0 { + return s + } } if s := optr.releaseVersion; len(s) > 0 { return s @@ -480,21 +482,23 @@ func (optr *Operator) currentVersionString(config *configv1.ClusterVersion) stri return "" } +// desiredVersion returns the update that is currently targeted by the config. +func (optr *Operator) desiredVersion(config *configv1.ClusterVersion) configv1.Update { + if update := config.Spec.DesiredUpdate; update != nil { + return *update + } + return optr.currentVersion() +} + // versionString returns a string describing the desired version. -func (optr *Operator) desiredVersionString(config *configv1.ClusterVersion) string { - var s string - if v := config.Spec.DesiredUpdate; v != nil { - if len(v.Payload) > 0 { - s = v.Payload - } - if len(v.Version) > 0 { - s = v.Version - } +func versionString(update configv1.Update) string { + if len(update.Version) > 0 { + return update.Version } - if len(s) == 0 { - s = optr.currentVersionString(config) + if len(update.Payload) > 0 { + return update.Payload } - return s + return "" } // currentVersion returns an update object describing the current known cluster version. diff --git a/pkg/cvo/cvo_test.go b/pkg/cvo/cvo_test.go index b999f94d35..b6a57e5bbc 100644 --- a/pkg/cvo/cvo_test.go +++ b/pkg/cvo/cvo_test.go @@ -203,6 +203,20 @@ func TestOperator_sync(t *testing.T) { `, }, } + content_4_0_1 := map[string]interface{}{ + "manifests": map[string]interface{}{}, + "release-manifests": map[string]interface{}{ + "image-references": ` + { + "kind": "ImageStream", + "apiVersion": "image.openshift.io/v1", + "metadata": { + "name": "4.0.1" + } + } + `, + }, + } tests := []struct { name string @@ -259,10 +273,10 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, }, + Desired: configv1.Update{Version: "4.0.1", Payload: "payload/image:v4.0.1"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -289,15 +303,16 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Version: "0.0.1-abc", Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + {Version: "4.0.1", Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}, CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}}, }, + Desired: configv1.Update{Version: "0.0.1-abc", Payload: "payload/image:v4.0.1"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, {Type: configv1.OperatorFailing, Status: configv1.ConditionTrue, Reason: "UpdatePayloadIntegrity", Message: "unable to apply object"}, - {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: "UpdatePayloadIntegrity", Message: "Unable to apply 4.0.1: the contents of the update are invalid"}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: "UpdatePayloadIntegrity", Message: "Unable to apply 0.0.1-abc: the contents of the update are invalid"}, {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, }, }, @@ -311,7 +326,22 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + Version: "4.0.1", + Payload: "payload/image:v4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", Payload: "payload/image:v4.0.1", }, @@ -346,9 +376,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1"}, }, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -381,9 +410,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1"}, }, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -402,9 +430,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "0.0.1-abc", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "0.0.1-abc", Payload: "payload/image:v4.0.1"}, }, VersionHash: "y_Kc5IQiIyU=", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -456,9 +483,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1"}, }, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -477,9 +503,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "0.0.1-abc", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "0.0.1-abc", Payload: "payload/image:v4.0.1"}, }, VersionHash: "y_Kc5IQiIyU=", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -508,9 +533,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1"}, }, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -541,9 +565,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "4.0.1", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "4.0.1", Payload: "payload/image:v4.0.1"}, }, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -562,9 +585,8 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Version: "0.0.1-abc", - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Version: "0.0.1-abc", Payload: "payload/image:v4.0.1"}, }, VersionHash: "y_Kc5IQiIyU=", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -612,9 +634,15 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "", // we don't know our payload yet and releaseVersion is unset + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -627,7 +655,340 @@ func TestOperator_sync(t *testing.T) { }, }, { - name: "after initial status is set, set reconciling and hash", + name: "record a new version entry if the controller is restarted with a new image", + content: content1, + optr: Operator{ + releaseImage: "payload/image:v4.0.2", + releaseVersion: "4.0.2", + namespace: "test", + name: "default", + client: fakeClientsetWithUpdates(&configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "", // we didn't know our payload before + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Initializing, will work towards payload/image:v4.0.1"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }), + }, + wantActions: func(t *testing.T, optr *Operator) { + f := optr.client.(*fake.Clientset) + act := f.Actions() + if len(act) != 2 { + t.Fatalf("unknown actions: %d %#v", len(act), act) + } + expectGet(t, act[0], "clusterversions", "", "default") + expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.2", Version: "4.0.2"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Initializing, will work towards payload/image:v4.0.1"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }) + }, + }, + { + name: "when user cancels desired update, clear status desired", + content: content1, + optr: Operator{ + releaseImage: "payload/image:v4.0.1", + releaseVersion: "4.0.1", + namespace: "test", + name: "default", + client: fakeClientsetWithUpdates(&configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.2"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards 4.0.2"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }), + }, + wantActions: func(t *testing.T, optr *Operator) { + f := optr.client.(*fake.Clientset) + act := f.Actions() + if len(act) != 2 { + t.Fatalf("unknown actions: %d %#v", len(act), act) + } + expectGet(t, act[0], "clusterversions", "", "default") + expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ + Version: "4.0.1", + Payload: "payload/image:v4.0.1", + }, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + // we don't reset the message here until the payload is loaded + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards 4.0.2"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }) + }, + }, + { + name: "after desired update is cancelled, go to reconciling", + content: content_4_0_1, + optr: Operator{ + releaseImage: "payload/image:v4.0.1", + releaseVersion: "4.0.1", + namespace: "test", + name: "default", + client: fakeClientsetWithUpdates(&configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1", Version: "4.0.1"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + // we don't reset the message here until the payload is loaded + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards 4.0.2"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }), + }, + wantActions: func(t *testing.T, optr *Operator) { + f := optr.client.(*fake.Clientset) + act := f.Actions() + if len(act) != 3 { + t.Fatalf("unknown actions: %d %#v", len(act), act) + } + expectGet(t, act[0], "clusterversions", "", "default") + expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "1", + }, + Spec: configv1.ClusterVersionSpec{ + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1", Version: "4.0.1"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + // we correct the message that was incorrect from the previous state + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards 4.0.1"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }) + expectUpdateStatus(t, act[2], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "2", + }, + Spec: configv1.ClusterVersionSpec{ + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.2", + Version: "4.0.2", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ + Version: "4.0.1", + Payload: "payload/image:v4.0.1", + }, + VersionHash: "y_Kc5IQiIyU=", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionTrue, Message: "Done applying 4.0.1"}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Message: "Cluster version is 4.0.1"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }) + }, + }, + { + name: "after initial status is set, set reconciling and hash and correct version number", content: content1, optr: Operator{ releaseImage: "payload/image:v4.0.1", @@ -644,14 +1005,19 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + { + State: configv1.PartialUpdate, + Payload: "payload/image:v4.0.1", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + }, }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, - {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards payload/image:v4.0.1"}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Initializing, will work towards payload/image:v4.0.1"}, {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, }, }, @@ -660,10 +1026,11 @@ func TestOperator_sync(t *testing.T) { wantActions: func(t *testing.T, optr *Operator) { f := optr.client.(*fake.Clientset) act := f.Actions() - if len(act) != 2 { + if len(act) != 3 { t.Fatalf("unknown actions: %d %#v", len(act), act) } expectGet(t, act[0], "clusterversions", "", "default") + // will use the version from content1 (the payload) when we set the progressing condition expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "default", @@ -674,10 +1041,35 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", - // loads the version from the payload on disk + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", Version: "0.0.1-abc", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1", Version: "0.0.1-abc"}, + VersionHash: "", + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Message: "Working towards 0.0.1-abc"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + }, + }, + }) + expectUpdateStatus(t, act[2], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + ResourceVersion: "2", + }, + Spec: configv1.ClusterVersionSpec{ + Upstream: configv1.URL("http://localhost:8080/graph"), + Channel: "fast", + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + {State: configv1.CompletedUpdate, Payload: "payload/image:v4.0.1", Version: "0.0.1-abc", StartedTime: metav1.Time{Time: time.Unix(1, 0)}, CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}}, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, VersionHash: "y_Kc5IQiIyU=", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -712,9 +1104,17 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + CompletionTime: &metav1.Time{Time: time.Unix(1, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, Generation: 2, Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -800,7 +1200,12 @@ func TestOperator_sync(t *testing.T) { {Version: "4.0.2", Payload: "test/image:1"}, {Version: "4.0.3", Payload: "test/image:2"}, }, - Current: configv1.Update{Payload: "payload/image:v4.0.1"}, + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + }, + Desired: configv1.Update{ + Payload: "payload/image:v4.0.1", + }, Generation: 2, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -877,7 +1282,12 @@ func TestOperator_sync(t *testing.T) { {Version: "4.0.2", Payload: "test/image:1"}, {Version: "4.0.3", Payload: "test/image:2"}, }, - Current: configv1.Update{Payload: "payload/image:v4.0.1"}, + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + }, + Desired: configv1.Update{ + Payload: "payload/image:v4.0.1", + }, Generation: 2, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -949,7 +1359,12 @@ func TestOperator_sync(t *testing.T) { Channel: "", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{Payload: "payload/image:v4.0.1"}, + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + }, + Desired: configv1.Update{ + Payload: "payload/image:v4.0.1", + }, Generation: 2, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -961,6 +1376,146 @@ func TestOperator_sync(t *testing.T) { }) }, }, + { + name: "user requested a version that isn't in the updates or history", + content: content1, + optr: Operator{ + releaseImage: "payload/image:v4.0.1", + namespace: "test", + name: "default", + client: fakeClientsetWithUpdates(&configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Generation: 2, + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + DesiredUpdate: &configv1.Update{ + Version: "4.0.4", + }, + }, + Status: configv1.ClusterVersionStatus{ + AvailableUpdates: []configv1.Update{ + {Version: "4.0.2", Payload: "test/image:1"}, + {Version: "4.0.3", Payload: "test/image:2"}, + }, + }, + }), + }, + wantActions: func(t *testing.T, optr *Operator) { + f := optr.client.(*fake.Clientset) + act := f.Actions() + if len(act) != 2 { + t.Fatalf("unknown actions: %d %#v", len(act), act) + } + expectGet(t, act[0], "clusterversions", "", "default") + expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Generation: 2, + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + DesiredUpdate: &configv1.Update{ + Version: "4.0.4", + }, + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Version: "4.0.4", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}, CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}}, + }, + Desired: configv1.Update{ + Version: "4.0.4", + }, + AvailableUpdates: []configv1.Update{ + {Version: "4.0.2", Payload: "test/image:1"}, + {Version: "4.0.3", Payload: "test/image:2"}, + }, + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: "InvalidClusterVersion", Message: "Stopped at payload/image:v4.0.1: the cluster version is invalid"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + {Type: ClusterVersionInvalid, Status: configv1.ConditionTrue, Reason: "InvalidClusterVersion", Message: "The cluster version is invalid: spec.desiredUpdate.version: Invalid value: \"4.0.4\": when payload is empty the update must be a previous version or an available update"}, + }, + }, + }) + }, + }, + { + name: "user requested a version has duplicates", + content: content1, + optr: Operator{ + releaseImage: "payload/image:v4.0.1", + namespace: "test", + name: "default", + client: fakeClientsetWithUpdates(&configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Generation: 2, + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + DesiredUpdate: &configv1.Update{ + Version: "4.0.3", + }, + }, + Status: configv1.ClusterVersionStatus{ + AvailableUpdates: []configv1.Update{ + {Version: "4.0.2", Payload: "test/image:1"}, + {Version: "4.0.3", Payload: "test/image:2"}, + {Version: "4.0.3", Payload: "test/image:3"}, + }, + }, + }), + }, + wantActions: func(t *testing.T, optr *Operator) { + f := optr.client.(*fake.Clientset) + act := f.Actions() + if len(act) != 2 { + t.Fatalf("unknown actions: %d %#v", len(act), act) + } + expectGet(t, act[0], "clusterversions", "", "default") + expectUpdateStatus(t, act[1], "clusterversions", "", &configv1.ClusterVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Generation: 2, + }, + Spec: configv1.ClusterVersionSpec{ + ClusterID: configv1.ClusterID(id), + Upstream: configv1.URL("http://localhost:8080/graph"), + DesiredUpdate: &configv1.Update{ + Version: "4.0.3", + }, + }, + Status: configv1.ClusterVersionStatus{ + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Version: "4.0.3", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}, CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}}, + }, + Desired: configv1.Update{ + Version: "4.0.3", + }, + AvailableUpdates: []configv1.Update{ + {Version: "4.0.2", Payload: "test/image:1"}, + {Version: "4.0.3", Payload: "test/image:2"}, + {Version: "4.0.3", Payload: "test/image:3"}, + }, + Conditions: []configv1.ClusterOperatorStatusCondition{ + {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: "InvalidClusterVersion", Message: "Stopped at payload/image:v4.0.1: the cluster version is invalid"}, + {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, + {Type: ClusterVersionInvalid, Status: configv1.ConditionTrue, Reason: "InvalidClusterVersion", Message: "The cluster version is invalid: spec.desiredUpdate.version: Invalid value: \"4.0.3\": there are multiple possible payloads for this version, specify the exact payload"}, + }, + }, + }) + }, + }, { name: "payload hash matches content hash, act as reconcile, no need to apply", content: content1, @@ -981,10 +1536,18 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ // loads the version from the payload on disk + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, VersionHash: "y_Kc5IQiIyU=", Generation: 2, @@ -1027,10 +1590,18 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ // loads the version from the payload on disk + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, VersionHash: "unknown_hash", Generation: 2, @@ -1061,10 +1632,15 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", - Version: "0.0.1-abc", + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, }, + Desired: configv1.Update{Version: "0.0.1-abc", Payload: "payload/image:v4.0.1"}, Generation: 2, VersionHash: "unknown_hash", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -1085,10 +1661,17 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", - // loads the version from the payload on disk + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, Generation: 2, VersionHash: "y_Kc5IQiIyU=", @@ -1140,7 +1723,10 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ + History: []configv1.UpdateHistory{ + {State: configv1.PartialUpdate, Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, + }, + Desired: configv1.Update{ Payload: "payload/image:v4.0.1", }, VersionHash: "", @@ -1174,9 +1760,10 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, @@ -1208,14 +1795,15 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1", Version: "0.0.1-abc", StartedTime: metav1.Time{Time: time.Unix(1, 0)}}, }, + Desired: configv1.Update{Payload: "payload/image:v4.0.1", Version: "0.0.1-abc"}, VersionHash: "", Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, {Type: configv1.OperatorFailing, Status: configv1.ConditionFalse}, - {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: "", Message: "Reconciling payload/image:v4.0.1: the cluster version is invalid"}, + {Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: "", Message: "Reconciling 0.0.1-abc: the cluster version is invalid"}, {Type: configv1.RetrievedUpdates, Status: configv1.ConditionFalse}, {Type: ClusterVersionInvalid, Status: configv1.ConditionTrue, Reason: "InvalidClusterVersion", Message: "The cluster version is invalid:\n* spec.upstream: Invalid value: \"#%GG\": must be a valid URL or empty\n* spec.clusterID: Invalid value: \"not-valid-cluster-id\": must be an RFC4122-variant UUID\n"}, }, @@ -1234,9 +1822,18 @@ func TestOperator_sync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + { + State: configv1.CompletedUpdate, + Payload: "payload/image:v4.0.1", + Version: "0.0.1-abc", + StartedTime: metav1.Time{Time: time.Unix(1, 0)}, + CompletionTime: &metav1.Time{Time: time.Unix(2, 0)}, + }, + }, + Desired: configv1.Update{ Version: "0.0.1-abc", + Payload: "payload/image:v4.0.1", }, VersionHash: "y_Kc5IQiIyU=", Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -1331,8 +1928,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, }, }, @@ -1370,8 +1967,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, }, }, @@ -1409,8 +2006,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionTrue, Message: "Done applying payload/image:v4.0.1"}, @@ -1453,8 +2050,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionTrue, Message: "Done applying payload/image:v4.0.1"}, @@ -1509,8 +2106,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, Conditions: []configv1.ClusterOperatorStatusCondition{ {Type: configv1.OperatorAvailable, Status: configv1.ConditionTrue, Message: "Done applying payload/image:v4.0.1"}, @@ -1564,8 +2161,8 @@ func TestOperator_availableUpdatesSync(t *testing.T) { Channel: "fast", }, Status: configv1.ClusterVersionStatus{ - Current: configv1.Update{ - Payload: "payload/image:v4.0.1", + History: []configv1.UpdateHistory{ + {Payload: "payload/image:v4.0.1"}, }, Generation: 2, Conditions: []configv1.ClusterOperatorStatusCondition{ @@ -1708,6 +2305,14 @@ func expectMutation(t *testing.T, a ktesting.Action, verb string, resource, subr for i := range in.Status.Conditions { in.Status.Conditions[i].LastTransitionTime.Time = time.Time{} } + for i, item := range in.Status.History { + if !item.StartedTime.IsZero() { + in.Status.History[i].StartedTime.Time = time.Unix(1, 0) + } + if item.CompletionTime != nil { + in.Status.History[i].CompletionTime.Time = time.Unix(2, 0) + } + } } e, a := fmt.Sprintf("%s/%s", resource, namespace), fmt.Sprintf("%s/%s", at.GetResource().Resource, at.GetNamespace()) diff --git a/pkg/cvo/status.go b/pkg/cvo/status.go index 9d414de165..8085f345ee 100644 --- a/pkg/cvo/status.go +++ b/pkg/cvo/status.go @@ -15,6 +15,68 @@ import ( "github.com/openshift/cluster-version-operator/lib/resourcemerge" ) +func mergeEqualVersions(current *configv1.UpdateHistory, desired *configv1.Update) bool { + if len(desired.Payload) > 0 && desired.Payload == current.Payload { + if len(current.Version) == 0 || desired.Version == current.Version { + current.Version = desired.Version + return true + } + } + if len(desired.Version) > 0 && desired.Version == current.Version { + if len(current.Payload) == 0 || desired.Payload == current.Payload { + current.Payload = desired.Payload + return true + } + } + return false +} + +func mergeOperatorHistory(config *configv1.ClusterVersion, current configv1.Update, now metav1.Time) { + if len(config.Status.History) == 0 { + config.Status.History = append(config.Status.History, configv1.UpdateHistory{ + Version: current.Version, + Payload: current.Payload, + + State: configv1.PartialUpdate, + StartedTime: now, + }) + } + + last := &config.Status.History[0] + desired := config.Spec.DesiredUpdate + if desired == nil { + desired = ¤t + } + + if !mergeEqualVersions(last, desired) { + last.CompletionTime = &now + config.Status.History = append([]configv1.UpdateHistory{ + { + Version: desired.Version, + Payload: desired.Payload, + + State: configv1.PartialUpdate, + StartedTime: now, + }, + }, config.Status.History...) + last = &config.Status.History[0] + } + + if len(config.Status.History) > 10 { + config.Status.History = config.Status.History[:10] + } + + switch { + case resourcemerge.IsOperatorStatusConditionTrue(config.Status.Conditions, configv1.OperatorAvailable): + last.State = configv1.CompletedUpdate + if last.CompletionTime == nil { + last.CompletionTime = &now + } + } + + config.Status.Desired = *desired +} + // ClusterVersionInvalid indicates that the cluster version has an error that prevents the server from // taking action. The cluster version operator will only reconcile the current state as long as this // condition is set. @@ -25,12 +87,13 @@ const ClusterVersionInvalid configv1.ClusterStatusConditionType = "Invalid" func (optr *Operator) syncInitialObjectStatus(original *configv1.ClusterVersion, errs field.ErrorList) (bool, error) { config := original.DeepCopy() - config.Status.Current = optr.currentVersion() if updated := optr.getAvailableUpdates().NeedsUpdate(config); updated != nil { config = updated } now := metav1.Now() + target := optr.desiredVersion(original) + current := optr.currentVersion() // ensure the initial state of all conditions is set if resourcemerge.FindOperatorStatusCondition(config.Status.Conditions, configv1.OperatorAvailable) == nil { @@ -44,7 +107,7 @@ func (optr *Operator) syncInitialObjectStatus(original *configv1.ClusterVersion, resourcemerge.SetOperatorStatusCondition(&config.Status.Conditions, configv1.ClusterOperatorStatusCondition{ Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, - Message: fmt.Sprintf("Initializing, will work towards %s", optr.desiredVersionString(config)), + Message: fmt.Sprintf("Initializing, will work towards %s", versionString(target)), LastTransitionTime: now, }) } @@ -79,7 +142,7 @@ func (optr *Operator) syncInitialObjectStatus(original *configv1.ClusterVersion, Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: reason, - Message: fmt.Sprintf("Stopped at %s: the cluster version is invalid", optr.desiredVersionString(config)), + Message: fmt.Sprintf("Stopped at %s: the cluster version is invalid", versionString(current)), LastTransitionTime: now, }) } @@ -88,16 +151,21 @@ func (optr *Operator) syncInitialObjectStatus(original *configv1.ClusterVersion, resourcemerge.RemoveOperatorStatusCondition(&config.Status.Conditions, ClusterVersionInvalid) } + // ensure we record the initial state so the user can roll back + if len(config.Status.History) == 0 { + mergeOperatorHistory(config, current, now) + } + mergeOperatorHistory(config, target, now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) return updated != nil && updated.ResourceVersion != original.ResourceVersion, err } -func (optr *Operator) syncProgressingStatus(config *configv1.ClusterVersion) error { +func (optr *Operator) syncProgressingStatus(config *configv1.ClusterVersion, update configv1.Update) error { original := config.DeepCopy() config.Status.Generation = config.Generation - config.Status.Current = optr.currentVersion() now := metav1.Now() @@ -117,25 +185,27 @@ func (optr *Operator) syncProgressingStatus(config *configv1.ClusterVersion) err Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: reason, - Message: fmt.Sprintf("Unable to apply %s: %s", optr.desiredVersionString(config), msg), + Message: fmt.Sprintf("Unable to apply %s: %s", versionString(update), msg), LastTransitionTime: now, }) } else if resourcemerge.IsOperatorStatusConditionTrue(config.Status.Conditions, ClusterVersionInvalid) { resourcemerge.SetOperatorStatusCondition(&config.Status.Conditions, configv1.ClusterOperatorStatusCondition{ Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, - Message: fmt.Sprintf("Reconciling %s: the cluster version is invalid", optr.desiredVersionString(config)), + Message: fmt.Sprintf("Reconciling %s: the cluster version is invalid", versionString(update)), LastTransitionTime: now, }) } else { resourcemerge.SetOperatorStatusCondition(&config.Status.Conditions, configv1.ClusterOperatorStatusCondition{ Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, - Message: fmt.Sprintf("Working towards %s", optr.desiredVersionString(config)), + Message: fmt.Sprintf("Working towards %s", versionString(update)), LastTransitionTime: now, }) } + mergeOperatorHistory(config, update, now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) return err @@ -144,7 +214,6 @@ func (optr *Operator) syncProgressingStatus(config *configv1.ClusterVersion) err func (optr *Operator) syncAvailableStatus(config *configv1.ClusterVersion, current configv1.Update, versionHash string) error { original := config.DeepCopy() - config.Status.Current = current config.Status.VersionHash = versionHash config.Status.Generation = config.Generation @@ -169,7 +238,7 @@ func (optr *Operator) syncAvailableStatus(config *configv1.ClusterVersion, curre Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: "InvalidClusterVersion", - Message: fmt.Sprintf("Stopped at %s: the cluster version is invalid", optr.desiredVersionString(config)), + Message: fmt.Sprintf("Stopped at %s: the cluster version is invalid", versionString(current)), LastTransitionTime: now, }) } else { @@ -182,6 +251,8 @@ func (optr *Operator) syncAvailableStatus(config *configv1.ClusterVersion, curre }) } + mergeOperatorHistory(config, current, now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) return err @@ -191,7 +262,6 @@ func (optr *Operator) syncPayloadFailingStatus(original *configv1.ClusterVersion config := original.DeepCopy() config.Status.Generation = config.Generation - config.Status.Current = optr.currentVersion() now := metav1.Now() var reason string @@ -201,6 +271,8 @@ func (optr *Operator) syncPayloadFailingStatus(original *configv1.ClusterVersion msg = summaryForReason(reason) } + target := optr.desiredVersion(original) + // leave the available condition alone // set the failing condition @@ -218,7 +290,7 @@ func (optr *Operator) syncPayloadFailingStatus(original *configv1.ClusterVersion Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: reason, - Message: fmt.Sprintf("Unable to apply %s: %s", optr.desiredVersionString(config), msg), + Message: fmt.Sprintf("Unable to apply %s: %s", versionString(target), msg), LastTransitionTime: now, }) } else { @@ -226,11 +298,13 @@ func (optr *Operator) syncPayloadFailingStatus(original *configv1.ClusterVersion Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: reason, - Message: fmt.Sprintf("Error while reconciling %s: %s", optr.desiredVersionString(config), msg), + Message: fmt.Sprintf("Error while reconciling %s: %s", versionString(target), msg), LastTransitionTime: now, }) } + mergeOperatorHistory(config, target, now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) return err @@ -240,7 +314,6 @@ func (optr *Operator) syncUpdateFailingStatus(original *configv1.ClusterVersion, config := original.DeepCopy() config.Status.Generation = config.Generation - config.Status.Current = optr.currentVersion() now := metav1.Now() var reason string @@ -250,6 +323,8 @@ func (optr *Operator) syncUpdateFailingStatus(original *configv1.ClusterVersion, msg = summaryForReason(reason) } + target := optr.desiredVersion(original) + // clear the available condition resourcemerge.SetOperatorStatusCondition(&config.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse, LastTransitionTime: now}) @@ -268,7 +343,7 @@ func (optr *Operator) syncUpdateFailingStatus(original *configv1.ClusterVersion, Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue, Reason: reason, - Message: fmt.Sprintf("Unable to apply %s: %s", optr.desiredVersionString(config), msg), + Message: fmt.Sprintf("Unable to apply %s: %s", versionString(target), msg), LastTransitionTime: now, }) } else { @@ -276,11 +351,13 @@ func (optr *Operator) syncUpdateFailingStatus(original *configv1.ClusterVersion, Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse, Reason: reason, - Message: fmt.Sprintf("Error while reconciling %s: %s", optr.desiredVersionString(config), msg), + Message: fmt.Sprintf("Error while reconciling %s: %s", versionString(target), msg), LastTransitionTime: now, }) } + mergeOperatorHistory(config, target, now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) return err @@ -310,8 +387,6 @@ func (optr *Operator) syncFailingStatus(config *configv1.ClusterVersion, ierr er original := config.DeepCopy() - config.Status.Current = optr.currentVersion() - now := metav1.Now() msg := fmt.Sprintf("Error ensuring the cluster version is up to date: %v", ierr) @@ -338,6 +413,8 @@ func (optr *Operator) syncFailingStatus(config *configv1.ClusterVersion, ierr er LastTransitionTime: now, }) + mergeOperatorHistory(config, optr.currentVersion(), now) + updated, err := applyClusterVersionStatus(optr.client.ConfigV1(), config, original) optr.rememberLastUpdate(updated) if err != nil { diff --git a/pkg/cvo/updatepayload.go b/pkg/cvo/updatepayload.go index c28f464c80..d2605a0643 100644 --- a/pkg/cvo/updatepayload.go +++ b/pkg/cvo/updatepayload.go @@ -2,6 +2,7 @@ package cvo import ( "bytes" + "crypto/md5" "encoding/base64" "fmt" "hash/fnv" @@ -183,11 +184,15 @@ func (optr *Operator) updatePayloadDir(config *configv1.ClusterVersion) (string, } func (optr *Operator) targetUpdatePayloadDir(config *configv1.ClusterVersion) (string, error) { - if !isTargetSet(config.Spec.DesiredUpdate) { + payload, ok := findUpdatePayload(config) + if !ok { return "", nil } + hash := md5.New() + hash.Write([]byte(payload)) + payloadHash := base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) - tdir := filepath.Join(targetUpdatePayloadsDir, config.Spec.DesiredUpdate.Version) + tdir := filepath.Join(targetUpdatePayloadsDir, payloadHash) err := validateUpdatePayload(tdir) if os.IsNotExist(err) { // the dirs don't exist, try fetching the payload to tdir. @@ -313,7 +318,27 @@ func copyPayloadCmd(tdir string) string { return fmt.Sprintf("%s && %s", cvoCmd, releaseCmd) } -func isTargetSet(desired *configv1.Update) bool { - return desired != nil && desired.Payload != "" && - desired.Version != "" +func findUpdatePayload(config *configv1.ClusterVersion) (string, bool) { + update := config.Spec.DesiredUpdate + if update == nil { + return "", false + } + if len(update.Payload) == 0 { + return findPayloadForVersion(config, update.Version) + } + return update.Payload, len(update.Payload) > 0 +} + +func findPayloadForVersion(config *configv1.ClusterVersion, version string) (string, bool) { + for _, update := range config.Status.AvailableUpdates { + if update.Version == version { + return update.Payload, len(update.Payload) > 0 + } + } + for _, history := range config.Status.History { + if history.Version == version { + return history.Payload, len(history.Payload) > 0 + } + } + return "", false } diff --git a/vendor/github.com/openshift/api/config/v1/register.go b/vendor/github.com/openshift/api/config/v1/register.go index 0a2ad39357..eed769098d 100644 --- a/vendor/github.com/openshift/api/config/v1/register.go +++ b/vendor/github.com/openshift/api/config/v1/register.go @@ -42,7 +42,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ConsoleList{}, &DNS{}, &DNSList{}, - &GenericControllerConfig{}, &IdentityProvider{}, &IdentityProviderList{}, &Image{}, diff --git a/vendor/github.com/openshift/api/config/v1/types.go b/vendor/github.com/openshift/api/config/v1/types.go index 370c265c08..7445c4fff8 100644 --- a/vendor/github.com/openshift/api/config/v1/types.go +++ b/vendor/github.com/openshift/api/config/v1/types.go @@ -5,14 +5,21 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// ConfigMapReference references the location of a configmap. +// ConfigMapReference references a configmap in the openshift-config namespace. type ConfigMapReference struct { - Namespace string `json:"namespace"` - Name string `json:"name"` + Name string `json:"name"` // Key allows pointing to a specific key/value inside of the configmap. This is useful for logical file references. Key string `json:"filename,omitempty"` } +// LocalSecretReference references a secret within the local namespace +type LocalSecretReference struct { + // Name of the secret in the local namespace + Name string `json:"name"` + // Key selects a specific key within the local secret. Must be a valid secret key. + Key string `json:"key,omitempty"` +} + // HTTPServingInfo holds configuration for serving HTTP type HTTPServingInfo struct { // ServingInfo is the HTTP serving information @@ -245,12 +252,8 @@ type ClientConnectionOverrides struct { Burst int32 `json:"burst"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - // GenericControllerConfig provides information to configure a controller type GenericControllerConfig struct { - metav1.TypeMeta `json:",inline"` - // ServingInfo is the HTTP serving information for the controller's endpoints ServingInfo HTTPServingInfo `json:"servingInfo,omitempty"` diff --git a/vendor/github.com/openshift/api/config/v1/types_authentication.go b/vendor/github.com/openshift/api/config/v1/types_authentication.go index 281dca7acd..af181c34e7 100644 --- a/vendor/github.com/openshift/api/config/v1/types_authentication.go +++ b/vendor/github.com/openshift/api/config/v1/types_authentication.go @@ -7,7 +7,6 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Authentication holds cluster-wide information about Authentication. The canonical name is `cluster` -// TODO this object is an example of a possible grouping and is subject to change or removal type Authentication struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -20,13 +19,34 @@ type Authentication struct { } type AuthenticationSpec struct { - // webhook token auth config (ttl) - // external token address - // serviceAccountOAuthGrantMethod or remove/disallow it as an option + // oauthMetadata contains the discovery endpoint data for OAuth 2.0 + // Authorization Server Metadata for an external OAuth server. + // This discovery document can be viewed from its served location: + // oc get --raw '/.well-known/oauth-authorization-server' + // For further details, see the IETF Draft: + // https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2 + // If oauthMetadata.name is non-empty, this value has precedence + // over the observed value stored in status.oauthMetadata + // +optional + OAuthMetadata ConfigMapReference `json:"oauthMetadata"` + + // webhookTokenAuthenticators configures remote token reviewers. + // These remote authentication webhooks can be used to verify bearer tokens + // via the tokenreviews.authentication.k8s.io REST API. This is required to + // honor bearer tokens that are provisioned by an external authentication service. + WebhookTokenAuthenticators []WebhookTokenAuthenticator `json:"webhookTokenAuthenticators"` } type AuthenticationStatus struct { - // internal token address + // oauthMetadata contains the discovery endpoint data for OAuth 2.0 + // Authorization Server Metadata for an external OAuth server. + // This discovery document can be viewed from its served location: + // oc get --raw '/.well-known/oauth-authorization-server' + // For further details, see the IETF Draft: + // https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2 + // This contains the observed value based on cluster state. + // An explicitly set value in spec.oauthMetadata has precedence over this field. + OAuthMetadata ConfigMapReference `json:"oauthMetadata"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -37,3 +57,11 @@ type AuthenticationList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Authentication `json:"items"` } + +// webhookTokenAuthenticator holds the necessary configuration options for a remote token authenticator +type WebhookTokenAuthenticator struct { + // kubeConfig contains kube config file data which describes how to access the remote webhook service. + // For further details, see: + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication + KubeConfig LocalSecretReference `json:"kubeConfig"` +} diff --git a/vendor/github.com/openshift/api/config/v1/types_build.go b/vendor/github.com/openshift/api/config/v1/types_build.go index 480c1d321c..49d3d0fe68 100644 --- a/vendor/github.com/openshift/api/config/v1/types_build.go +++ b/vendor/github.com/openshift/api/config/v1/types_build.go @@ -32,17 +32,20 @@ type BuildSpec struct { } type BuildDefaults struct { - // GitHTTPProxy is the location of the HTTPProxy for Git source + // DefaultProxy contains the default proxy settings for all build operations, including image pull/push + // and source download. + // + // Values can be overrode by setting the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables + // in the build config's strategy. // +optional - GitHTTPProxy string `json:"gitHTTPProxy,omitempty"` + DefaultProxy *ProxyConfig `json:"defaultProxy,omitempty"` - // GitHTTPSProxy is the location of the HTTPSProxy for Git source + // GitProxy contains the proxy settings for git operations only. If set, this will override + // any Proxy settings for all git commands, such as git clone. + // + // Values that are not set here will be inherited from DefaultProxy. // +optional - GitHTTPSProxy string `json:"gitHTTPSProxy,omitempty"` - - // GitNoProxy is the list of domains for which the proxy should not be used - // +optional - GitNoProxy string `json:"gitNoProxy,omitempty"` + GitProxy *ProxyConfig `json:"gitProxy,omitempty"` // Env is a set of default environment variables that will be applied to the // build if the specified variables do not exist on the build @@ -69,6 +72,21 @@ type ImageLabel struct { Value string `json:"value,omitempty"` } +// ProxyConfig defines what proxies to use for an operation +type ProxyConfig struct { + // HttpProxy is the URL of the proxy for HTTP requests + // +optional + HTTPProxy string `json:"httpProxy,omitempty"` + + // HttpsProxy is the URL of the proxy for HTTPS requests + // +optional + HTTPSProxy string `json:"httpsProxy,omitempty"` + + // NoProxy is the list of domains for which the proxy should not be used + // +optional + NoProxy string `json:"noProxy,omitempty"` +} + type BuildOverrides struct { // ImageLabels is a list of docker labels that are applied to the resulting image. // If user provided a label in their Build/BuildConfig with the same name as one in this diff --git a/vendor/github.com/openshift/api/config/v1/types_cluster_version.go b/vendor/github.com/openshift/api/config/v1/types_cluster_version.go index e012443ea2..a43b12a69a 100644 --- a/vendor/github.com/openshift/api/config/v1/types_cluster_version.go +++ b/vendor/github.com/openshift/api/config/v1/types_cluster_version.go @@ -73,11 +73,19 @@ type ClusterVersionSpec struct { // progress, or is failing. // +k8s:deepcopy-gen=true type ClusterVersionStatus struct { - // current is the version that the cluster will be reconciled to. This - // value may be empty during cluster startup, and then will be set whenever - // a new update is being applied. Use the conditions array to know whether - // the update is complete. - Current Update `json:"current"` + // desired is the version that the cluster is reconciling towards. + // If the cluster is not yet fully initialized desired will be set + // with the information available, which may be a payload or a tag. + Desired Update `json:"desired"` + + // history contains a list of the most recent versions applied to the cluster. + // This value may be empty during cluster startup, and then will be updated + // when a new update is being applied. The newest update is first in the + // list and it is ordered by recency. Updates in the history have state + // Completed if the rollout completed - if an update was failing or halfway + // applied the state will be Partial. Only a limited amount of update history + // is preserved. + History []UpdateHistory `json:"history"` // generation reports which version of the spec is being processed. // If this value is not equal to metadata.generation, then the @@ -106,6 +114,46 @@ type ClusterVersionStatus struct { AvailableUpdates []Update `json:"availableUpdates"` } +// UpdateState is a constant representing whether an update was successfully +// applied to the cluster or not. +type UpdateState string + +const ( + // CompletedUpdate indicates an update was successfully applied + // to the cluster (all resource updates were successful). + CompletedUpdate UpdateState = "Completed" + // PartialUpdate indicates an update was never completely applied + // or is currently being applied. + PartialUpdate UpdateState = "Partial" +) + +// UpdateHistory is a single attempted update to the cluster. +type UpdateHistory struct { + // state reflects whether the update was fully applied. The Partial state + // indicates the update is not fully applied, while the Completed state + // indicates the update was successfully rolled out at least once (all + // parts of the update successfully applied). + State UpdateState `json:"state"` + + // startedTime is the time at which the update was started. + StartedTime metav1.Time `json:"startedTime"` + // completionTime, if set, is when the update was fully applied. The update + // that is currently being applied will have a null completion time. + // Completion time will always be set for entries that are not the current + // update (usually to the started time of the next update). + CompletionTime *metav1.Time `json:"completionTime"` + + // version is a semantic versioning identifying the update version. If the + // requested payload does not define a version, or if a failure occurs + // retrieving the payload, this value may be empty. + // + // +optional + Version string `json:"version"` + // payload is a container image location that contains the update. This value + // is always populated. + Payload string `json:"payload"` +} + // ClusterID is string RFC4122 uuid. type ClusterID string diff --git a/vendor/github.com/openshift/api/config/v1/types_dns.go b/vendor/github.com/openshift/api/config/v1/types_dns.go index 44fa6e4d27..c371895471 100644 --- a/vendor/github.com/openshift/api/config/v1/types_dns.go +++ b/vendor/github.com/openshift/api/config/v1/types_dns.go @@ -20,6 +20,12 @@ type DNS struct { } type DNSSpec struct { + // baseDomain is the base domain of the cluster. All managed DNS records will + // be sub-domains of this base. + // + // For example, given the base domain `openshift.example.com`, an API server + // DNS record may be created for `cluster-api.openshift.example.com`. + BaseDomain string `json:"baseDomain"` } type DNSStatus struct { diff --git a/vendor/github.com/openshift/api/config/v1/types_infrastructure.go b/vendor/github.com/openshift/api/config/v1/types_infrastructure.go index 234e872c0b..42efc79db9 100644 --- a/vendor/github.com/openshift/api/config/v1/types_infrastructure.go +++ b/vendor/github.com/openshift/api/config/v1/types_infrastructure.go @@ -25,9 +25,31 @@ type InfrastructureSpec struct { } type InfrastructureStatus struct { - // type + // platform is the underlying infrastructure provider for the cluster. This + // value controls whether infrastructure automation such as service load + // balancers, dynamic volume provisioning, machine creation and deletion, and + // other integrations are enabled. If None, no infrastructure automation is + // enabled. + Platform PlatformType `json:"platform,omitempty"` } +// platformType is a specific supported infrastructure provider. +type PlatformType string + +const ( + // awsPlatform represents Amazon AWS. + AWSPlatform PlatformType = "AWS" + + // openStackPlatform represents OpenStack. + OpenStackPlatform PlatformType = "OpenStack" + + // libvirtPlatform represents libvirt. + LibvirtPlatform PlatformType = "Libvirt" + + // nonePlatform means there is no infrastructure provider. + NonePlatform PlatformType = "None" +) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type InfrastructureList struct { diff --git a/vendor/github.com/openshift/api/config/v1/types_ingress.go b/vendor/github.com/openshift/api/config/v1/types_ingress.go index e8467a0906..d9b81988f5 100644 --- a/vendor/github.com/openshift/api/config/v1/types_ingress.go +++ b/vendor/github.com/openshift/api/config/v1/types_ingress.go @@ -20,7 +20,10 @@ type Ingress struct { } type IngressSpec struct { - // default suffix. It goes here or it gets removed from server + // domain is used to generate a default host name for a route when the + // route's host name is empty. The generated host name will follow this + // pattern: "..". + Domain string `json:"domain"` } type IngressStatus struct { diff --git a/vendor/github.com/openshift/api/config/v1/types_network.go b/vendor/github.com/openshift/api/config/v1/types_network.go index aaea1aab14..144ba15b53 100644 --- a/vendor/github.com/openshift/api/config/v1/types_network.go +++ b/vendor/github.com/openshift/api/config/v1/types_network.go @@ -13,20 +13,56 @@ type Network struct { // Standard object's metadata. metav1.ObjectMeta `json:"metadata,omitempty"` - // spec holds user settable values for configuration + // spec holds user settable values for configuration. Spec NetworkSpec `json:"spec"` // status holds observed values from the cluster. They may not be overridden. Status NetworkStatus `json:"status"` } +// NetworkSpec is the desired network configuration. +// As a general rule, this SHOULD NOT be read directly. Instead, you should +// consume the NetworkStatus, as it indicates the currently deployed configuration. +// Currently, none of these fields may be changed after installation. type NetworkSpec struct { - // serviceCIDR - // servicePortRange - // vxlanPort - // ClusterNetworks []ClusterNetworkEntry `json:"clusterNetworks"` + // IP address pool to use for pod IPs. + ClusterNetwork []ClusterNetworkEntry `json:"clusterNetwork"` + + // IP address pool for services. + // Currently, we only support a single entry here. + ServiceNetwork []string `json:"serviceNetwork"` + + // NetworkType is the plugin that is to be deployed (e.g. OpenShiftSDN). + // This should match a value that the cluster-network-operator understands, + // or else no networking will be installed. + // Currently supported values are: + // - OpenShiftSDN + NetworkType string `json:"networkType"` } +// NetworkStatus is the current network configuration. type NetworkStatus struct { + // IP address pool to use for pod IPs. + ClusterNetwork []ClusterNetworkEntry `json:"clusterNetwork"` + + // IP address pool for services. + // Currently, we only support a single entry here. + ServiceNetwork []string `json:"serviceNetwork"` + + // NetworkType is the plugin that is deployed (e.g. OpenShiftSDN). + NetworkType string `json:"networkType"` + + // ClusterNetworkMTU is the MTU for inter-pod networking. + ClusterNetworkMTU int `json:"clusterNetworkMTU"` +} + +// ClusterNetworkEntry is a contiguous block of IP addresses from which pod IPs +// are allocated. +type ClusterNetworkEntry struct { + // The complete block for pod IPs. + CIDR string `json:"cidr"` + + // The size (prefix) of block to allocate to each node. + HostPrefix uint32 `json:"hostPrefix"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/github.com/openshift/api/config/v1/types_oauth.go b/vendor/github.com/openshift/api/config/v1/types_oauth.go index d4402ed338..91cffacdc1 100644 --- a/vendor/github.com/openshift/api/config/v1/types_oauth.go +++ b/vendor/github.com/openshift/api/config/v1/types_oauth.go @@ -1,37 +1,550 @@ package v1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OAuth Server and Identity Provider Config // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // OAuth holds cluster-wide information about OAuth. The canonical name is `cluster` -// TODO this object is an example of a possible grouping and is subject to change or removal type OAuth struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - - // spec holds user settable values for configuration - Spec OAuthSpec `json:"spec"` - // status holds observed values from the cluster. They may not be overridden. - Status OAuthStatus `json:"status"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec OAuthSpec `json:"spec"` + Status OAuthStatus `json:"status,omitempty"` } +// OAuthSpec contains desired cluster auth configuration type OAuthSpec struct { - // options for configuring the embedded oauth server. - // possibly wellknown? + // identityProviders is an ordered list of ways for a user to identify themselves + IdentityProviders []OAuthIdentityProvider `json:"identityProviders"` + + // tokenConfig contains options for authorization and access tokens + TokenConfig TokenConfig `json:"tokenConfig"` + + // templates allow you to customize pages like the login page. + // +optional + Templates OAuthTemplates `json:"templates"` } +// OAuthStatus shows current known state of OAuth server in the cluster type OAuthStatus struct { + // TODO Fill in } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// TokenConfig holds the necessary configuration options for authorization and access tokens +type TokenConfig struct { + // authorizeTokenMaxAgeSeconds defines the maximum age of authorize tokens + AuthorizeTokenMaxAgeSeconds int32 `json:"authorizeTokenMaxAgeSeconds"` + // accessTokenMaxAgeSeconds defines the maximum age of access tokens + AccessTokenMaxAgeSeconds int32 `json:"accessTokenMaxAgeSeconds"` + // accessTokenInactivityTimeoutSeconds defines the default token + // inactivity timeout for tokens granted by any client. + // The value represents the maximum amount of time that can occur between + // consecutive uses of the token. Tokens become invalid if they are not + // used within this temporal window. The user will need to acquire a new + // token to regain access once a token times out. + // Valid values are integer values: + // x < 0 Tokens time out is enabled but tokens never timeout unless configured per client (e.g. `-1`) + // x = 0 Tokens time out is disabled (default) + // x > 0 Tokens time out if there is no activity for x seconds + // The current minimum allowed value for X is 300 (5 minutes) + // +optional + AccessTokenInactivityTimeoutSeconds int32 `json:"accessTokenInactivityTimeoutSeconds,omitempty"` +} + +const ( + // LoginTemplateKey is the default key of the login template + LoginTemplateKey = "login.html" + // ProviderSelectionTemplateKey is the default key for the provider selection template + ProviderSelectionTemplateKey = "providers.html" + // ErrorsTemplateKey is the default key for the errors template + ErrorsTemplateKey = "errors.html" +) + +// OAuthTemplates allow for customization of pages like the login page +type OAuthTemplates struct { + // login is a reference to a secret that specifies a go template to use to render the login page. + // If a key is not specified, the key `login.html` is used to locate the template data. + // If unspecified, the default login page is used. + // +optional + Login LocalSecretReference `json:"login,omitemtpy"` + + // providerSelection is a reference to a secret that specifies a go template to use to render + // the provider selection page. + // If a key is not specified, the key `providers.html` is used to locate the template data. + // If unspecified, the default provider selection page is used. + // +optional + ProviderSelection LocalSecretReference `json:"providerSelection,omitempty"` + + // error is a reference to a secret that specifies a go template to use to render error pages + // during the authentication or grant flow. + // If a key is not specified, the key `errrors.html` is used to locate the template data. + // If unspecified, the default error page is used. + // +optional + Error LocalSecretReference `json:"error,omitempty"` +} + +// OAuthIdentityProvider provides identities for users authenticating using credentials +type OAuthIdentityProvider struct { + // name is used to qualify the identities returned by this provider. + // - It MUST be unique and not shared by any other identity provider used + // - It MUST be a vlid path segment: name cannot equal "." or ".." or contain "/" or "%" + // Ref: https://godoc.org/k8s.io/apimachinery/pkg/api/validation/path#ValidatePathSegmentName + Name string `json:"name"` + + // challenge indicates whether to issue WWW-Authenticate challenges for this provider + UseAsChallenger bool `json:"challenge"` + // login indicates whether to use this identity provider for unauthenticated browsers to login against + UseAsLogin bool `json:"login"` + + // mappingMethod determines how identities from this provider are mapped to users + // Defaults to "claim" + // +optional + MappingMethod MappingMethodType `json:"mappingMethod"` + + // grantMethod: allow, deny, prompt + // This method will be used only if the specific OAuth client doesn't provide a strategy + // of their own. Valid grant handling methods are: + // - auto: always approves grant requests, useful for trusted clients + // - prompt: prompts the end user for approval of grant requests, useful for third-party clients + // - deny: always denies grant requests, useful for black-listed clients + // Defaults to "prompt" if not set. + // +optional + GrantMethod GrantHandlerType `json:"grantMethod"` + + // IdentityProvidersConfig + ProviderConfig IdentityProviderConfig `json:",inline"` +} + +// MappingMethodType specifies how new identities should be mapped to users when they log in +type MappingMethodType string + +const ( + // MappingMethodClaim provisions a user with the identity’s preferred user name. Fails if a user + // with that user name is already mapped to another identity. + // Default. + MappingMethodClaim MappingMethodType = "claim" + + // MappingMethodLookup looks up existing users already mapped to an identity but does not + // automatically provision users or identities. Requires identities and users be set up + // manually or using an external process. + MappingMethodLookup MappingMethodType = "lookup" + + // MappingMethodAdd provisions a user with the identity’s preferred user name. If a user with + // that user name already exists, the identity is mapped to the existing user, adding to any + // existing identity mappings for the user. + MappingMethodAdd MappingMethodType = "add" + + // MappingMethodGenerate provisions a user with the identity’s preferred user name. If a user + // with the preferred user name is already mapped to an existing identity, a unique user name is + // generated, e.g. myuser2. This method should not be used in combination with external + // processes that require exact matches between openshift user names and the idp user name + // such as LDAP group sync. + MappingMethodGenerate MappingMethodType = "generate" +) + +// GrantHandlerType are the valid strategies for handling grant requests +type GrantHandlerType string + +const ( + // GrantHandlerAuto auto-approves client authorization grant requests + GrantHandlerAuto GrantHandlerType = "auto" + // GrantHandlerPrompt prompts the user to approve new client authorization grant requests + GrantHandlerPrompt GrantHandlerType = "prompt" + // GrantHandlerDeny auto-denies client authorization grant requests + GrantHandlerDeny GrantHandlerType = "deny" +) + +type IdentityProviderType string + +const ( + // IdentityProviderTypeBasicAuth provides identities for users authenticating with HTTP Basic Auth + IdentityProviderTypeBasicAuth IdentityProviderType = "BasicAuth" + + // IdentityProviderTypeAllowAll provides identities for all users authenticating using non-empty passwords + IdentityProviderTypeAllowAll IdentityProviderType = "AllowAll" + + // IdentityProviderTypeDenyAll provides no identities for users + IdentityProviderTypeDenyAll IdentityProviderType = "DenyAll" + + // IdentityProviderTypeHTPasswd provides identities from an HTPasswd file + IdentityProviderTypeHTPasswd IdentityProviderType = "HTPasswd" + + // IdentityProviderTypeLDAP provides identities for users authenticating using LDAP credentials + IdentityProviderTypeLDAP IdentityProviderType = "LDAP" + + // IdentityProviderTypeKeystone provides identitities for users authenticating using keystone password credentials + IdentityProviderTypeKeystone IdentityProviderType = "Keystone" + + // IdentityProviderTypeRequestHeader provides identities for users authenticating using request header credentials + IdentityProviderTypeRequestHeader IdentityProviderType = "RequestHeader" + + // IdentityProviderTypeGitHub provides identities for users authenticating using GitHub credentials + IdentityProviderTypeGitHub IdentityProviderType = "GitHub" + + // IdentityProviderTypeGitLab provides identities for users authenticating using GitLab credentials + IdentityProviderTypeGitLab IdentityProviderType = "GitLab" + + // IdentityProviderTypeGoogle provides identities for users authenticating using Google credentials + IdentityProviderTypeGoogle IdentityProviderType = "Google" + + // IdentityProviderTypeOpenID provides identities for users authenticating using OpenID credentials + IdentityProviderTypeOpenID IdentityProviderType = "OpenID" +) + +// IdentityProviderConfig contains configuration for using a specific identity provider +type IdentityProviderConfig struct { + // type identifies the identity provider type for this entry. + Type IdentityProviderType `json:"type"` + + // Provider-specific configuration + // The json tag MUST match the `Type` specified above, case-insensitively + // e.g. For `Type: "LDAP"`, the `LDAPPasswordIdentityProvider` configuration should be provided + + // basicAuth contains configuration options for the BasicAuth IdP + // +optional + BasicAuth *BasicAuthPasswordIdentityProvider `json:"basicAuth,omitempty"` + + // allowAll enables the AllowAllIdentityProvider which provides identities for users + // authenticating using non-empty passwords. + // Defaults to `false`, i.e. allowAll set to off + // +optional + AllowAll bool `json:"allowAll,omitempty"` + + // denyAll enables the DenyAllPasswordIdentityProvider which provides no identities for users + // Defaults to `false`, ie. denyAll set to off + // +optional + DenyAll bool `json:"denyAll,omitempty"` + + // htpasswd enables user authentication using an HTPasswd file to validate credentials + // +optional + HTPasswd *HTPasswdPasswordIdentityProvider `json:"htpasswd,omitempty"` + + // ldap enables user authentication using LDAP credentials + // +optional + LDAP *LDAPPasswordIdentityProvider `json:"ldap,omitempty"` + + // keystone enables user authentication using keystone password credentials + // +optional + Keystone *KeystonePasswordIdentityProvider `json:"keystone,omitempty"` + + // requestHeader enables user authentication using request header credentials + RequestHeader *RequestHeaderIdentityProvider `json:"requestHeader,omitempty"` + + // github enables user authentication using GitHub credentials + // +optional + GitHub *GitHubIdentityProvider `json:"github,omitempty"` + + // gitlab enables user authentication using GitLab credentials + // +optional + GitLab *GitLabIdentityProvider `json:"gitlab,omitempty"` + + // google enables user authentication using Google credentials + // +optional + Google *GoogleIdentityProvider `json:"google,omitempty"` + + // openID enables user authentication using OpenID credentials + // +optional + OpenID *OpenIDIdentityProvider `json:"openID,omitempty"` +} + +// BasicAuthPasswordIdentityProvider provides identities for users authenticating using HTTP basic auth credentials +type BasicAuthPasswordIdentityProvider struct { + // OAuthRemoteConnectionInfo contains information about how to connect to the external basic auth server + OAuthRemoteConnectionInfo `json:",inline"` +} + +// RemoteConnectionInfo holds information necessary for establishing a remote connection +type OAuthRemoteConnectionInfo struct { + // url is the remote URL to connect to + URL string `json:"url"` + // ca is a reference to a ConfigMap containing the CA for verifying TLS connections + CA ConfigMapReference `json:"ca"` + + // tlsClientCert references a secret containing the TLS client certificate to present when + // connecting to the server. + // Looks under the key "tls.cert" for the data unless a lookup key is specified in the secret ref + TLSClientCert LocalSecretReference `json:"tlsClientCert"` + + // tlsClientKey references a secret containing the TLS private key for the client certificate + // Looks under the key "tls.key" for the data unless a lookup key is specified in the secret ref + TLSClientKey LocalSecretReference `json:"tlsClientKey"` +} + +// HTPasswdDataKey is the default key for the htpasswd file data in a secret +const HTPasswdDataKey = "htpasswd" + +// HTPasswdPasswordIdentityProvider provides identities for users authenticating using htpasswd credentials +type HTPasswdPasswordIdentityProvider struct { + // fileData is a reference to a secret containing the data to use as the htpasswd file + // Looks under the key `htpasswd` unless a lookup key is specified in the secret ref + FileData LocalSecretReference `json:"fileData"` +} + +const ( + // BindPasswordKey is default the key for the LDAP bind password in a secret + BindPasswordKey = "bindPassword" + // ClientSecretKey is the key for the oauth client secret data in a secret + ClientSecretKey = "clientSecret" +) + +// LDAPPasswordIdentityProvider provides identities for users authenticating using LDAP credentials +type LDAPPasswordIdentityProvider struct { + // url is an RFC 2255 URL which specifies the LDAP search parameters to use. + // The syntax of the URL is: + // ldap://host:port/basedn?attribute?scope?filter + URL string `json:"url"` + + // bindDN is an optional DN to bind with during the search phase. + // +optional + BindDN string `json:"bindDN"` + + // bindPassword is a reference to the secret containing an optional password to bind + // with during the search phase. + // Looks under the key `bindPassword` unless a lookup key is specified in the secret ref + // +optional + BindPassword LocalSecretReference `json:"bindPassword"` + + // insecure, if true, indicates the connection should not use TLS + // WARNING: Should not be set to `true` with the URL scheme "ldaps://" as "ldaps://" URLs always + // attempt to connect using TLS, even when `insecure` is set to `true` + // When `true`, "ldap://" URLS connect insecurely. When `false`, "ldap://" URLs are upgraded to + // a TLS connection using StartTLS as specified in https://tools.ietf.org/html/rfc2830. + Insecure bool `json:"insecure"` + + // ca is a reference to a ConfigMap containing an optional trusted certificate authority bundle + // to use when making requests to the server. + // If empty, the default system roots are used. + // +optional + CA ConfigMapReference `json:"ca"` + + // attributes maps LDAP attributes to identities + Attributes LDAPAttributeMapping `json:"attributes"` +} + +// LDAPAttributeMapping maps LDAP attributes to OpenShift identity fields +type LDAPAttributeMapping struct { + // id is the list of attributes whose values should be used as the user ID. Required. + // First non-empty attribute is used. At least one attribute is required. If none of the listed + // attribute have a value, authentication fails. + // LDAP standard identity attribute is "dn" + ID []string `json:"id"` + // preferredUsername is the list of attributes whose values should be used as the preferred username. + // LDAP standard login attribute is "uid" + // +optional + PreferredUsername []string `json:"preferredUsername"` + // name is the list of attributes whose values should be used as the display name. Optional. + // If unspecified, no display name is set for the identity + // LDAP standard display name attribute is "cn" + // +optional + Name []string `json:"name"` + // email is the list of attributes whose values should be used as the email address. Optional. + // If unspecified, no email is set for the identity + // +optional + Email []string `json:"email"` +} + +// KeystonePasswordIdentityProvider provides identities for users authenticating using keystone password credentials +type KeystonePasswordIdentityProvider struct { + // OAuthRemoteConnectionInfo contains information about how to connect to the keystone server + OAuthRemoteConnectionInfo `json:",inline"` + // domainName is required for keystone v3 + DomainName string `json:"domainName"` + // useKeystoneIdentity flag indicates that user should be authenticated by username, not keystone ID + // DEPRECATED - only use this option for legacy systems to ensure backwards compatibiity + // +optional + LegacyLookupByUsername bool `json:"useKeystoneIdentity"` +} + +// RequestHeaderIdentityProvider provides identities for users authenticating using request header credentials +type RequestHeaderIdentityProvider struct { + // loginURL is a URL to redirect unauthenticated /authorize requests to + // Unauthenticated requests from OAuth clients which expect interactive logins will be redirected here + // ${url} is replaced with the current URL, escaped to be safe in a query parameter + // https://www.example.com/sso-login?then=${url} + // ${query} is replaced with the current query string + // https://www.example.com/auth-proxy/oauth/authorize?${query} + // Required when UseAsLogin is set to true. + LoginURL string `json:"loginURL"` + + // challengeURL is a URL to redirect unauthenticated /authorize requests to + // Unauthenticated requests from OAuth clients which expect WWW-Authenticate challenges will be + // redirected here. + // ${url} is replaced with the current URL, escaped to be safe in a query parameter + // https://www.example.com/sso-login?then=${url} + // ${query} is replaced with the current query string + // https://www.example.com/auth-proxy/oauth/authorize?${query} + // Required when UseAsChallenger is set to true. + ChallengeURL string `json:"challengeURL"` + + // clientCA is a reference to a configmap with the trusted signer certs. If empty, no request + // verification is done, and any direct request to the OAuth server can impersonate any identity + // from this provider, merely by setting a request header. + // +optional + ClientCA ConfigMapReference `json:"ca"` + + // clientCommonNames is an optional list of common names to require a match from. If empty, any + // client certificate validated against the clientCA bundle is considered authoritative. + // +optional + ClientCommonNames []string `json:"clientCommonNames"` + + // headers is the set of headers to check for identity information + Headers []string `json:"headers"` + + // preferredUsernameHeaders is the set of headers to check for the preferred username + PreferredUsernameHeaders []string `json:"preferredUsernameHeaders"` + + // nameHeaders is the set of headers to check for the display name + NameHeaders []string `json:"nameHeaders"` + + // emailHeaders is the set of headers to check for the email address + EmailHeaders []string `json:"emailHeaders"` +} + +// GitHubIdentityProvider provides identities for users authenticating using GitHub credentials +type GitHubIdentityProvider struct { + // clientID is the oauth client ID + ClientID string `json:"clientID"` + + // clientSecret is is a reference to the secret containing the oauth client secret + // The secret referenced must contain a key named `clientSecret` containing the secret data. + ClientSecret LocalSecretReference `json:"clientSecret"` + + // organizations optionally restricts which organizations are allowed to log in + // +optional + Organizations []string `json:"organizations"` + // teams optionally restricts which teams are allowed to log in. Format is /. + // +optional + Teams []string `json:"teams"` + + // hostname is the optional domain (e.g. "mycompany.com") for use with a hosted instance of + // GitHub Enterprise. + // It must match the GitHub Enterprise settings value configured at /setup/settings#hostname. + // +optional + Hostname string `json:"hostname"` + + // ca is a reference to a ConfigMap containing an optional trusted certificate authority bundle + // to use when making requests to the server. + // If empty, the default system roots are used. + // This can only be configured when hostname is set to a non-empty value. + // +optional + CA ConfigMapReference `json:"ca"` +} + +// GitLabIdentityProvider provides identities for users authenticating using GitLab credentials +type GitLabIdentityProvider struct { + // ca is a reference to a ConfigMap containing an optional trusted certificate authority bundle + // to use when making requests to the server. + // If empty, the default system roots are used. + // +optional + CA ConfigMapReference `json:"ca"` + + // url is the oauth server base URL + URL string `json:"url"` + + // clientID is the oauth client ID + ClientID string `json:"clientID"` + + // clientSecret is is a reference to the secret containing the oauth client secret + // The secret referenced must contain a key named `clientSecret` containing the secret data. + ClientSecret LocalSecretReference `json:"clientSecret"` + + // legacy determines that OAuth2 should be used, not OIDC + // +optional + LegacyOAuth2 bool `json:"legacy,omitempty"` +} + +// GoogleIdentityProvider provides identities for users authenticating using Google credentials +type GoogleIdentityProvider struct { + // clientID is the oauth client ID + ClientID string `json:"clientID"` + + // clientSecret is is a reference to the secret containing the oauth client secret + // The secret referenced must contain a key named `clientSecret` containing the secret data. + ClientSecret LocalSecretReference `json:"clientSecret"` + + // hostedDomain is the optional Google App domain (e.g. "mycompany.com") to restrict logins to + // +optional + HostedDomain string `json:"hostedDomain"` +} + +// OpenIDIdentityProvider provides identities for users authenticating using OpenID credentials +type OpenIDIdentityProvider struct { + // ca is a reference to a ConfigMap containing an optional trusted certificate authority bundle + // to use when making requests to the server. + // If empty, the default system roots are used. + // +optional + CA ConfigMapReference `json:"ca"` + + // clientID is the oauth client ID + ClientID string `json:"clientID"` + + // clientSecret is is a reference to the secret containing the oauth client secret + // The secret referenced must contain a key named `clientSecret` containing the secret data. + ClientSecret LocalSecretReference `json:"clientSecret"` + + // extraScopes are any scopes to request in addition to the standard "openid" scope. + // +optional + ExtraScopes []string `json:"extraScopes"` + + // extraAuthorizeParameters are any custom parameters to add to the authorize request. + // +optional + ExtraAuthorizeParameters map[string]string `json:"extraAuthorizeParameters"` + + // urls to use to authenticate + URLs OpenIDURLs `json:"urls"` + + // claims mappings + Claims OpenIDClaims `json:"claims"` +} + +// OpenIDURLs are URLs to use when authenticating with an OpenID identity provider +type OpenIDURLs struct { + // authorize is the oauth authorization URL + Authorize string `json:"authorize"` + // token is the oauth token granting URL + Token string `json:"token"` + // userInfo is the optional userinfo URL. + // If present, a granted access_token is used to request claims + // If empty, a granted id_token is parsed for claims + // +optional + UserInfo string `json:"userInfo"` +} + +// UserIDClaim is used in the `ID` field for an `OpenIDClaim` +// Per http://openid.net/specs/openid-connect-core-1_0.html#ClaimStability +// "The sub (subject) and iss (issuer) Claims, used together, are the only Claims that an RP can +// rely upon as a stable identifier for the End-User, since the sub Claim MUST be locally unique +// and never reassigned within the Issuer for a particular End-User, as described in Section 2. +// Therefore, the only guaranteed unique identifier for a given End-User is the combination of the +// iss Claim and the sub Claim." +const UserIDClaim = "sub" + +// OpenIDClaims contains a list of OpenID claims to use when authenticating with an OpenID identity provider +type OpenIDClaims struct { + // preferredUsername is the list of claims whose values should be used as the preferred username. + // If unspecified, the preferred username is determined from the value of the id claim + // +optional + PreferredUsername []string `json:"preferredUsername"` + // name is the list of claims whose values should be used as the display name. Optional. + // If unspecified, no display name is set for the identity + // +optional + Name []string `json:"name"` + // email is the list of claims whose values should be used as the email address. Optional. + // If unspecified, no email is set for the identity + // +optional + Email []string `json:"email"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type OAuthList struct { metav1.TypeMeta `json:",inline"` - // Standard object's metadata. metav1.ListMeta `json:"metadata,omitempty"` Items []OAuth `json:"items"` } diff --git a/vendor/github.com/openshift/api/config/v1/types_swagger_doc_generated.go b/vendor/github.com/openshift/api/config/v1/types_swagger_doc_generated.go index 1fd8c16e8a..55b534c2e1 100644 --- a/vendor/github.com/openshift/api/config/v1/types_swagger_doc_generated.go +++ b/vendor/github.com/openshift/api/config/v1/types_swagger_doc_generated.go @@ -61,7 +61,7 @@ func (ClientConnectionOverrides) SwaggerDoc() map[string]string { } var map_ConfigMapReference = map[string]string{ - "": "ConfigMapReference references the location of a configmap.", + "": "ConfigMapReference references a configmap in the openshift-config namespace.", "filename": "Key allows pointing to a specific key/value inside of the configmap. This is useful for logical file references.", } @@ -162,6 +162,16 @@ func (LeaderElection) SwaggerDoc() map[string]string { return map_LeaderElection } +var map_LocalSecretReference = map[string]string{ + "": "LocalSecretReference references a secret within the local namespace", + "name": "Name of the secret in the local namespace", + "key": "Key selects a specific key within the local secret. Must be a valid secret key.", +} + +func (LocalSecretReference) SwaggerDoc() map[string]string { + return map_LocalSecretReference +} + var map_NamedCertificate = map[string]string{ "": "NamedCertificate specifies a certificate/key, and the names it should be served for", "names": "Names is a list of DNS names this certificate should be used to secure A name can be a normal DNS name, or can contain leading wildcard segments.", diff --git a/vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go b/vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go index 2e376017cf..e0fed9683e 100644 --- a/vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go +++ b/vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go @@ -48,7 +48,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status return } @@ -107,6 +107,12 @@ func (in *AuthenticationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthenticationSpec) DeepCopyInto(out *AuthenticationSpec) { *out = *in + out.OAuthMetadata = in.OAuthMetadata + if in.WebhookTokenAuthenticators != nil { + in, out := &in.WebhookTokenAuthenticators, &out.WebhookTokenAuthenticators + *out = make([]WebhookTokenAuthenticator, len(*in)) + copy(*out, *in) + } return } @@ -123,6 +129,7 @@ func (in *AuthenticationSpec) DeepCopy() *AuthenticationSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthenticationStatus) DeepCopyInto(out *AuthenticationStatus) { *out = *in + out.OAuthMetadata = in.OAuthMetadata return } @@ -136,6 +143,23 @@ func (in *AuthenticationStatus) DeepCopy() *AuthenticationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BasicAuthPasswordIdentityProvider) DeepCopyInto(out *BasicAuthPasswordIdentityProvider) { + *out = *in + out.OAuthRemoteConnectionInfo = in.OAuthRemoteConnectionInfo + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuthPasswordIdentityProvider. +func (in *BasicAuthPasswordIdentityProvider) DeepCopy() *BasicAuthPasswordIdentityProvider { + if in == nil { + return nil + } + out := new(BasicAuthPasswordIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Build) DeepCopyInto(out *Build) { *out = *in @@ -166,6 +190,24 @@ func (in *Build) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BuildDefaults) DeepCopyInto(out *BuildDefaults) { *out = *in + if in.DefaultProxy != nil { + in, out := &in.DefaultProxy, &out.DefaultProxy + if *in == nil { + *out = nil + } else { + *out = new(ProxyConfig) + **out = **in + } + } + if in.GitProxy != nil { + in, out := &in.GitProxy, &out.GitProxy + if *in == nil { + *out = nil + } else { + *out = new(ProxyConfig) + **out = **in + } + } if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]core_v1.EnvVar, len(*in)) @@ -305,6 +347,22 @@ func (in *ClientConnectionOverrides) DeepCopy() *ClientConnectionOverrides { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterNetworkEntry) DeepCopyInto(out *ClusterNetworkEntry) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterNetworkEntry. +func (in *ClusterNetworkEntry) DeepCopy() *ClusterNetworkEntry { + if in == nil { + return nil + } + out := new(ClusterNetworkEntry) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterOperator) DeepCopyInto(out *ClusterOperator) { *out = *in @@ -517,7 +575,14 @@ func (in *ClusterVersionSpec) DeepCopy() *ClusterVersionSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterVersionStatus) DeepCopyInto(out *ClusterVersionStatus) { *out = *in - out.Current = in.Current + out.Desired = in.Desired + if in.History != nil { + in, out := &in.History, &out.History + *out = make([]UpdateHistory, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]ClusterOperatorStatusCondition, len(*in)) @@ -869,7 +934,6 @@ func (in *GenericAPIServerConfig) DeepCopy() *GenericAPIServerConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GenericControllerConfig) DeepCopyInto(out *GenericControllerConfig) { *out = *in - out.TypeMeta = in.TypeMeta in.ServingInfo.DeepCopyInto(&out.ServingInfo) out.LeaderElection = in.LeaderElection out.Authentication = in.Authentication @@ -887,12 +951,84 @@ func (in *GenericControllerConfig) DeepCopy() *GenericControllerConfig { return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GenericControllerConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubIdentityProvider) DeepCopyInto(out *GitHubIdentityProvider) { + *out = *in + out.ClientSecret = in.ClientSecret + if in.Organizations != nil { + in, out := &in.Organizations, &out.Organizations + *out = make([]string, len(*in)) + copy(*out, *in) } - return nil + if in.Teams != nil { + in, out := &in.Teams, &out.Teams + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.CA = in.CA + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubIdentityProvider. +func (in *GitHubIdentityProvider) DeepCopy() *GitHubIdentityProvider { + if in == nil { + return nil + } + out := new(GitHubIdentityProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitLabIdentityProvider) DeepCopyInto(out *GitLabIdentityProvider) { + *out = *in + out.CA = in.CA + out.ClientSecret = in.ClientSecret + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitLabIdentityProvider. +func (in *GitLabIdentityProvider) DeepCopy() *GitLabIdentityProvider { + if in == nil { + return nil + } + out := new(GitLabIdentityProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GoogleIdentityProvider) DeepCopyInto(out *GoogleIdentityProvider) { + *out = *in + out.ClientSecret = in.ClientSecret + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GoogleIdentityProvider. +func (in *GoogleIdentityProvider) DeepCopy() *GoogleIdentityProvider { + if in == nil { + return nil + } + out := new(GoogleIdentityProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTPasswdPasswordIdentityProvider) DeepCopyInto(out *HTPasswdPasswordIdentityProvider) { + *out = *in + out.FileData = in.FileData + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTPasswdPasswordIdentityProvider. +func (in *HTPasswdPasswordIdentityProvider) DeepCopy() *HTPasswdPasswordIdentityProvider { + if in == nil { + return nil + } + out := new(HTPasswdPasswordIdentityProvider) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -940,6 +1076,103 @@ func (in *IdentityProvider) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IdentityProviderConfig) DeepCopyInto(out *IdentityProviderConfig) { + *out = *in + if in.BasicAuth != nil { + in, out := &in.BasicAuth, &out.BasicAuth + if *in == nil { + *out = nil + } else { + *out = new(BasicAuthPasswordIdentityProvider) + **out = **in + } + } + if in.HTPasswd != nil { + in, out := &in.HTPasswd, &out.HTPasswd + if *in == nil { + *out = nil + } else { + *out = new(HTPasswdPasswordIdentityProvider) + **out = **in + } + } + if in.LDAP != nil { + in, out := &in.LDAP, &out.LDAP + if *in == nil { + *out = nil + } else { + *out = new(LDAPPasswordIdentityProvider) + (*in).DeepCopyInto(*out) + } + } + if in.Keystone != nil { + in, out := &in.Keystone, &out.Keystone + if *in == nil { + *out = nil + } else { + *out = new(KeystonePasswordIdentityProvider) + **out = **in + } + } + if in.RequestHeader != nil { + in, out := &in.RequestHeader, &out.RequestHeader + if *in == nil { + *out = nil + } else { + *out = new(RequestHeaderIdentityProvider) + (*in).DeepCopyInto(*out) + } + } + if in.GitHub != nil { + in, out := &in.GitHub, &out.GitHub + if *in == nil { + *out = nil + } else { + *out = new(GitHubIdentityProvider) + (*in).DeepCopyInto(*out) + } + } + if in.GitLab != nil { + in, out := &in.GitLab, &out.GitLab + if *in == nil { + *out = nil + } else { + *out = new(GitLabIdentityProvider) + **out = **in + } + } + if in.Google != nil { + in, out := &in.Google, &out.Google + if *in == nil { + *out = nil + } else { + *out = new(GoogleIdentityProvider) + **out = **in + } + } + if in.OpenID != nil { + in, out := &in.OpenID, &out.OpenID + if *in == nil { + *out = nil + } else { + *out = new(OpenIDIdentityProvider) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityProviderConfig. +func (in *IdentityProviderConfig) DeepCopy() *IdentityProviderConfig { + if in == nil { + return nil + } + out := new(IdentityProviderConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IdentityProviderList) DeepCopyInto(out *IdentityProviderList) { *out = *in @@ -1316,6 +1549,23 @@ func (in *IngressStatus) DeepCopy() *IngressStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeystonePasswordIdentityProvider) DeepCopyInto(out *KeystonePasswordIdentityProvider) { + *out = *in + out.OAuthRemoteConnectionInfo = in.OAuthRemoteConnectionInfo + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeystonePasswordIdentityProvider. +func (in *KeystonePasswordIdentityProvider) DeepCopy() *KeystonePasswordIdentityProvider { + if in == nil { + return nil + } + out := new(KeystonePasswordIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeClientConfig) DeepCopyInto(out *KubeClientConfig) { *out = *in @@ -1333,6 +1583,61 @@ func (in *KubeClientConfig) DeepCopy() *KubeClientConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LDAPAttributeMapping) DeepCopyInto(out *LDAPAttributeMapping) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PreferredUsername != nil { + in, out := &in.PreferredUsername, &out.PreferredUsername + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Email != nil { + in, out := &in.Email, &out.Email + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPAttributeMapping. +func (in *LDAPAttributeMapping) DeepCopy() *LDAPAttributeMapping { + if in == nil { + return nil + } + out := new(LDAPAttributeMapping) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LDAPPasswordIdentityProvider) DeepCopyInto(out *LDAPPasswordIdentityProvider) { + *out = *in + out.BindPassword = in.BindPassword + out.CA = in.CA + in.Attributes.DeepCopyInto(&out.Attributes) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LDAPPasswordIdentityProvider. +func (in *LDAPPasswordIdentityProvider) DeepCopy() *LDAPPasswordIdentityProvider { + if in == nil { + return nil + } + out := new(LDAPPasswordIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeaderElection) DeepCopyInto(out *LeaderElection) { *out = *in @@ -1352,6 +1657,22 @@ func (in *LeaderElection) DeepCopy() *LeaderElection { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalSecretReference) DeepCopyInto(out *LocalSecretReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalSecretReference. +func (in *LocalSecretReference) DeepCopy() *LocalSecretReference { + if in == nil { + return nil + } + out := new(LocalSecretReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamedCertificate) DeepCopyInto(out *NamedCertificate) { *out = *in @@ -1379,8 +1700,8 @@ func (in *Network) DeepCopyInto(out *Network) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -1438,6 +1759,16 @@ func (in *NetworkList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { *out = *in + if in.ClusterNetwork != nil { + in, out := &in.ClusterNetwork, &out.ClusterNetwork + *out = make([]ClusterNetworkEntry, len(*in)) + copy(*out, *in) + } + if in.ServiceNetwork != nil { + in, out := &in.ServiceNetwork, &out.ServiceNetwork + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1454,6 +1785,16 @@ func (in *NetworkSpec) DeepCopy() *NetworkSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkStatus) DeepCopyInto(out *NetworkStatus) { *out = *in + if in.ClusterNetwork != nil { + in, out := &in.ClusterNetwork, &out.ClusterNetwork + *out = make([]ClusterNetworkEntry, len(*in)) + copy(*out, *in) + } + if in.ServiceNetwork != nil { + in, out := &in.ServiceNetwork, &out.ServiceNetwork + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1472,7 +1813,7 @@ func (in *OAuth) DeepCopyInto(out *OAuth) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status return } @@ -1495,6 +1836,23 @@ func (in *OAuth) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OAuthIdentityProvider) DeepCopyInto(out *OAuthIdentityProvider) { + *out = *in + in.ProviderConfig.DeepCopyInto(&out.ProviderConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuthIdentityProvider. +func (in *OAuthIdentityProvider) DeepCopy() *OAuthIdentityProvider { + if in == nil { + return nil + } + out := new(OAuthIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OAuthList) DeepCopyInto(out *OAuthList) { *out = *in @@ -1528,9 +1886,37 @@ func (in *OAuthList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OAuthRemoteConnectionInfo) DeepCopyInto(out *OAuthRemoteConnectionInfo) { + *out = *in + out.CA = in.CA + out.TLSClientCert = in.TLSClientCert + out.TLSClientKey = in.TLSClientKey + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuthRemoteConnectionInfo. +func (in *OAuthRemoteConnectionInfo) DeepCopy() *OAuthRemoteConnectionInfo { + if in == nil { + return nil + } + out := new(OAuthRemoteConnectionInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OAuthSpec) DeepCopyInto(out *OAuthSpec) { *out = *in + if in.IdentityProviders != nil { + in, out := &in.IdentityProviders, &out.IdentityProviders + *out = make([]OAuthIdentityProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.TokenConfig = in.TokenConfig + out.Templates = in.Templates return } @@ -1560,6 +1946,104 @@ func (in *OAuthStatus) DeepCopy() *OAuthStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OAuthTemplates) DeepCopyInto(out *OAuthTemplates) { + *out = *in + out.Login = in.Login + out.ProviderSelection = in.ProviderSelection + out.Error = in.Error + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuthTemplates. +func (in *OAuthTemplates) DeepCopy() *OAuthTemplates { + if in == nil { + return nil + } + out := new(OAuthTemplates) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenIDClaims) DeepCopyInto(out *OpenIDClaims) { + *out = *in + if in.PreferredUsername != nil { + in, out := &in.PreferredUsername, &out.PreferredUsername + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Email != nil { + in, out := &in.Email, &out.Email + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenIDClaims. +func (in *OpenIDClaims) DeepCopy() *OpenIDClaims { + if in == nil { + return nil + } + out := new(OpenIDClaims) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenIDIdentityProvider) DeepCopyInto(out *OpenIDIdentityProvider) { + *out = *in + out.CA = in.CA + out.ClientSecret = in.ClientSecret + if in.ExtraScopes != nil { + in, out := &in.ExtraScopes, &out.ExtraScopes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExtraAuthorizeParameters != nil { + in, out := &in.ExtraAuthorizeParameters, &out.ExtraAuthorizeParameters + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + out.URLs = in.URLs + in.Claims.DeepCopyInto(&out.Claims) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenIDIdentityProvider. +func (in *OpenIDIdentityProvider) DeepCopy() *OpenIDIdentityProvider { + if in == nil { + return nil + } + out := new(OpenIDIdentityProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenIDURLs) DeepCopyInto(out *OpenIDURLs) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenIDURLs. +func (in *OpenIDURLs) DeepCopy() *OpenIDURLs { + if in == nil { + return nil + } + out := new(OpenIDURLs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Project) DeepCopyInto(out *Project) { *out = *in @@ -1653,6 +2137,22 @@ func (in *ProjectStatus) DeepCopy() *ProjectStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyConfig) DeepCopyInto(out *ProxyConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfig. +func (in *ProxyConfig) DeepCopy() *ProxyConfig { + if in == nil { + return nil + } + out := new(ProxyConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RegistryLocation) DeepCopyInto(out *RegistryLocation) { *out = *in @@ -1686,6 +2186,48 @@ func (in *RemoteConnectionInfo) DeepCopy() *RemoteConnectionInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestHeaderIdentityProvider) DeepCopyInto(out *RequestHeaderIdentityProvider) { + *out = *in + out.ClientCA = in.ClientCA + if in.ClientCommonNames != nil { + in, out := &in.ClientCommonNames, &out.ClientCommonNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PreferredUsernameHeaders != nil { + in, out := &in.PreferredUsernameHeaders, &out.PreferredUsernameHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.NameHeaders != nil { + in, out := &in.NameHeaders, &out.NameHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EmailHeaders != nil { + in, out := &in.EmailHeaders, &out.EmailHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderIdentityProvider. +func (in *RequestHeaderIdentityProvider) DeepCopy() *RequestHeaderIdentityProvider { + if in == nil { + return nil + } + out := new(RequestHeaderIdentityProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Scheduling) DeepCopyInto(out *Scheduling) { *out = *in @@ -1841,6 +2383,22 @@ func (in *StringSourceSpec) DeepCopy() *StringSourceSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TokenConfig) DeepCopyInto(out *TokenConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenConfig. +func (in *TokenConfig) DeepCopy() *TokenConfig { + if in == nil { + return nil + } + out := new(TokenConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Update) DeepCopyInto(out *Update) { *out = *in @@ -1856,3 +2414,45 @@ func (in *Update) DeepCopy() *Update { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpdateHistory) DeepCopyInto(out *UpdateHistory) { + *out = *in + in.StartedTime.DeepCopyInto(&out.StartedTime) + if in.CompletionTime != nil { + in, out := &in.CompletionTime, &out.CompletionTime + if *in == nil { + *out = nil + } else { + *out = (*in).DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateHistory. +func (in *UpdateHistory) DeepCopy() *UpdateHistory { + if in == nil { + return nil + } + out := new(UpdateHistory) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookTokenAuthenticator) DeepCopyInto(out *WebhookTokenAuthenticator) { + *out = *in + out.KubeConfig = in.KubeConfig + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookTokenAuthenticator. +func (in *WebhookTokenAuthenticator) DeepCopy() *WebhookTokenAuthenticator { + if in == nil { + return nil + } + out := new(WebhookTokenAuthenticator) + in.DeepCopyInto(out) + return out +}