github.com/oam-dev/kubevela@v1.9.11/pkg/addon/helper.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 addon 18 19 import ( 20 "context" 21 "fmt" 22 "path/filepath" 23 24 "github.com/pkg/errors" 25 v1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 "k8s.io/client-go/discovery" 28 "k8s.io/client-go/rest" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 32 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 33 "github.com/oam-dev/kubevela/apis/types" 34 "github.com/oam-dev/kubevela/pkg/multicluster" 35 "github.com/oam-dev/kubevela/pkg/oam" 36 addonutil "github.com/oam-dev/kubevela/pkg/utils/addon" 37 "github.com/oam-dev/kubevela/pkg/utils/apply" 38 "github.com/oam-dev/kubevela/pkg/utils/common" 39 ) 40 41 const ( 42 // disabled indicates the addon is disabled 43 disabled = "disabled" 44 // enabled indicates the addon is enabled 45 enabled = "enabled" 46 // enabling indicates the addon is enabling 47 enabling = "enabling" 48 // disabling indicates the addon related app is deleting 49 disabling = "disabling" 50 // suspend indicates the addon related app is suspended 51 suspend = "suspend" 52 ) 53 54 // EnableAddon will enable addon with dependency check, source is where addon from. 55 func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache, registries []Registry, opts ...InstallOption) (string, error) { 56 h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, registries, opts...) 57 pkg, err := h.loadInstallPackage(name, version) 58 if err != nil { 59 return "", err 60 } 61 if err := validateAddonPackage(pkg); err != nil { 62 return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon: %s", name)) 63 } 64 return h.enableAddon(pkg) 65 } 66 67 // DisableAddon will disable addon from cluster. 68 func DisableAddon(ctx context.Context, cli client.Client, name string, config *rest.Config, force bool) error { 69 app, err := FetchAddonRelatedApp(ctx, cli, name) 70 // if app not exist, report error 71 if err != nil { 72 return err 73 } 74 75 if !force { 76 var usingAddonApp []v1beta1.Application 77 usingAddonApp, err = checkAddonHasBeenUsed(ctx, cli, name, *app, config) 78 if err != nil { 79 return err 80 } 81 if len(usingAddonApp) != 0 { 82 return errors.New(appsDependsOnAddonErrInfo(usingAddonApp)) 83 } 84 } 85 86 if err := cli.Delete(ctx, app); err != nil { 87 return err 88 } 89 return nil 90 } 91 92 // EnableAddonByLocalDir enable an addon from local dir 93 func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}, opts ...InstallOption) (string, error) { 94 absDir, err := filepath.Abs(dir) 95 if err != nil { 96 return "", err 97 } 98 r := localReader{dir: absDir, name: name} 99 metas, err := r.ListAddonMeta() 100 if err != nil { 101 return "", err 102 } 103 meta := metas[r.name] 104 UIData, err := GetUIDataFromReader(r, &meta, UIMetaOptions) 105 if err != nil { 106 return "", err 107 } 108 pkg, err := GetInstallPackageFromReader(r, &meta, UIData) 109 if err != nil { 110 return "", err 111 } 112 if err := validateAddonPackage(pkg); err != nil { 113 return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon by local dir: %s", dir)) 114 } 115 h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, nil, opts...) 116 needEnableAddonNames, err := h.checkDependency(pkg) 117 if err != nil { 118 return "", err 119 } 120 if len(needEnableAddonNames) > 0 { 121 return "", fmt.Errorf("you must first enable dependencies: %v", needEnableAddonNames) 122 } 123 return h.enableAddon(pkg) 124 } 125 126 // GetAddonStatus is general func for cli and apiServer get addon status 127 func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status, error) { 128 var addonStatus Status 129 130 app, err := FetchAddonRelatedApp(ctx, cli, name) 131 if err != nil { 132 if apierrors.IsNotFound(err) { 133 addonStatus.AddonPhase = disabled 134 return addonStatus, nil 135 } 136 return addonStatus, err 137 } 138 labels := app.GetLabels() 139 addonStatus.AppStatus = &app.Status 140 addonStatus.InstalledVersion = labels[oam.LabelAddonVersion] 141 addonStatus.InstalledRegistry = labels[oam.LabelAddonRegistry] 142 143 var clusters = make(map[string]map[string]interface{}) 144 for _, r := range app.Status.AppliedResources { 145 if r.Cluster == "" { 146 r.Cluster = multicluster.ClusterLocalName 147 } 148 // TODO(wonderflow): we should collect all the necessary information as observability, currently we only collect cluster name 149 clusters[r.Cluster] = make(map[string]interface{}) 150 } 151 addonStatus.Clusters = clusters 152 153 if app.Status.Workflow != nil && app.Status.Workflow.Suspend { 154 addonStatus.AddonPhase = suspend 155 return addonStatus, nil 156 } 157 158 // Get addon parameters 159 var sec v1.Secret 160 err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: addonutil.Addon2SecName(name)}, &sec) 161 if err != nil { 162 // Not found error can be ignored. Others can't. 163 if !apierrors.IsNotFound(err) { 164 return addonStatus, err 165 } 166 } else { 167 // Although normally `else` is not preferred, we must use `else` here. 168 args, err := FetchArgsFromSecret(&sec) 169 if err != nil { 170 return addonStatus, err 171 } 172 addonStatus.Parameters = args 173 } 174 175 switch app.Status.Phase { 176 case commontypes.ApplicationRunning: 177 addonStatus.AddonPhase = enabled 178 return addonStatus, nil 179 case commontypes.ApplicationDeleting: 180 addonStatus.AddonPhase = disabling 181 return addonStatus, nil 182 default: 183 addonStatus.AddonPhase = enabling 184 return addonStatus, nil 185 } 186 } 187 188 // FindAddonPackagesDetailFromRegistry find addons' WholeInstallPackage from registries, empty registryName indicates matching all 189 func FindAddonPackagesDetailFromRegistry(ctx context.Context, k8sClient client.Client, addonNames []string, registryNames []string) ([]*WholeAddonPackage, error) { 190 var addons []*WholeAddonPackage 191 var registries []Registry 192 193 if len(addonNames) == 0 { 194 return nil, fmt.Errorf("no addon name specified") 195 } 196 registryDataStore := NewRegistryDataStore(k8sClient) 197 198 // Find matched registries 199 if len(registryNames) == 0 { 200 // Empty registryNames will match all registries 201 regs, err := registryDataStore.ListRegistries(ctx) 202 if err != nil { 203 return nil, err 204 } 205 registries = regs 206 } else { 207 // Only match specified registries 208 for _, registryName := range registryNames { 209 r, err := registryDataStore.GetRegistry(ctx, registryName) 210 if err != nil { 211 continue 212 } 213 registries = append(registries, r) 214 } 215 } 216 217 if len(registries) == 0 { 218 return nil, ErrRegistryNotExist 219 } 220 221 // Found addons, for deduplication purposes 222 foundAddons := make(map[string]bool) 223 merge := func(addon *WholeAddonPackage) { 224 if _, ok := foundAddons[addon.Name]; !ok { 225 foundAddons[addon.Name] = true 226 } 227 addons = append(addons, addon) 228 } 229 230 // Find matched addons in registries 231 for _, r := range registries { 232 if IsVersionRegistry(r) { 233 vr := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{ 234 Username: r.Helm.Username, 235 Password: r.Helm.Password, 236 InsecureSkipTLS: r.Helm.InsecureSkipTLS, 237 }) 238 for _, addonName := range addonNames { 239 wholePackage, err := vr.GetDetailedAddon(ctx, addonName, "") 240 if err != nil { 241 continue 242 } 243 merge(wholePackage) 244 } 245 } else { 246 meta, err := r.ListAddonMeta() 247 if err != nil { 248 continue 249 } 250 251 for _, addonName := range addonNames { 252 sourceMeta, ok := meta[addonName] 253 if !ok { 254 continue 255 } 256 uiData, err := r.GetUIData(&sourceMeta, UIMetaOptions) 257 if err != nil { 258 continue 259 } 260 installPackage, err := r.GetInstallPackage(&sourceMeta, uiData) 261 if err != nil { 262 continue 263 } 264 // Combine UIData and InstallPackage into WholeAddonPackage 265 wholePackage := &WholeAddonPackage{ 266 InstallPackage: *installPackage, 267 APISchema: uiData.APISchema, 268 Detail: uiData.Detail, 269 AvailableVersions: uiData.AvailableVersions, 270 RegistryName: uiData.RegistryName, 271 } 272 merge(wholePackage) 273 } 274 } 275 } 276 277 if len(addons) == 0 { 278 return nil, ErrNotExist 279 } 280 281 return addons, nil 282 } 283 284 // Status contain addon phase and related app status 285 type Status struct { 286 AddonPhase string 287 AppStatus *commontypes.AppStatus 288 // the status of multiple clusters 289 Clusters map[string]map[string]interface{} `json:"clusters,omitempty"` 290 InstalledVersion string 291 Parameters map[string]interface{} 292 // Where the addon is from. Can be empty if not installed. 293 InstalledRegistry string 294 }