github.com/oam-dev/kubevela@v1.9.11/pkg/utils/common/common.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 common 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/tls" 23 "crypto/x509" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io" 28 "net/http" 29 neturl "net/url" 30 "os" 31 "os/exec" 32 "path/filepath" 33 "runtime/debug" 34 35 "cuelang.org/go/cue" 36 "cuelang.org/go/cue/cuecontext" 37 "cuelang.org/go/encoding/openapi" 38 "github.com/AlecAivazis/survey/v2" 39 "github.com/hashicorp/hcl/v2/hclparse" 40 "github.com/oam-dev/terraform-config-inspect/tfconfig" 41 kruise "github.com/openkruise/kruise-api/apps/v1alpha1" 42 kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" 43 yamlv3 "gopkg.in/yaml.v3" 44 v1 "k8s.io/api/core/v1" 45 crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 46 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 47 k8sruntime "k8s.io/apimachinery/pkg/runtime" 48 "k8s.io/apimachinery/pkg/runtime/schema" 49 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 50 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" 51 metricsV1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" 52 ocmclusterv1 "open-cluster-management.io/api/cluster/v1" 53 ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" 54 ocmworkv1 "open-cluster-management.io/api/work/v1" 55 "sigs.k8s.io/controller-runtime/pkg/client" 56 "sigs.k8s.io/controller-runtime/pkg/client/config" 57 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 58 "sigs.k8s.io/yaml" 59 60 "github.com/kubevela/workflow/pkg/cue/model/value" 61 "github.com/kubevela/workflow/pkg/cue/packages" 62 clustergatewayapi "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1" 63 terraformapiv1 "github.com/oam-dev/terraform-controller/api/v1beta1" 64 terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2" 65 66 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 67 68 oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev" 69 "github.com/oam-dev/kubevela/apis/types" 70 velacue "github.com/oam-dev/kubevela/pkg/cue" 71 "github.com/oam-dev/kubevela/pkg/cue/process" 72 "github.com/oam-dev/kubevela/pkg/oam" 73 ) 74 75 var ( 76 // Scheme defines the default KubeVela schema 77 Scheme = k8sruntime.NewScheme() 78 ) 79 80 // CreateCustomNamespace display the create namespace message 81 const CreateCustomNamespace = "create new namespace" 82 83 func init() { 84 _ = clientgoscheme.AddToScheme(Scheme) 85 _ = apiregistrationv1.AddToScheme(Scheme) 86 _ = crdv1.AddToScheme(Scheme) 87 _ = oamcore.AddToScheme(Scheme) 88 _ = kruise.AddToScheme(Scheme) 89 _ = terraformapi.AddToScheme(Scheme) 90 _ = terraformapiv1.AddToScheme(Scheme) 91 _ = ocmclusterv1alpha1.Install(Scheme) 92 _ = ocmclusterv1.Install(Scheme) 93 _ = ocmworkv1.Install(Scheme) 94 _ = clustergatewayapi.AddToScheme(Scheme) 95 _ = metricsV1beta1api.AddToScheme(Scheme) 96 _ = kruisev1alpha1.AddToScheme(Scheme) 97 _ = gatewayv1beta1.AddToScheme(Scheme) 98 _ = workflowv1alpha1.AddToScheme(Scheme) 99 // +kubebuilder:scaffold:scheme 100 } 101 102 // HTTPOption define the https options 103 type HTTPOption struct { 104 Username string `json:"username,omitempty"` 105 Password string `json:"password,omitempty"` 106 CaFile string `json:"caFile,omitempty"` 107 CertFile string `json:"certFile,omitempty"` 108 KeyFile string `json:"keyFile,omitempty"` 109 InsecureSkipTLS bool `json:"insecureSkipTLS,omitempty"` 110 } 111 112 // InitBaseRestConfig will return reset config for create controller runtime client 113 func InitBaseRestConfig() (Args, error) { 114 args := Args{ 115 Schema: Scheme, 116 } 117 _, err := args.GetConfig() 118 if err != nil && os.Getenv("IGNORE_KUBE_CONFIG") != "true" { 119 fmt.Println("get kubeConfig err", err) 120 os.Exit(1) 121 } else if err != nil { 122 return Args{}, err 123 } 124 return args, nil 125 } 126 127 // HTTPGetResponse use HTTP option and default client to send request and get raw response 128 func HTTPGetResponse(ctx context.Context, url string, opts *HTTPOption) (*http.Response, error) { 129 // Change NewRequest to NewRequestWithContext and pass context it 130 if _, err := neturl.ParseRequestURI(url); err != nil { 131 return nil, err 132 } 133 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 134 if err != nil { 135 return nil, err 136 } 137 httpClient := &http.Client{} 138 if opts != nil && len(opts.Username) != 0 && len(opts.Password) != 0 { 139 req.SetBasicAuth(opts.Username, opts.Password) 140 } 141 if opts != nil && opts.InsecureSkipTLS { 142 httpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} // nolint 143 } 144 // if specify the caFile, we cannot re-use the default httpClient, so create a new one. 145 if opts != nil && (len(opts.CaFile) != 0 || len(opts.KeyFile) != 0 || len(opts.CertFile) != 0) { 146 // must set MinVersion of TLS, otherwise will report GoSec error G402 147 tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} 148 tr := http.Transport{} 149 if len(opts.CaFile) != 0 { 150 c := x509.NewCertPool() 151 if !(c.AppendCertsFromPEM([]byte(opts.CaFile))) { 152 return nil, fmt.Errorf("failed to append certificates") 153 } 154 tlsConfig.RootCAs = c 155 } 156 if len(opts.CertFile) != 0 && len(opts.KeyFile) != 0 { 157 cert, err := tls.X509KeyPair([]byte(opts.CertFile), []byte(opts.KeyFile)) 158 if err != nil { 159 return nil, err 160 } 161 tlsConfig.Certificates = append(tlsConfig.Certificates, cert) 162 } 163 tr.TLSClientConfig = tlsConfig 164 defer tr.CloseIdleConnections() 165 httpClient.Transport = &tr 166 } 167 return httpClient.Do(req) 168 } 169 170 // HTTPGetWithOption use HTTP option and default client to send get request 171 func HTTPGetWithOption(ctx context.Context, url string, opts *HTTPOption) ([]byte, error) { 172 resp, err := HTTPGetResponse(ctx, url, opts) 173 if err != nil { 174 return nil, err 175 } 176 //nolint:errcheck 177 defer resp.Body.Close() 178 return io.ReadAll(resp.Body) 179 } 180 181 // HTTPGetKubernetesObjects use HTTP requests to load resources from remote url 182 func HTTPGetKubernetesObjects(ctx context.Context, url string) ([]*unstructured.Unstructured, error) { 183 resp, err := HTTPGetResponse(ctx, url, nil) 184 if err != nil { 185 return nil, err 186 } 187 //nolint:errcheck 188 defer resp.Body.Close() 189 decoder := yamlv3.NewDecoder(resp.Body) 190 var uns []*unstructured.Unstructured 191 for { 192 obj := &unstructured.Unstructured{Object: map[string]interface{}{}} 193 if err := decoder.Decode(obj.Object); err != nil { 194 if errors.Is(err, io.EOF) { 195 break 196 } 197 return nil, fmt.Errorf("failed to decode object: %w", err) 198 } 199 uns = append(uns, obj) 200 } 201 return uns, nil 202 } 203 204 // GetCUEParameterValue converts definitions to cue format 205 func GetCUEParameterValue(cueStr string, pd *packages.PackageDiscover) (cue.Value, error) { 206 template, err := value.NewValue(cueStr+velacue.BaseTemplate, pd, "") 207 if err != nil { 208 return cue.Value{}, err 209 } 210 val, err := template.LookupValue(process.ParameterFieldName) 211 if err != nil || !val.CueValue().Exists() { 212 return cue.Value{}, velacue.ErrParameterNotExist 213 } 214 215 return val.CueValue(), nil 216 } 217 218 // GenOpenAPI generates OpenAPI json schema from cue.Instance 219 func GenOpenAPI(val *value.Value) (b []byte, err error) { 220 defer func() { 221 if r := recover(); r != nil { 222 err = fmt.Errorf("invalid cue definition to generate open api: %v", r) 223 debug.PrintStack() 224 return 225 } 226 }() 227 if val.CueValue().Err() != nil { 228 return nil, val.CueValue().Err() 229 } 230 paramOnlyVal, err := RefineParameterValue(val) 231 if err != nil { 232 return nil, err 233 } 234 defaultConfig := &openapi.Config{ExpandReferences: true} 235 b, err = openapi.Gen(paramOnlyVal, defaultConfig) 236 if err != nil { 237 return nil, err 238 } 239 var out = &bytes.Buffer{} 240 _ = json.Indent(out, b, "", " ") 241 return out.Bytes(), nil 242 } 243 244 // GenOpenAPIWithCueX generates OpenAPI json schema from cue.Instance 245 func GenOpenAPIWithCueX(val cue.Value) (b []byte, err error) { 246 defer func() { 247 if r := recover(); r != nil { 248 err = fmt.Errorf("invalid cue definition to generate open api: %v", r) 249 debug.PrintStack() 250 return 251 } 252 }() 253 if val.Err() != nil { 254 return nil, val.Err() 255 } 256 paramOnlyVal := FillParameterDefinitionFieldIfNotExist(val) 257 defaultConfig := &openapi.Config{ExpandReferences: true} 258 b, err = openapi.Gen(paramOnlyVal, defaultConfig) 259 if err != nil { 260 return nil, err 261 } 262 var out = &bytes.Buffer{} 263 _ = json.Indent(out, b, "", " ") 264 return out.Bytes(), nil 265 } 266 267 // RefineParameterValue refines cue value to merely include `parameter` identifier 268 func RefineParameterValue(val *value.Value) (cue.Value, error) { 269 defaultValue := cuecontext.New().CompileString("#parameter: {}") 270 parameterPath := cue.MakePath(cue.Def(process.ParameterFieldName)) 271 v, err := val.MakeValue("{}") 272 if err != nil { 273 return defaultValue, err 274 } 275 paramVal, err := val.LookupValue(process.ParameterFieldName) 276 if err != nil { 277 // nolint:nilerr 278 return defaultValue, nil 279 } 280 switch k := paramVal.CueValue().IncompleteKind(); k { 281 case cue.BottomKind: 282 return defaultValue, nil 283 default: 284 paramOnlyVal := v.CueValue().FillPath(parameterPath, paramVal.CueValue()) 285 return paramOnlyVal, nil 286 } 287 } 288 289 // FillParameterDefinitionFieldIfNotExist refines cue value to merely include `parameter` identifier 290 func FillParameterDefinitionFieldIfNotExist(val cue.Value) cue.Value { 291 defaultValue := cuecontext.New().CompileString("#parameter: {}") 292 defPath := cue.ParsePath("#" + process.ParameterFieldName) 293 if paramVal := val.LookupPath(cue.ParsePath(process.ParameterFieldName)); paramVal.Exists() { 294 if paramVal.IncompleteKind() == cue.BottomKind { 295 return defaultValue 296 } 297 paramOnlyVal := val.Context().CompileString("{}").FillPath(defPath, paramVal) 298 return paramOnlyVal 299 } 300 return defaultValue 301 } 302 303 // RealtimePrintCommandOutput prints command output in real time 304 // If logFile is "", it will prints the stdout, or it will write to local file 305 func RealtimePrintCommandOutput(cmd *exec.Cmd, logFile string) error { 306 var writer io.Writer 307 if logFile == "" { 308 writer = io.MultiWriter(os.Stdout) 309 } else { 310 if _, err := os.Stat(filepath.Dir(logFile)); err != nil { 311 return err 312 } 313 f, err := os.Create(filepath.Clean(logFile)) 314 if err != nil { 315 return err 316 } 317 writer = io.MultiWriter(f) 318 } 319 cmd.Stdout = writer 320 cmd.Stderr = writer 321 if err := cmd.Run(); err != nil { 322 return err 323 } 324 return nil 325 } 326 327 // AskToChooseOneNamespace ask for choose one namespace as env 328 func AskToChooseOneNamespace(c client.Client, envMeta *types.EnvMeta) error { 329 var nsList v1.NamespaceList 330 if err := c.List(context.TODO(), &nsList); err != nil { 331 return err 332 } 333 var ops = []string{CreateCustomNamespace} 334 for _, r := range nsList.Items { 335 ops = append(ops, r.Name) 336 } 337 prompt := &survey.Select{ 338 Message: "Would you like to choose an existing namespaces as your env?", 339 Options: ops, 340 } 341 err := survey.AskOne(prompt, &envMeta.Namespace) 342 if err != nil { 343 return fmt.Errorf("choosing namespace err %w", err) 344 } 345 if envMeta.Namespace == CreateCustomNamespace { 346 err = survey.AskOne(&survey.Input{ 347 Message: "Please name the new namespace:", 348 }, &envMeta.Namespace) 349 if err != nil { 350 return err 351 } 352 return nil 353 } 354 for _, ns := range nsList.Items { 355 if ns.Name == envMeta.Namespace && envMeta.Name == "" { 356 envMeta.Name = ns.Labels[oam.LabelNamespaceOfEnvName] 357 return nil 358 } 359 } 360 return nil 361 } 362 363 // ReadYamlToObject will read a yaml K8s object to runtime.Object 364 func ReadYamlToObject(path string, object k8sruntime.Object) error { 365 data, err := os.ReadFile(filepath.Clean(path)) 366 if err != nil { 367 return err 368 } 369 return yaml.Unmarshal(data, object) 370 } 371 372 // ParseTerraformVariables get variables from Terraform Configuration 373 func ParseTerraformVariables(configuration string) (map[string]*tfconfig.Variable, map[string]*tfconfig.Output, error) { 374 p := hclparse.NewParser() 375 hclFile, diagnostic := p.ParseHCL([]byte(configuration), "") 376 if diagnostic != nil { 377 return nil, nil, errors.New(diagnostic.Error()) 378 } 379 mod := tfconfig.Module{Variables: map[string]*tfconfig.Variable{}, Outputs: map[string]*tfconfig.Output{}} 380 diagnostic = tfconfig.LoadModuleFromFile(hclFile, &mod) 381 if diagnostic != nil { 382 return nil, nil, errors.New(diagnostic.Error()) 383 } 384 return mod.Variables, mod.Outputs, nil 385 } 386 387 // GenerateUnstructuredObj generate UnstructuredObj 388 func GenerateUnstructuredObj(name, ns string, gvk schema.GroupVersionKind) *unstructured.Unstructured { 389 u := &unstructured.Unstructured{} 390 u.SetGroupVersionKind(gvk) 391 u.SetName(name) 392 u.SetNamespace(ns) 393 return u 394 } 395 396 // SetSpecObjIntoUnstructuredObj set UnstructuredObj spec field 397 func SetSpecObjIntoUnstructuredObj(spec interface{}, u *unstructured.Unstructured) error { 398 bts, err := json.Marshal(spec) 399 if err != nil { 400 return err 401 } 402 data := make(map[string]interface{}) 403 if err := json.Unmarshal(bts, &data); err != nil { 404 return err 405 } 406 _ = unstructured.SetNestedMap(u.Object, data, "spec") 407 return nil 408 } 409 410 // NewK8sClient init a local k8s client which add oamcore scheme 411 func NewK8sClient() (client.Client, error) { 412 conf, err := config.GetConfig() 413 if err != nil { 414 return nil, err 415 } 416 scheme := k8sruntime.NewScheme() 417 if err := clientgoscheme.AddToScheme(scheme); err != nil { 418 return nil, err 419 } 420 if err := oamcore.AddToScheme(scheme); err != nil { 421 return nil, err 422 } 423 424 k8sClient, err := client.New(conf, client.Options{Scheme: scheme}) 425 if err != nil { 426 return nil, err 427 } 428 return k8sClient, nil 429 }