blob: 1ac67b7b843679a47371ef78301e6d96dd1dd2be [file] [log] [blame]
// 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 helmreconciler
import (
"fmt"
"io"
"strconv"
"strings"
)
import (
jsonpatch "github.com/evanphx/json-patch/v5"
"istio.io/pkg/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubectl/pkg/scheme"
)
import (
"github.com/apache/dubbo-go-pixiu/operator/pkg/name"
)
const (
// MetadataNamespace is the namespace for mesh metadata (labels, annotations)
MetadataNamespace = "install.operator.istio.io"
// OwningResourceName represents the name of the owner to which the resource relates
OwningResourceName = MetadataNamespace + "/owning-resource"
// OwningResourceNamespace represents the namespace of the owner to which the resource relates
OwningResourceNamespace = MetadataNamespace + "/owning-resource-namespace"
// operatorLabelStr indicates Istio operator is managing this resource.
operatorLabelStr = name.OperatorAPINamespace + "/managed"
// operatorReconcileStr indicates that the operator will reconcile the resource.
operatorReconcileStr = "Reconcile"
// IstioComponentLabelStr indicates which Istio component a resource belongs to.
IstioComponentLabelStr = name.OperatorAPINamespace + "/component"
// istioVersionLabelStr indicates the Istio version of the installation.
istioVersionLabelStr = name.OperatorAPINamespace + "/version"
// istioOperatorInstallPrefix indicates the name of resource istiooperators.install.istio.io for Istio operator installation.
istioOperatorsInstallPrefix = "installed-state"
)
var (
// TestMode sets the controller into test mode. Used for unit tests to bypass things like waiting on resources.
TestMode = false
scope = log.RegisterScope("installer", "installer", 0)
)
func init() {
// Tree representation and wait channels are an inversion of ComponentDependencies and are constructed from it.
buildInstallTree()
}
// ComponentTree represents a tree of component dependencies.
type (
ComponentTree map[name.ComponentName]interface{}
componentNameToListMap map[name.ComponentName][]name.ComponentName
)
var (
// ComponentDependencies is a tree of component dependencies. The semantics are ComponentDependencies[cname] gives
// the subtree of components that must wait for cname to be installed before starting installation themselves.
ComponentDependencies = componentNameToListMap{
name.PilotComponentName: {
name.CNIComponentName,
name.IngressComponentName,
name.EgressComponentName,
},
name.IstioBaseComponentName: {
name.PilotComponentName,
},
}
// InstallTree is a top down hierarchy tree of dependencies where children must wait for the parent to complete
// before starting installation.
InstallTree = make(ComponentTree)
)
// buildInstallTree builds a tree from buildInstallTree where parents are the root of each subtree.
func buildInstallTree() {
// Starting with root, recursively insert each first level child into each node.
insertChildrenRecursive(name.IstioBaseComponentName, InstallTree, ComponentDependencies)
}
func insertChildrenRecursive(componentName name.ComponentName, tree ComponentTree, children componentNameToListMap) {
tree[componentName] = make(ComponentTree)
for _, child := range children[componentName] {
insertChildrenRecursive(child, tree[componentName].(ComponentTree), children)
}
}
// InstallTreeString returns a string representation of the dependency tree.
func InstallTreeString() string {
var sb strings.Builder
buildInstallTreeString(name.IstioBaseComponentName, "", &sb)
return sb.String()
}
func buildInstallTreeString(componentName name.ComponentName, prefix string, sb io.StringWriter) {
_, _ = sb.WriteString(prefix + string(componentName) + "\n")
if _, ok := InstallTree[componentName].(ComponentTree); !ok {
return
}
for k := range InstallTree[componentName].(ComponentTree) {
buildInstallTreeString(k, prefix+" ", sb)
}
}
// applyOverlay applies an overlay using JSON patch strategy over the current Object in place.
func applyOverlay(current, overlay *unstructured.Unstructured) error {
cj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, current)
if err != nil {
return err
}
overlayUpdated := overlay.DeepCopy()
if strings.EqualFold(current.GetKind(), "service") {
if err := saveClusterIP(current, overlayUpdated); err != nil {
return err
}
saveNodePorts(current, overlayUpdated)
}
uj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, overlayUpdated)
if err != nil {
return err
}
merged, err := jsonpatch.MergePatch(cj, uj)
if err != nil {
return err
}
return runtime.DecodeInto(unstructured.UnstructuredJSONScheme, merged, current)
}
// createPortMap returns a map, mapping the value of the port and value of the nodePort
func createPortMap(current *unstructured.Unstructured) map[string]uint32 {
portMap := make(map[string]uint32)
svc := &v1.Service{}
if err := scheme.Scheme.Convert(current, svc, nil); err != nil {
log.Error(err.Error())
return portMap
}
for _, p := range svc.Spec.Ports {
portMap[strconv.Itoa(int(p.Port))] = uint32(p.NodePort)
}
return portMap
}
// saveNodePorts transfers the port values from the current cluster into the overlay
func saveNodePorts(current, overlay *unstructured.Unstructured) {
portMap := createPortMap(current)
ports, _, _ := unstructured.NestedFieldNoCopy(overlay.Object, "spec", "ports")
portList, ok := ports.([]interface{})
if !ok {
return
}
for _, port := range portList {
m, ok := port.(map[string]interface{})
if !ok {
continue
}
if nodePortNum, ok := m["nodePort"]; ok && fmt.Sprintf("%v", nodePortNum) == "0" {
if portNum, ok := m["port"]; ok {
if v, ok := portMap[fmt.Sprintf("%v", portNum)]; ok {
m["nodePort"] = v
}
}
}
}
}
// saveClusterIP copies the cluster IP from the current cluster into the overlay
func saveClusterIP(current, overlay *unstructured.Unstructured) error {
// Save the value of spec.clusterIP set by the cluster
if clusterIP, found, err := unstructured.NestedString(current.Object, "spec",
"clusterIP"); err != nil {
return err
} else if found {
if err := unstructured.SetNestedField(overlay.Object, clusterIP, "spec",
"clusterIP"); err != nil {
return err
}
}
return nil
}
// getIstioOperatorCRName get the Istio operator crd name based on specified revision
func getIstioOperatorCRName(revision string) string {
name := istioOperatorsInstallPrefix
if revision == "" || revision == "default" {
return name
}
return name + "-" + revision
}