diff --git a/PROJECT b/PROJECT index 54844738f..39fc24f3e 100644 --- a/PROJECT +++ b/PROJECT @@ -27,6 +27,14 @@ resources: kind: IstioRevision path: github.com/istio-ecosystem/sail-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: sailoperator.io + kind: IstioRevisionTag + path: github.com/istio-ecosystem/sail-operator/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: false diff --git a/api/v1alpha1/istiorevisiontags_types.go b/api/v1alpha1/istiorevisiontags_types.go new file mode 100644 index 000000000..8d2788564 --- /dev/null +++ b/api/v1alpha1/istiorevisiontags_types.go @@ -0,0 +1,204 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + IstioRevisionTagKind = "IstioRevisionTag" + DefaultRevisionTag = "default" +) + +// IstioRevisionTagSpec defines the desired state of IstioRevisionTag +type IstioRevisionTagSpec struct { + // +kubebuilder:validation:Required + TargetRef IstioRevisionTagTargetReference `json:"targetRef"` +} + +// IstioRevisionTagTargetReference can reference either Istio or IstioRevision objects in the cluster. +type IstioRevisionTagTargetReference struct { + // Kind is the kind of the target resource. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Required + Kind string `json:"kind"` + + // Name is the name of the target resource. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Required + Name string `json:"name"` +} + +// IstioRevisionStatus defines the observed state of IstioRevision +type IstioRevisionTagStatus struct { + // ObservedGeneration is the most recent generation observed for this + // IstioRevisionTag object. It corresponds to the object's generation, which is + // updated on mutation by the API Server. The information in the status + // pertains to this particular generation of the object. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Represents the latest available observations of the object's current state. + Conditions []IstioRevisionTagCondition `json:"conditions,omitempty"` + + // Reports the current state of the object. + State IstioRevisionTagConditionReason `json:"state,omitempty"` + + // IstiodNamespace stores the namespace of the corresponding Istiod instance + IstiodNamespace string `json:"istiodNamespace"` + + // IstioRevision stores the name of the referenced IstioRevision + IstioRevision string `json:"istioRevision"` +} + +// GetCondition returns the condition of the specified type +func (s *IstioRevisionTagStatus) GetCondition(conditionType IstioRevisionTagConditionType) IstioRevisionTagCondition { + if s != nil { + for i := range s.Conditions { + if s.Conditions[i].Type == conditionType { + return s.Conditions[i] + } + } + } + return IstioRevisionTagCondition{Type: conditionType, Status: metav1.ConditionUnknown} +} + +// SetCondition sets a specific condition in the list of conditions +func (s *IstioRevisionTagStatus) SetCondition(condition IstioRevisionTagCondition) { + var now time.Time + if testTime == nil { + now = time.Now() + } else { + now = *testTime + } + + // The lastTransitionTime only gets serialized out to the second. This can + // break update skipping, as the time in the resource returned from the client + // may not match the time in our cached status during a reconcile. We truncate + // here to save any problems down the line. + lastTransitionTime := metav1.NewTime(now.Truncate(time.Second)) + + for i, prevCondition := range s.Conditions { + if prevCondition.Type == condition.Type { + if prevCondition.Status != condition.Status { + condition.LastTransitionTime = lastTransitionTime + } else { + condition.LastTransitionTime = prevCondition.LastTransitionTime + } + s.Conditions[i] = condition + return + } + } + + // If the condition does not exist, initialize the lastTransitionTime + condition.LastTransitionTime = lastTransitionTime + s.Conditions = append(s.Conditions, condition) +} + +// IstioRevisionCondition represents a specific observation of the IstioRevision object's state. +type IstioRevisionTagCondition struct { + // The type of this condition. + Type IstioRevisionTagConditionType `json:"type,omitempty"` + + // The status of this condition. Can be True, False or Unknown. + Status metav1.ConditionStatus `json:"status,omitempty"` + + // Unique, single-word, CamelCase reason for the condition's last transition. + Reason IstioRevisionTagConditionReason `json:"reason,omitempty"` + + // Human-readable message indicating details about the last transition. + Message string `json:"message,omitempty"` + + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` +} + +// IstioRevisionConditionType represents the type of the condition. Condition stages are: +// Installed, Reconciled, Ready +type IstioRevisionTagConditionType string + +// IstioRevisionConditionReason represents a short message indicating how the condition came +// to be in its present state. +type IstioRevisionTagConditionReason string + +const ( + // IstioRevisionConditionReconciled signifies whether the controller has + // successfully reconciled the resources defined through the CR. + IstioRevisionTagConditionReconciled IstioRevisionTagConditionType = "Reconciled" + + // IstioRevisionTagNameAlreadyExists indicates that the a revision with the same name as the IstioRevisionTag already exists. + IstioRevisionTagReasonNameAlreadyExists IstioRevisionTagConditionReason = "NameAlreadyExists" + + // IstioRevisionTagReasonReferenceNotFound indicates that the resource referenced by the tag's TargetRef was not found + IstioRevisionTagReasonReferenceNotFound IstioRevisionTagConditionReason = "RefNotFound" + + // IstioRevisionReasonReconcileError indicates that the reconciliation of the resource has failed, but will be retried. + IstioRevisionTagReasonReconcileError IstioRevisionTagConditionReason = "ReconcileError" +) + +const ( + // IstioRevisionConditionInUse signifies whether any workload is configured to use the revision. + IstioRevisionTagConditionInUse IstioRevisionTagConditionType = "InUse" + + // IstioRevisionReasonReferencedByWorkloads indicates that the revision is referenced by at least one pod or namespace. + IstioRevisionTagReasonReferencedByWorkloads IstioRevisionTagConditionReason = "ReferencedByWorkloads" + + // IstioRevisionReasonNotReferenced indicates that the revision is not referenced by any pod or namespace. + IstioRevisionTagReasonNotReferenced IstioRevisionTagConditionReason = "NotReferencedByAnything" + + // IstioRevisionReasonUsageCheckFailed indicates that the operator could not check whether any workloads use the revision. + IstioRevisionTagReasonUsageCheckFailed IstioRevisionTagConditionReason = "UsageCheckFailed" +) + +const ( + // IstioRevisionTagReasonHealthy indicates that the revision tag has been successfully reconciled and is in use. + IstioRevisionTagReasonHealthy IstioRevisionTagConditionReason = "Healthy" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster,shortName=istiorevtag,categories=istio-io +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state",description="The current state of this object." +// +kubebuilder:printcolumn:name="In use",type="string",JSONPath=".status.conditions[?(@.type==\"InUse\")].status",description="Whether the tag is being used by workloads." +// +kubebuilder:printcolumn:name="Revision",type="string",JSONPath=".status.istioRevision",description="The IstioRevision this object is referencing." +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the object" + +// IstioRevisionTag references a Istio or IstioRevision object and serves as an alias for sidecar injection. +type IstioRevisionTag struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec IstioRevisionTagSpec `json:"spec,omitempty"` + Status IstioRevisionTagStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// IstioRevisionList contains a list of IstioRevision +type IstioRevisionTagList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IstioRevisionTag `json:"items"` +} + +func init() { + SchemeBuilder.Register(&IstioRevisionTag{}, &IstioRevisionTagList{}) +} diff --git a/api/v1alpha1/values_types.gen.go b/api/v1alpha1/values_types.gen.go index c2eea6744..be9cd2d1f 100644 --- a/api/v1alpha1/values_types.gen.go +++ b/api/v1alpha1/values_types.gen.go @@ -932,9 +932,7 @@ type Values struct { // // Deprecated: Marked as deprecated in pkg/apis/values_types.proto. IstiodRemote *IstiodRemoteConfig `json:"istiodRemote,omitempty"` - // Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration - // is created for each alias. - RevisionTags []string `json:"revisionTags,omitempty"` + // The name of the default revision in the cluster. DefaultRevision *string `json:"defaultRevision,omitempty"` // Specifies which installation configuration profile to apply. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a4261bfe4..502b18c4b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1209,6 +1209,134 @@ func (in *IstioRevisionStatus) DeepCopy() *IstioRevisionStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTag) DeepCopyInto(out *IstioRevisionTag) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTag. +func (in *IstioRevisionTag) DeepCopy() *IstioRevisionTag { + if in == nil { + return nil + } + out := new(IstioRevisionTag) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IstioRevisionTag) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTagCondition) DeepCopyInto(out *IstioRevisionTagCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTagCondition. +func (in *IstioRevisionTagCondition) DeepCopy() *IstioRevisionTagCondition { + if in == nil { + return nil + } + out := new(IstioRevisionTagCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTagList) DeepCopyInto(out *IstioRevisionTagList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IstioRevisionTag, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTagList. +func (in *IstioRevisionTagList) DeepCopy() *IstioRevisionTagList { + if in == nil { + return nil + } + out := new(IstioRevisionTagList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IstioRevisionTagList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTagSpec) DeepCopyInto(out *IstioRevisionTagSpec) { + *out = *in + out.TargetRef = in.TargetRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTagSpec. +func (in *IstioRevisionTagSpec) DeepCopy() *IstioRevisionTagSpec { + if in == nil { + return nil + } + out := new(IstioRevisionTagSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTagStatus) DeepCopyInto(out *IstioRevisionTagStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]IstioRevisionTagCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTagStatus. +func (in *IstioRevisionTagStatus) DeepCopy() *IstioRevisionTagStatus { + if in == nil { + return nil + } + out := new(IstioRevisionTagStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioRevisionTagTargetReference) DeepCopyInto(out *IstioRevisionTagTargetReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioRevisionTagTargetReference. +func (in *IstioRevisionTagTargetReference) DeepCopy() *IstioRevisionTagTargetReference { + if in == nil { + return nil + } + out := new(IstioRevisionTagTargetReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IstioSpec) DeepCopyInto(out *IstioSpec) { *out = *in @@ -4988,11 +5116,6 @@ func (in *Values) DeepCopyInto(out *Values) { *out = new(IstiodRemoteConfig) (*in).DeepCopyInto(*out) } - if in.RevisionTags != nil { - in, out := &in.RevisionTags, &out.RevisionTags - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.DefaultRevision != nil { in, out := &in.DefaultRevision, &out.DefaultRevision *out = new(string) diff --git a/bundle/manifests/sailoperator.clusterserviceversion.yaml b/bundle/manifests/sailoperator.clusterserviceversion.yaml index 445147a99..59aa172aa 100644 --- a/bundle/manifests/sailoperator.clusterserviceversion.yaml +++ b/bundle/manifests/sailoperator.clusterserviceversion.yaml @@ -217,6 +217,12 @@ spec: displayName: Helm Values path: values version: v1alpha1 + - description: IstioRevisionTag references a Istio or IstioRevision object and + serves as an alias for sidecar injection. + displayName: Istio Revision Tag + kind: IstioRevisionTag + name: istiorevisiontags.sailoperator.io + version: v1alpha1 - description: |- Istio represents an Istio Service Mesh deployment consisting of one or more control plane instances (represented by one or more IstioRevision objects). @@ -404,6 +410,32 @@ spec: - get - patch - update + - apiGroups: + - sailoperator.io + resources: + - istiorevisiontags + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - sailoperator.io + resources: + - istiorevisiontags/finalizers + verbs: + - update + - apiGroups: + - sailoperator.io + resources: + - istiorevisiontags/status + verbs: + - get + - patch + - update - apiGroups: - sailoperator.io resources: diff --git a/bundle/manifests/sailoperator.io_istiorevisions.yaml b/bundle/manifests/sailoperator.io_istiorevisions.yaml index 29d602daf..b1340e61f 100644 --- a/bundle/manifests/sailoperator.io_istiorevisions.yaml +++ b/bundle/manifests/sailoperator.io_istiorevisions.yaml @@ -9177,13 +9177,6 @@ spec: description: Identifies the revision this installation is associated with. type: string - revisionTags: - description: |- - Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration - is created for each alias. - items: - type: string - type: array sidecarInjectorWebhook: description: Configuration for the sidecar injector webhook. properties: diff --git a/bundle/manifests/sailoperator.io_istiorevisiontags.yaml b/bundle/manifests/sailoperator.io_istiorevisiontags.yaml new file mode 100644 index 000000000..5268c89dc --- /dev/null +++ b/bundle/manifests/sailoperator.io_istiorevisiontags.yaml @@ -0,0 +1,149 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + creationTimestamp: null + name: istiorevisiontags.sailoperator.io +spec: + group: sailoperator.io + names: + categories: + - istio-io + kind: IstioRevisionTag + listKind: IstioRevisionTagList + plural: istiorevisiontags + shortNames: + - istiorevtag + singular: istiorevisiontag + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The current state of this object. + jsonPath: .status.state + name: Status + type: string + - description: Whether the tag is being used by workloads. + jsonPath: .status.conditions[?(@.type=="InUse")].status + name: In use + type: string + - description: The IstioRevision this object is referencing. + jsonPath: .status.istioRevision + name: Revision + type: string + - description: The age of the object + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: IstioRevisionTag references a Istio or IstioRevision object and + serves as an alias for sidecar injection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioRevisionTagSpec defines the desired state of IstioRevisionTag + properties: + targetRef: + description: IstioRevisionTagTargetReference can reference either + Istio or IstioRevision objects in the cluster. + properties: + kind: + description: Kind is the kind of the target resource. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: IstioRevisionStatus defines the observed state of IstioRevision + properties: + conditions: + description: Represents the latest available observations of the object's + current state. + items: + description: IstioRevisionCondition represents a specific observation + of the IstioRevision object's state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + the last transition. + type: string + reason: + description: Unique, single-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: The status of this condition. Can be True, False + or Unknown. + type: string + type: + description: The type of this condition. + type: string + type: object + type: array + istioRevision: + description: IstioRevision stores the name of the referenced IstioRevision + type: string + istiodNamespace: + description: IstiodNamespace stores the namespace of the corresponding + Istiod instance + type: string + observedGeneration: + description: |- + ObservedGeneration is the most recent generation observed for this + IstioRevisionTag object. It corresponds to the object's generation, which is + updated on mutation by the API Server. The information in the status + pertains to this particular generation of the object. + format: int64 + type: integer + state: + description: Reports the current state of the object. + type: string + required: + - istioRevision + - istiodNamespace + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/sailoperator.io_istios.yaml b/bundle/manifests/sailoperator.io_istios.yaml index 61a82141b..1bb97aec9 100644 --- a/bundle/manifests/sailoperator.io_istios.yaml +++ b/bundle/manifests/sailoperator.io_istios.yaml @@ -9246,13 +9246,6 @@ spec: description: Identifies the revision this installation is associated with. type: string - revisionTags: - description: |- - Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration - is created for each alias. - items: - type: string - type: array sidecarInjectorWebhook: description: Configuration for the sidecar injector webhook. properties: diff --git a/chart/crds/sailoperator.io_istiorevisions.yaml b/chart/crds/sailoperator.io_istiorevisions.yaml index 5708dbe4d..6b6209ed2 100644 --- a/chart/crds/sailoperator.io_istiorevisions.yaml +++ b/chart/crds/sailoperator.io_istiorevisions.yaml @@ -9177,13 +9177,6 @@ spec: description: Identifies the revision this installation is associated with. type: string - revisionTags: - description: |- - Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration - is created for each alias. - items: - type: string - type: array sidecarInjectorWebhook: description: Configuration for the sidecar injector webhook. properties: diff --git a/chart/crds/sailoperator.io_istiorevisiontags.yaml b/chart/crds/sailoperator.io_istiorevisiontags.yaml new file mode 100644 index 000000000..d96fdd6ec --- /dev/null +++ b/chart/crds/sailoperator.io_istiorevisiontags.yaml @@ -0,0 +1,143 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: istiorevisiontags.sailoperator.io +spec: + group: sailoperator.io + names: + categories: + - istio-io + kind: IstioRevisionTag + listKind: IstioRevisionTagList + plural: istiorevisiontags + shortNames: + - istiorevtag + singular: istiorevisiontag + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The current state of this object. + jsonPath: .status.state + name: Status + type: string + - description: Whether the tag is being used by workloads. + jsonPath: .status.conditions[?(@.type=="InUse")].status + name: In use + type: string + - description: The IstioRevision this object is referencing. + jsonPath: .status.istioRevision + name: Revision + type: string + - description: The age of the object + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: IstioRevisionTag references a Istio or IstioRevision object and + serves as an alias for sidecar injection. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioRevisionTagSpec defines the desired state of IstioRevisionTag + properties: + targetRef: + description: IstioRevisionTagTargetReference can reference either + Istio or IstioRevision objects in the cluster. + properties: + kind: + description: Kind is the kind of the target resource. + maxLength: 253 + minLength: 1 + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: IstioRevisionStatus defines the observed state of IstioRevision + properties: + conditions: + description: Represents the latest available observations of the object's + current state. + items: + description: IstioRevisionCondition represents a specific observation + of the IstioRevision object's state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + the last transition. + type: string + reason: + description: Unique, single-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: The status of this condition. Can be True, False + or Unknown. + type: string + type: + description: The type of this condition. + type: string + type: object + type: array + istioRevision: + description: IstioRevision stores the name of the referenced IstioRevision + type: string + istiodNamespace: + description: IstiodNamespace stores the namespace of the corresponding + Istiod instance + type: string + observedGeneration: + description: |- + ObservedGeneration is the most recent generation observed for this + IstioRevisionTag object. It corresponds to the object's generation, which is + updated on mutation by the API Server. The information in the status + pertains to this particular generation of the object. + format: int64 + type: integer + state: + description: Reports the current state of the object. + type: string + required: + - istioRevision + - istiodNamespace + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/chart/crds/sailoperator.io_istios.yaml b/chart/crds/sailoperator.io_istios.yaml index 51b234a6d..e71fe5a43 100644 --- a/chart/crds/sailoperator.io_istios.yaml +++ b/chart/crds/sailoperator.io_istios.yaml @@ -9246,13 +9246,6 @@ spec: description: Identifies the revision this installation is associated with. type: string - revisionTags: - description: |- - Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration - is created for each alias. - items: - type: string - type: array sidecarInjectorWebhook: description: Configuration for the sidecar injector webhook. properties: diff --git a/chart/samples/istio-sample-gw-api.yaml b/chart/samples/istio-sample-gw-api.yaml new file mode 100644 index 000000000..251977305 --- /dev/null +++ b/chart/samples/istio-sample-gw-api.yaml @@ -0,0 +1,16 @@ +apiVersion: sailoperator.io/v1alpha1 +kind: Istio +metadata: + name: gateway-controller +spec: + version: v1.23.0 + namespace: gateway-controller + updateStrategy: + type: InPlace + inactiveRevisionDeletionGracePeriodSeconds: 30 + values: + pilot: + resources: + requests: + cpu: 100m + memory: 1024Mi diff --git a/chart/samples/istio-sample-revisionbased.yaml b/chart/samples/istio-sample-revisionbased.yaml new file mode 100644 index 000000000..47fb2f055 --- /dev/null +++ b/chart/samples/istio-sample-revisionbased.yaml @@ -0,0 +1,18 @@ +apiVersion: sailoperator.io/v1alpha1 +kind: Istio +metadata: + name: default +spec: + version: v1.23.2 + namespace: istio-system + updateStrategy: + type: RevisionBased +--- +apiVersion: sailoperator.io/v1alpha1 +kind: IstioRevisionTag +metadata: + name: default +spec: + targetRef: + kind: Istio + name: default diff --git a/chart/templates/rbac/role.yaml b/chart/templates/rbac/role.yaml index b5165226e..28c2a0bb6 100644 --- a/chart/templates/rbac/role.yaml +++ b/chart/templates/rbac/role.yaml @@ -82,6 +82,32 @@ rules: - get - patch - update +- apiGroups: + - sailoperator.io + resources: + - istiorevisiontags + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - sailoperator.io + resources: + - istiorevisiontags/finalizers + verbs: + - update +- apiGroups: + - sailoperator.io + resources: + - istiorevisiontags/status + verbs: + - get + - patch + - update - apiGroups: - sailoperator.io resources: diff --git a/cmd/main.go b/cmd/main.go index 1d8b4cac2..873648e9e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,6 +23,7 @@ import ( "github.com/istio-ecosystem/sail-operator/controllers/istio" "github.com/istio-ecosystem/sail-operator/controllers/istiocni" "github.com/istio-ecosystem/sail-operator/controllers/istiorevision" + "github.com/istio-ecosystem/sail-operator/controllers/istiorevisiontag" "github.com/istio-ecosystem/sail-operator/controllers/webhook" "github.com/istio-ecosystem/sail-operator/pkg/config" "github.com/istio-ecosystem/sail-operator/pkg/enqueuelogger" @@ -150,6 +151,13 @@ func main() { os.Exit(1) } + err = istiorevisiontag.NewReconciler(reconcilerCfg, mgr.GetClient(), mgr.GetScheme(), chartManager). + SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "IstioRevisionTag") + os.Exit(1) + } + err = istiocni.NewReconciler(reconcilerCfg, mgr.GetClient(), mgr.GetScheme(), chartManager). SetupWithManager(mgr) if err != nil { diff --git a/controllers/istio/istio_controller.go b/controllers/istio/istio_controller.go index b013c9bf1..b492f39e4 100644 --- a/controllers/istio/istio_controller.go +++ b/controllers/istio/istio_controller.go @@ -140,14 +140,14 @@ func getPruningGracePeriod(istio *v1alpha1.Istio) time.Duration { func (r *Reconciler) getActiveRevision(ctx context.Context, istio *v1alpha1.Istio) (v1alpha1.IstioRevision, error) { rev := v1alpha1.IstioRevision{} - err := r.Client.Get(ctx, getActiveRevisionKey(istio), &rev) + err := r.Client.Get(ctx, GetActiveRevisionKey(istio), &rev) if err != nil { return rev, fmt.Errorf("get failed: %w", err) } return rev, nil } -func getActiveRevisionKey(istio *v1alpha1.Istio) types.NamespacedName { +func GetActiveRevisionKey(istio *v1alpha1.Istio) types.NamespacedName { return types.NamespacedName{ Name: getActiveRevisionName(istio), } diff --git a/controllers/istiorevision/istiorevision_controller.go b/controllers/istiorevision/istiorevision_controller.go index fb022237c..bd9968af8 100644 --- a/controllers/istiorevision/istiorevision_controller.go +++ b/controllers/istiorevision/istiorevision_controller.go @@ -24,6 +24,7 @@ import ( "github.com/go-logr/logr" "github.com/istio-ecosystem/sail-operator/api/v1alpha1" + "github.com/istio-ecosystem/sail-operator/controllers/istio" "github.com/istio-ecosystem/sail-operator/pkg/config" "github.com/istio-ecosystem/sail-operator/pkg/constants" "github.com/istio-ecosystem/sail-operator/pkg/enqueuelogger" @@ -56,15 +57,6 @@ import ( "istio.io/istio/pkg/ptr" ) -const ( - IstioInjectionLabel = "istio-injection" - IstioInjectionEnabledValue = "enabled" - IstioRevLabel = "istio.io/rev" - IstioSidecarInjectLabel = "sidecar.istio.io/inject" - - istiodChartName = "istiod" -) - // Reconciler reconciles an IstioRevision object type Reconciler struct { client.Client @@ -152,6 +144,11 @@ func (r *Reconciler) validate(ctx context.Context, rev *v1alpha1.IstioRevision) if rev.Spec.Values.Global == nil || rev.Spec.Values.Global.IstioNamespace == nil || *rev.Spec.Values.Global.IstioNamespace != rev.Spec.Namespace { return reconciler.NewValidationError("spec.values.global.istioNamespace does not match spec.namespace") } + + if tagExists, err := validation.IstioRevisionTagExists(ctx, r.Client, rev.Name); tagExists || err != nil { + return reconciler.NewValidationError("an IstioRevisionTag exists with this name") + } + return nil } @@ -169,22 +166,22 @@ func (r *Reconciler) installHelmCharts(ctx context.Context, rev *v1alpha1.IstioR _, err := r.ChartManager.UpgradeOrInstallChart(ctx, r.getChartDir(rev), values, rev.Spec.Namespace, getReleaseName(rev), ownerReference) if err != nil { - return fmt.Errorf("failed to install/update Helm chart %q: %w", istiodChartName, err) + return fmt.Errorf("failed to install/update Helm chart %q: %w", constants.IstiodChartName, err) } return nil } func getReleaseName(rev *v1alpha1.IstioRevision) string { - return fmt.Sprintf("%s-%s", rev.Name, istiodChartName) + return fmt.Sprintf("%s-%s", rev.Name, constants.IstiodChartName) } func (r *Reconciler) getChartDir(rev *v1alpha1.IstioRevision) string { - return path.Join(r.Config.ResourceDirectory, rev.Spec.Version, "charts", istiodChartName) + return path.Join(r.Config.ResourceDirectory, rev.Spec.Version, "charts", constants.IstiodChartName) } func (r *Reconciler) uninstallHelmCharts(ctx context.Context, rev *v1alpha1.IstioRevision) error { if _, err := r.ChartManager.UninstallChart(ctx, getReleaseName(rev), rev.Spec.Namespace); err != nil { - return fmt.Errorf("failed to uninstall Helm chart %q: %w", istiodChartName, err) + return fmt.Errorf("failed to uninstall Helm chart %q: %w", constants.IstiodChartName, err) } return nil } @@ -212,6 +209,10 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { // The handler triggers the reconciliation of the referenced IstioRevision CR so that its InUse condition is updated. podHandler := wrapEventHandler(logger, handler.EnqueueRequestsFromMapFunc(r.mapPodToReconcileRequest)) + // revisionTagHandler handles IstioRevisionTags that reference the IstioRevision CR via their targetRef. + // The handler triggers the reconciliation of the referenced IstioRevision CR so that its InUse condition is updated. + revisionTagHandler := wrapEventHandler(logger, handler.EnqueueRequestsFromMapFunc(r.mapRevisionTagToReconcileRequest)) + return ctrl.NewControllerManagedBy(mgr). WithOptions(controller.Options{ LogConstructor: func(req *reconcile.Request) logr.Logger { @@ -244,6 +245,9 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { // +lint-watches:ignore: Pod (not found in charts, but must be watched to reconcile IstioRevision when a pod references it) Watches(&corev1.Pod{}, podHandler, builder.WithPredicates(ignoreStatusChange())). + // +lint-watches:ignore: IstioRevisionTag (not found in charts, but must be watched to reconcile IstioRevision when a pod references it) + Watches(&v1alpha1.IstioRevisionTag{}, revisionTagHandler). + // cluster-scoped resources Watches(&rbacv1.ClusterRole{}, ownedResourceHandler). Watches(&rbacv1.ClusterRoleBinding{}, ownedResourceHandler). @@ -393,6 +397,18 @@ func (r *Reconciler) isRevisionReferencedByWorkloads(ctx context.Context, rev *v log := logf.FromContext(ctx) nsList := corev1.NamespaceList{} nsMap := map[string]corev1.Namespace{} + // if an IstioRevision is referenced by a revisionTag, it's considered as InUse + revisionTagList := v1alpha1.IstioRevisionTagList{} + if err := r.Client.List(ctx, &revisionTagList); err != nil { + return false, fmt.Errorf("failed to list IstioRevisionTags: %w", err) + } + for _, tag := range revisionTagList.Items { + if tag.Status.IstioRevision == rev.Name { + log.V(2).Info("Revision is referenced by IstioRevisionTag", "IstioRevisionTag", tag.Name) + return true, nil + } + } + if err := r.Client.List(ctx, &nsList); err != nil { // TODO: can we optimize this by specifying a label selector return false, fmt.Errorf("failed to list namespaces: %w", err) } @@ -435,10 +451,10 @@ func podReferencesRevision(pod corev1.Pod, ns corev1.Namespace, rev *v1alpha1.Is } func getReferencedRevisionFromNamespace(labels map[string]string) string { - if labels[IstioInjectionLabel] == IstioInjectionEnabledValue { + if labels[constants.IstioInjectionLabel] == constants.IstioInjectionEnabledValue { return v1alpha1.DefaultRevision } - revision := labels[IstioRevLabel] + revision := labels[constants.IstioRevLabel] if revision != "" { return revision } @@ -449,21 +465,21 @@ func getReferencedRevisionFromNamespace(labels map[string]string) string { func getReferencedRevisionFromPod(podLabels, podAnnotations, nsLabels map[string]string) string { // if pod was already injected, the revision that did the injection is specified in the istio.io/rev annotation - revision := podAnnotations[IstioRevLabel] + revision := podAnnotations[constants.IstioRevLabel] if revision != "" { return revision } // pod is marked for injection by a specific revision, but wasn't injected (e.g. because it was created before the revision was applied) revisionFromNamespace := getReferencedRevisionFromNamespace(nsLabels) - if podLabels[IstioSidecarInjectLabel] != "false" { + if podLabels[constants.IstioSidecarInjectLabel] != "false" { if revisionFromNamespace != "" { return revisionFromNamespace } - revisionFromPod := podLabels[IstioRevLabel] + revisionFromPod := podLabels[constants.IstioRevLabel] if revisionFromPod != "" { return revisionFromPod - } else if podLabels[IstioSidecarInjectLabel] == "true" { + } else if podLabels[constants.IstioSidecarInjectLabel] == "true" { return v1alpha1.DefaultRevision } } @@ -534,6 +550,22 @@ func (r *Reconciler) mapPodToReconcileRequest(ctx context.Context, pod client.Ob return nil } +func (r *Reconciler) mapRevisionTagToReconcileRequest(ctx context.Context, revisionTag client.Object) []reconcile.Request { + tag, ok := revisionTag.(*v1alpha1.IstioRevisionTag) + if ok { + if tag.Spec.TargetRef.Kind == v1alpha1.IstioRevisionKind { + return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: tag.Spec.TargetRef.Name}}} + } else if tag.Spec.TargetRef.Kind == v1alpha1.IstioKind { + i := &v1alpha1.Istio{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: tag.Spec.TargetRef.Name}, i); err != nil { + return nil + } + return []reconcile.Request{{NamespacedName: istio.GetActiveRevisionKey(i)}} + } + } + return nil +} + // ignoreStatusChange returns a predicate that ignores watch events where only the resource status changes; if // there are any other changes to the resource, the event is not ignored. // This ensures that the controller doesn't reconcile the entire IstioRevision every time the status of an owned diff --git a/controllers/istiorevisiontag/istiorevisiontag_controller.go b/controllers/istiorevisiontag/istiorevisiontag_controller.go new file mode 100644 index 000000000..4e58b4f91 --- /dev/null +++ b/controllers/istiorevisiontag/istiorevisiontag_controller.go @@ -0,0 +1,570 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package istiorevisiontag + +import ( + "context" + "errors" + "fmt" + "path" + "reflect" + + "github.com/go-logr/logr" + "github.com/istio-ecosystem/sail-operator/api/v1alpha1" + "github.com/istio-ecosystem/sail-operator/pkg/config" + "github.com/istio-ecosystem/sail-operator/pkg/constants" + "github.com/istio-ecosystem/sail-operator/pkg/enqueuelogger" + "github.com/istio-ecosystem/sail-operator/pkg/errlist" + "github.com/istio-ecosystem/sail-operator/pkg/helm" + "github.com/istio-ecosystem/sail-operator/pkg/kube" + "github.com/istio-ecosystem/sail-operator/pkg/reconciler" + admissionv1 "k8s.io/api/admissionregistration/v1" + autoscalingv2 "k8s.io/api/autoscaling/v2" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "istio.io/istio/pkg/ptr" +) + +const ( + revisionTagsChartName = "revisiontags" + + sailOperatorReferencedRevisionLabel = "sailoperator.io/referenced-revision" +) + +// Reconciler reconciles an IstioRevisionTag object +type Reconciler struct { + client.Client + Scheme *runtime.Scheme + Config config.ReconcilerConfig + ChartManager *helm.ChartManager +} + +func NewReconciler(reconcilerCfg config.ReconcilerConfig, client client.Client, scheme *runtime.Scheme, chartManager *helm.ChartManager) *Reconciler { + return &Reconciler{ + Client: client, + Scheme: scheme, + Config: reconcilerCfg, + ChartManager: chartManager, + } +} + +// +kubebuilder:rbac:groups=sailoperator.io,resources=istiorevisiontags,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=sailoperator.io,resources=istiorevisiontags/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=sailoperator.io,resources=istiorevisiontags/finalizers,verbs=update +// +kubebuilder:rbac:groups="admissionregistration.k8s.io",resources=mutatingwebhookconfigurations,verbs="*" +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile +func (r *Reconciler) Reconcile(ctx context.Context, tag *v1alpha1.IstioRevisionTag) (ctrl.Result, error) { + log := logf.FromContext(ctx).WithValues("IstioRevisionTag", tag.Name) + + rev, reconcileErr := r.doReconcile(ctx, tag) + + log.Info("Reconciliation done. Updating labels and status.") + labelsErr := r.updateLabels(ctx, tag, rev) + statusErr := r.updateStatus(ctx, tag, rev, reconcileErr) + + reconcileErr = UnpackReconcilerError(reconcileErr) + + return ctrl.Result{}, errors.Join(reconcileErr, labelsErr, statusErr) +} + +func (r *Reconciler) doReconcile(ctx context.Context, tag *v1alpha1.IstioRevisionTag) (*v1alpha1.IstioRevision, error) { + log := logf.FromContext(ctx).WithValues("IstioRevisionTag", tag.Name) + if err := r.validate(ctx, tag); err != nil { + return nil, err + } + + log.Info("Retrieving referenced IstioRevision for IstioRevisionTag") + rev, err := r.getIstioRevision(ctx, tag.Spec.TargetRef) + if rev == nil || err != nil { + return nil, err + } + + log.Info("Installing Helm chart") + return rev, r.installHelmCharts(ctx, tag, rev) +} + +func (r *Reconciler) Finalize(ctx context.Context, tag *v1alpha1.IstioRevisionTag) error { + return r.uninstallHelmCharts(ctx, tag) +} + +func (r *Reconciler) validate(ctx context.Context, tag *v1alpha1.IstioRevisionTag) error { + if tag.Spec.TargetRef.Kind == "" || tag.Spec.TargetRef.Name == "" { + return reconciler.NewValidationError("spec.targetRef not set") + } + rev := v1alpha1.IstioRevision{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: tag.Name}, &rev); err == nil { + return NewNameAlreadyExistsError("there is an IstioRevision with this name", err) + } else if !apierrors.IsNotFound(err) { + return err + } + if tag.Spec.TargetRef.Kind == v1alpha1.IstioKind { + i := v1alpha1.Istio{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: tag.Spec.TargetRef.Name}, &i); err != nil { + if apierrors.IsNotFound(err) { + return NewReferenceNotFoundError("referenced Istio resource does not exist", err) + } + return reconciler.NewValidationError("failed to get referenced Istio resource: " + err.Error()) + } + } else if tag.Spec.TargetRef.Kind == v1alpha1.IstioRevisionKind { + if err := r.Client.Get(ctx, types.NamespacedName{Name: tag.Spec.TargetRef.Name}, &rev); err != nil { + if apierrors.IsNotFound(err) { + return NewReferenceNotFoundError("referenced IstioRevision resource does not exist", err) + } + return reconciler.NewValidationError("failed to get referenced IstioRevision resource: " + err.Error()) + } + } + return nil +} + +func (r *Reconciler) getIstioRevision(ctx context.Context, ref v1alpha1.IstioRevisionTagTargetReference) (*v1alpha1.IstioRevision, error) { + var revisionName string + if ref.Kind == v1alpha1.IstioRevisionKind { + revisionName = ref.Name + } else if ref.Kind == v1alpha1.IstioKind { + i := v1alpha1.Istio{} + err := r.Client.Get(ctx, types.NamespacedName{Name: ref.Name}, &i) + if err != nil { + return nil, err + } + if i.Status.ActiveRevisionName == "" { + return nil, reconciler.NewTransitoryError("referenced Istio has no active revision") + } + revisionName = i.Status.ActiveRevisionName + } else { + return nil, reconciler.NewValidationError("unknown targetRef.kind") + } + + rev := v1alpha1.IstioRevision{} + err := r.Client.Get(ctx, types.NamespacedName{Name: revisionName}, &rev) + if err != nil { + return nil, err + } + return &rev, nil +} + +func (r *Reconciler) installHelmCharts(ctx context.Context, tag *v1alpha1.IstioRevisionTag, rev *v1alpha1.IstioRevision) error { + ownerReference := metav1.OwnerReference{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: v1alpha1.IstioRevisionTagKind, + Name: tag.Name, + UID: tag.UID, + Controller: ptr.Of(true), + BlockOwnerDeletion: ptr.Of(true), + } + + // TODO: add in values not in spec + rev.Spec.Values.RevisionTags = []string{tag.Name} + values := helm.FromValues(rev.Spec.Values) + + _, err := r.ChartManager.UpgradeOrInstallChart(ctx, r.getChartDir(rev), + values, rev.Spec.Namespace, getReleaseName(tag), ownerReference) + if err != nil { + return fmt.Errorf("failed to install/update Helm chart %q: %w", revisionTagsChartName, err) + } + return nil +} + +func getReleaseName(tag *v1alpha1.IstioRevisionTag) string { + return fmt.Sprintf("%s-%s", tag.Name, revisionTagsChartName) +} + +func (r *Reconciler) getChartDir(tag *v1alpha1.IstioRevision) string { + return path.Join(r.Config.ResourceDirectory, tag.Spec.Version, "charts", revisionTagsChartName) +} + +func (r *Reconciler) uninstallHelmCharts(ctx context.Context, tag *v1alpha1.IstioRevisionTag) error { + if _, err := r.ChartManager.UninstallChart(ctx, getReleaseName(tag), tag.Status.IstiodNamespace); err != nil { + return fmt.Errorf("failed to uninstall Helm chart %q: %w", revisionTagsChartName, err) + } + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + logger := mgr.GetLogger().WithName("ctrlr").WithName("revtag") + + // mainObjectHandler handles the IstioRevisionTag watch events + mainObjectHandler := wrapEventHandler(logger, &handler.EnqueueRequestForObject{}) + + // ownedResourceHandler handles resources that are owned by the IstioRevisionTag CR + ownedResourceHandler := wrapEventHandler( + logger, handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &v1alpha1.IstioRevisionTag{}, handler.OnlyControllerOwner())) + + // operatorResourcesHandler handles watch events from operator CRDs Istio and IstioRevision + operatorResourcesHandler := wrapEventHandler(logger, handler.EnqueueRequestsFromMapFunc(r.mapOperatorResourceToReconcileRequest)) + // nsHandler triggers reconciliation in two cases: + // - when a namespace that references the IstioRevisionTag CR via the istio.io/rev + // or istio-injection labels is updated, so that the InUse condition of + // the IstioRevisionTag CR is updated. + nsHandler := wrapEventHandler(logger, handler.EnqueueRequestsFromMapFunc(r.mapNamespaceToReconcileRequest)) + + // podHandler handles pods that reference the IstioRevisionTag CR via the istio.io/rev or sidecar.istio.io/inject labels. + // The handler triggers the reconciliation of the referenced IstioRevision CR so that its InUse condition is updated. + podHandler := wrapEventHandler(logger, handler.EnqueueRequestsFromMapFunc(r.mapPodToReconcileRequest)) + + return ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.Options{ + LogConstructor: func(req *reconcile.Request) logr.Logger { + log := logger + if req != nil { + log = log.WithValues("IstioRevisionTag", req.Name) + } + return log + }, + }). + // we use the Watches function instead of For(), so that we can wrap the handler so that events that cause the object to be enqueued are logged + Watches(&v1alpha1.IstioRevisionTag{}, mainObjectHandler). + Named("istiorevisiontag"). + // watches related to in-use detection + Watches(&corev1.Namespace{}, nsHandler, builder.WithPredicates(ignoreStatusChange())). + Watches(&corev1.Pod{}, podHandler, builder.WithPredicates(ignoreStatusChange())). + + // cluster-scoped resources + Watches(&v1alpha1.Istio{}, operatorResourcesHandler). + Watches(&v1alpha1.IstioRevision{}, operatorResourcesHandler). + Watches(&admissionv1.MutatingWebhookConfiguration{}, ownedResourceHandler). + Complete(reconciler.NewStandardReconcilerWithFinalizer[*v1alpha1.IstioRevisionTag](r.Client, r.Reconcile, r.Finalize, constants.FinalizerName)) +} + +func (r *Reconciler) determineStatus(ctx context.Context, tag *v1alpha1.IstioRevisionTag, + rev *v1alpha1.IstioRevision, reconcileErr error, +) (v1alpha1.IstioRevisionTagStatus, error) { + var errs errlist.Builder + reconciledCondition := r.determineReconciledCondition(reconcileErr) + + inUseCondition, err := r.determineInUseCondition(ctx, tag) + errs.Add(err) + + status := *tag.Status.DeepCopy() + status.ObservedGeneration = tag.Generation + if reconciledCondition.Status == metav1.ConditionTrue && rev != nil { + status.IstiodNamespace = rev.Spec.Namespace + status.IstioRevision = rev.Name + } + status.SetCondition(reconciledCondition) + status.SetCondition(inUseCondition) + status.State = deriveState(reconciledCondition, inUseCondition) + return status, errs.Error() +} + +func (r *Reconciler) updateStatus(ctx context.Context, tag *v1alpha1.IstioRevisionTag, rev *v1alpha1.IstioRevision, reconcileErr error) error { + var errs errlist.Builder + + status, err := r.determineStatus(ctx, tag, rev, reconcileErr) + if err != nil { + errs.Add(fmt.Errorf("failed to determine status: %w", err)) + } + + if !reflect.DeepEqual(tag.Status, status) { + if err := r.Client.Status().Patch(ctx, tag, kube.NewStatusPatch(status)); err != nil { + errs.Add(fmt.Errorf("failed to patch status: %w", err)) + } + } + return errs.Error() +} + +func deriveState(reconciledCondition, inUseCondition v1alpha1.IstioRevisionTagCondition) v1alpha1.IstioRevisionTagConditionReason { + if reconciledCondition.Status != metav1.ConditionTrue { + return reconciledCondition.Reason + } + if inUseCondition.Status != metav1.ConditionTrue { + return inUseCondition.Reason + } + return v1alpha1.IstioRevisionTagReasonHealthy +} + +func (r *Reconciler) determineReconciledCondition(err error) v1alpha1.IstioRevisionTagCondition { + c := v1alpha1.IstioRevisionTagCondition{Type: v1alpha1.IstioRevisionTagConditionReconciled} + + if err == nil { + c.Status = metav1.ConditionTrue + } else if IsNameAlreadyExistsError(err) { + c.Status = metav1.ConditionFalse + c.Reason = v1alpha1.IstioRevisionTagReasonNameAlreadyExists + c.Message = err.Error() + } else if IsReferenceNotFoundError(err) { + c.Status = metav1.ConditionFalse + c.Reason = v1alpha1.IstioRevisionTagReasonReferenceNotFound + c.Message = err.Error() + } else { + c.Status = metav1.ConditionFalse + c.Reason = v1alpha1.IstioRevisionTagReasonReconcileError + c.Message = fmt.Sprintf("error reconciling resource: %v", err) + } + return c +} + +func (r *Reconciler) determineInUseCondition(ctx context.Context, tag *v1alpha1.IstioRevisionTag) (v1alpha1.IstioRevisionTagCondition, error) { + c := v1alpha1.IstioRevisionTagCondition{Type: v1alpha1.IstioRevisionTagConditionInUse} + + isReferenced, err := r.isRevisionTagReferencedByWorkloads(ctx, tag) + if err == nil { + if isReferenced { + c.Status = metav1.ConditionTrue + c.Reason = v1alpha1.IstioRevisionTagReasonReferencedByWorkloads + c.Message = "Referenced by at least one pod or namespace" + } else { + c.Status = metav1.ConditionFalse + c.Reason = v1alpha1.IstioRevisionTagReasonNotReferenced + c.Message = "Not referenced by any pod or namespace" + } + return c, nil + } + c.Status = metav1.ConditionUnknown + c.Reason = v1alpha1.IstioRevisionTagReasonUsageCheckFailed + c.Message = fmt.Sprintf("failed to determine if revision tag is in use: %v", err) + return c, fmt.Errorf("failed to determine if IstioRevisionTag is in use: %w", err) +} + +func (r *Reconciler) updateLabels(ctx context.Context, tag *v1alpha1.IstioRevisionTag, rev *v1alpha1.IstioRevision) error { + updatedTag := tag.DeepCopy() + if rev == nil { + delete(updatedTag.Labels, sailOperatorReferencedRevisionLabel) + } else { + if updatedTag.Labels == nil { + updatedTag.Labels = make(map[string]string, 1) + } + updatedTag.Labels[sailOperatorReferencedRevisionLabel] = rev.Name + } + return r.Patch(ctx, updatedTag, client.MergeFrom(tag)) +} + +func (r *Reconciler) isRevisionTagReferencedByWorkloads(ctx context.Context, tag *v1alpha1.IstioRevisionTag) (bool, error) { + log := logf.FromContext(ctx) + nsList := corev1.NamespaceList{} + nsMap := map[string]corev1.Namespace{} + if err := r.Client.List(ctx, &nsList); err != nil { // TODO: can we optimize this by specifying a label selector + return false, fmt.Errorf("failed to list namespaces: %w", err) + } + for _, ns := range nsList.Items { + if namespaceReferencesRevisionTag(ns, tag) { + log.V(2).Info("RevisionTag is referenced by Namespace", "Namespace", ns.Name) + return true, nil + } + nsMap[ns.Name] = ns + } + + podList := corev1.PodList{} + if err := r.Client.List(ctx, &podList); err != nil { // TODO: can we optimize this by specifying a label selector + return false, fmt.Errorf("failed to list pods: %w", err) + } + for _, pod := range podList.Items { + if podReferencesRevisionTag(pod, tag) { + log.V(2).Info("RevisionTag is referenced by Pod", "Pod", client.ObjectKeyFromObject(&pod)) + return true, nil + } + } + + rev, err := r.getIstioRevision(ctx, tag.Spec.TargetRef) + if err != nil { + return false, err + } + + if tag.Name == v1alpha1.DefaultRevision && rev.Spec.Values != nil && + rev.Spec.Values.SidecarInjectorWebhook != nil && + rev.Spec.Values.SidecarInjectorWebhook.EnableNamespacesByDefault != nil && + *rev.Spec.Values.SidecarInjectorWebhook.EnableNamespacesByDefault { + return true, nil + } + + log.V(2).Info("RevisionTag is not referenced by any Pod or Namespace") + return false, nil +} + +func namespaceReferencesRevisionTag(ns corev1.Namespace, tag *v1alpha1.IstioRevisionTag) bool { + return tag.Name == getReferencedRevisionFromNamespace(ns.Labels) +} + +func podReferencesRevisionTag(pod corev1.Pod, tag *v1alpha1.IstioRevisionTag) bool { + return tag.Name == getReferencedRevisionTagFromPod(pod.GetLabels()) +} + +func getReferencedRevisionFromNamespace(labels map[string]string) string { + if labels[constants.IstioInjectionLabel] == constants.IstioInjectionEnabledValue { + return v1alpha1.DefaultRevision + } + revision := labels[constants.IstioRevLabel] + if revision != "" { + return revision + } + // TODO: if .Values.sidecarInjectorWebhook.enableNamespacesByDefault is true, then all namespaces except system namespaces should use the "default" revision + + return "" +} + +func getReferencedRevisionTagFromPod(podLabels map[string]string) string { + // we only look at pod labels to identify injection intent, as the annotation only references the real revision name instead of the tag + if podLabels[constants.IstioInjectionLabel] == constants.IstioInjectionEnabledValue { + return v1alpha1.DefaultRevision + } else if podLabels[constants.IstioSidecarInjectLabel] != "false" && podLabels[constants.IstioRevLabel] != "" { + return podLabels[constants.IstioRevLabel] + } + + return "" +} + +func (r *Reconciler) mapNamespaceToReconcileRequest(ctx context.Context, ns client.Object) []reconcile.Request { + var requests []reconcile.Request + + // Check if the namespace references an IstioRevisionTag in its labels + revision := getReferencedRevisionFromNamespace(ns.GetLabels()) + if revision != "" { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: revision}}) + } + return requests +} + +func (r *Reconciler) mapPodToReconcileRequest(ctx context.Context, pod client.Object) []reconcile.Request { + revision := getReferencedRevisionTagFromPod(pod.GetLabels()) + if revision != "" { + return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: revision}}} + } + return nil +} + +func (r *Reconciler) mapOperatorResourceToReconcileRequest(ctx context.Context, obj client.Object) []reconcile.Request { + var revisionName string + if i, ok := obj.(*v1alpha1.Istio); ok && i.Status.ActiveRevisionName != "" { + revisionName = i.Status.ActiveRevisionName + } else if rev, ok := obj.(*v1alpha1.IstioRevision); ok { + revisionName = rev.Name + } else { + return nil + } + tags := v1alpha1.IstioRevisionTagList{} + labelSelector := map[string]string{ + sailOperatorReferencedRevisionLabel: revisionName, + } + err := r.Client.List(ctx, &tags, &client.ListOptions{LabelSelector: labels.SelectorFromSet(labelSelector)}) + if err != nil { + return nil + } + requests := []reconcile.Request{} + for _, revision := range tags.Items { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: revision.Name}}) + } + return requests +} + +// ignoreStatusChange returns a predicate that ignores watch events where only the resource status changes; if +// there are any other changes to the resource, the event is not ignored. +// This ensures that the controller doesn't reconcile the entire IstioRevisionTag every time the status of an owned +// resource is updated. Without this predicate, the controller would continuously reconcile the IstioRevisionTag +// because the status.currentMetrics of the HorizontalPodAutoscaler object was updated. +func ignoreStatusChange() predicate.Funcs { + return predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + return specWasUpdated(e.ObjectOld, e.ObjectNew) || + !reflect.DeepEqual(e.ObjectNew.GetLabels(), e.ObjectOld.GetLabels()) || + !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations()) || + !reflect.DeepEqual(e.ObjectNew.GetOwnerReferences(), e.ObjectOld.GetOwnerReferences()) || + !reflect.DeepEqual(e.ObjectNew.GetFinalizers(), e.ObjectOld.GetFinalizers()) + }, + } +} + +func specWasUpdated(oldObject client.Object, newObject client.Object) bool { + // for HPAs, k8s doesn't set metadata.generation, so we actually have to check whether the spec was updated + if oldHpa, ok := oldObject.(*autoscalingv2.HorizontalPodAutoscaler); ok { + if newHpa, ok := newObject.(*autoscalingv2.HorizontalPodAutoscaler); ok { + return !reflect.DeepEqual(oldHpa.Spec, newHpa.Spec) + } + } + + // for other resources, comparing the metadata.generation suffices + return oldObject.GetGeneration() != newObject.GetGeneration() +} + +func wrapEventHandler(logger logr.Logger, handler handler.EventHandler) handler.EventHandler { + return enqueuelogger.WrapIfNecessary(v1alpha1.IstioRevisionTagKind, logger, handler) +} + +func UnpackReconcilerError(err error) error { + // unpack reconcileError + if IsNameAlreadyExistsError(err) { + e := NameAlreadyExistsError{} + errors.As(err, &e) + return e.OriginalError + } else if IsReferenceNotFoundError(err) { + e := ReferenceNotFoundError{} + errors.As(err, &e) + return e.OriginalError + } + return err +} + +type NameAlreadyExistsError struct { + Message string + OriginalError error +} + +func (err NameAlreadyExistsError) Error() string { + return err.Message +} + +func NewNameAlreadyExistsError(message string, originalError error) NameAlreadyExistsError { + return NameAlreadyExistsError{ + Message: message, + OriginalError: originalError, + } +} + +func IsNameAlreadyExistsError(err error) bool { + if _, ok := err.(NameAlreadyExistsError); ok { + return true + } + return false +} + +type ReferenceNotFoundError struct { + Message string + OriginalError error +} + +func (err ReferenceNotFoundError) Error() string { + return err.Message +} + +func NewReferenceNotFoundError(message string, originalError error) ReferenceNotFoundError { + return ReferenceNotFoundError{ + Message: message, + OriginalError: originalError, + } +} + +func IsReferenceNotFoundError(err error) bool { + if _, ok := err.(ReferenceNotFoundError); ok { + return true + } + return false +} diff --git a/docs/api-reference/sailoperator.io.md b/docs/api-reference/sailoperator.io.md index 6fdbe9628..4f828fc51 100644 --- a/docs/api-reference/sailoperator.io.md +++ b/docs/api-reference/sailoperator.io.md @@ -15,6 +15,8 @@ Package v1alpha1 contains API Schema definitions for the sailoperator.io v1alpha - [IstioList](#istiolist) - [IstioRevision](#istiorevision) - [IstioRevisionList](#istiorevisionlist) +- [IstioRevisionTag](#istiorevisiontag) +- [IstioRevisionTagList](#istiorevisiontaglist) @@ -875,6 +877,163 @@ _Appears in:_ | `state` _[IstioRevisionConditionReason](#istiorevisionconditionreason)_ | Reports the current state of the object. | | | +#### IstioRevisionTag + + + +IstioRevisionTag references a Istio or IstioRevision object and serves as an alias for sidecar injection. + + + +_Appears in:_ +- [IstioRevisionTagList](#istiorevisiontaglist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `sailoperator.io/v1alpha1` | | | +| `kind` _string_ | `IstioRevisionTag` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[IstioRevisionTagSpec](#istiorevisiontagspec)_ | | | | +| `status` _[IstioRevisionTagStatus](#istiorevisiontagstatus)_ | | | | + + +#### IstioRevisionTagCondition + + + +IstioRevisionCondition represents a specific observation of the IstioRevision object's state. + + + +_Appears in:_ +- [IstioRevisionTagStatus](#istiorevisiontagstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `type` _[IstioRevisionTagConditionType](#istiorevisiontagconditiontype)_ | The type of this condition. | | | +| `status` _[ConditionStatus](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#conditionstatus-v1-meta)_ | The status of this condition. Can be True, False or Unknown. | | | +| `reason` _[IstioRevisionTagConditionReason](#istiorevisiontagconditionreason)_ | Unique, single-word, CamelCase reason for the condition's last transition. | | | +| `message` _string_ | Human-readable message indicating details about the last transition. | | | +| `lastTransitionTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Last time the condition transitioned from one status to another. | | | + + +#### IstioRevisionTagConditionReason + +_Underlying type:_ _string_ + +IstioRevisionConditionReason represents a short message indicating how the condition came +to be in its present state. + + + +_Appears in:_ +- [IstioRevisionTagCondition](#istiorevisiontagcondition) +- [IstioRevisionTagStatus](#istiorevisiontagstatus) + +| Field | Description | +| --- | --- | +| `NameAlreadyExists` | IstioRevisionTagNameAlreadyExists indicates that the a revision with the same name as the IstioRevisionTag already exists. | +| `RefNotFound` | IstioRevisionTagReasonReferenceNotFound indicates that the resource referenced by the tag's TargetRef was not found | +| `ReconcileError` | IstioRevisionReasonReconcileError indicates that the reconciliation of the resource has failed, but will be retried. | +| `ReferencedByWorkloads` | IstioRevisionReasonReferencedByWorkloads indicates that the revision is referenced by at least one pod or namespace. | +| `NotReferencedByAnything` | IstioRevisionReasonNotReferenced indicates that the revision is not referenced by any pod or namespace. | +| `UsageCheckFailed` | IstioRevisionReasonUsageCheckFailed indicates that the operator could not check whether any workloads use the revision. | +| `Healthy` | IstioRevisionTagReasonHealthy indicates that the revision tag has been successfully reconciled and is in use. | + + +#### IstioRevisionTagConditionType + +_Underlying type:_ _string_ + +IstioRevisionConditionType represents the type of the condition. Condition stages are: +Installed, Reconciled, Ready + + + +_Appears in:_ +- [IstioRevisionTagCondition](#istiorevisiontagcondition) + +| Field | Description | +| --- | --- | +| `Reconciled` | IstioRevisionConditionReconciled signifies whether the controller has successfully reconciled the resources defined through the CR. | +| `InUse` | IstioRevisionConditionInUse signifies whether any workload is configured to use the revision. | + + +#### IstioRevisionTagList + + + +IstioRevisionList contains a list of IstioRevision + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `sailoperator.io/v1alpha1` | | | +| `kind` _string_ | `IstioRevisionTagList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[IstioRevisionTag](#istiorevisiontag) array_ | | | | + + +#### IstioRevisionTagSpec + + + +IstioRevisionTagSpec defines the desired state of IstioRevisionTag + + + +_Appears in:_ +- [IstioRevisionTag](#istiorevisiontag) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `targetRef` _[IstioRevisionTagTargetReference](#istiorevisiontagtargetreference)_ | | | Required: \{\} | + + +#### IstioRevisionTagStatus + + + +IstioRevisionStatus defines the observed state of IstioRevision + + + +_Appears in:_ +- [IstioRevisionTag](#istiorevisiontag) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the most recent generation observed for this IstioRevisionTag object. It corresponds to the object's generation, which is updated on mutation by the API Server. The information in the status pertains to this particular generation of the object. | | | +| `conditions` _[IstioRevisionTagCondition](#istiorevisiontagcondition) array_ | Represents the latest available observations of the object's current state. | | | +| `state` _[IstioRevisionTagConditionReason](#istiorevisiontagconditionreason)_ | Reports the current state of the object. | | | +| `istiodNamespace` _string_ | IstiodNamespace stores the namespace of the corresponding Istiod instance | | | +| `istioRevision` _string_ | IstioRevision stores the name of the referenced IstioRevision | | | + + +#### IstioRevisionTagTargetReference + + + +IstioRevisionTagTargetReference can reference either Istio or IstioRevision objects in the cluster. + + + +_Appears in:_ +- [IstioRevisionTagSpec](#istiorevisiontagspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `kind` _string_ | Kind is the kind of the target resource. | | MaxLength: 253 MinLength: 1 Required: \{\} | +| `name` _string_ | Name is the name of the target resource. | | MaxLength: 253 MinLength: 1 Required: \{\} | + + #### IstioSpec @@ -2924,7 +3083,6 @@ _Appears in:_ | `meshConfig` _[MeshConfig](#meshconfig)_ | Defines runtime configuration of components, including Istiod and istio-agent behavior. See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options. TODO can this import the real mesh config API? | | | | `base` _[BaseConfig](#baseconfig)_ | Configuration for the base component. | | | | `istiodRemote` _[IstiodRemoteConfig](#istiodremoteconfig)_ | Configuration for istiod-remote. DEPRECATED - istiod-remote chart is removed and replaced with `istio-discovery --set values.istiodRemote.enabled=true` Deprecated: Marked as deprecated in pkg/apis/values_types.proto. | | | -| `revisionTags` _string array_ | Specifies the aliases for the Istio control plane revision. A MutatingWebhookConfiguration is created for each alias. | | | | `defaultRevision` _string_ | The name of the default revision in the cluster. | | | | `profile` _string_ | Specifies which installation configuration profile to apply. | | | | `compatibilityVersion` _string_ | Specifies the compatibility version to use. When this is set, the control plane will be configured with the same defaults as the specified version. | | | diff --git a/hack/api_transformer/transform.yaml b/hack/api_transformer/transform.yaml index 6b77497ae..7266c1440 100644 --- a/hack/api_transformer/transform.yaml +++ b/hack/api_transformer/transform.yaml @@ -69,6 +69,7 @@ inputFiles: - Values.Gateways # operator doesn't support deployment of ingress/egress gateways - Values.OwnerName # operator sets this internally - Values.Ztunnel # ambient is not yet supported + - Values.RevisionTags # Revision Tags are only supported through IstioRevisionTag CRD - SidecarInjectorConfig.ObjectSelector # appears to be unused - CNIConfig.Enabled # CNI is enabled by the mere presence of the IstioCNI resource - CNIConfig.LogLevel # deprecated and replaced with CNIConfig.logging.level diff --git a/hack/download-charts.sh b/hack/download-charts.sh index ad01c97bf..c674cd93e 100755 --- a/hack/download-charts.sh +++ b/hack/download-charts.sh @@ -85,6 +85,9 @@ function patchIstioCharts() { echo "patching istio charts ${CHARTS_DIR}/cni/templates/clusterrole.yaml " # NOTE: everything in the patchIstioCharts should be here only temporarily, # until we push the required changes upstream + + # add permissions for CNI to use the privileged SCC. This has been added upstream in 1.23.0, + # so this can be removed once we remove support for versions <1.23 sed -i '0,/rules:/s//rules:\ - apiGroups: ["security.openshift.io"] \ resources: ["securitycontextconstraints"] \ @@ -115,6 +118,22 @@ function convertIstioProfiles() { done } +function createRevisionTagChart() { + mkdir -p "${CHARTS_DIR}/revisiontags/templates" + echo "apiVersion: v2 +appVersion: ${ISTIO_VERSION} +description: Helm chart for istio revision tags +name: revisiontags +sources: +- https://github.com/istio-ecosystem/sail-operator +version: 0.1.0 +" > "${CHARTS_DIR}/revisiontags/Chart.yaml" + cp "${CHARTS_DIR}/istiod/values.yaml" "${CHARTS_DIR}/revisiontags/values.yaml" + cp "${CHARTS_DIR}/istiod/templates/revision-tags.yaml" "${CHARTS_DIR}/revisiontags/templates/revision-tags.yaml" + cp "${CHARTS_DIR}/istiod/templates/zzz_profile.yaml" "${CHARTS_DIR}/revisiontags/templates/zzz_profile.yaml" +} + downloadIstioManifests patchIstioCharts -convertIstioProfiles \ No newline at end of file +convertIstioProfiles +createRevisionTagChart diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 39ca7fa06..34169e316 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -76,4 +76,19 @@ const ( // WebhookReadinessProbeTimeoutSecondsAnnotationKey is an annotation on the istio-sidecar-injection MutatingWebhookConfiguration that // specifies the timeout for the readiness probe WebhookReadinessProbeTimeoutSecondsAnnotationKey = MetadataNamespace + "/readinessProbe.timeoutSeconds" + + // IstioInjectionLabel is the label that is used to configure injection for the 'default' IstioRevision + IstioInjectionLabel = "istio-injection" + + // IstioInjectionEnabledValue is the value for IstioInjectionLabel + IstioInjectionEnabledValue = "enabled" + + // IstioRevLabel is the label that is used to configure injection for non-default IstioRevisions + IstioRevLabel = "istio.io/rev" + + // IstioSidecarInjectLabel is the label that is used to configure injection for specific workloads + IstioSidecarInjectLabel = "sidecar.istio.io/inject" + + // IstiodChartName is the name of the chart that installs istiod + IstiodChartName = "istiod" ) diff --git a/pkg/reconciler/errors.go b/pkg/reconciler/errors.go index 3fd452990..8e682e58e 100644 --- a/pkg/reconciler/errors.go +++ b/pkg/reconciler/errors.go @@ -32,3 +32,21 @@ func IsValidationError(err error) bool { e := &ValidationError{} return errors.As(err, &e) } + +// TransitoryError is an error returned by a Reconciler that will usually resolve itself when retrying, e.g. some resource not yet reconciled +type TransitoryError struct { + message string +} + +func (v TransitoryError) Error() string { + return "transitory error: " + v.message +} + +func NewTransitoryError(message string) error { + return &TransitoryError{message: message} +} + +func IsTransitoryError(err error) bool { + e := &TransitoryError{} + return errors.As(err, &e) +} diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go index d61cf03e7..8d8e7f600 100644 --- a/pkg/reconciler/reconciler.go +++ b/pkg/reconciler/reconciler.go @@ -99,6 +99,12 @@ func (r *StandardReconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) case errors.IsConflict(err): log.Info("Conflict detected. Retrying...") return ctrl.Result{Requeue: true}, nil + case errors.IsNotFound(err): + log.Info("Resource not found. Retrying...", "error", err) + return ctrl.Result{Requeue: true}, nil + case IsTransitoryError(err): + log.Info("Reconciliation failed. Retrying...", "error", err) + return ctrl.Result{Requeue: true}, nil case IsValidationError(err): log.Info("Validation failed", "error", err) return ctrl.Result{}, nil diff --git a/pkg/test/environment.go b/pkg/test/environment.go index 5f46853a5..ea379025f 100644 --- a/pkg/test/environment.go +++ b/pkg/test/environment.go @@ -40,6 +40,10 @@ func SetupEnv(logWriter io.Writer, installCRDs bool) (*envtest.Environment, clie ErrorIfCRDPathMissing: true, } + // disabling mutatingwebhooks to avoid failing calls to the injection webhooks + // once we implement mutatingwebhooks in the operator we might have to find another way + testEnv.ControlPlane.GetAPIServer().Configure().Append("disable-admission-plugins", "MutatingAdmissionWebhook") + cfg, err := testEnv.Start() if err != nil || cfg == nil { panic(err) diff --git a/pkg/validation/namespace.go b/pkg/validation/namespace.go index 8e95eb9b3..09c493059 100644 --- a/pkg/validation/namespace.go +++ b/pkg/validation/namespace.go @@ -18,6 +18,7 @@ import ( "context" "fmt" + "github.com/istio-ecosystem/sail-operator/api/v1alpha1" "github.com/istio-ecosystem/sail-operator/pkg/reconciler" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -39,3 +40,14 @@ func ValidateTargetNamespace(ctx context.Context, cl client.Client, namespace st } return nil } + +func IstioRevisionTagExists(ctx context.Context, cl client.Client, name string) (bool, error) { + tag := &v1alpha1.IstioRevisionTag{} + if err := cl.Get(ctx, types.NamespacedName{Name: name}, tag); err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/resources/latest/charts/revisiontags/Chart.yaml b/resources/latest/charts/revisiontags/Chart.yaml new file mode 100644 index 000000000..58d88fd57 --- /dev/null +++ b/resources/latest/charts/revisiontags/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +appVersion: latest +description: Helm chart for istio revision tags +name: revisiontags +sources: +- https://github.com/istio-ecosystem/sail-operator +version: 0.1.0 + diff --git a/resources/latest/charts/revisiontags/templates/revision-tags.yaml b/resources/latest/charts/revisiontags/templates/revision-tags.yaml new file mode 100644 index 000000000..31c221d30 --- /dev/null +++ b/resources/latest/charts/revisiontags/templates/revision-tags.yaml @@ -0,0 +1,151 @@ +# Adapted from istio-discovery/templates/mutatingwebhook.yaml +# Removed paths for legacy and default selectors since a revision tag +# is inherently created from a specific revision +# TODO BML istiodRemote.injectionURL is invalid to set if `istiodRemote.enabled` is false, we should express that. +{{- $whv := dict + "revision" .Values.revision + "injectionPath" .Values.istiodRemote.injectionPath + "injectionURL" .Values.istiodRemote.injectionURL + "reinvocationPolicy" .Values.sidecarInjectorWebhook.reinvocationPolicy + "namespace" .Release.Namespace }} +{{- define "core" }} +{{- /* Kubernetes unfortunately requires a unique name for the webhook in some newer versions, so we assign +a unique prefix to each. */}} +- name: {{.Prefix}}sidecar-injector.istio.io + clientConfig: + {{- if .injectionURL }} + url: "{{ .injectionURL }}" + {{- else }} + service: + name: istiod{{- if not (eq .revision "") }}-{{ .revision }}{{- end }} + namespace: {{ .namespace }} + path: "{{ .injectionPath }}" + port: 443 + {{- end }} + sideEffects: None + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Fail + admissionReviewVersions: ["v1"] +{{- end }} +# Not created if istiod is running remotely +{{- if not .Values.istiodRemote.enabled }} +{{- range $tagName := $.Values.revisionTags }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: +{{- if eq $.Release.Namespace "istio-system"}} + name: istio-revision-tag-{{ $tagName }} +{{- else }} + name: istio-revision-tag-{{ $tagName }}-{{ $.Release.Namespace }} +{{- end }} + labels: + istio.io/tag: {{ $tagName }} + istio.io/rev: {{ $.Values.revision | default "default" | quote }} + install.operator.istio.io/owning-resource: {{ $.Values.ownerName | default "unknown" }} + operator.istio.io/component: "Pilot" + app: sidecar-injector + release: {{ $.Release.Name }} + app.kubernetes.io/name: "istiod" + {{- include "istio.labels" $ | nindent 4 }} +{{- if $.Values.sidecarInjectorWebhookAnnotations }} + annotations: +{{ toYaml $.Values.sidecarInjectorWebhookAnnotations | indent 4 }} +{{- end }} +webhooks: +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: DoesNotExist + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + +{{- /* When the tag is "default" we want to create webhooks for the default revision */}} +{{- /* These webhooks should be kept in sync with istio-discovery/templates/mutatingwebhook.yaml */}} +{{- if (eq $tagName "default") }} + +{{- /* Case 1: Namespace selector enabled, and object selector is not injected */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: In + values: + - enabled + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + +{{- /* Case 2: no namespace label, but object selector is enabled (and revision label is not, which has priority) */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: In + values: + - "true" + - key: istio.io/rev + operator: DoesNotExist + +{{- if $.Values.sidecarInjectorWebhook.enableNamespacesByDefault }} +{{- /* Special case 3: no labels at all */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "auto.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","kube-public","kube-node-lease","local-path-storage"] + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist +{{- end }} + +{{- end }} +--- +{{- end }} +{{- end }} diff --git a/resources/latest/charts/revisiontags/templates/zzz_profile.yaml b/resources/latest/charts/revisiontags/templates/zzz_profile.yaml new file mode 100644 index 000000000..35623047c --- /dev/null +++ b/resources/latest/charts/revisiontags/templates/zzz_profile.yaml @@ -0,0 +1,74 @@ +{{/* +WARNING: DO NOT EDIT, THIS FILE IS A PROBABLY COPY. +The original version of this file is located at /manifests directory. +If you want to make a change in this file, edit the original one and run "make gen". + +Complex logic ahead... +We have three sets of values, in order of precedence (last wins): +1. The builtin values.yaml defaults +2. The profile the user selects +3. Users input (-f or --set) + +Unfortunately, Helm provides us (1) and (3) together (as .Values), making it hard to insert (2). + +However, we can workaround this by placing all of (1) under a specific key (.Values.defaults). +We can then merge the profile onto the defaults, then the user settings onto that. +Finally, we can set all of that under .Values so the chart behaves without awareness. +*/}} +{{- if $.Values.defaults}} +{{ fail (cat + "Setting with .default prefix found; remove it. For example, replace `--set defaults.hub=foo` with `--set hub=foo`. Defaults set:\n" + ($.Values.defaults | toYaml |nindent 4) +) }} +{{- end }} +{{- $defaults := $.Values._internal_defaults_do_not_set }} +{{- $_ := unset $.Values "_internal_defaults_do_not_set" }} +{{- $profile := dict }} +{{- with .Values.profile }} +{{- with $.Files.Get (printf "files/profile-%s.yaml" .)}} +{{- $profile = (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown profile" $.Values.profile) }} +{{- end }} +{{- end }} +{{- with .Values.compatibilityVersion }} +{{- with $.Files.Get (printf "files/profile-compatibility-version-%s.yaml" .) }} +{{- $ignore := mustMergeOverwrite $profile (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown compatibility version" $.Values.compatibilityVersion) }} +{{- end }} +{{- end }} +{{- if ($.Values.global).platform }} +{{- with $.Files.Get (printf "files/profile-platform-%s.yaml" ($.Values.global).platform) }} +{{- $ignore := mustMergeOverwrite $profile (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown platform" ($.Values.global).platform) }} +{{- end }} +{{- end }} +{{- if $profile }} +{{- $a := mustMergeOverwrite $defaults $profile }} +{{- end }} +# Flatten globals, if defined on a per-chart basis +{{- if false }} +{{- $a := mustMergeOverwrite $defaults ($profile.global) ($.Values.global | default dict) }} +{{- end }} +{{- $b := set $ "Values" (mustMergeOverwrite $defaults $.Values) }} + +{{/* +Labels that should be applied to ALL resources. +*/}} +{{- define "istio.labels" -}} +{{- if .Release.Service -}} +app.kubernetes.io/managed-by: {{ .Release.Service | quote }} +{{- end }} +{{- if .Release.Name }} +app.kubernetes.io/instance: {{ .Release.Name | quote }} +{{- end }} +app.kubernetes.io/part-of: "istio" +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +{{- if and .Chart.Name .Chart.Version }} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end -}} diff --git a/resources/latest/charts/revisiontags/values.yaml b/resources/latest/charts/revisiontags/values.yaml new file mode 100644 index 000000000..57e612e41 --- /dev/null +++ b/resources/latest/charts/revisiontags/values.yaml @@ -0,0 +1,539 @@ +# "_internal_defaults_do_not_set" is a workaround for Helm limitations. Users should NOT set "._internal_defaults_do_not_set" explicitly, but rather directly set the fields internally. +# For instance, instead of `--set _internal_defaults_do_not_set.foo=bar``, just set `--set foo=bar`. +_internal_defaults_do_not_set: + autoscaleEnabled: true + autoscaleMin: 1 + autoscaleMax: 5 + autoscaleBehavior: {} + replicaCount: 1 + rollingMaxSurge: 100% + rollingMaxUnavailable: 25% + + hub: "" + tag: "" + variant: "" + + # Can be a full hub/image:tag + image: pilot + traceSampling: 1.0 + + # Resources for a small pilot install + resources: + requests: + cpu: 500m + memory: 2048Mi + + # Set to `type: RuntimeDefault` to use the default profile if available. + seccompProfile: {} + + # Whether to use an existing CNI installation + cni: + enabled: false + provider: default + + # Additional container arguments + extraContainerArgs: [] + + env: {} + + # Settings related to the untaint controller + # This controller will remove `cni.istio.io/not-ready` from nodes when the istio-cni pod becomes ready + # It should be noted that cluster operator/owner is responsible for having the taint set by their infrastructure provider when new nodes are added to the cluster; the untaint controller does not taint nodes + taint: + # Controls whether or not the untaint controller is active + enabled: false + # What namespace the untaint controller should watch for istio-cni pods. This is only required when istio-cni is running in a different namespace than istiod + namespace: "" + + affinity: {} + + tolerations: [] + + cpu: + targetAverageUtilization: 80 + memory: {} + # targetAverageUtilization: 80 + + # Additional volumeMounts to the istiod container + volumeMounts: [] + + # Additional volumes to the istiod pod + volumes: [] + + # Inject initContainers into the istiod pod + initContainers: [] + + nodeSelector: {} + podAnnotations: {} + serviceAnnotations: {} + serviceAccountAnnotations: {} + sidecarInjectorWebhookAnnotations: {} + + topologySpreadConstraints: [] + + # You can use jwksResolverExtraRootCA to provide a root certificate + # in PEM format. This will then be trusted by pilot when resolving + # JWKS URIs. + jwksResolverExtraRootCA: "" + + # The following is used to limit how long a sidecar can be connected + # to a pilot. It balances out load across pilot instances at the cost of + # increasing system churn. + keepaliveMaxServerConnectionAge: 30m + + # Additional labels to apply to the deployment. + deploymentLabels: {} + + ## Mesh config settings + + # Install the mesh config map, generated from values.yaml. + # If false, pilot wil use default values (by default) or user-supplied values. + configMap: true + + # Additional labels to apply on the pod level for monitoring and logging configuration. + podLabels: {} + + # Setup how istiod Service is configured. See https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ipFamilyPolicy: "" + ipFamilies: [] + + # Ambient mode only. + # Set this if you install ztunnel to a different namespace from `istiod`. + # If set, `istiod` will allow connections from trusted node proxy ztunnels + # in the provided namespace. + # If unset, `istiod` will assume the trusted node proxy ztunnel resides + # in the same namespace as itself. + trustedZtunnelNamespace: "" + + sidecarInjectorWebhook: + # You can use the field called alwaysInjectSelector and neverInjectSelector which will always inject the sidecar or + # always skip the injection on pods that match that label selector, regardless of the global policy. + # See https://istio.io/docs/setup/kubernetes/additional-setup/sidecar-injection/#more-control-adding-exceptions + neverInjectSelector: [] + alwaysInjectSelector: [] + + # injectedAnnotations are additional annotations that will be added to the pod spec after injection + # This is primarily to support PSP annotations. For example, if you defined a PSP with the annotations: + # + # annotations: + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # + # The PSP controller would add corresponding annotations to the pod spec for each container. However, this happens before + # the inject adds additional containers, so we must specify them explicitly here. With the above example, we could specify: + # injectedAnnotations: + # container.apparmor.security.beta.kubernetes.io/istio-init: runtime/default + # container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default + injectedAnnotations: {} + + # This enables injection of sidecar in all namespaces, + # with the exception of namespaces with "istio-injection:disabled" annotation + # Only one environment should have this enabled. + enableNamespacesByDefault: false + + # Mutations that occur after the sidecar injector are not handled by default, as the Istio sidecar injector is only run + # once. For example, an OPA sidecar injected after the Istio sidecar will not have it's liveness/readiness probes rewritten. + # Setting this to `IfNeeded` will result in the sidecar injector being run again if additional mutations occur. + reinvocationPolicy: Never + + rewriteAppHTTPProbe: true + + # Templates defines a set of custom injection templates that can be used. For example, defining: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # Then starting a pod with the `inject.istio.io/templates: hello` annotation, will result in the pod + # being injected with the hello=world labels. + # This is intended for advanced configuration only; most users should use the built in template + templates: {} + + # Default templates specifies a set of default templates that are used in sidecar injection. + # By default, a template `sidecar` is always provided, which contains the template of default sidecar. + # To inject other additional templates, define it using the `templates` option, and add it to + # the default templates list. + # For example: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # defaultTemplates: ["sidecar", "hello"] + defaultTemplates: [] + istiodRemote: + # If `true`, indicates that this cluster/install should consume a "remote istiod" installation, + # and istiod itself will NOT be installed in this cluster - only the support resources necessary + # to utilize a remote instance. + enabled: false + # Sidecar injector mutating webhook configuration clientConfig.url value. + # For example: https://$remotePilotAddress:15017/inject + # The host should not refer to a service running in the cluster; use a service reference by specifying + # the clientConfig.service field instead. + injectionURL: "" + + # Sidecar injector mutating webhook configuration path value for the clientConfig.service field. + # Override to pass env variables, for example: /inject/cluster/remote/net/network2 + injectionPath: "/inject" + + injectionCABundle: "" + telemetry: + enabled: true + v2: + # For Null VM case now. + # This also enables metadata exchange. + enabled: true + # Indicate if prometheus stats filter is enabled or not + prometheus: + enabled: true + # stackdriver filter settings. + stackdriver: + enabled: false + # Revision is set as 'version' label and part of the resource names when installing multiple control planes. + revision: "" + + # Revision tags are aliases to Istio control plane revisions + revisionTags: [] + + # For Helm compatibility. + ownerName: "" + + # meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior + # See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options + meshConfig: + enablePrometheusMerge: true + + experimental: + stableValidationPolicy: false + + global: + # Used to locate istiod. + istioNamespace: istio-system + # List of cert-signers to allow "approve" action in the istio cluster role + # + # certSigners: + # - clusterissuers.cert-manager.io/istio-ca + certSigners: [] + # enable pod disruption budget for the control plane, which is used to + # ensure Istio control plane components are gradually upgraded or recovered. + defaultPodDisruptionBudget: + enabled: true + # The values aren't mutable due to a current PodDisruptionBudget limitation + # minAvailable: 1 + + # A minimal set of requested resources to applied to all deployments so that + # Horizontal Pod Autoscaler will be able to function (if set). + # Each component can overwrite these default values by adding its own resources + # block in the relevant section below and setting the desired resources values. + defaultResources: + requests: + cpu: 10m + # memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + + # Default hub for Istio images. + # Releases are published to docker hub under 'istio' project. + # Dev builds from prow are on gcr.io + hub: gcr.io/istio-testing + # Default tag for Istio images. + tag: 1.24-alpha.bb972b546125d3f001cc11114e9fc95486b891ec + # Variant of the image to use. + # Currently supported are: [debug, distroless] + variant: "" + + # Specify image pull policy if default behavior isn't desired. + # Default behavior: latest images will be Always else IfNotPresent. + imagePullPolicy: "" + + # ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace + # to use for pulling any images in pods that reference this ServiceAccount. + # For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) + # ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. + # Must be set for any cluster configured with private docker registry. + imagePullSecrets: [] + # - private-registry-key + + # Enabled by default in master for maximising testing. + istiod: + enableAnalysis: false + + # To output all istio components logs in json format by adding --log_as_json argument to each container argument + logAsJson: false + + # Comma-separated minimum per-scope logging level of messages to output, in the form of :,: + # The control plane has different scopes depending on component, but can configure default log level across all components + # If empty, default scope and level will be used as configured in code + logging: + level: "default:info" + + omitSidecarInjectorConfigMap: false + + # Configure whether Operator manages webhook configurations. The current behavior + # of Istiod is to manage its own webhook configurations. + # When this option is set as true, Istio Operator, instead of webhooks, manages the + # webhook configurations. When this option is set as false, webhooks manage their + # own webhook configurations. + operatorManageWebhooks: false + + # Custom DNS config for the pod to resolve names of services in other + # clusters. Use this to add additional search domains, and other settings. + # see + # https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#dns-config + # This does not apply to gateway pods as they typically need a different + # set of DNS settings than the normal application pods (e.g., in + # multicluster scenarios). + # NOTE: If using templates, follow the pattern in the commented example below. + #podDNSSearchNamespaces: + #- global + #- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global" + + # Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and + # system-node-critical, it is better to configure this in order to make sure your Istio pods + # will not be killed because of low priority class. + # Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + # for more detail. + priorityClassName: "" + + proxy: + image: proxyv2 + + # This controls the 'policy' in the sidecar injector. + autoInject: enabled + + # CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # cluster domain. Default value is "cluster.local". + clusterDomain: "cluster.local" + + # Per Component log level for proxy, applies to gateways and sidecars. If a component level is + # not set, then the global "logLevel" will be used. + componentLogLevel: "misc:error" + + # istio ingress capture allowlist + # examples: + # Redirect only selected ports: --includeInboundPorts="80,8080" + excludeInboundPorts: "" + includeInboundPorts: "*" + + # istio egress capture allowlist + # https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly + # example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" + # would only capture egress traffic on those two IP Ranges, all other outbound traffic would + # be allowed by the sidecar + includeIPRanges: "*" + excludeIPRanges: "" + includeOutboundPorts: "" + excludeOutboundPorts: "" + + # Log level for proxy, applies to gateways and sidecars. + # Expected values are: trace|debug|info|warning|error|critical|off + logLevel: warning + + # Specify the path to the outlier event log. + # Example: /dev/stdout + outlierLogPath: "" + + #If set to true, istio-proxy container will have privileged securityContext + privileged: false + + # The number of successive failed probes before indicating readiness failure. + readinessFailureThreshold: 4 + + # The initial delay for readiness probes in seconds. + readinessInitialDelaySeconds: 0 + + # The period between readiness probes. + readinessPeriodSeconds: 15 + + # Enables or disables a startup probe. + # For optimal startup times, changing this should be tied to the readiness probe values. + # + # If the probe is enabled, it is recommended to have delay=0s,period=15s,failureThreshold=4. + # This ensures the pod is marked ready immediately after the startup probe passes (which has a 1s poll interval), + # and doesn't spam the readiness endpoint too much + # + # If the probe is disabled, it is recommended to have delay=1s,period=2s,failureThreshold=30. + # This ensures the startup is reasonable fast (polling every 2s). 1s delay is used since the startup is not often ready instantly. + startupProbe: + enabled: true + failureThreshold: 600 # 10 minutes + + # Resources for the sidecar. + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 2000m + memory: 1024Mi + + # Default port for Pilot agent health checks. A value of 0 will disable health checking. + statusPort: 15020 + + # Specify which tracer to use. One of: zipkin, lightstep, datadog, stackdriver, none. + # If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. + tracer: "none" + + proxy_init: + # Base name for the proxy_init container, used to configure iptables. + image: proxyv2 + # Bypasses iptables idempotency handling, and attempts to apply iptables rules regardless of table state, which may cause unrecoverable failures. + # Do not use unless you need to work around an issue of the idempotency handling. This flag will be removed in future releases. + forceApplyIptables: false + + # configure remote pilot and istiod service and endpoint + remotePilotAddress: "" + + ############################################################################################## + # The following values are found in other charts. To effectively modify these values, make # + # make sure they are consistent across your Istio helm charts # + ############################################################################################## + + # The customized CA address to retrieve certificates for the pods in the cluster. + # CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. + # If not set explicitly, default to the Istio discovery address. + caAddress: "" + + # Enable control of remote clusters. + externalIstiod: false + + # Configure a remote cluster as the config cluster for an external istiod. + configCluster: false + + # configValidation enables the validation webhook for Istio configuration. + configValidation: true + + # Mesh ID means Mesh Identifier. It should be unique within the scope where + # meshes will interact with each other, but it is not required to be + # globally/universally unique. For example, if any of the following are true, + # then two meshes must have different Mesh IDs: + # - Meshes will have their telemetry aggregated in one place + # - Meshes will be federated together + # - Policy will be written referencing one mesh from the other + # + # If an administrator expects that any of these conditions may become true in + # the future, they should ensure their meshes have different Mesh IDs + # assigned. + # + # Within a multicluster mesh, each cluster must be (manually or auto) + # configured to have the same Mesh ID value. If an existing cluster 'joins' a + # multicluster mesh, it will need to be migrated to the new mesh ID. Details + # of migration TBD, and it may be a disruptive operation to change the Mesh + # ID post-install. + # + # If the mesh admin does not specify a value, Istio will use the value of the + # mesh's Trust Domain. The best practice is to select a proper Trust Domain + # value. + meshID: "" + + # Configure the mesh networks to be used by the Split Horizon EDS. + # + # The following example defines two networks with different endpoints association methods. + # For `network1` all endpoints that their IP belongs to the provided CIDR range will be + # mapped to network1. The gateway for this network example is specified by its public IP + # address and port. + # The second network, `network2`, in this example is defined differently with all endpoints + # retrieved through the specified Multi-Cluster registry being mapped to network2. The + # gateway is also defined differently with the name of the gateway service on the remote + # cluster. The public IP for the gateway will be determined from that remote service (only + # LoadBalancer gateway service type is currently supported, for a NodePort type gateway service, + # it still need to be configured manually). + # + # meshNetworks: + # network1: + # endpoints: + # - fromCidr: "192.168.0.1/24" + # gateways: + # - address: 1.1.1.1 + # port: 80 + # network2: + # endpoints: + # - fromRegistry: reg1 + # gateways: + # - registryServiceName: istio-ingressgateway.istio-system.svc.cluster.local + # port: 443 + # + meshNetworks: {} + + # Use the user-specified, secret volume mounted key and certs for Pilot and workloads. + mountMtlsCerts: false + + multiCluster: + # Set to true to connect two kubernetes clusters via their respective + # ingressgateway services when pods in each cluster cannot directly + # talk to one another. All clusters should be using Istio mTLS and must + # have a shared root CA for this model to work. + enabled: false + # Should be set to the name of the cluster this installation will run in. This is required for sidecar injection + # to properly label proxies + clusterName: "" + + # Network defines the network this cluster belong to. This name + # corresponds to the networks in the map of mesh networks. + network: "" + + # Configure the certificate provider for control plane communication. + # Currently, two providers are supported: "kubernetes" and "istiod". + # As some platforms may not have kubernetes signing APIs, + # Istiod is the default + pilotCertProvider: istiod + + sds: + # The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. + # When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the + # JWT is intended for the CA. + token: + aud: istio-ca + + sts: + # The service port used by Security Token Service (STS) server to handle token exchange requests. + # Setting this port to a non-zero value enables STS server. + servicePort: 0 + + # The name of the CA for workload certificates. + # For example, when caName=GkeWorkloadCertificate, GKE workload certificates + # will be used as the certificates for workloads. + # The default value is "" and when caName="", the CA will be configured by other + # mechanisms (e.g., environmental variable CA_PROVIDER). + caName: "" + + waypoint: + # Resources for the waypoint proxy. + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "2" + memory: 1Gi + + # If specified, affinity defines the scheduling constraints of waypoint pods. + affinity: {} + + # Topology Spread Constraints for the waypoint proxy. + topologySpreadConstraints: [] + + # Node labels for the waypoint proxy. + nodeSelector: {} + + # Tolerations for the waypoint proxy. + tolerations: [] + + base: + # For istioctl usage to disable istio config crds in base + enableIstioConfigCRDs: true + + # Gateway Settings + gateways: + # Define the security context for the pod. + # If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. + # On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. + securityContext: {} + + # Set to `type: RuntimeDefault` to use the default profile for templated gateways, if your container runtime supports it + seccompProfile: {} diff --git a/resources/v1.21.6/charts/revisiontags/Chart.yaml b/resources/v1.21.6/charts/revisiontags/Chart.yaml new file mode 100644 index 000000000..260de5046 --- /dev/null +++ b/resources/v1.21.6/charts/revisiontags/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +appVersion: v1.21.6 +description: Helm chart for istio revision tags +name: revisiontags +sources: +- https://github.com/istio-ecosystem/sail-operator +version: 0.1.0 + diff --git a/resources/v1.21.6/charts/revisiontags/templates/revision-tags.yaml b/resources/v1.21.6/charts/revisiontags/templates/revision-tags.yaml new file mode 100644 index 000000000..5884e18e3 --- /dev/null +++ b/resources/v1.21.6/charts/revisiontags/templates/revision-tags.yaml @@ -0,0 +1,141 @@ +# Adapted from istio-discovery/templates/mutatingwebhook.yaml +# Removed paths for legacy and default selectors since a revision tag +# is inherently created from a specific revision +{{- $whv := dict + "revision" .Values.revision + "injectionPath" .Values.istiodRemote.injectionPath + "injectionURL" .Values.istiodRemote.injectionURL + "reinvocationPolicy" .Values.sidecarInjectorWebhook.reinvocationPolicy + "namespace" .Release.Namespace }} +{{- define "core" }} +{{- /* Kubernetes unfortunately requires a unique name for the webhook in some newer versions, so we assign +a unique prefix to each. */}} +- name: {{.Prefix}}sidecar-injector.istio.io + clientConfig: + {{- if .injectionURL }} + url: "{{ .injectionURL }}" + {{- else }} + service: + name: istiod{{- if not (eq .revision "") }}-{{ .revision }}{{- end }} + namespace: {{ .namespace }} + path: "{{ .injectionPath }}" + port: 443 + {{- end }} + sideEffects: None + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Fail + admissionReviewVersions: ["v1beta1", "v1"] +{{- end }} +{{- range $tagName := $.Values.revisionTags }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: +{{- if eq $.Release.Namespace "istio-system"}} + name: istio-revision-tag-{{ $tagName }} +{{- else }} + name: istio-revision-tag-{{ $tagName }}-{{ $.Release.Namespace }} +{{- end }} + labels: + istio.io/tag: {{ $tagName }} + istio.io/rev: {{ $.Values.revision | default "default" | quote }} + install.operator.istio.io/owning-resource: {{ $.Values.ownerName | default "unknown" }} + operator.istio.io/component: "Pilot" + app: sidecar-injector + release: {{ $.Release.Name }} +webhooks: +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: DoesNotExist + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + +{{- /* When the tag is "default" we want to create webhooks for the default revision */}} +{{- /* These webhooks should be kept in sync with istio-discovery/templates/mutatingwebhook.yaml */}} +{{- if (eq $tagName "default") }} + +{{- /* Case 1: Namespace selector enabled, and object selector is not injected */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: In + values: + - enabled + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + +{{- /* Case 2: no namespace label, but object selector is enabled (and revision label is not, which has priority) */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: In + values: + - "true" + - key: istio.io/rev + operator: DoesNotExist + +{{- if $.Values.sidecarInjectorWebhook.enableNamespacesByDefault }} +{{- /* Special case 3: no labels at all */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "auto.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","kube-public","kube-node-lease","local-path-storage"] + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist +{{- end }} + +{{- end }} +--- +{{- end }} diff --git a/resources/v1.21.6/charts/revisiontags/templates/zzz_profile.yaml b/resources/v1.21.6/charts/revisiontags/templates/zzz_profile.yaml new file mode 100644 index 000000000..6588debf9 --- /dev/null +++ b/resources/v1.21.6/charts/revisiontags/templates/zzz_profile.yaml @@ -0,0 +1,34 @@ +{{/* +Complex logic ahead... +We have three sets of values, in order of precedence (last wins): +1. The builtin values.yaml defaults +2. The profile the user selects +3. Users input (-f or --set) + +Unfortunately, Helm provides us (1) and (3) together (as .Values), making it hard to insert (2). + +However, we can workaround this by placing all of (1) under a specific key (.Values.defaults). +We can then merge the profile onto the defaults, then the user settings onto that. +Finally, we can set all of that under .Values so the chart behaves without awareness. +*/}} +{{- $defaults := $.Values.defaults }} +{{- $_ := unset $.Values "defaults" }} +{{- $profile := dict }} +{{- with .Values.profile }} +{{- with $.Files.Get (printf "files/profile-%s.yaml" .)}} +{{- $profile = (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown profile" $.Values.profile) }} +{{- end }} +{{- end }} +{{- with .Values.compatibilityVersion }} +{{- with $.Files.Get (printf "files/profile-compatibility-version-%s.yaml" .) }} +{{- $ignore := mustMergeOverwrite $profile (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown compatibility version" $.Values.compatibilityVersion) }} +{{- end }} +{{- end }} +{{- if $profile }} +{{- $a := mustMergeOverwrite $defaults $profile }} +{{- end }} +{{- $b := set $ "Values" (mustMergeOverwrite $defaults $.Values) }} diff --git a/resources/v1.21.6/charts/revisiontags/values.yaml b/resources/v1.21.6/charts/revisiontags/values.yaml new file mode 100644 index 000000000..2a80fa50d --- /dev/null +++ b/resources/v1.21.6/charts/revisiontags/values.yaml @@ -0,0 +1,500 @@ +defaults: + #.Values.pilot for discovery and mesh wide config + + ## Discovery Settings + pilot: + autoscaleEnabled: true + autoscaleMin: 1 + autoscaleMax: 5 + autoscaleBehavior: {} + replicaCount: 1 + rollingMaxSurge: 100% + rollingMaxUnavailable: 25% + + hub: "" + tag: "" + variant: "" + + # Can be a full hub/image:tag + image: pilot + traceSampling: 1.0 + + # Resources for a small pilot install + resources: + requests: + cpu: 500m + memory: 2048Mi + + # Set to `type: RuntimeDefault` to use the default profile if available. + seccompProfile: {} + + # Additional container arguments + extraContainerArgs: [] + + env: {} + + affinity: {} + + tolerations: [] + + cpu: + targetAverageUtilization: 80 + memory: {} + # targetAverageUtilization: 80 + + # Additional volumeMounts to the istiod container + volumeMounts: [] + + # Additional volumes to the istiod pod + volumes: [] + + nodeSelector: {} + podAnnotations: {} + serviceAnnotations: {} + + topologySpreadConstraints: [] + + # You can use jwksResolverExtraRootCA to provide a root certificate + # in PEM format. This will then be trusted by pilot when resolving + # JWKS URIs. + jwksResolverExtraRootCA: "" + + # This is used to set the source of configuration for + # the associated address in configSource, if nothing is specified + # the default MCP is assumed. + configSource: + subscribedResources: [] + + plugins: [] + + # The following is used to limit how long a sidecar can be connected + # to a pilot. It balances out load across pilot instances at the cost of + # increasing system churn. + keepaliveMaxServerConnectionAge: 30m + + # Additional labels to apply to the deployment. + deploymentLabels: {} + + ## Mesh config settings + + # Install the mesh config map, generated from values.yaml. + # If false, pilot wil use default values (by default) or user-supplied values. + configMap: true + + # Additional labels to apply on the pod level for monitoring and logging configuration. + podLabels: {} + + # Setup how istiod Service is configured. See https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ipFamilyPolicy: "" + ipFamilies: [] + + sidecarInjectorWebhook: + # You can use the field called alwaysInjectSelector and neverInjectSelector which will always inject the sidecar or + # always skip the injection on pods that match that label selector, regardless of the global policy. + # See https://istio.io/docs/setup/kubernetes/additional-setup/sidecar-injection/#more-control-adding-exceptions + neverInjectSelector: [] + alwaysInjectSelector: [] + + # injectedAnnotations are additional annotations that will be added to the pod spec after injection + # This is primarily to support PSP annotations. For example, if you defined a PSP with the annotations: + # + # annotations: + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # + # The PSP controller would add corresponding annotations to the pod spec for each container. However, this happens before + # the inject adds additional containers, so we must specify them explicitly here. With the above example, we could specify: + # injectedAnnotations: + # container.apparmor.security.beta.kubernetes.io/istio-init: runtime/default + # container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default + injectedAnnotations: {} + + # This enables injection of sidecar in all namespaces, + # with the exception of namespaces with "istio-injection:disabled" annotation + # Only one environment should have this enabled. + enableNamespacesByDefault: false + + # Mutations that occur after the sidecar injector are not handled by default, as the Istio sidecar injector is only run + # once. For example, an OPA sidecar injected after the Istio sidecar will not have it's liveness/readiness probes rewritten. + # Setting this to `IfNeeded` will result in the sidecar injector being run again if additional mutations occur. + reinvocationPolicy: Never + + rewriteAppHTTPProbe: true + + # Templates defines a set of custom injection templates that can be used. For example, defining: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # Then starting a pod with the `inject.istio.io/templates: hello` annotation, will result in the pod + # being injected with the hello=world labels. + # This is intended for advanced configuration only; most users should use the built in template + templates: {} + + # Default templates specifies a set of default templates that are used in sidecar injection. + # By default, a template `sidecar` is always provided, which contains the template of default sidecar. + # To inject other additional templates, define it using the `templates` option, and add it to + # the default templates list. + # For example: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # defaultTemplates: ["sidecar", "hello"] + defaultTemplates: [] + istiodRemote: + # Sidecar injector mutating webhook configuration clientConfig.url value. + # For example: https://$remotePilotAddress:15017/inject + # The host should not refer to a service running in the cluster; use a service reference by specifying + # the clientConfig.service field instead. + injectionURL: "" + + # Sidecar injector mutating webhook configuration path value for the clientConfig.service field. + # Override to pass env variables, for example: /inject/cluster/remote/net/network2 + injectionPath: "/inject" + telemetry: + enabled: true + v2: + # For Null VM case now. + # This also enables metadata exchange. + enabled: true + # Indicate if prometheus stats filter is enabled or not + prometheus: + enabled: true + # stackdriver filter settings. + stackdriver: + enabled: false + # Revision is set as 'version' label and part of the resource names when installing multiple control planes. + revision: "" + + # Revision tags are aliases to Istio control plane revisions + revisionTags: [] + + # For Helm compatibility. + ownerName: "" + + # meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior + # See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options + meshConfig: + enablePrometheusMerge: true + + global: + # Used to locate istiod. + istioNamespace: istio-system + # List of cert-signers to allow "approve" action in the istio cluster role + # + # certSigners: + # - clusterissuers.cert-manager.io/istio-ca + certSigners: [] + # enable pod disruption budget for the control plane, which is used to + # ensure Istio control plane components are gradually upgraded or recovered. + defaultPodDisruptionBudget: + enabled: true + # The values aren't mutable due to a current PodDisruptionBudget limitation + # minAvailable: 1 + + # A minimal set of requested resources to applied to all deployments so that + # Horizontal Pod Autoscaler will be able to function (if set). + # Each component can overwrite these default values by adding its own resources + # block in the relevant section below and setting the desired resources values. + defaultResources: + requests: + cpu: 10m + # memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + + # Default hub for Istio images. + # Releases are published to docker hub under 'istio' project. + # Dev builds from prow are on gcr.io + hub: docker.io/istio + # Default tag for Istio images. + tag: 1.21.6 + # Variant of the image to use. + # Currently supported are: [debug, distroless] + variant: "" + + # Specify image pull policy if default behavior isn't desired. + # Default behavior: latest images will be Always else IfNotPresent. + imagePullPolicy: "" + + # ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace + # to use for pulling any images in pods that reference this ServiceAccount. + # For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) + # ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. + # Must be set for any cluster configured with private docker registry. + imagePullSecrets: [] + # - private-registry-key + + # Enabled by default in master for maximising testing. + istiod: + enableAnalysis: false + + # To output all istio components logs in json format by adding --log_as_json argument to each container argument + logAsJson: false + + # Comma-separated minimum per-scope logging level of messages to output, in the form of :,: + # The control plane has different scopes depending on component, but can configure default log level across all components + # If empty, default scope and level will be used as configured in code + logging: + level: "default:info" + + omitSidecarInjectorConfigMap: false + + # Whether to restrict the applications namespace the controller manages; + # If not set, controller watches all namespaces + oneNamespace: false + + # Configure whether Operator manages webhook configurations. The current behavior + # of Istiod is to manage its own webhook configurations. + # When this option is set as true, Istio Operator, instead of webhooks, manages the + # webhook configurations. When this option is set as false, webhooks manage their + # own webhook configurations. + operatorManageWebhooks: false + + # Custom DNS config for the pod to resolve names of services in other + # clusters. Use this to add additional search domains, and other settings. + # see + # https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#dns-config + # This does not apply to gateway pods as they typically need a different + # set of DNS settings than the normal application pods (e.g., in + # multicluster scenarios). + # NOTE: If using templates, follow the pattern in the commented example below. + #podDNSSearchNamespaces: + #- global + #- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global" + + # Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and + # system-node-critical, it is better to configure this in order to make sure your Istio pods + # will not be killed because of low priority class. + # Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + # for more detail. + priorityClassName: "" + + proxy: + image: proxyv2 + + # This controls the 'policy' in the sidecar injector. + autoInject: enabled + + # CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # cluster domain. Default value is "cluster.local". + clusterDomain: "cluster.local" + + # Per Component log level for proxy, applies to gateways and sidecars. If a component level is + # not set, then the global "logLevel" will be used. + componentLogLevel: "misc:error" + + # If set, newly injected sidecars will have core dumps enabled. + enableCoreDump: false + + # istio ingress capture allowlist + # examples: + # Redirect only selected ports: --includeInboundPorts="80,8080" + excludeInboundPorts: "" + includeInboundPorts: "*" + + # istio egress capture allowlist + # https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly + # example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" + # would only capture egress traffic on those two IP Ranges, all other outbound traffic would + # be allowed by the sidecar + includeIPRanges: "*" + excludeIPRanges: "" + includeOutboundPorts: "" + excludeOutboundPorts: "" + + # Log level for proxy, applies to gateways and sidecars. + # Expected values are: trace|debug|info|warning|error|critical|off + logLevel: warning + + #If set to true, istio-proxy container will have privileged securityContext + privileged: false + + # The number of successive failed probes before indicating readiness failure. + readinessFailureThreshold: 4 + + # The initial delay for readiness probes in seconds. + readinessInitialDelaySeconds: 0 + + # The period between readiness probes. + readinessPeriodSeconds: 15 + + # Enables or disables a startup probe. + # For optimal startup times, changing this should be tied to the readiness probe values. + # + # If the probe is enabled, it is recommended to have delay=0s,period=15s,failureThreshold=4. + # This ensures the pod is marked ready immediately after the startup probe passes (which has a 1s poll interval), + # and doesn't spam the readiness endpoint too much + # + # If the probe is disabled, it is recommended to have delay=1s,period=2s,failureThreshold=30. + # This ensures the startup is reasonable fast (polling every 2s). 1s delay is used since the startup is not often ready instantly. + startupProbe: + enabled: true + failureThreshold: 600 # 10 minutes + + # Resources for the sidecar. + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 2000m + memory: 1024Mi + + # Default port for Pilot agent health checks. A value of 0 will disable health checking. + statusPort: 15020 + + # Specify which tracer to use. One of: zipkin, lightstep, datadog, stackdriver. + # If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. + tracer: "zipkin" + + proxy_init: + # Base name for the proxy_init container, used to configure iptables. + image: proxyv2 + + # configure remote pilot and istiod service and endpoint + remotePilotAddress: "" + + ############################################################################################## + # The following values are found in other charts. To effectively modify these values, make # + # make sure they are consistent across your Istio helm charts # + ############################################################################################## + + # The customized CA address to retrieve certificates for the pods in the cluster. + # CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. + # If not set explicitly, default to the Istio discovery address. + caAddress: "" + + # Configure a remote cluster data plane controlled by an external istiod. + # When set to true, istiod is not deployed locally and only a subset of the other + # discovery charts are enabled. + externalIstiod: false + + # Configure a remote cluster as the config cluster for an external istiod. + configCluster: false + + # Configure the policy for validating JWT. + # Currently, two options are supported: "third-party-jwt" and "first-party-jwt". + jwtPolicy: "third-party-jwt" + + # Mesh ID means Mesh Identifier. It should be unique within the scope where + # meshes will interact with each other, but it is not required to be + # globally/universally unique. For example, if any of the following are true, + # then two meshes must have different Mesh IDs: + # - Meshes will have their telemetry aggregated in one place + # - Meshes will be federated together + # - Policy will be written referencing one mesh from the other + # + # If an administrator expects that any of these conditions may become true in + # the future, they should ensure their meshes have different Mesh IDs + # assigned. + # + # Within a multicluster mesh, each cluster must be (manually or auto) + # configured to have the same Mesh ID value. If an existing cluster 'joins' a + # multicluster mesh, it will need to be migrated to the new mesh ID. Details + # of migration TBD, and it may be a disruptive operation to change the Mesh + # ID post-install. + # + # If the mesh admin does not specify a value, Istio will use the value of the + # mesh's Trust Domain. The best practice is to select a proper Trust Domain + # value. + meshID: "" + + # Configure the mesh networks to be used by the Split Horizon EDS. + # + # The following example defines two networks with different endpoints association methods. + # For `network1` all endpoints that their IP belongs to the provided CIDR range will be + # mapped to network1. The gateway for this network example is specified by its public IP + # address and port. + # The second network, `network2`, in this example is defined differently with all endpoints + # retrieved through the specified Multi-Cluster registry being mapped to network2. The + # gateway is also defined differently with the name of the gateway service on the remote + # cluster. The public IP for the gateway will be determined from that remote service (only + # LoadBalancer gateway service type is currently supported, for a NodePort type gateway service, + # it still need to be configured manually). + # + # meshNetworks: + # network1: + # endpoints: + # - fromCidr: "192.168.0.1/24" + # gateways: + # - address: 1.1.1.1 + # port: 80 + # network2: + # endpoints: + # - fromRegistry: reg1 + # gateways: + # - registryServiceName: istio-ingressgateway.istio-system.svc.cluster.local + # port: 443 + # + meshNetworks: {} + + # Use the user-specified, secret volume mounted key and certs for Pilot and workloads. + mountMtlsCerts: false + + multiCluster: + # Set to true to connect two kubernetes clusters via their respective + # ingressgateway services when pods in each cluster cannot directly + # talk to one another. All clusters should be using Istio mTLS and must + # have a shared root CA for this model to work. + enabled: false + # Should be set to the name of the cluster this installation will run in. This is required for sidecar injection + # to properly label proxies + clusterName: "" + + # Network defines the network this cluster belong to. This name + # corresponds to the networks in the map of mesh networks. + network: "" + + # Configure the certificate provider for control plane communication. + # Currently, two providers are supported: "kubernetes" and "istiod". + # As some platforms may not have kubernetes signing APIs, + # Istiod is the default + pilotCertProvider: istiod + + sds: + # The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. + # When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the + # JWT is intended for the CA. + token: + aud: istio-ca + + sts: + # The service port used by Security Token Service (STS) server to handle token exchange requests. + # Setting this port to a non-zero value enables STS server. + servicePort: 0 + + # The name of the CA for workload certificates. + # For example, when caName=GkeWorkloadCertificate, GKE workload certificates + # will be used as the certificates for workloads. + # The default value is "" and when caName="", the CA will be configured by other + # mechanisms (e.g., environmental variable CA_PROVIDER). + caName: "" + + # whether to use autoscaling/v2 template for HPA settings + # for internal usage only, not to be configured by users. + autoscalingv2API: true + + base: + # For istioctl usage to disable istio config crds in base + enableIstioConfigCRDs: true + + # keep in sync with settings used when installing the Istio CNI chart + istio_cni: + enabled: false + chained: true + + # Gateway Settings + gateways: + # Define the security context for the pod. + # If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. + # On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. + securityContext: {} diff --git a/resources/v1.22.5/charts/revisiontags/Chart.yaml b/resources/v1.22.5/charts/revisiontags/Chart.yaml new file mode 100644 index 000000000..9f9907289 --- /dev/null +++ b/resources/v1.22.5/charts/revisiontags/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +appVersion: v1.22.5 +description: Helm chart for istio revision tags +name: revisiontags +sources: +- https://github.com/istio-ecosystem/sail-operator +version: 0.1.0 + diff --git a/resources/v1.22.5/charts/revisiontags/templates/revision-tags.yaml b/resources/v1.22.5/charts/revisiontags/templates/revision-tags.yaml new file mode 100644 index 000000000..5884e18e3 --- /dev/null +++ b/resources/v1.22.5/charts/revisiontags/templates/revision-tags.yaml @@ -0,0 +1,141 @@ +# Adapted from istio-discovery/templates/mutatingwebhook.yaml +# Removed paths for legacy and default selectors since a revision tag +# is inherently created from a specific revision +{{- $whv := dict + "revision" .Values.revision + "injectionPath" .Values.istiodRemote.injectionPath + "injectionURL" .Values.istiodRemote.injectionURL + "reinvocationPolicy" .Values.sidecarInjectorWebhook.reinvocationPolicy + "namespace" .Release.Namespace }} +{{- define "core" }} +{{- /* Kubernetes unfortunately requires a unique name for the webhook in some newer versions, so we assign +a unique prefix to each. */}} +- name: {{.Prefix}}sidecar-injector.istio.io + clientConfig: + {{- if .injectionURL }} + url: "{{ .injectionURL }}" + {{- else }} + service: + name: istiod{{- if not (eq .revision "") }}-{{ .revision }}{{- end }} + namespace: {{ .namespace }} + path: "{{ .injectionPath }}" + port: 443 + {{- end }} + sideEffects: None + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Fail + admissionReviewVersions: ["v1beta1", "v1"] +{{- end }} +{{- range $tagName := $.Values.revisionTags }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: +{{- if eq $.Release.Namespace "istio-system"}} + name: istio-revision-tag-{{ $tagName }} +{{- else }} + name: istio-revision-tag-{{ $tagName }}-{{ $.Release.Namespace }} +{{- end }} + labels: + istio.io/tag: {{ $tagName }} + istio.io/rev: {{ $.Values.revision | default "default" | quote }} + install.operator.istio.io/owning-resource: {{ $.Values.ownerName | default "unknown" }} + operator.istio.io/component: "Pilot" + app: sidecar-injector + release: {{ $.Release.Name }} +webhooks: +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: DoesNotExist + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + +{{- /* When the tag is "default" we want to create webhooks for the default revision */}} +{{- /* These webhooks should be kept in sync with istio-discovery/templates/mutatingwebhook.yaml */}} +{{- if (eq $tagName "default") }} + +{{- /* Case 1: Namespace selector enabled, and object selector is not injected */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: In + values: + - enabled + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + +{{- /* Case 2: no namespace label, but object selector is enabled (and revision label is not, which has priority) */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: In + values: + - "true" + - key: istio.io/rev + operator: DoesNotExist + +{{- if $.Values.sidecarInjectorWebhook.enableNamespacesByDefault }} +{{- /* Special case 3: no labels at all */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "auto.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","kube-public","kube-node-lease","local-path-storage"] + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist +{{- end }} + +{{- end }} +--- +{{- end }} diff --git a/resources/v1.22.5/charts/revisiontags/templates/zzz_profile.yaml b/resources/v1.22.5/charts/revisiontags/templates/zzz_profile.yaml new file mode 100644 index 000000000..2d0bd4af7 --- /dev/null +++ b/resources/v1.22.5/charts/revisiontags/templates/zzz_profile.yaml @@ -0,0 +1,43 @@ +{{/* +WARNING: DO NOT EDIT, THIS FILE IS A PROBABLY COPY. +The original version of this file is located at /manifests directory. +If you want to make a change in this file, edit the original one and run "make gen". + +Complex logic ahead... +We have three sets of values, in order of precedence (last wins): +1. The builtin values.yaml defaults +2. The profile the user selects +3. Users input (-f or --set) + +Unfortunately, Helm provides us (1) and (3) together (as .Values), making it hard to insert (2). + +However, we can workaround this by placing all of (1) under a specific key (.Values.defaults). +We can then merge the profile onto the defaults, then the user settings onto that. +Finally, we can set all of that under .Values so the chart behaves without awareness. +*/}} +{{- $globals := $.Values.global | default dict | deepCopy }} +{{- $defaults := $.Values.defaults }} +{{- $_ := unset $.Values "defaults" }} +{{- $profile := dict }} +{{- with .Values.profile }} +{{- with $.Files.Get (printf "files/profile-%s.yaml" .)}} +{{- $profile = (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown profile" $.Values.profile) }} +{{- end }} +{{- end }} +{{- with .Values.compatibilityVersion }} +{{- with $.Files.Get (printf "files/profile-compatibility-version-%s.yaml" .) }} +{{- $ignore := mustMergeOverwrite $profile (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown compatibility version" $.Values.compatibilityVersion) }} +{{- end }} +{{- end }} +{{- if $profile }} +{{- $a := mustMergeOverwrite $defaults $profile }} +{{- end }} +# Flatten globals, if defined on a per-chart basis +{{- if false }} +{{- $a := mustMergeOverwrite $defaults $globals }} +{{- end }} +{{- $b := set $ "Values" (mustMergeOverwrite $defaults $.Values) }} diff --git a/resources/v1.22.5/charts/revisiontags/values.yaml b/resources/v1.22.5/charts/revisiontags/values.yaml new file mode 100644 index 000000000..1eff1e7b3 --- /dev/null +++ b/resources/v1.22.5/charts/revisiontags/values.yaml @@ -0,0 +1,514 @@ +defaults: + #.Values.pilot for discovery and mesh wide config + + ## Discovery Settings + pilot: + autoscaleEnabled: true + autoscaleMin: 1 + autoscaleMax: 5 + autoscaleBehavior: {} + replicaCount: 1 + rollingMaxSurge: 100% + rollingMaxUnavailable: 25% + + hub: "" + tag: "" + variant: "" + + # Can be a full hub/image:tag + image: pilot + traceSampling: 1.0 + + # Resources for a small pilot install + resources: + requests: + cpu: 500m + memory: 2048Mi + + # Set to `type: RuntimeDefault` to use the default profile if available. + seccompProfile: {} + + # Whether to use an existing CNI installation + cni: + enabled: false + provider: default + + # Additional container arguments + extraContainerArgs: [] + + env: {} + + # Settings related to the untaint controller + # This controller will remove `cni.istio.io/not-ready` from nodes when the istio-cni pod becomes ready + # It should be noted that cluster operator/owner is responsible for having the taint set by their infrastructure provider when new nodes are added to the cluster; the untaint controller does not taint nodes + taint: + # Controls whether or not the untaint controller is active + enabled: false + # What namespace the untaint controller should watch for istio-cni pods. This is only required when istio-cni is running in a different namespace than istiod + namespace: "" + + affinity: {} + + tolerations: [] + + cpu: + targetAverageUtilization: 80 + memory: {} + # targetAverageUtilization: 80 + + # Additional volumeMounts to the istiod container + volumeMounts: [] + + # Additional volumes to the istiod pod + volumes: [] + + nodeSelector: {} + podAnnotations: {} + serviceAnnotations: {} + serviceAccountAnnotations: {} + + topologySpreadConstraints: [] + + # You can use jwksResolverExtraRootCA to provide a root certificate + # in PEM format. This will then be trusted by pilot when resolving + # JWKS URIs. + jwksResolverExtraRootCA: "" + + # This is used to set the source of configuration for + # the associated address in configSource, if nothing is specified + # the default MCP is assumed. + configSource: + subscribedResources: [] + + # The following is used to limit how long a sidecar can be connected + # to a pilot. It balances out load across pilot instances at the cost of + # increasing system churn. + keepaliveMaxServerConnectionAge: 30m + + # Additional labels to apply to the deployment. + deploymentLabels: {} + + ## Mesh config settings + + # Install the mesh config map, generated from values.yaml. + # If false, pilot wil use default values (by default) or user-supplied values. + configMap: true + + # Additional labels to apply on the pod level for monitoring and logging configuration. + podLabels: {} + + # Setup how istiod Service is configured. See https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ipFamilyPolicy: "" + ipFamilies: [] + + sidecarInjectorWebhook: + # You can use the field called alwaysInjectSelector and neverInjectSelector which will always inject the sidecar or + # always skip the injection on pods that match that label selector, regardless of the global policy. + # See https://istio.io/docs/setup/kubernetes/additional-setup/sidecar-injection/#more-control-adding-exceptions + neverInjectSelector: [] + alwaysInjectSelector: [] + + # injectedAnnotations are additional annotations that will be added to the pod spec after injection + # This is primarily to support PSP annotations. For example, if you defined a PSP with the annotations: + # + # annotations: + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # + # The PSP controller would add corresponding annotations to the pod spec for each container. However, this happens before + # the inject adds additional containers, so we must specify them explicitly here. With the above example, we could specify: + # injectedAnnotations: + # container.apparmor.security.beta.kubernetes.io/istio-init: runtime/default + # container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default + injectedAnnotations: {} + + # This enables injection of sidecar in all namespaces, + # with the exception of namespaces with "istio-injection:disabled" annotation + # Only one environment should have this enabled. + enableNamespacesByDefault: false + + # Mutations that occur after the sidecar injector are not handled by default, as the Istio sidecar injector is only run + # once. For example, an OPA sidecar injected after the Istio sidecar will not have it's liveness/readiness probes rewritten. + # Setting this to `IfNeeded` will result in the sidecar injector being run again if additional mutations occur. + reinvocationPolicy: Never + + rewriteAppHTTPProbe: true + + # Templates defines a set of custom injection templates that can be used. For example, defining: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # Then starting a pod with the `inject.istio.io/templates: hello` annotation, will result in the pod + # being injected with the hello=world labels. + # This is intended for advanced configuration only; most users should use the built in template + templates: {} + + # Default templates specifies a set of default templates that are used in sidecar injection. + # By default, a template `sidecar` is always provided, which contains the template of default sidecar. + # To inject other additional templates, define it using the `templates` option, and add it to + # the default templates list. + # For example: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # defaultTemplates: ["sidecar", "hello"] + defaultTemplates: [] + istiodRemote: + # Sidecar injector mutating webhook configuration clientConfig.url value. + # For example: https://$remotePilotAddress:15017/inject + # The host should not refer to a service running in the cluster; use a service reference by specifying + # the clientConfig.service field instead. + injectionURL: "" + + # Sidecar injector mutating webhook configuration path value for the clientConfig.service field. + # Override to pass env variables, for example: /inject/cluster/remote/net/network2 + injectionPath: "/inject" + + injectionCABundle: "" + telemetry: + enabled: true + v2: + # For Null VM case now. + # This also enables metadata exchange. + enabled: true + # Indicate if prometheus stats filter is enabled or not + prometheus: + enabled: true + # stackdriver filter settings. + stackdriver: + enabled: false + # Revision is set as 'version' label and part of the resource names when installing multiple control planes. + revision: "" + + # Revision tags are aliases to Istio control plane revisions + revisionTags: [] + + # For Helm compatibility. + ownerName: "" + + # meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior + # See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options + meshConfig: + enablePrometheusMerge: true + + experimental: + stableValidationPolicy: false + + global: + # Used to locate istiod. + istioNamespace: istio-system + # List of cert-signers to allow "approve" action in the istio cluster role + # + # certSigners: + # - clusterissuers.cert-manager.io/istio-ca + certSigners: [] + # enable pod disruption budget for the control plane, which is used to + # ensure Istio control plane components are gradually upgraded or recovered. + defaultPodDisruptionBudget: + enabled: true + # The values aren't mutable due to a current PodDisruptionBudget limitation + # minAvailable: 1 + + # A minimal set of requested resources to applied to all deployments so that + # Horizontal Pod Autoscaler will be able to function (if set). + # Each component can overwrite these default values by adding its own resources + # block in the relevant section below and setting the desired resources values. + defaultResources: + requests: + cpu: 10m + # memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + + # Default hub for Istio images. + # Releases are published to docker hub under 'istio' project. + # Dev builds from prow are on gcr.io + hub: docker.io/istio + # Default tag for Istio images. + tag: 1.22.5 + # Variant of the image to use. + # Currently supported are: [debug, distroless] + variant: "" + + # Specify image pull policy if default behavior isn't desired. + # Default behavior: latest images will be Always else IfNotPresent. + imagePullPolicy: "" + + # ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace + # to use for pulling any images in pods that reference this ServiceAccount. + # For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) + # ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. + # Must be set for any cluster configured with private docker registry. + imagePullSecrets: [] + # - private-registry-key + + # Enabled by default in master for maximising testing. + istiod: + enableAnalysis: false + + # To output all istio components logs in json format by adding --log_as_json argument to each container argument + logAsJson: false + + # Comma-separated minimum per-scope logging level of messages to output, in the form of :,: + # The control plane has different scopes depending on component, but can configure default log level across all components + # If empty, default scope and level will be used as configured in code + logging: + level: "default:info" + + omitSidecarInjectorConfigMap: false + + # Configure whether Operator manages webhook configurations. The current behavior + # of Istiod is to manage its own webhook configurations. + # When this option is set as true, Istio Operator, instead of webhooks, manages the + # webhook configurations. When this option is set as false, webhooks manage their + # own webhook configurations. + operatorManageWebhooks: false + + # Custom DNS config for the pod to resolve names of services in other + # clusters. Use this to add additional search domains, and other settings. + # see + # https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#dns-config + # This does not apply to gateway pods as they typically need a different + # set of DNS settings than the normal application pods (e.g., in + # multicluster scenarios). + # NOTE: If using templates, follow the pattern in the commented example below. + #podDNSSearchNamespaces: + #- global + #- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global" + + # Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and + # system-node-critical, it is better to configure this in order to make sure your Istio pods + # will not be killed because of low priority class. + # Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + # for more detail. + priorityClassName: "" + + proxy: + image: proxyv2 + + # This controls the 'policy' in the sidecar injector. + autoInject: enabled + + # CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # cluster domain. Default value is "cluster.local". + clusterDomain: "cluster.local" + + # Per Component log level for proxy, applies to gateways and sidecars. If a component level is + # not set, then the global "logLevel" will be used. + componentLogLevel: "misc:error" + + # If set, newly injected sidecars will have core dumps enabled. + enableCoreDump: false + + # istio ingress capture allowlist + # examples: + # Redirect only selected ports: --includeInboundPorts="80,8080" + excludeInboundPorts: "" + includeInboundPorts: "*" + + # istio egress capture allowlist + # https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly + # example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" + # would only capture egress traffic on those two IP Ranges, all other outbound traffic would + # be allowed by the sidecar + includeIPRanges: "*" + excludeIPRanges: "" + includeOutboundPorts: "" + excludeOutboundPorts: "" + + # Log level for proxy, applies to gateways and sidecars. + # Expected values are: trace|debug|info|warning|error|critical|off + logLevel: warning + + #If set to true, istio-proxy container will have privileged securityContext + privileged: false + + # The number of successive failed probes before indicating readiness failure. + readinessFailureThreshold: 4 + + # The initial delay for readiness probes in seconds. + readinessInitialDelaySeconds: 0 + + # The period between readiness probes. + readinessPeriodSeconds: 15 + + # Enables or disables a startup probe. + # For optimal startup times, changing this should be tied to the readiness probe values. + # + # If the probe is enabled, it is recommended to have delay=0s,period=15s,failureThreshold=4. + # This ensures the pod is marked ready immediately after the startup probe passes (which has a 1s poll interval), + # and doesn't spam the readiness endpoint too much + # + # If the probe is disabled, it is recommended to have delay=1s,period=2s,failureThreshold=30. + # This ensures the startup is reasonable fast (polling every 2s). 1s delay is used since the startup is not often ready instantly. + startupProbe: + enabled: true + failureThreshold: 600 # 10 minutes + + # Resources for the sidecar. + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 2000m + memory: 1024Mi + + # Default port for Pilot agent health checks. A value of 0 will disable health checking. + statusPort: 15020 + + # Specify which tracer to use. One of: zipkin, lightstep, datadog, stackdriver, none. + # If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. + tracer: "none" + + proxy_init: + # Base name for the proxy_init container, used to configure iptables. + image: proxyv2 + + # configure remote pilot and istiod service and endpoint + remotePilotAddress: "" + + ############################################################################################## + # The following values are found in other charts. To effectively modify these values, make # + # make sure they are consistent across your Istio helm charts # + ############################################################################################## + + # The customized CA address to retrieve certificates for the pods in the cluster. + # CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. + # If not set explicitly, default to the Istio discovery address. + caAddress: "" + + # Configure a remote cluster data plane controlled by an external istiod. + # When set to true, istiod is not deployed locally and only a subset of the other + # discovery charts are enabled. + externalIstiod: false + + # Configure a remote cluster as the config cluster for an external istiod. + configCluster: false + + # configValidation enables the validation webhook for Istio configuration. + configValidation: true + + # Mesh ID means Mesh Identifier. It should be unique within the scope where + # meshes will interact with each other, but it is not required to be + # globally/universally unique. For example, if any of the following are true, + # then two meshes must have different Mesh IDs: + # - Meshes will have their telemetry aggregated in one place + # - Meshes will be federated together + # - Policy will be written referencing one mesh from the other + # + # If an administrator expects that any of these conditions may become true in + # the future, they should ensure their meshes have different Mesh IDs + # assigned. + # + # Within a multicluster mesh, each cluster must be (manually or auto) + # configured to have the same Mesh ID value. If an existing cluster 'joins' a + # multicluster mesh, it will need to be migrated to the new mesh ID. Details + # of migration TBD, and it may be a disruptive operation to change the Mesh + # ID post-install. + # + # If the mesh admin does not specify a value, Istio will use the value of the + # mesh's Trust Domain. The best practice is to select a proper Trust Domain + # value. + meshID: "" + + # Configure the mesh networks to be used by the Split Horizon EDS. + # + # The following example defines two networks with different endpoints association methods. + # For `network1` all endpoints that their IP belongs to the provided CIDR range will be + # mapped to network1. The gateway for this network example is specified by its public IP + # address and port. + # The second network, `network2`, in this example is defined differently with all endpoints + # retrieved through the specified Multi-Cluster registry being mapped to network2. The + # gateway is also defined differently with the name of the gateway service on the remote + # cluster. The public IP for the gateway will be determined from that remote service (only + # LoadBalancer gateway service type is currently supported, for a NodePort type gateway service, + # it still need to be configured manually). + # + # meshNetworks: + # network1: + # endpoints: + # - fromCidr: "192.168.0.1/24" + # gateways: + # - address: 1.1.1.1 + # port: 80 + # network2: + # endpoints: + # - fromRegistry: reg1 + # gateways: + # - registryServiceName: istio-ingressgateway.istio-system.svc.cluster.local + # port: 443 + # + meshNetworks: {} + + # Use the user-specified, secret volume mounted key and certs for Pilot and workloads. + mountMtlsCerts: false + + multiCluster: + # Set to true to connect two kubernetes clusters via their respective + # ingressgateway services when pods in each cluster cannot directly + # talk to one another. All clusters should be using Istio mTLS and must + # have a shared root CA for this model to work. + enabled: false + # Should be set to the name of the cluster this installation will run in. This is required for sidecar injection + # to properly label proxies + clusterName: "" + + # Network defines the network this cluster belong to. This name + # corresponds to the networks in the map of mesh networks. + network: "" + + # Configure the certificate provider for control plane communication. + # Currently, two providers are supported: "kubernetes" and "istiod". + # As some platforms may not have kubernetes signing APIs, + # Istiod is the default + pilotCertProvider: istiod + + sds: + # The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. + # When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the + # JWT is intended for the CA. + token: + aud: istio-ca + + sts: + # The service port used by Security Token Service (STS) server to handle token exchange requests. + # Setting this port to a non-zero value enables STS server. + servicePort: 0 + + # The name of the CA for workload certificates. + # For example, when caName=GkeWorkloadCertificate, GKE workload certificates + # will be used as the certificates for workloads. + # The default value is "" and when caName="", the CA will be configured by other + # mechanisms (e.g., environmental variable CA_PROVIDER). + caName: "" + + # whether to use autoscaling/v2 template for HPA settings + # for internal usage only, not to be configured by users. + autoscalingv2API: true + + base: + # For istioctl usage to disable istio config crds in base + enableIstioConfigCRDs: true + + # `istio_cni` has been deprecated and will be removed in a future release. use `pilot.cni` instead + istio_cni: + # `chained` has been deprecated and will be removed in a future release. use `provider` instead + chained: true + provider: default + + # Gateway Settings + gateways: + # Define the security context for the pod. + # If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. + # On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. + securityContext: {} diff --git a/resources/v1.23.2/charts/revisiontags/Chart.yaml b/resources/v1.23.2/charts/revisiontags/Chart.yaml new file mode 100644 index 000000000..382523174 --- /dev/null +++ b/resources/v1.23.2/charts/revisiontags/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +appVersion: v1.23.2 +description: Helm chart for istio revision tags +name: revisiontags +sources: +- https://github.com/istio-ecosystem/sail-operator +version: 0.1.0 + diff --git a/resources/v1.23.2/charts/revisiontags/templates/revision-tags.yaml b/resources/v1.23.2/charts/revisiontags/templates/revision-tags.yaml new file mode 100644 index 000000000..be80804bc --- /dev/null +++ b/resources/v1.23.2/charts/revisiontags/templates/revision-tags.yaml @@ -0,0 +1,141 @@ +# Adapted from istio-discovery/templates/mutatingwebhook.yaml +# Removed paths for legacy and default selectors since a revision tag +# is inherently created from a specific revision +{{- $whv := dict + "revision" .Values.revision + "injectionPath" .Values.istiodRemote.injectionPath + "injectionURL" .Values.istiodRemote.injectionURL + "reinvocationPolicy" .Values.sidecarInjectorWebhook.reinvocationPolicy + "namespace" .Release.Namespace }} +{{- define "core" }} +{{- /* Kubernetes unfortunately requires a unique name for the webhook in some newer versions, so we assign +a unique prefix to each. */}} +- name: {{.Prefix}}sidecar-injector.istio.io + clientConfig: + {{- if .injectionURL }} + url: "{{ .injectionURL }}" + {{- else }} + service: + name: istiod{{- if not (eq .revision "") }}-{{ .revision }}{{- end }} + namespace: {{ .namespace }} + path: "{{ .injectionPath }}" + port: 443 + {{- end }} + sideEffects: None + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Fail + admissionReviewVersions: ["v1"] +{{- end }} +{{- range $tagName := $.Values.revisionTags }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: +{{- if eq $.Release.Namespace "istio-system"}} + name: istio-revision-tag-{{ $tagName }} +{{- else }} + name: istio-revision-tag-{{ $tagName }}-{{ $.Release.Namespace }} +{{- end }} + labels: + istio.io/tag: {{ $tagName }} + istio.io/rev: {{ $.Values.revision | default "default" | quote }} + install.operator.istio.io/owning-resource: {{ $.Values.ownerName | default "unknown" }} + operator.istio.io/component: "Pilot" + app: sidecar-injector + release: {{ $.Release.Name }} +webhooks: +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "rev.object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio.io/rev + operator: DoesNotExist + - key: istio-injection + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + - key: istio.io/rev + operator: In + values: + - "{{ $tagName }}" + +{{- /* When the tag is "default" we want to create webhooks for the default revision */}} +{{- /* These webhooks should be kept in sync with istio-discovery/templates/mutatingwebhook.yaml */}} +{{- if (eq $tagName "default") }} + +{{- /* Case 1: Namespace selector enabled, and object selector is not injected */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "namespace.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: In + values: + - enabled + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: NotIn + values: + - "false" + +{{- /* Case 2: no namespace label, but object selector is enabled (and revision label is not, which has priority) */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "object.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: In + values: + - "true" + - key: istio.io/rev + operator: DoesNotExist + +{{- if $.Values.sidecarInjectorWebhook.enableNamespacesByDefault }} +{{- /* Special case 3: no labels at all */}} +{{- include "core" (mergeOverwrite (deepCopy $whv) (dict "Prefix" "auto.") ) }} + namespaceSelector: + matchExpressions: + - key: istio-injection + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist + - key: "kubernetes.io/metadata.name" + operator: "NotIn" + values: ["kube-system","kube-public","kube-node-lease","local-path-storage"] + objectSelector: + matchExpressions: + - key: sidecar.istio.io/inject + operator: DoesNotExist + - key: istio.io/rev + operator: DoesNotExist +{{- end }} + +{{- end }} +--- +{{- end }} diff --git a/resources/v1.23.2/charts/revisiontags/templates/zzz_profile.yaml b/resources/v1.23.2/charts/revisiontags/templates/zzz_profile.yaml new file mode 100644 index 000000000..2d0bd4af7 --- /dev/null +++ b/resources/v1.23.2/charts/revisiontags/templates/zzz_profile.yaml @@ -0,0 +1,43 @@ +{{/* +WARNING: DO NOT EDIT, THIS FILE IS A PROBABLY COPY. +The original version of this file is located at /manifests directory. +If you want to make a change in this file, edit the original one and run "make gen". + +Complex logic ahead... +We have three sets of values, in order of precedence (last wins): +1. The builtin values.yaml defaults +2. The profile the user selects +3. Users input (-f or --set) + +Unfortunately, Helm provides us (1) and (3) together (as .Values), making it hard to insert (2). + +However, we can workaround this by placing all of (1) under a specific key (.Values.defaults). +We can then merge the profile onto the defaults, then the user settings onto that. +Finally, we can set all of that under .Values so the chart behaves without awareness. +*/}} +{{- $globals := $.Values.global | default dict | deepCopy }} +{{- $defaults := $.Values.defaults }} +{{- $_ := unset $.Values "defaults" }} +{{- $profile := dict }} +{{- with .Values.profile }} +{{- with $.Files.Get (printf "files/profile-%s.yaml" .)}} +{{- $profile = (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown profile" $.Values.profile) }} +{{- end }} +{{- end }} +{{- with .Values.compatibilityVersion }} +{{- with $.Files.Get (printf "files/profile-compatibility-version-%s.yaml" .) }} +{{- $ignore := mustMergeOverwrite $profile (. | fromYaml) }} +{{- else }} +{{ fail (cat "unknown compatibility version" $.Values.compatibilityVersion) }} +{{- end }} +{{- end }} +{{- if $profile }} +{{- $a := mustMergeOverwrite $defaults $profile }} +{{- end }} +# Flatten globals, if defined on a per-chart basis +{{- if false }} +{{- $a := mustMergeOverwrite $defaults $globals }} +{{- end }} +{{- $b := set $ "Values" (mustMergeOverwrite $defaults $.Values) }} diff --git a/resources/v1.23.2/charts/revisiontags/values.yaml b/resources/v1.23.2/charts/revisiontags/values.yaml new file mode 100644 index 000000000..fbb4d2695 --- /dev/null +++ b/resources/v1.23.2/charts/revisiontags/values.yaml @@ -0,0 +1,525 @@ +# "defaults" is a workaround for Helm limitations. Users should NOT set ".defaults" explicitly, but rather directly set the fields internally. +# For instance, instead of `--set defaults.foo=bar`, just set `--set foo=bar`. +defaults: + #.Values.pilot for discovery and mesh wide config + + ## Discovery Settings + pilot: + autoscaleEnabled: true + autoscaleMin: 1 + autoscaleMax: 5 + autoscaleBehavior: {} + replicaCount: 1 + rollingMaxSurge: 100% + rollingMaxUnavailable: 25% + + hub: "" + tag: "" + variant: "" + + # Can be a full hub/image:tag + image: pilot + traceSampling: 1.0 + + # Resources for a small pilot install + resources: + requests: + cpu: 500m + memory: 2048Mi + + # Set to `type: RuntimeDefault` to use the default profile if available. + seccompProfile: {} + + # Whether to use an existing CNI installation + cni: + enabled: false + provider: default + + # Additional container arguments + extraContainerArgs: [] + + env: {} + + # Settings related to the untaint controller + # This controller will remove `cni.istio.io/not-ready` from nodes when the istio-cni pod becomes ready + # It should be noted that cluster operator/owner is responsible for having the taint set by their infrastructure provider when new nodes are added to the cluster; the untaint controller does not taint nodes + taint: + # Controls whether or not the untaint controller is active + enabled: false + # What namespace the untaint controller should watch for istio-cni pods. This is only required when istio-cni is running in a different namespace than istiod + namespace: "" + + affinity: {} + + tolerations: [] + + cpu: + targetAverageUtilization: 80 + memory: {} + # targetAverageUtilization: 80 + + # Additional volumeMounts to the istiod container + volumeMounts: [] + + # Additional volumes to the istiod pod + volumes: [] + + nodeSelector: {} + podAnnotations: {} + serviceAnnotations: {} + serviceAccountAnnotations: {} + + topologySpreadConstraints: [] + + # You can use jwksResolverExtraRootCA to provide a root certificate + # in PEM format. This will then be trusted by pilot when resolving + # JWKS URIs. + jwksResolverExtraRootCA: "" + + # The following is used to limit how long a sidecar can be connected + # to a pilot. It balances out load across pilot instances at the cost of + # increasing system churn. + keepaliveMaxServerConnectionAge: 30m + + # Additional labels to apply to the deployment. + deploymentLabels: {} + + ## Mesh config settings + + # Install the mesh config map, generated from values.yaml. + # If false, pilot wil use default values (by default) or user-supplied values. + configMap: true + + # Additional labels to apply on the pod level for monitoring and logging configuration. + podLabels: {} + + # Setup how istiod Service is configured. See https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ipFamilyPolicy: "" + ipFamilies: [] + + # Ambient mode only. + # Set this if you install ztunnel to a different namespace from `istiod`. + # If set, `istiod` will allow connections from trusted node proxy ztunnels + # in the provided namespace. + # If unset, `istiod` will assume the trusted node proxy ztunnel resides + # in the same namespace as itself. + trustedZtunnelNamespace: "" + + sidecarInjectorWebhook: + # You can use the field called alwaysInjectSelector and neverInjectSelector which will always inject the sidecar or + # always skip the injection on pods that match that label selector, regardless of the global policy. + # See https://istio.io/docs/setup/kubernetes/additional-setup/sidecar-injection/#more-control-adding-exceptions + neverInjectSelector: [] + alwaysInjectSelector: [] + + # injectedAnnotations are additional annotations that will be added to the pod spec after injection + # This is primarily to support PSP annotations. For example, if you defined a PSP with the annotations: + # + # annotations: + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # + # The PSP controller would add corresponding annotations to the pod spec for each container. However, this happens before + # the inject adds additional containers, so we must specify them explicitly here. With the above example, we could specify: + # injectedAnnotations: + # container.apparmor.security.beta.kubernetes.io/istio-init: runtime/default + # container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default + injectedAnnotations: {} + + # This enables injection of sidecar in all namespaces, + # with the exception of namespaces with "istio-injection:disabled" annotation + # Only one environment should have this enabled. + enableNamespacesByDefault: false + + # Mutations that occur after the sidecar injector are not handled by default, as the Istio sidecar injector is only run + # once. For example, an OPA sidecar injected after the Istio sidecar will not have it's liveness/readiness probes rewritten. + # Setting this to `IfNeeded` will result in the sidecar injector being run again if additional mutations occur. + reinvocationPolicy: Never + + rewriteAppHTTPProbe: true + + # Templates defines a set of custom injection templates that can be used. For example, defining: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # Then starting a pod with the `inject.istio.io/templates: hello` annotation, will result in the pod + # being injected with the hello=world labels. + # This is intended for advanced configuration only; most users should use the built in template + templates: {} + + # Default templates specifies a set of default templates that are used in sidecar injection. + # By default, a template `sidecar` is always provided, which contains the template of default sidecar. + # To inject other additional templates, define it using the `templates` option, and add it to + # the default templates list. + # For example: + # + # templates: + # hello: | + # metadata: + # labels: + # hello: world + # + # defaultTemplates: ["sidecar", "hello"] + defaultTemplates: [] + istiodRemote: + # Sidecar injector mutating webhook configuration clientConfig.url value. + # For example: https://$remotePilotAddress:15017/inject + # The host should not refer to a service running in the cluster; use a service reference by specifying + # the clientConfig.service field instead. + injectionURL: "" + + # Sidecar injector mutating webhook configuration path value for the clientConfig.service field. + # Override to pass env variables, for example: /inject/cluster/remote/net/network2 + injectionPath: "/inject" + + injectionCABundle: "" + telemetry: + enabled: true + v2: + # For Null VM case now. + # This also enables metadata exchange. + enabled: true + # Indicate if prometheus stats filter is enabled or not + prometheus: + enabled: true + # stackdriver filter settings. + stackdriver: + enabled: false + # Revision is set as 'version' label and part of the resource names when installing multiple control planes. + revision: "" + + # Revision tags are aliases to Istio control plane revisions + revisionTags: [] + + # For Helm compatibility. + ownerName: "" + + # meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior + # See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options + meshConfig: + enablePrometheusMerge: true + + experimental: + stableValidationPolicy: false + + global: + # Used to locate istiod. + istioNamespace: istio-system + # List of cert-signers to allow "approve" action in the istio cluster role + # + # certSigners: + # - clusterissuers.cert-manager.io/istio-ca + certSigners: [] + # enable pod disruption budget for the control plane, which is used to + # ensure Istio control plane components are gradually upgraded or recovered. + defaultPodDisruptionBudget: + enabled: true + # The values aren't mutable due to a current PodDisruptionBudget limitation + # minAvailable: 1 + + # A minimal set of requested resources to applied to all deployments so that + # Horizontal Pod Autoscaler will be able to function (if set). + # Each component can overwrite these default values by adding its own resources + # block in the relevant section below and setting the desired resources values. + defaultResources: + requests: + cpu: 10m + # memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + + # Default hub for Istio images. + # Releases are published to docker hub under 'istio' project. + # Dev builds from prow are on gcr.io + hub: docker.io/istio + # Default tag for Istio images. + tag: 1.23.2 + # Variant of the image to use. + # Currently supported are: [debug, distroless] + variant: "" + + # Specify image pull policy if default behavior isn't desired. + # Default behavior: latest images will be Always else IfNotPresent. + imagePullPolicy: "" + + # ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace + # to use for pulling any images in pods that reference this ServiceAccount. + # For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) + # ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. + # Must be set for any cluster configured with private docker registry. + imagePullSecrets: [] + # - private-registry-key + + # Enabled by default in master for maximising testing. + istiod: + enableAnalysis: false + + # To output all istio components logs in json format by adding --log_as_json argument to each container argument + logAsJson: false + + # Comma-separated minimum per-scope logging level of messages to output, in the form of :,: + # The control plane has different scopes depending on component, but can configure default log level across all components + # If empty, default scope and level will be used as configured in code + logging: + level: "default:info" + + omitSidecarInjectorConfigMap: false + + # Configure whether Operator manages webhook configurations. The current behavior + # of Istiod is to manage its own webhook configurations. + # When this option is set as true, Istio Operator, instead of webhooks, manages the + # webhook configurations. When this option is set as false, webhooks manage their + # own webhook configurations. + operatorManageWebhooks: false + + # Custom DNS config for the pod to resolve names of services in other + # clusters. Use this to add additional search domains, and other settings. + # see + # https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#dns-config + # This does not apply to gateway pods as they typically need a different + # set of DNS settings than the normal application pods (e.g., in + # multicluster scenarios). + # NOTE: If using templates, follow the pattern in the commented example below. + #podDNSSearchNamespaces: + #- global + #- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global" + + # Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and + # system-node-critical, it is better to configure this in order to make sure your Istio pods + # will not be killed because of low priority class. + # Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + # for more detail. + priorityClassName: "" + + proxy: + image: proxyv2 + + # This controls the 'policy' in the sidecar injector. + autoInject: enabled + + # CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # cluster domain. Default value is "cluster.local". + clusterDomain: "cluster.local" + + # Per Component log level for proxy, applies to gateways and sidecars. If a component level is + # not set, then the global "logLevel" will be used. + componentLogLevel: "misc:error" + + # If set, newly injected sidecars will have core dumps enabled. + enableCoreDump: false + + # istio ingress capture allowlist + # examples: + # Redirect only selected ports: --includeInboundPorts="80,8080" + excludeInboundPorts: "" + includeInboundPorts: "*" + + # istio egress capture allowlist + # https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly + # example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" + # would only capture egress traffic on those two IP Ranges, all other outbound traffic would + # be allowed by the sidecar + includeIPRanges: "*" + excludeIPRanges: "" + includeOutboundPorts: "" + excludeOutboundPorts: "" + + # Log level for proxy, applies to gateways and sidecars. + # Expected values are: trace|debug|info|warning|error|critical|off + logLevel: warning + + # Specify the path to the outlier event log. + # Example: /dev/stdout + outlierLogPath: "" + + #If set to true, istio-proxy container will have privileged securityContext + privileged: false + + # The number of successive failed probes before indicating readiness failure. + readinessFailureThreshold: 4 + + # The initial delay for readiness probes in seconds. + readinessInitialDelaySeconds: 0 + + # The period between readiness probes. + readinessPeriodSeconds: 15 + + # Enables or disables a startup probe. + # For optimal startup times, changing this should be tied to the readiness probe values. + # + # If the probe is enabled, it is recommended to have delay=0s,period=15s,failureThreshold=4. + # This ensures the pod is marked ready immediately after the startup probe passes (which has a 1s poll interval), + # and doesn't spam the readiness endpoint too much + # + # If the probe is disabled, it is recommended to have delay=1s,period=2s,failureThreshold=30. + # This ensures the startup is reasonable fast (polling every 2s). 1s delay is used since the startup is not often ready instantly. + startupProbe: + enabled: true + failureThreshold: 600 # 10 minutes + + # Resources for the sidecar. + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 2000m + memory: 1024Mi + + # Default port for Pilot agent health checks. A value of 0 will disable health checking. + statusPort: 15020 + + # Specify which tracer to use. One of: zipkin, lightstep, datadog, stackdriver, none. + # If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. + tracer: "none" + + proxy_init: + # Base name for the proxy_init container, used to configure iptables. + image: proxyv2 + + # configure remote pilot and istiod service and endpoint + remotePilotAddress: "" + + ############################################################################################## + # The following values are found in other charts. To effectively modify these values, make # + # make sure they are consistent across your Istio helm charts # + ############################################################################################## + + # The customized CA address to retrieve certificates for the pods in the cluster. + # CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. + # If not set explicitly, default to the Istio discovery address. + caAddress: "" + + # Configure a remote cluster data plane controlled by an external istiod. + # When set to true, istiod is not deployed locally and only a subset of the other + # discovery charts are enabled. + externalIstiod: false + + # Configure a remote cluster as the config cluster for an external istiod. + configCluster: false + + # configValidation enables the validation webhook for Istio configuration. + configValidation: true + + # Mesh ID means Mesh Identifier. It should be unique within the scope where + # meshes will interact with each other, but it is not required to be + # globally/universally unique. For example, if any of the following are true, + # then two meshes must have different Mesh IDs: + # - Meshes will have their telemetry aggregated in one place + # - Meshes will be federated together + # - Policy will be written referencing one mesh from the other + # + # If an administrator expects that any of these conditions may become true in + # the future, they should ensure their meshes have different Mesh IDs + # assigned. + # + # Within a multicluster mesh, each cluster must be (manually or auto) + # configured to have the same Mesh ID value. If an existing cluster 'joins' a + # multicluster mesh, it will need to be migrated to the new mesh ID. Details + # of migration TBD, and it may be a disruptive operation to change the Mesh + # ID post-install. + # + # If the mesh admin does not specify a value, Istio will use the value of the + # mesh's Trust Domain. The best practice is to select a proper Trust Domain + # value. + meshID: "" + + # Configure the mesh networks to be used by the Split Horizon EDS. + # + # The following example defines two networks with different endpoints association methods. + # For `network1` all endpoints that their IP belongs to the provided CIDR range will be + # mapped to network1. The gateway for this network example is specified by its public IP + # address and port. + # The second network, `network2`, in this example is defined differently with all endpoints + # retrieved through the specified Multi-Cluster registry being mapped to network2. The + # gateway is also defined differently with the name of the gateway service on the remote + # cluster. The public IP for the gateway will be determined from that remote service (only + # LoadBalancer gateway service type is currently supported, for a NodePort type gateway service, + # it still need to be configured manually). + # + # meshNetworks: + # network1: + # endpoints: + # - fromCidr: "192.168.0.1/24" + # gateways: + # - address: 1.1.1.1 + # port: 80 + # network2: + # endpoints: + # - fromRegistry: reg1 + # gateways: + # - registryServiceName: istio-ingressgateway.istio-system.svc.cluster.local + # port: 443 + # + meshNetworks: {} + + # Use the user-specified, secret volume mounted key and certs for Pilot and workloads. + mountMtlsCerts: false + + multiCluster: + # Set to true to connect two kubernetes clusters via their respective + # ingressgateway services when pods in each cluster cannot directly + # talk to one another. All clusters should be using Istio mTLS and must + # have a shared root CA for this model to work. + enabled: false + # Should be set to the name of the cluster this installation will run in. This is required for sidecar injection + # to properly label proxies + clusterName: "" + + # Network defines the network this cluster belong to. This name + # corresponds to the networks in the map of mesh networks. + network: "" + + # Configure the certificate provider for control plane communication. + # Currently, two providers are supported: "kubernetes" and "istiod". + # As some platforms may not have kubernetes signing APIs, + # Istiod is the default + pilotCertProvider: istiod + + sds: + # The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. + # When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the + # JWT is intended for the CA. + token: + aud: istio-ca + + sts: + # The service port used by Security Token Service (STS) server to handle token exchange requests. + # Setting this port to a non-zero value enables STS server. + servicePort: 0 + + # The name of the CA for workload certificates. + # For example, when caName=GkeWorkloadCertificate, GKE workload certificates + # will be used as the certificates for workloads. + # The default value is "" and when caName="", the CA will be configured by other + # mechanisms (e.g., environmental variable CA_PROVIDER). + caName: "" + + # whether to use autoscaling/v2 template for HPA settings + # for internal usage only, not to be configured by users. + autoscalingv2API: true + + base: + # For istioctl usage to disable istio config crds in base + enableIstioConfigCRDs: true + + # `istio_cni` has been deprecated and will be removed in a future release. use `pilot.cni` instead + istio_cni: + # `chained` has been deprecated and will be removed in a future release. use `provider` instead + chained: true + provider: default + + # Gateway Settings + gateways: + # Define the security context for the pod. + # If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. + # On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. + securityContext: {} + + # Set to `type: RuntimeDefault` to use the default profile for templated gateways, if your container runtime supports it + seccompProfile: {} diff --git a/tests/integration/api/istio_test.go b/tests/integration/api/istio_test.go index ce7254443..8aab63239 100644 --- a/tests/integration/api/istio_test.go +++ b/tests/integration/api/istio_test.go @@ -28,6 +28,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "istio.io/istio/pkg/ptr" @@ -424,22 +425,22 @@ var _ = Describe("Istio resource", Ordered, func() { }) When("strategy is changed to InPlace", func() { + var oldRevisionKey types.NamespacedName BeforeAll(func() { + oldRevisionKey = getRevisionKey(istio, supportedversion.New) Expect(k8sClient.Get(ctx, istioKey, istio)).To(Succeed()) istio.Spec.UpdateStrategy.Type = v1alpha1.UpdateStrategyTypeInPlace Expect(k8sClient.Update(ctx, istio)).To(Succeed()) }) It("creates an IstioRevision with no version in the name", func() { - revKey := client.ObjectKey{Name: istio.Name} - Eventually(k8sClient.Get).WithArguments(ctx, revKey, rev).Should(Succeed()) + Eventually(k8sClient.Get).WithArguments(ctx, oldRevisionKey, rev).Should(Succeed()) Expect(rev.Spec.Version).To(Equal(istio.Spec.Version)) }) if withWorkloads { It("doesn't delete the previous IstioRevision while workloads reference it", func() { - revKey := getRevisionKey(istio, supportedversion.New) - Consistently(k8sClient.Get).WithArguments(ctx, revKey, rev).Should(Succeed()) + Consistently(k8sClient.Get).WithArguments(ctx, oldRevisionKey, rev).Should(Succeed()) }) When("workloads are moved to the IstioRevision with no version in the name", func() { @@ -450,23 +451,20 @@ var _ = Describe("Istio resource", Ordered, func() { It("doesn't immediately delete the previous IstioRevision", func() { marginOfError := 2 * time.Second - revKey := getRevisionKey(istio, supportedversion.New) - Consistently(k8sClient.Get, gracePeriod-marginOfError).WithArguments(ctx, revKey, rev).Should(Succeed()) + Consistently(k8sClient.Get, gracePeriod-marginOfError).WithArguments(ctx, oldRevisionKey, rev).Should(Succeed()) }) When("grace period expires", func() { It("deletes the previous IstioRevision", func() { - revKey := getRevisionKey(istio, supportedversion.New) - Eventually(k8sClient.Get).WithArguments(ctx, revKey, rev).Should(ReturnNotFoundError()) + Eventually(k8sClient.Get).WithArguments(ctx, oldRevisionKey, rev).Should(ReturnNotFoundError()) }) }) }) } else { When("grace period expires", func() { It("deletes the previous IstioRevision", func() { - revKey := getRevisionKey(istio, supportedversion.New) marginOfError := 30 * time.Second - Eventually(k8sClient.Get, gracePeriod+marginOfError).WithArguments(ctx, revKey, rev).Should(ReturnNotFoundError()) + Eventually(k8sClient.Get, gracePeriod+marginOfError).WithArguments(ctx, oldRevisionKey, rev).Should(ReturnNotFoundError()) }) }) } @@ -509,5 +507,8 @@ func getRevisionName(istio *v1alpha1.Istio, version string) string { if istio.Name == "" { panic("istio.Name is empty") } + if istio.Spec.UpdateStrategy.Type == v1alpha1.UpdateStrategyTypeInPlace { + return istio.Name + } return istio.Name + "-" + strings.ReplaceAll(version, ".", "-") } diff --git a/tests/integration/api/istiorevisiontag_test.go b/tests/integration/api/istiorevisiontag_test.go new file mode 100644 index 000000000..75acd211d --- /dev/null +++ b/tests/integration/api/istiorevisiontag_test.go @@ -0,0 +1,382 @@ +//go:build integration + +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "context" + "time" + + "github.com/istio-ecosystem/sail-operator/api/v1alpha1" + . "github.com/istio-ecosystem/sail-operator/pkg/test/util/ginkgo" + "github.com/istio-ecosystem/sail-operator/pkg/test/util/supportedversion" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "istio.io/istio/pkg/ptr" +) + +var _ = Describe("IstioRevisionTag resource", Ordered, func() { + const ( + defaultTagName = "default" + istioName = "test-istio" + istioRevisionTagNamespace = "istiorevisiontag-test" + workloadNamespace = "istiorevisiontag-test-workloads" + + gracePeriod = 5 * time.Second + ) + istio := &v1alpha1.Istio{} + istioKey := client.ObjectKey{Name: istioName} + defaultTagKey := client.ObjectKey{Name: defaultTagName} + workloadNamespaceKey := client.ObjectKey{Name: workloadNamespace} + tag := &v1alpha1.IstioRevisionTag{} + + SetDefaultEventuallyTimeout(30 * time.Second) + SetDefaultEventuallyPollingInterval(time.Second) + + SetDefaultConsistentlyDuration(10 * time.Second) + SetDefaultConsistentlyPollingInterval(time.Second) + + ctx := context.Background() + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: istioRevisionTagNamespace, + }, + } + workloadNs := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: workloadNamespace, + }, + } + workload := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-workload", + Namespace: workloadNamespace, + Labels: map[string]string{ + "istio.io/rev": "default", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + Image: "test", + }, + }, + }, + } + BeforeAll(func() { + Step("Creating the Namespaces to perform the tests") + Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) + Expect(k8sClient.Create(ctx, workloadNs)).To(Succeed()) + }) + + AfterAll(func() { + // TODO(user): Attention if you improve this code by adding other context test you MUST + // be aware of the current delete namespace limitations. + // More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations + Step("Deleting the Namespace to perform the tests") + Expect(k8sClient.Delete(ctx, namespace)).To(Succeed()) + }) + + for _, referencedResource := range []string{v1alpha1.IstioKind, v1alpha1.IstioRevisionKind} { + for _, updateStrategy := range []v1alpha1.UpdateStrategyType{v1alpha1.UpdateStrategyTypeRevisionBased, v1alpha1.UpdateStrategyTypeInPlace} { + Describe("referencing "+referencedResource+" resource, "+string(updateStrategy)+" update", func() { + BeforeAll(func() { + Step("Creating the Istio resource") + istio = &v1alpha1.Istio{ + ObjectMeta: metav1.ObjectMeta{ + Name: istioName, + }, + Spec: v1alpha1.IstioSpec{ + Version: supportedversion.Old, + Namespace: istioRevisionTagNamespace, + UpdateStrategy: &v1alpha1.IstioUpdateStrategy{ + Type: updateStrategy, + InactiveRevisionDeletionGracePeriodSeconds: ptr.Of(int64(gracePeriod.Seconds())), + }, + }, + } + Expect(k8sClient.Create(ctx, istio)).To(Succeed()) + }) + + AfterAll(func() { + deleteAllIstioRevisionTags(ctx) + deleteAllIstiosAndRevisions(ctx) + }) + + When("creating the IstioRevisionTag", func() { + BeforeAll(func() { + targetRef := v1alpha1.IstioRevisionTagTargetReference{ + Kind: referencedResource, + Name: getRevisionName(istio, istio.Spec.Version), + } + if referencedResource == v1alpha1.IstioKind { + targetRef.Name = istioName + } + tag = &v1alpha1.IstioRevisionTag{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.IstioRevisionTagSpec{ + TargetRef: targetRef, + }, + } + Expect(k8sClient.Create(ctx, tag)).To(Succeed()) + }) + It("updates IstioRevisionTag status", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.ObservedGeneration).To(Equal(tag.Generation)) + g.Expect(tag.Status.IstioRevision).To(Equal(getRevisionName(istio, supportedversion.Old))) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionFalse)) + }).Should(Succeed()) + }) + }) + When("workload ns is labeled with istio-injection label", func() { + BeforeAll(func() { + Expect(k8sClient.Get(ctx, workloadNamespaceKey, workloadNs)).To(Succeed()) + workloadNs.Labels["istio-injection"] = "enabled" + Expect(k8sClient.Update(ctx, workloadNs)).To(Succeed()) + }) + It("updates IstioRevisionTag status and detects that the revision tag is in use", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.ObservedGeneration).To(Equal(tag.Generation)) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionTrue)) + }).Should(Succeed()) + }) + }) + + When("updating the Istio control plane version", func() { + BeforeAll(func() { + Expect(k8sClient.Get(ctx, istioKey, istio)).To(Succeed()) + istio.Spec.Version = supportedversion.Default + Expect(k8sClient.Update(ctx, istio)).To(Succeed()) + }) + + if referencedResource == v1alpha1.IstioRevisionKind { + It("updates IstioRevisionTag status and still references old revision", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.IstioRevision).To(Equal(getRevisionName(istio, supportedversion.Old))) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionTrue)) + }).Should(Succeed()) + }) + } else { + It("updates IstioRevisionTag status and shows new referenced revision", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.IstioRevision).To(Equal(getRevisionName(istio, supportedversion.New))) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionTrue)) + }).Should(Succeed()) + }) + } + }) + + When("deleting the label on the workload namespace", func() { + BeforeAll(func() { + Expect(k8sClient.Get(ctx, workloadNamespaceKey, workloadNs)).To(Succeed()) + delete(workloadNs.Labels, "istio-injection") + Expect(k8sClient.Update(ctx, workloadNs)).To(Succeed()) + }) + + It("updates IstioRevisionTag status and detects that the tag is no longer in use", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionFalse)) + }).Should(Succeed()) + }) + if referencedResource == v1alpha1.IstioRevisionKind && updateStrategy == v1alpha1.UpdateStrategyTypeRevisionBased { + It("does not delete the referenced IstioRevision even though it is no longer in use and not the active revision", func() { + revKey := client.ObjectKey{Name: getRevisionName(istio, supportedversion.Old)} + rev := &v1alpha1.IstioRevision{} + Consistently(k8sClient.Get).WithArguments(ctx, revKey, rev).Should(Succeed()) + }) + } + }) + + When("creating a Pod that references the tag", func() { + BeforeAll(func() { + Expect(k8sClient.Create(ctx, workload.DeepCopy())).To(Succeed()) + }) + + AfterAll(func() { + deletePod(ctx, workload) + }) + + It("updates IstioRevisionTag status and detects that the revision tag is in use", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.ObservedGeneration).To(Equal(tag.Generation)) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionInUse).Status).To(Equal(metav1.ConditionTrue)) + }).Should(Succeed()) + }) + }) + }) + } + } + + When("Creating an Istio with name 'default' and attempting to create another IstioRevisionTag with the same name", func() { + BeforeAll(func() { + Step("Creating the Istio resources") + istio = &v1alpha1.Istio{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.IstioSpec{ + Version: supportedversion.Old, + Namespace: istioRevisionTagNamespace, + UpdateStrategy: &v1alpha1.IstioUpdateStrategy{ + Type: v1alpha1.UpdateStrategyTypeInPlace, + InactiveRevisionDeletionGracePeriodSeconds: ptr.Of(int64(gracePeriod.Seconds())), + }, + }, + } + Expect(k8sClient.Create(ctx, istio)).To(Succeed()) + istio = &v1alpha1.Istio{ + ObjectMeta: metav1.ObjectMeta{ + Name: istioName, + }, + Spec: v1alpha1.IstioSpec{ + Version: supportedversion.Old, + Namespace: istioRevisionTagNamespace, + UpdateStrategy: &v1alpha1.IstioUpdateStrategy{ + Type: v1alpha1.UpdateStrategyTypeInPlace, + InactiveRevisionDeletionGracePeriodSeconds: ptr.Of(int64(gracePeriod.Seconds())), + }, + }, + } + Expect(k8sClient.Create(ctx, istio)).To(Succeed()) + tag = &v1alpha1.IstioRevisionTag{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.IstioRevisionTagSpec{ + TargetRef: v1alpha1.IstioRevisionTagTargetReference{ + Kind: "Istio", + Name: istioName, + }, + }, + } + Expect(k8sClient.Create(ctx, tag)).To(Succeed()) + }) + + AfterAll(func() { + deleteAllIstioRevisionTags(ctx) + deleteAllIstiosAndRevisions(ctx) + }) + + It("fails to reconcile IstioRevisionTag", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.ObservedGeneration).To(Equal(tag.Generation)) + }).Should(Succeed()) + Consistently(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionReconciled).Status).To(Equal(metav1.ConditionFalse)) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionReconciled).Reason).To(Equal(v1alpha1.IstioRevisionTagReasonNameAlreadyExists)) + }).Should(Succeed()) + }) + }) + + When("Creating an IstioRevisionTag with a dangling TargetRef", func() { + BeforeAll(func() { + tag = &v1alpha1.IstioRevisionTag{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.IstioRevisionTagSpec{ + TargetRef: v1alpha1.IstioRevisionTagTargetReference{ + Kind: "Istio", + Name: istioName, + }, + }, + } + Expect(k8sClient.Create(ctx, tag)).To(Succeed()) + }) + + AfterAll(func() { + deleteAllIstiosAndRevisions(ctx) + deleteAllIstioRevisionTags(ctx) + }) + + It("fails to reconcile IstioRevisionTag", func() { + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.ObservedGeneration).To(Equal(tag.Generation)) + }).Should(Succeed()) + Consistently(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, defaultTagKey, tag)).To(Succeed()) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionReconciled).Status).To(Equal(metav1.ConditionFalse)) + g.Expect(tag.Status.GetCondition(v1alpha1.IstioRevisionTagConditionReconciled).Reason).To(Equal(v1alpha1.IstioRevisionTagReasonReferenceNotFound)) + }).Should(Succeed()) + }) + + When("attempting to create IstioRevision with same name as the tag's", func() { + BeforeAll(func() { + istio = &v1alpha1.Istio{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: v1alpha1.IstioSpec{ + Version: supportedversion.Old, + Namespace: istioRevisionTagNamespace, + UpdateStrategy: &v1alpha1.IstioUpdateStrategy{ + Type: v1alpha1.UpdateStrategyTypeInPlace, + InactiveRevisionDeletionGracePeriodSeconds: ptr.Of(int64(gracePeriod.Seconds())), + }, + }, + } + Expect(k8sClient.Create(ctx, istio)).To(Succeed()) + }) + + It("fails with ValidationError", func() { + Eventually(func(g Gomega) { + rev := &v1alpha1.IstioRevision{} + g.Expect(k8sClient.Get(ctx, defaultTagKey, rev)).To(Succeed()) + condition := rev.Status.GetCondition(v1alpha1.IstioRevisionConditionReconciled) + g.Expect(condition.Status).To(Equal(metav1.ConditionFalse)) + g.Expect(condition.Message).To(ContainSubstring("an IstioRevisionTag exists with this name")) + }).Should(Succeed()) + }) + }) + }) +}) + +func deleteAllIstioRevisionTags(ctx context.Context) { + Step("Deleting all IstioRevisionTags") + Eventually(k8sClient.DeleteAllOf).WithArguments(ctx, &v1alpha1.IstioRevisionTag{}).Should(Succeed()) + Eventually(func(g Gomega) { + list := &v1alpha1.IstioRevisionTagList{} + g.Expect(k8sClient.List(ctx, list)).To(Succeed()) + g.Expect(list.Items).To(BeEmpty()) + }).Should(Succeed()) +} + +func deletePod(ctx context.Context, pod *corev1.Pod) { + Step("Deleting pod") + Eventually(k8sClient.Delete).WithArguments(ctx, pod).Should(Succeed()) + Eventually(func(g Gomega) { + p := &corev1.Pod{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, p)).To(ReturnNotFoundError()) + }).Should(Succeed()) +} diff --git a/tests/integration/api/suite_test.go b/tests/integration/api/suite_test.go index 662b9ea80..a4cc42f09 100644 --- a/tests/integration/api/suite_test.go +++ b/tests/integration/api/suite_test.go @@ -24,6 +24,7 @@ import ( "github.com/istio-ecosystem/sail-operator/controllers/istio" "github.com/istio-ecosystem/sail-operator/controllers/istiocni" "github.com/istio-ecosystem/sail-operator/controllers/istiorevision" + "github.com/istio-ecosystem/sail-operator/controllers/istiorevisiontag" "github.com/istio-ecosystem/sail-operator/pkg/config" "github.com/istio-ecosystem/sail-operator/pkg/helm" "github.com/istio-ecosystem/sail-operator/pkg/scheme" @@ -83,6 +84,7 @@ var _ = BeforeSuite(func() { scheme := mgr.GetScheme() Expect(istio.NewReconciler(cfg, cl, scheme).SetupWithManager(mgr)).To(Succeed()) Expect(istiorevision.NewReconciler(cfg, cl, scheme, chartManager).SetupWithManager(mgr)).To(Succeed()) + Expect(istiorevisiontag.NewReconciler(cfg, cl, scheme, chartManager).SetupWithManager(mgr)).To(Succeed()) Expect(istiocni.NewReconciler(cfg, cl, scheme, chartManager).SetupWithManager(mgr)).To(Succeed()) // create new cancellable context