Files
kubevela/pkg/utils/common/common.go
T
+2 7103f8ff52 Feat: merge master into apiserver (#2660)
* Feat(rollout): fill rolloutBatches if empty when scale up/down (#2569)

* Feat: fill rolloutBatches if empty

* Fix: fix unit-test

* Test: add more test

Fix: lint

Fix: fix lint

* Update release.yml (#2537)

* Feat: add registry, merge registry and cap center (#2528)

* Feat: add registry command

* Refactor: comp/trait command combine with registry

* Feat: refactor `vela comp/trait`

* Fix: import

* Fix: fix if type is autodetects.core.oam.dev

* Fix: fix list from url

* Fix: test

* Feat: add test

* Fix: remove dup test

* Fix: test

* Fix: test

* Fix: fix label filter

* Fix: reviewable

* Fix test

* fix personal repo in test

* Fix test

* Fix test

* add some boundary check

* reviewable

* Fix: fix nocalhost trait (#2577)

* fix incorrect addon status (#2576)

* Fix(cli): client-side throttling in vela CLI (#2581)

* fix cli throttling

* fix import

* set to a lower value

* remove addon with no defs (#2574)

* Feat: vela logs support multicluster (#2593)

* Feat: add basic multiple cluster logs

* fix context

* Fix select style

* Fix select style

* remove useless env

* fix naming

* Feat: vela cluster support use ocm to join/list/detach cluster (#2599)

* Feat: add render component and apply component remaining (#2587)

* Feat: add render component and apply component remaining

* fix ut

* fix e2e

* allow import package in custom status cue template (#2585)

Co-authored-by: chwetion <chwetion@foxmail.com>

* Fix: abnormal aux name (#2612)

* Feat: store workflow step def properties in cm (#2592)

* Fix: fix notification def

* Feat: store workflow step def properties in cm

* fix ci

* fix data race

* Fix: change Initializer to Application for addon Observability (#2615)

In this doc, updated the Observability implementation from initializer
to Application. I also store definitions as it's not well stored in
vela-templates/addons/observability

* Fix: fix backport param (#2611)

* Fix: add owner reference in workflow context cm (#2573)

* Fix: add owner reference in workflow context cm

* fix ci

* delete useless test case

* Fix: op.delete bugs (#2622)

* Fix: op.delete some bugs

* Fix: app status update error

Fix: make reviewable

* Fix: show reconcile error log (#2626)

* Feat: add reconcile timeout configuration for vela-core (#2630)

* Fix: patch status retry while conflict happens (#2629)

* Fix: allow definition schema cm can be same name in different definition type (#2618)

* Fix: fix definition schema cm name

* fix ut

* fix ut

* fix show

* add switch default case

* Feat: remove envbinding policy into workflow (#2556)

Fix: add more test

* Feat: add vela prob to test cluster (#2635)

* Fix: upgrade stern lib to avoid panic for vela logs (#2650)

* Fix: filter loggable workload in vela logs (#2651)

* Fix: filter loggable workload in vela logs

* reviewable

* Feat: add vela exec for multi cluster (#2299)

fix

support vela exec

* Fix: health check will check for multiclusters (#2645)

* Fix: minor fix for vela cli printing (#2655)

* Fix: minor fix for vela cli printing

* add dockerfile go mod cache

* Feat: support apiserver-related multicluster features (#2625)

* Feat: remove envbinding policy into workflow

Feat: add support for env change (env gc)

Fix: fix rollout timeout setting bug

* Feat: support disable trait and env without workflow

* Fix: add hint for replaced value

Co-authored-by: wyike <wangyike_wyk@163.com>
Co-authored-by: basefas <basefas@hotmail.com>
Co-authored-by: qiaozp <47812250+chivalryq@users.noreply.github.com>
Co-authored-by: Tianxin Dong <dongtianxin.tx@alibaba-inc.com>
Co-authored-by: yangsoon <yangsoonlx@gmail.com>
Co-authored-by: Chwetion <137953601@qq.com>
Co-authored-by: chwetion <chwetion@foxmail.com>
Co-authored-by: Jian.Li <74582607+leejanee@users.noreply.github.com>
Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2021-11-09 13:06:55 +08:00

428 lines
13 KiB
Go

/*
Copyright 2021 The KubeVela 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 common
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/format"
"cuelang.org/go/encoding/openapi"
"github.com/AlecAivazis/survey/v2"
"github.com/hashicorp/hcl/v2/hclparse"
clustergatewayapi "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/terraform-config-inspect/tfconfig"
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
istioclientv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/util/flowcontrol"
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
ocmworkv1 "open-cluster-management.io/api/work/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/yaml"
oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
oamstandard "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/model"
)
var (
// Scheme defines the default KubeVela schema
Scheme = k8sruntime.NewScheme()
)
func init() {
_ = clientgoscheme.AddToScheme(Scheme)
_ = apiregistrationv1.AddToScheme(Scheme)
_ = crdv1.AddToScheme(Scheme)
_ = oamcore.AddToScheme(Scheme)
_ = oamstandard.AddToScheme(Scheme)
_ = istioclientv1beta1.AddToScheme(Scheme)
_ = certmanager.AddToScheme(Scheme)
_ = kruise.AddToScheme(Scheme)
_ = terraformv1beta1.AddToScheme(Scheme)
_ = ocmclusterv1alpha1.Install(Scheme)
_ = ocmclusterv1.Install(Scheme)
_ = ocmworkv1.Install(Scheme)
_ = clustergatewayapi.AddToScheme(Scheme)
// +kubebuilder:scaffold:scheme
}
// InitBaseRestConfig will return reset config for create controller runtime client
func InitBaseRestConfig() (Args, error) {
restConf, err := config.GetConfig()
if err != nil && os.Getenv("IGNORE_KUBE_CONFIG") != "true" {
fmt.Println("get kubeConfig err", err)
os.Exit(1)
} else if err != nil {
return Args{}, err
}
restConf.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(100, 200)
return Args{
Config: restConf,
Schema: Scheme,
}, nil
}
// globalClient will be a client for whole command lifecycle
var globalClient client.Client
// SetGlobalClient will set a client for one cli command
func SetGlobalClient(clt client.Client) error {
globalClient = clt
return nil
}
// GetClient will K8s client in args
func GetClient() (client.Client, error) {
if globalClient != nil {
return globalClient, nil
}
return nil, errors.New("client not set, call SetGlobalClient first")
}
// HTTPGet will send GET http request with context
func HTTPGet(ctx context.Context, url string) ([]byte, error) {
// Change NewRequest to NewRequestWithContext and pass context it
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
//nolint:errcheck
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// GetCUEParameterValue converts definitions to cue format
func GetCUEParameterValue(cueStr string) (cue.Value, error) {
r := cue.Runtime{}
template, err := r.Compile("", cueStr+velacue.BaseTemplate)
if err != nil {
return cue.Value{}, err
}
tempStruct, err := template.Value().Struct()
if err != nil {
return cue.Value{}, err
}
// find the parameter definition
var paraDef cue.FieldInfo
var found bool
for i := 0; i < tempStruct.Len(); i++ {
paraDef = tempStruct.Field(i)
if paraDef.Name == model.ParameterFieldName {
found = true
break
}
}
if !found {
return cue.Value{}, errors.New("parameter not exist")
}
arguments := paraDef.Value
return arguments, nil
}
// GenOpenAPI generates OpenAPI json schema from cue.Instance
func GenOpenAPI(inst *cue.Instance) ([]byte, error) {
if inst.Err != nil {
return nil, inst.Err
}
paramOnlyIns, err := RefineParameterInstance(inst)
if err != nil {
return nil, err
}
defaultConfig := &openapi.Config{}
b, err := openapi.Gen(paramOnlyIns, defaultConfig)
if err != nil {
return nil, err
}
var out = &bytes.Buffer{}
_ = json.Indent(out, b, "", " ")
return out.Bytes(), nil
}
// extractParameterDefinitionNodeFromInstance extracts the `#parameter` ast.Node from root instance, if failed fall back to `parameter` by LookUpDef
func extractParameterDefinitionNodeFromInstance(inst *cue.Instance) ast.Node {
opts := []cue.Option{cue.All(), cue.DisallowCycles(true), cue.ResolveReferences(true), cue.Docs(true)}
node := inst.Value().Syntax(opts...)
if fileNode, ok := node.(*ast.File); ok {
for _, decl := range fileNode.Decls {
if field, ok := decl.(*ast.Field); ok {
if label, ok := field.Label.(*ast.Ident); ok && label.Name == "#"+model.ParameterFieldName {
return decl.(*ast.Field).Value
}
}
}
}
paramVal := inst.LookupDef(model.ParameterFieldName)
return paramVal.Syntax(opts...)
}
// RefineParameterInstance refines cue instance to merely include `parameter` identifier
func RefineParameterInstance(inst *cue.Instance) (*cue.Instance, error) {
r := cue.Runtime{}
paramVal := inst.LookupDef(model.ParameterFieldName)
var paramOnlyStr string
switch k := paramVal.IncompleteKind(); k {
case cue.StructKind, cue.ListKind:
paramSyntax, _ := format.Node(extractParameterDefinitionNodeFromInstance(inst))
paramOnlyStr = fmt.Sprintf("#%s: %s\n", model.ParameterFieldName, string(paramSyntax))
case cue.IntKind, cue.StringKind, cue.FloatKind, cue.BoolKind:
paramOnlyStr = fmt.Sprintf("#%s: %v", model.ParameterFieldName, paramVal)
case cue.BottomKind:
paramOnlyStr = fmt.Sprintf("#%s: {}", model.ParameterFieldName)
default:
return nil, fmt.Errorf("unsupport parameter kind: %s", k.String())
}
paramOnlyIns, err := r.Compile("-", paramOnlyStr)
if err != nil {
return nil, err
}
return paramOnlyIns, nil
}
// RealtimePrintCommandOutput prints command output in real time
// If logFile is "", it will prints the stdout, or it will write to local file
func RealtimePrintCommandOutput(cmd *exec.Cmd, logFile string) error {
var writer io.Writer
if logFile == "" {
writer = io.MultiWriter(os.Stdout)
} else {
if _, err := os.Stat(filepath.Dir(logFile)); err != nil {
return err
}
f, err := os.Create(filepath.Clean(logFile))
if err != nil {
return err
}
writer = io.MultiWriter(f)
}
cmd.Stdout = writer
cmd.Stderr = writer
if err := cmd.Run(); err != nil {
return err
}
return nil
}
// ClusterObject2Map convert ClusterObjectReference to a readable map
func ClusterObject2Map(refs []common.ClusterObjectReference) map[string]string {
clusterResourceRefTmpl := "Cluster: %s | Namespace: %s | Component: %s | Kind: %s"
objs := make(map[string]string, len(refs))
for _, r := range refs {
if r.Cluster == "" {
r.Cluster = "local"
}
objs[r.Cluster+"/"+r.Namespace+"/"+r.Name] = fmt.Sprintf(clusterResourceRefTmpl, r.Cluster, r.Namespace, r.Name, r.Kind)
}
return objs
}
// ResourceLocation indicates the resource location
type ResourceLocation struct {
Cluster string
Namespace string
}
func filterWorkload(resources []common.ClusterObjectReference) []common.ClusterObjectReference {
var filteredOR []common.ClusterObjectReference
loggableWorkload := map[string]bool{
"Deployment": true,
"StatefulSet": true,
"CloneSet": true,
"Job": true,
}
for _, r := range resources {
if _, ok := loggableWorkload[r.Kind]; ok {
filteredOR = append(filteredOR, r)
}
}
return filteredOR
}
// AskToChooseOneEnvResource will ask users to select one applied resource of the application if more than one
// resources is a map for component to applied resources
// return the selected ClusterObjectReference
func AskToChooseOneEnvResource(app *v1beta1.Application) (*common.ClusterObjectReference, error) {
resources := app.Status.AppliedResources
if len(resources) == 0 {
return nil, fmt.Errorf("no resources in the application deployed yet")
}
resources = filterWorkload(resources)
// filter locations
if len(resources) == 0 {
return nil, fmt.Errorf("no supported workload resources detected in deployed resources")
}
if len(resources) == 1 {
return &resources[0], nil
}
opMap := ClusterObject2Map(resources)
var ops []string
for _, r := range opMap {
ops = append(ops, r)
}
prompt := &survey.Select{
Message: fmt.Sprintf("You have %d deployed resources in your app. Please choose one:", len(ops)),
Options: ops,
}
var selectedRsc string
err := survey.AskOne(prompt, &selectedRsc)
if err != nil {
return nil, fmt.Errorf("choosing resource err %w", err)
}
for k, resource := range ops {
if selectedRsc == resource {
return &resources[k], nil
}
}
return nil, fmt.Errorf("choosing resource err %w", err)
}
// AskToChooseOneService will ask users to select one service of the application if more than one
func AskToChooseOneService(svcNames []string) (string, error) {
if len(svcNames) == 0 {
return "", fmt.Errorf("no service exist in the application")
}
if len(svcNames) == 1 {
return svcNames[0], nil
}
prompt := &survey.Select{
Message: "You have multiple services in your app. Please choose one service: ",
Options: svcNames,
}
var svcName string
err := survey.AskOne(prompt, &svcName)
if err != nil {
return "", fmt.Errorf("choosing service err %w", err)
}
return svcName, nil
}
// AskToChooseOnePods will ask users to select one pods of the resource if more than one
func AskToChooseOnePods(podNames []string) (string, error) {
if len(podNames) == 0 {
return "", fmt.Errorf("no service exist in the application")
}
if len(podNames) == 1 {
return podNames[0], nil
}
prompt := &survey.Select{
Message: "You have multiple pods in the specified resource. Please choose one: ",
Options: podNames,
}
var svcName string
err := survey.AskOne(prompt, &svcName)
if err != nil {
return "", fmt.Errorf("choosing pod err %w", err)
}
return svcName, nil
}
// ReadYamlToObject will read a yaml K8s object to runtime.Object
func ReadYamlToObject(path string, object k8sruntime.Object) error {
data, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return err
}
return yaml.Unmarshal(data, object)
}
// ParseTerraformVariables get variables from Terraform Configuration
func ParseTerraformVariables(configuration string) (map[string]*tfconfig.Variable, error) {
p := hclparse.NewParser()
hclFile, diagnostic := p.ParseHCL([]byte(configuration), "")
if diagnostic != nil {
return nil, errors.New(diagnostic.Error())
}
mod := tfconfig.Module{Variables: map[string]*tfconfig.Variable{}}
diagnostic = tfconfig.LoadModuleFromFile(hclFile, &mod)
if diagnostic != nil {
return nil, errors.New(diagnostic.Error())
}
return mod.Variables, nil
}
// GenerateUnstructuredObj generate UnstructuredObj
func GenerateUnstructuredObj(name, ns string, gvk schema.GroupVersionKind) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
u.SetName(name)
u.SetNamespace(ns)
return u
}
// SetSpecObjIntoUnstructuredObj set UnstructuredObj spec field
func SetSpecObjIntoUnstructuredObj(spec interface{}, u *unstructured.Unstructured) error {
bts, err := json.Marshal(spec)
if err != nil {
return err
}
data := make(map[string]interface{})
if err := json.Unmarshal(bts, &data); err != nil {
return err
}
_ = unstructured.SetNestedMap(u.Object, data, "spec")
return nil
}
// NewK8sClient init a local k8s client which add oamcore scheme
func NewK8sClient() (client.Client, error) {
conf, err := config.GetConfig()
if err != nil {
return nil, err
}
scheme := k8sruntime.NewScheme()
if err := clientgoscheme.AddToScheme(scheme); err != nil {
return nil, err
}
if err := oamcore.AddToScheme(scheme); err != nil {
return nil, err
}
k8sClient, err := client.New(conf, client.Options{Scheme: scheme})
if err != nil {
return nil, err
}
return k8sClient, nil
}