github.com/oam-dev/kubevela@v1.9.11/pkg/workflow/providers/multicluster/multicluster.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package multicluster 18 19 import ( 20 "github.com/pkg/errors" 21 "sigs.k8s.io/controller-runtime/pkg/client" 22 23 monitorContext "github.com/kubevela/pkg/monitor/context" 24 wfContext "github.com/kubevela/workflow/pkg/context" 25 "github.com/kubevela/workflow/pkg/cue/model/value" 26 wfTypes "github.com/kubevela/workflow/pkg/types" 27 28 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 29 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 30 "github.com/oam-dev/kubevela/pkg/appfile" 31 "github.com/oam-dev/kubevela/pkg/multicluster" 32 pkgpolicy "github.com/oam-dev/kubevela/pkg/policy" 33 "github.com/oam-dev/kubevela/pkg/policy/envbinding" 34 oamProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/oam" 35 ) 36 37 const ( 38 // ProviderName is provider name for install. 39 ProviderName = "multicluster" 40 ) 41 42 type provider struct { 43 client.Client 44 app *v1beta1.Application 45 af *appfile.Appfile 46 apply oamProvider.ComponentApply 47 healthCheck oamProvider.ComponentHealthCheck 48 renderer oamProvider.WorkloadRenderer 49 } 50 51 // MakePlacementDecisions 52 // Deprecated 53 func (p *provider) MakePlacementDecisions(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ wfTypes.Action) error { 54 policy, err := v.GetString("inputs", "policyName") 55 if err != nil { 56 return err 57 } 58 env, err := v.GetString("inputs", "envName") 59 if err != nil { 60 return err 61 } 62 val, err := v.LookupValue("inputs", "placement") 63 if err != nil { 64 return err 65 } 66 67 // TODO detect env change 68 placement := &v1alpha1.EnvPlacement{} 69 if err = val.UnmarshalTo(placement); err != nil { 70 return errors.Wrapf(err, "failed to parse placement while making placement decision") 71 } 72 73 var namespace, clusterName string 74 // check if namespace selector is valid 75 if placement.NamespaceSelector != nil { 76 if len(placement.NamespaceSelector.Labels) != 0 { 77 return errors.Errorf("invalid env %s: namespace selector in cluster-gateway does not support label selector for now", env) 78 } 79 namespace = placement.NamespaceSelector.Name 80 } 81 // check if cluster selector is valid 82 if placement.ClusterSelector != nil { 83 if len(placement.ClusterSelector.Labels) != 0 { 84 return errors.Errorf("invalid env %s: cluster selector does not support label selector for now", env) 85 } 86 clusterName = placement.ClusterSelector.Name 87 } 88 // set fallback cluster 89 if clusterName == "" { 90 clusterName = multicluster.ClusterLocalName 91 } 92 // check if target cluster exists 93 if clusterName != multicluster.ClusterLocalName { 94 if _, err := multicluster.GetVirtualCluster(ctx, p.Client, clusterName); err != nil { 95 return errors.Wrapf(err, "failed to get cluster %s for env %s", clusterName, env) 96 } 97 } 98 // write result back 99 decisions := []v1alpha1.PlacementDecision{{ 100 Cluster: clusterName, 101 Namespace: namespace, 102 }} 103 if err = envbinding.WritePlacementDecisions(p.app, policy, env, decisions); err != nil { 104 return err 105 } 106 return v.FillObject(map[string]interface{}{"decisions": decisions}, "outputs") 107 } 108 109 // PatchApplication 110 // Deprecated 111 func (p *provider) PatchApplication(_ monitorContext.Context, _ wfContext.Context, v *value.Value, _ wfTypes.Action) error { 112 env, err := v.GetString("inputs", "envName") 113 if err != nil { 114 return err 115 } 116 patch := v1alpha1.EnvPatch{} 117 selector := &v1alpha1.EnvSelector{} 118 119 obj, err := v.LookupValue("inputs", "patch") 120 if err == nil { 121 if err = obj.UnmarshalTo(&patch); err != nil { 122 return errors.Wrapf(err, "failed to unmarshal patch for env %s", env) 123 } 124 } 125 obj, err = v.LookupValue("inputs", "selector") 126 if err == nil { 127 if err = obj.UnmarshalTo(selector); err != nil { 128 return errors.Wrapf(err, "failed to unmarshal selector for env %s", env) 129 } 130 } else { 131 selector = nil 132 } 133 134 newApp, err := envbinding.PatchApplication(p.app, &patch, selector) 135 if err != nil { 136 return errors.Wrapf(err, "failed to patch app for env %s", env) 137 } 138 return v.FillObject(newApp, "outputs") 139 } 140 141 func (p *provider) ListClusters(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ wfTypes.Action) error { 142 secrets, err := multicluster.ListExistingClusterSecrets(ctx, p.Client) 143 if err != nil { 144 return err 145 } 146 var clusters []string 147 for _, secret := range secrets { 148 clusters = append(clusters, secret.Name) 149 } 150 return v.FillObject(clusters, "outputs", "clusters") 151 } 152 153 func (p *provider) Deploy(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, act wfTypes.Action) error { 154 param := DeployParameter{} 155 if err := v.CueValue().Decode(¶m); err != nil { 156 return err 157 } 158 if param.Parallelism <= 0 { 159 return errors.Errorf("parallelism cannot be smaller than 1") 160 } 161 executor := NewDeployWorkflowStepExecutor(p.Client, p.af, p.apply, p.healthCheck, p.renderer, param) 162 healthy, reason, err := executor.Deploy(ctx) 163 if err != nil { 164 return err 165 } 166 if !healthy { 167 act.Wait(reason) 168 } 169 return nil 170 } 171 172 func (p *provider) GetPlacementsFromTopologyPolicies(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ wfTypes.Action) error { 173 policyNames, err := v.GetStringSlice("policies") 174 if err != nil { 175 return err 176 } 177 policies, err := selectPolicies(p.af.Policies, policyNames) 178 if err != nil { 179 return err 180 } 181 placements, err := pkgpolicy.GetPlacementsFromTopologyPolicies(ctx, p.Client, p.af.Namespace, policies, true) 182 if err != nil { 183 return err 184 } 185 return v.FillObject(placements, "placements") 186 } 187 188 // Install register handlers to provider discover. 189 func Install(p wfTypes.Providers, c client.Client, app *v1beta1.Application, af *appfile.Appfile, apply oamProvider.ComponentApply, healthCheck oamProvider.ComponentHealthCheck, renderer oamProvider.WorkloadRenderer) { 190 prd := &provider{Client: c, app: app, af: af, apply: apply, healthCheck: healthCheck, renderer: renderer} 191 p.Register(ProviderName, map[string]wfTypes.Handler{ 192 "make-placement-decisions": prd.MakePlacementDecisions, 193 "patch-application": prd.PatchApplication, 194 "list-clusters": prd.ListClusters, 195 "get-placements-from-topology-policies": prd.GetPlacementsFromTopologyPolicies, 196 "deploy": prd.Deploy, 197 }) 198 }