2021-08-03 18:00:49 +08:00
/ *
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 cli
import (
"bufio"
2021-08-06 17:10:52 +08:00
"bytes"
2021-08-03 18:00:49 +08:00
"context"
"fmt"
2023-05-12 14:24:49 +08:00
"io"
2021-08-03 18:00:49 +08:00
"os"
"os/exec"
"path"
"path/filepath"
2022-07-06 15:41:00 +08:00
"reflect"
2021-08-03 18:00:49 +08:00
"regexp"
2022-07-06 15:41:00 +08:00
"strconv"
2021-08-03 18:00:49 +08:00
"strings"
"time"
"cuelang.org/go/cue"
2022-09-20 15:18:31 +08:00
"cuelang.org/go/cue/cuecontext"
2021-08-03 18:00:49 +08:00
"cuelang.org/go/encoding/gocode/gocodec"
2023-04-07 14:12:37 +08:00
"github.com/kubevela/workflow/pkg/cue/model/sets"
2022-01-24 10:27:37 +08:00
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
2021-08-03 18:00:49 +08:00
"github.com/pkg/errors"
"github.com/spf13/cobra"
2021-08-06 17:10:52 +08:00
"gopkg.in/yaml.v3"
2021-08-03 18:00:49 +08:00
errors2 "k8s.io/apimachinery/pkg/api/errors"
2022-01-10 19:12:32 +08:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-08-03 18:00:49 +08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2022-01-10 19:12:32 +08:00
"k8s.io/apimachinery/pkg/runtime/serializer/json"
2021-08-03 18:00:49 +08:00
types2 "k8s.io/apimachinery/pkg/types"
2023-06-01 14:15:45 +08:00
"k8s.io/klog/v2"
2021-08-03 18:00:49 +08:00
"sigs.k8s.io/controller-runtime/pkg/client"
2022-01-10 19:12:32 +08:00
commontype "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
2021-08-03 18:00:49 +08:00
"github.com/oam-dev/kubevela/apis/types"
2022-09-02 12:55:03 +08:00
"github.com/oam-dev/kubevela/pkg/cue/process"
2021-12-09 20:31:19 +08:00
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
2023-02-21 15:54:44 +08:00
"github.com/oam-dev/kubevela/pkg/definition/gen_sdk"
2022-07-13 15:31:30 +08:00
"github.com/oam-dev/kubevela/pkg/utils"
2022-06-29 10:55:50 +08:00
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
2021-08-03 18:00:49 +08:00
"github.com/oam-dev/kubevela/pkg/utils/common"
2022-06-29 10:55:50 +08:00
"github.com/oam-dev/kubevela/pkg/utils/filters"
2022-07-24 16:34:13 +08:00
"github.com/oam-dev/kubevela/pkg/utils/util"
2023-05-05 19:26:13 +08:00
"github.com/oam-dev/kubevela/references/cuegen"
providergen "github.com/oam-dev/kubevela/references/cuegen/generators/provider"
2023-05-12 14:24:49 +08:00
"github.com/oam-dev/kubevela/references/docgen"
2021-08-03 18:00:49 +08:00
)
const (
2021-08-06 17:10:52 +08:00
// HelmChartNamespacePlaceholder is used as a placeholder for rendering definitions into helm chart format
HelmChartNamespacePlaceholder = "###HELM_NAMESPACE###"
// HelmChartFormatEnvName is the name of the environment variable to enable render helm chart format YAML
HelmChartFormatEnvName = "AS_HELM_CHART"
2021-08-03 18:00:49 +08:00
)
// DefinitionCommandGroup create the command group for `vela def` command to manage definitions
2022-07-24 16:34:13 +08:00
func DefinitionCommandGroup ( c common . Args , order string , ioStreams util . IOStreams ) * cobra . Command {
2021-08-03 18:00:49 +08:00
cmd := & cobra . Command {
Use : "def" ,
2023-04-27 10:02:47 +08:00
Short : "Manage definitions." ,
2022-01-10 17:45:20 +08:00
Long : "Manage X-Definitions for extension." ,
2021-08-03 18:00:49 +08:00
Annotations : map [ string ] string {
2022-01-06 13:29:02 +08:00
types . TagCommandOrder : order ,
types . TagCommandType : types . TypeExtension ,
2021-08-03 18:00:49 +08:00
} ,
}
cmd . AddCommand (
NewDefinitionGetCommand ( c ) ,
NewDefinitionListCommand ( c ) ,
NewDefinitionEditCommand ( c ) ,
2021-08-06 17:10:52 +08:00
NewDefinitionRenderCommand ( c ) ,
2022-07-24 16:34:13 +08:00
NewDefinitionApplyCommand ( c , ioStreams ) ,
2021-08-03 18:00:49 +08:00
NewDefinitionDelCommand ( c ) ,
NewDefinitionInitCommand ( c ) ,
NewDefinitionValidateCommand ( c ) ,
2023-05-12 14:24:49 +08:00
NewDefinitionDocGenCommand ( c , ioStreams ) ,
2023-04-27 10:02:47 +08:00
NewCapabilityShowCommand ( c , "" , ioStreams ) ,
2022-04-20 18:08:24 +08:00
NewDefinitionGenAPICommand ( c ) ,
2023-05-10 14:17:16 +08:00
NewDefinitionGenCUECommand ( c , ioStreams ) ,
2023-05-12 14:24:49 +08:00
NewDefinitionGenDocCommand ( c , ioStreams ) ,
2021-08-03 18:00:49 +08:00
)
return cmd
}
func getPrompt ( cmd * cobra . Command , reader * bufio . Reader , description string , prompt string , validate func ( string ) error ) ( string , error ) {
cmd . Printf ( description )
for {
cmd . Printf ( prompt )
resp , err := reader . ReadString ( '\n' )
resp = strings . TrimSpace ( resp )
if err != nil {
return "" , errors . Wrapf ( err , "failed to read user response" )
}
if validate == nil {
return resp , nil
}
err = validate ( resp )
if err != nil {
cmd . Println ( err )
} else {
return resp , nil
}
}
}
2022-08-08 17:57:48 +08:00
// nolint:staticcheck
2021-12-09 20:31:19 +08:00
func buildTemplateFromYAML ( templateYAML string , def * pkgdef . Definition ) error {
2022-07-18 19:22:55 +08:00
templateYAMLBytes , err := utils . ReadRemoteOrLocalPath ( templateYAML , false )
2021-08-03 18:00:49 +08:00
if err != nil {
return errors . Wrapf ( err , "failed to get template YAML file %s" , templateYAML )
}
yamlStrings := regexp . MustCompile ( ` \n---[^\n]*\n ` ) . Split ( string ( templateYAMLBytes ) , - 1 )
templateObject := map [ string ] interface { } {
2022-09-02 12:55:03 +08:00
process . OutputFieldName : map [ string ] interface { } { } ,
process . OutputsFieldName : map [ string ] interface { } { } ,
process . ParameterFieldName : map [ string ] interface { } { } ,
2021-08-03 18:00:49 +08:00
}
for index , yamlString := range yamlStrings {
var yamlObject map [ string ] interface { }
if err = yaml . Unmarshal ( [ ] byte ( yamlString ) , & yamlObject ) ; err != nil {
return errors . Wrapf ( err , "failed to unmarshal template yaml file" )
}
if index == 0 {
2022-09-02 12:55:03 +08:00
templateObject [ process . OutputFieldName ] = yamlObject
2021-08-03 18:00:49 +08:00
} else {
name , _ , _ := unstructured . NestedString ( yamlObject , "metadata" , "name" )
if name == "" {
name = fmt . Sprintf ( "output-%d" , index )
}
2022-09-02 12:55:03 +08:00
templateObject [ process . OutputsFieldName ] . ( map [ string ] interface { } ) [ name ] = yamlObject
2021-08-03 18:00:49 +08:00
}
}
2022-09-20 15:18:31 +08:00
codec := gocodec . New ( ( * cue . Runtime ) ( cuecontext . New ( ) ) , & gocodec . Config { } )
2021-08-03 18:00:49 +08:00
val , err := codec . Decode ( templateObject )
if err != nil {
return errors . Wrapf ( err , "failed to decode template into cue" )
}
templateString , err := sets . ToString ( val )
if err != nil {
return errors . Wrapf ( err , "failed to encode template cue string" )
}
2021-12-09 20:31:19 +08:00
err = unstructured . SetNestedField ( def . Object , templateString , pkgdef . DefinitionTemplateKeys ... )
2021-08-03 18:00:49 +08:00
if err != nil {
return errors . Wrapf ( err , "failed to merge template cue string" )
}
return nil
}
// NewDefinitionInitCommand create the `vela def init` command to help user initialize a definition locally
func NewDefinitionInitCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "init DEF_NAME" ,
Short : "Init a new definition" ,
2022-01-10 19:12:32 +08:00
Long : "Init a new definition with given arguments or interactively\n* We support parsing a single YAML file (like kubernetes objects) into the cue-style template. \n" +
"However, we do not support variables in YAML file currently, which prevents users from directly feeding files like helm chart directly. \n" +
"We may introduce such features in the future." ,
2021-08-03 18:00:49 +08:00
Example : "# Command below initiate an empty TraitDefinition named my-ingress\n" +
"> vela def init my-ingress -t trait --desc \"My ingress trait definition.\" > ./my-ingress.cue\n" +
"# Command below initiate a definition named my-def interactively and save it to ./my-def.cue\n" +
"> vela def init my-def -i --output ./my-def.cue\n" +
"# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" +
2022-01-10 19:12:32 +08:00
"> vela def init my-webservice -i --template-yaml ./template.yaml\n" +
2022-01-20 14:15:22 +08:00
"# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" +
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" +
"# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" +
"> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-01-10 19:12:32 +08:00
var defStr string
2021-08-03 18:00:49 +08:00
definitionType , err := cmd . Flags ( ) . GetString ( FlagType )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagType )
}
2022-06-18 10:34:44 +08:00
alias , err := cmd . Flags ( ) . GetString ( FlagAlias )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagAlias )
}
2021-08-03 18:00:49 +08:00
desc , err := cmd . Flags ( ) . GetString ( FlagDescription )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagDescription )
}
templateYAML , err := cmd . Flags ( ) . GetString ( FlagTemplateYAML )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagTemplateYAML )
}
output , err := cmd . Flags ( ) . GetString ( FlagOutput )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagOutput )
}
interactive , err := cmd . Flags ( ) . GetBool ( FlagInteractive )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagInteractive )
}
if interactive {
reader := bufio . NewReader ( cmd . InOrStdin ( ) )
if definitionType == "" {
2021-12-09 20:31:19 +08:00
if definitionType , err = getPrompt ( cmd , reader , "Please choose one definition type from the following values: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) + "\n" , "> Definition type: " , func ( resp string ) error {
if _ , ok := pkgdef . DefinitionTypeToKind [ resp ] ; ! ok {
2021-08-03 18:00:49 +08:00
return errors . New ( "invalid definition type" )
}
return nil
} ) ; err != nil {
return err
}
}
if desc == "" {
if desc , err = getPrompt ( cmd , reader , "" , "> Definition description: " , nil ) ; err != nil {
return err
}
}
if templateYAML == "" {
if templateYAML , err = getPrompt ( cmd , reader , "Please enter the location the template YAML file to build definition. Leave it empty to generate default template.\n" , "> Definition template filename: " , func ( resp string ) error {
if resp == "" {
return nil
}
_ , err = os . Stat ( resp )
return err
} ) ; err != nil {
return err
}
}
if output == "" {
if output , err = getPrompt ( cmd , reader , "Please enter the output location of the generated definition. Leave it empty to print definition to stdout.\n" , "> Definition output filename: " , nil ) ; err != nil {
return err
}
}
}
2021-12-09 20:31:19 +08:00
kind , ok := pkgdef . DefinitionTypeToKind [ definitionType ]
2021-08-03 18:00:49 +08:00
if ! ok {
return errors . New ( "invalid definition type" )
}
2022-01-10 19:12:32 +08:00
name := args [ 0 ]
provider , err := cmd . Flags ( ) . GetString ( FlagProvider )
2021-08-03 18:00:49 +08:00
if err != nil {
2022-01-10 19:12:32 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , FlagProvider )
}
if provider != "" {
defStr , err = generateTerraformTypedComponentDefinition ( cmd , name , kind , provider , desc )
if err != nil {
return errors . Wrapf ( err , "failed to generate Terraform typed component definition" )
}
} else {
def := pkgdef . Definition { Unstructured : unstructured . Unstructured { } }
def . SetGVK ( kind )
def . SetName ( name )
def . SetAnnotations ( map [ string ] string {
pkgdef . DescriptionKey : desc ,
2022-06-18 10:34:44 +08:00
pkgdef . AliasKey : alias ,
2022-01-10 19:12:32 +08:00
} )
def . SetLabels ( map [ string ] string { } )
def . Object [ "spec" ] = pkgdef . GetDefinitionDefaultSpec ( def . GetKind ( ) )
if templateYAML != "" {
if err = buildTemplateFromYAML ( templateYAML , & def ) ; err != nil {
return err
}
}
defStr , err = def . ToCUEString ( )
if err != nil {
return errors . Wrapf ( err , "failed to generate cue string" )
}
2021-08-03 18:00:49 +08:00
}
if output != "" {
2022-01-10 19:12:32 +08:00
if err = os . WriteFile ( path . Clean ( output ) , [ ] byte ( defStr ) , 0600 ) ; err != nil {
2021-08-03 18:00:49 +08:00
return errors . Wrapf ( err , "failed to write definition into %s" , output )
}
cmd . Printf ( "Definition written to %s\n" , output )
2022-01-10 19:12:32 +08:00
} else if _ , err = cmd . OutOrStdout ( ) . Write ( [ ] byte ( defStr + "\n" ) ) ; err != nil {
2021-08-03 18:00:49 +08:00
return errors . Wrapf ( err , "failed to write out cue string" )
}
return nil
} ,
}
2021-12-09 20:31:19 +08:00
cmd . Flags ( ) . StringP ( FlagType , "t" , "" , "Specify the type of the new definition. Valid types: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) )
2021-08-03 18:00:49 +08:00
cmd . Flags ( ) . StringP ( FlagDescription , "d" , "" , "Specify the description of the new definition." )
2022-06-18 10:34:44 +08:00
cmd . Flags ( ) . StringP ( FlagAlias , "a" , "" , "Specify the alias of the new definition." )
2022-02-09 15:17:04 +08:00
cmd . Flags ( ) . StringP ( FlagTemplateYAML , "f" , "" , "Specify the template yaml file that definition will use to build the schema. If empty, a default template for the given definition type will be used." )
2021-08-03 18:00:49 +08:00
cmd . Flags ( ) . StringP ( FlagOutput , "o" , "" , "Specify the output path of the generated definition. If empty, the definition will be printed in the console." )
cmd . Flags ( ) . BoolP ( FlagInteractive , "i" , false , "Specify whether use interactive process to help generate definitions." )
2022-08-23 21:15:32 +08:00
cmd . Flags ( ) . StringP ( FlagProvider , "p" , "" , "Specify which provider the cloud resource definition belongs to. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported." )
2022-01-10 19:12:32 +08:00
cmd . Flags ( ) . StringP ( FlagGit , "" , "" , "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set." )
2022-01-20 14:15:22 +08:00
cmd . Flags ( ) . StringP ( FlagLocal , "" , "" , "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set." )
2022-01-10 19:12:32 +08:00
cmd . Flags ( ) . StringP ( FlagPath , "" , "" , "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set." )
2021-08-03 18:00:49 +08:00
return cmd
}
2022-01-10 19:12:32 +08:00
func generateTerraformTypedComponentDefinition ( cmd * cobra . Command , name , kind , provider , desc string ) ( string , error ) {
if kind != v1beta1 . ComponentDefinitionKind {
return "" , errors . New ( "provider is only valid when the type of the definition is component" )
}
switch provider {
2022-12-03 12:42:52 +08:00
case "aws" , "azure" , "alibaba" , "tencent" , "gcp" , "baidu" , "elastic" , "ucloud" , "vsphere" , "huawei" :
2022-01-20 14:15:22 +08:00
var terraform * commontype . Terraform
2022-01-10 19:12:32 +08:00
git , err := cmd . Flags ( ) . GetString ( FlagGit )
if err != nil {
return "" , errors . Wrapf ( err , "failed to get `%s`" , FlagGit )
}
2022-01-20 14:15:22 +08:00
local , err := cmd . Flags ( ) . GetString ( FlagLocal )
if err != nil {
return "" , errors . Wrapf ( err , "failed to get `%s`" , FlagLocal )
}
if git != "" && local != "" {
return "" , errors . New ( "only one of --git and --local can be set" )
2022-01-10 19:12:32 +08:00
}
gitPath , err := cmd . Flags ( ) . GetString ( FlagPath )
if err != nil {
return "" , errors . Wrapf ( err , "failed to get `%s`" , FlagPath )
}
2022-01-20 14:15:22 +08:00
if git != "" {
if ! strings . HasPrefix ( git , "https://" ) || ! strings . HasSuffix ( git , ".git" ) {
return "" , errors . Errorf ( "invalid git url: %s" , git )
}
terraform = & commontype . Terraform {
Configuration : git ,
Type : "remote" ,
Path : gitPath ,
}
} else if local != "" {
2022-09-28 10:19:28 +08:00
hcl , err := os . ReadFile ( filepath . Clean ( local ) )
2022-01-20 14:15:22 +08:00
if err != nil {
return "" , errors . Wrapf ( err , "failed to read Terraform configuration from file %s" , local )
}
terraform = & commontype . Terraform {
Configuration : string ( hcl ) ,
}
}
2022-01-10 19:12:32 +08:00
def := v1beta1 . ComponentDefinition {
TypeMeta : metav1 . TypeMeta {
APIVersion : "core.oam.dev/v1beta1" ,
Kind : "ComponentDefinition" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : fmt . Sprintf ( "%s-%s" , provider , name ) ,
Namespace : types . DefaultKubeVelaNS ,
Annotations : map [ string ] string {
"definition.oam.dev/description" : desc ,
} ,
Labels : map [ string ] string {
"type" : "terraform" ,
} ,
} ,
Spec : v1beta1 . ComponentDefinitionSpec {
Workload : commontype . WorkloadTypeDescriptor {
Definition : commontype . WorkloadGVK {
2022-05-09 15:10:31 +08:00
APIVersion : "terraform.core.oam.dev/v1beta2" ,
2022-01-10 19:12:32 +08:00
Kind : "Configuration" ,
} ,
} ,
Schematic : & commontype . Schematic {
2022-01-20 14:15:22 +08:00
Terraform : terraform ,
2022-01-10 19:12:32 +08:00
} ,
} ,
}
2022-01-24 10:27:37 +08:00
if provider != "alibaba" {
def . Spec . Schematic . Terraform . ProviderReference = & crossplane . Reference {
Name : provider ,
Namespace : "default" ,
}
}
2022-01-10 19:12:32 +08:00
var out bytes . Buffer
err = json . NewSerializerWithOptions ( json . DefaultMetaFactory , nil , nil , json . SerializerOptions { Yaml : true } ) . Encode ( & def , & out )
if err != nil {
return "" , errors . Wrapf ( err , "failed to marshal component definition" )
}
return out . String ( ) , nil
default :
2022-08-23 21:15:32 +08:00
return "" , errors . Errorf ( "Provider `%s` is not supported. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported." , provider )
2022-01-10 19:12:32 +08:00
}
}
2021-12-09 20:31:19 +08:00
func getSingleDefinition ( cmd * cobra . Command , definitionName string , client client . Client , definitionType string , namespace string ) ( * pkgdef . Definition , error ) {
2022-06-29 10:55:50 +08:00
definitions , err := pkgdef . SearchDefinition ( client , definitionType , namespace , filters . ByName ( definitionName ) )
2021-08-03 18:00:49 +08:00
if err != nil {
return nil , err
}
if len ( definitions ) == 0 {
return nil , fmt . Errorf ( "definition not found" )
}
if len ( definitions ) > 1 {
table := newUITable ( )
table . AddRow ( "NAME" , "TYPE" , "NAMESPACE" , "DESCRIPTION" )
for _ , definition := range definitions {
desc := ""
if annotations := definition . GetAnnotations ( ) ; annotations != nil {
2021-12-09 20:31:19 +08:00
desc = annotations [ pkgdef . DescriptionKey ]
2021-08-03 18:00:49 +08:00
}
table . AddRow ( definition . GetName ( ) , definition . GetKind ( ) , definition . GetNamespace ( ) , desc )
}
cmd . Println ( table )
return nil , fmt . Errorf ( "found %d definitions, please specify which one to select with more arguments" , len ( definitions ) )
}
2021-12-09 20:31:19 +08:00
return & pkgdef . Definition { Unstructured : definitions [ 0 ] } , nil
2021-08-03 18:00:49 +08:00
}
2022-07-06 15:41:00 +08:00
// getDefRevs will search for DefinitionRevisions with specified conditions.
// Check SearchDefinitionRevisions for details.
func getDefRevs ( ctx context . Context , client client . Client , ns , defTypeStr , defName string , rev int64 ) ( [ ] v1beta1 . DefinitionRevision , error ) {
defType , ok := pkgdef . StringToDefinitionType [ defTypeStr ]
// Empty definition type is intentionally allowed, to allow the user to match all definition types
if defTypeStr != "" && ! ok {
return nil , fmt . Errorf ( "%s is not a valid type. Valid types are %v" , defTypeStr , reflect . ValueOf ( pkgdef . StringToDefinitionType ) . MapKeys ( ) )
}
return pkgdef . SearchDefinitionRevisions ( ctx , client , ns , defName , defType , rev )
}
// printDefRevs will print DefinitionRevisions
func printDefRevs ( ctx context . Context , cmd * cobra . Command , client client . Client , ns , defTypeStr , defName string ) error {
revs , err := getDefRevs ( ctx , client , ns , defTypeStr , defName , 0 )
if err != nil {
return err
}
table := newUITable ( )
table . AddRow ( "NAME" , "REVISION" , "TYPE" , "HASH" )
for _ , rev := range revs {
table . AddRow ( defName , rev . Spec . Revision , rev . Spec . DefinitionType , rev . Spec . RevisionHash )
}
cmd . Println ( table )
return nil
}
2021-08-03 18:00:49 +08:00
// NewDefinitionGetCommand create the `vela def get` command to get definition from k8s
func NewDefinitionGetCommand ( c common . Args ) * cobra . Command {
2022-07-06 15:41:00 +08:00
var listRevisions bool
var targetRevision string
2021-08-03 18:00:49 +08:00
cmd := & cobra . Command {
Use : "get NAME" ,
Short : "Get definition" ,
Long : "Get definition from kubernetes cluster" ,
Example : "# Command below will get the ComponentDefinition(or other definitions if exists) of webservice in all namespaces\n" +
"> vela def get webservice\n" +
"# Command below will get the TraitDefinition of annotations in namespace vela-system\n" +
"> vela def get annotations --type trait --namespace vela-system" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
definitionType , err := cmd . Flags ( ) . GetString ( FlagType )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagType )
}
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
2021-11-19 18:00:03 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2021-08-03 18:00:49 +08:00
}
k8sClient , err := c . GetClient ( )
if err != nil {
return errors . Wrapf ( err , "failed to get k8s client" )
}
2022-07-06 15:41:00 +08:00
if listRevisions {
return printDefRevs ( context . Background ( ) , cmd , k8sClient , namespace , definitionType , args [ 0 ] )
}
var def * pkgdef . Definition
// Get history Definition from DefinitionRevisions
if targetRevision != "" {
// "v1", "1", both need to work
targetRevision = strings . TrimPrefix ( targetRevision , "v" )
ver , err := strconv . Atoi ( targetRevision )
if err != nil {
return fmt . Errorf ( "invalid version: %w" , err )
}
// Get the user-specified revision.
revs , err := getDefRevs ( context . Background ( ) , k8sClient , namespace , definitionType , args [ 0 ] , int64 ( ver ) )
if err != nil {
return err
}
if len ( revs ) == 0 {
return fmt . Errorf ( "no %s with revision %s found in namespace %s" , args [ 0 ] , targetRevision , namespace )
}
// Now we have at least one DefinitionRevision (typically it will only be one).
// They all fit user's conditions. We will use the first one.
// Extract Definition from DefinitionRevision that we just got.
def , err = pkgdef . GetDefinitionFromDefinitionRevision ( & revs [ 0 ] )
if err != nil {
return err
}
} else {
def , err = getSingleDefinition ( cmd , args [ 0 ] , k8sClient , definitionType , namespace )
if err != nil {
return err
}
2021-08-03 18:00:49 +08:00
}
2022-07-06 15:41:00 +08:00
2021-08-03 18:00:49 +08:00
cueString , err := def . ToCUEString ( )
if err != nil {
return errors . Wrapf ( err , "failed to get cue format definition" )
}
if _ , err = cmd . OutOrStdout ( ) . Write ( [ ] byte ( cueString + "\n" ) ) ; err != nil {
return errors . Wrapf ( err , "failed to write out cue string" )
}
return nil
} ,
}
2021-12-09 20:31:19 +08:00
cmd . Flags ( ) . StringP ( FlagType , "t" , "" , "Specify which definition type to get. If empty, all types will be searched. Valid types: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) )
2022-07-06 15:41:00 +08:00
cmd . Flags ( ) . BoolVarP ( & listRevisions , "revisions" , "" , false , "List revisions of the specified definition." )
cmd . Flags ( ) . StringVarP ( & targetRevision , "revision" , "r" , "" , "Get the specified version of a definition." )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
2021-08-03 18:00:49 +08:00
return cmd
}
2023-05-12 14:24:49 +08:00
// NewDefinitionDocGenCommand create the `vela def doc-gen` command to generate documentation of definitions
func NewDefinitionDocGenCommand ( c common . Args , ioStreams util . IOStreams ) * cobra . Command {
2022-07-24 16:34:13 +08:00
var docPath , location , i18nPath string
2022-01-10 14:08:48 +08:00
cmd := & cobra . Command {
2022-01-12 11:35:41 +08:00
Use : "doc-gen NAME" ,
2022-07-18 19:22:55 +08:00
Short : "Generate documentation for definitions" ,
Long : "Generate documentation for definitions" ,
Example : "1. Generate documentation for ComponentDefinition webservice:\n" +
"> vela def doc-gen webservice -n vela-system\n" +
"2. Generate documentation for local CUE Definition file webservice.cue:\n" +
"> vela def doc-gen webservice.cue\n" +
"3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:\n" +
2022-03-08 16:58:39 +08:00
"> vela def doc-gen alibaba-vpc.yaml\n" ,
2022-07-18 19:22:55 +08:00
Deprecated : "This command has been replaced by 'vela show' or 'vela def show'." ,
2022-01-10 14:08:48 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
2022-07-18 19:22:55 +08:00
return fmt . Errorf ( "please specify definition name, cue file or a cloud resource definition yaml" )
2022-01-10 14:08:48 +08:00
}
2022-07-18 19:22:55 +08:00
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2022-01-10 14:08:48 +08:00
}
2022-07-24 16:34:13 +08:00
return ShowReferenceMarkdown ( context . Background ( ) , c , ioStreams , args [ 0 ] , docPath , location , i18nPath , namespace , 0 )
2022-01-17 16:18:59 +08:00
2022-01-10 14:08:48 +08:00
} ,
}
2022-07-24 16:34:13 +08:00
cmd . Flags ( ) . StringVarP ( & docPath , "path" , "p" , "" , "Specify the path for of the doc generated from definition." )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringVarP ( & location , "location" , "l" , "" , "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. " )
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
cmd . Flags ( ) . StringVarP ( & i18nPath , "i18n" , "" , "https://kubevela.io/reference-i18n.json" , "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. " )
2022-01-10 14:08:48 +08:00
return cmd
}
2021-08-03 18:00:49 +08:00
// NewDefinitionListCommand create the `vela def list` command to list definition from k8s
func NewDefinitionListCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "list" ,
2022-01-12 17:43:08 +08:00
Short : "List definitions." ,
Long : "List definitions in kubernetes cluster." ,
2021-08-03 18:00:49 +08:00
Example : "# Command below will list all definitions in all namespaces\n" +
"> vela def list\n" +
"# Command below will list all definitions in the vela-system namespace\n" +
"> vela def get annotations --type trait --namespace vela-system" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 0 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
definitionType , err := cmd . Flags ( ) . GetString ( FlagType )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagType )
}
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
2021-11-19 18:00:03 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2021-08-03 18:00:49 +08:00
}
2022-06-29 10:55:50 +08:00
addonName , err := cmd . Flags ( ) . GetString ( "from" )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , "from" )
}
2021-08-03 18:00:49 +08:00
k8sClient , err := c . GetClient ( )
if err != nil {
return errors . Wrapf ( err , "failed to get k8s client" )
}
2022-06-29 10:55:50 +08:00
definitions , err := pkgdef . SearchDefinition ( k8sClient ,
definitionType ,
namespace ,
filters . ByOwnerAddon ( addonName ) )
2021-08-03 18:00:49 +08:00
if err != nil {
return err
}
if len ( definitions ) == 0 {
cmd . Println ( "No definition found." )
return nil
}
2022-06-29 10:55:50 +08:00
// Determine if there is a definition in the list from some addons
// This is used to tell if we want the SOURCE-ADDON column
showSourceAddon := false
for _ , def := range definitions {
ownerRef := def . GetOwnerReferences ( )
if len ( ownerRef ) > 0 && strings . HasPrefix ( ownerRef [ 0 ] . Name , addonutil . AddonAppPrefix ) {
showSourceAddon = true
break
}
}
2021-08-03 18:00:49 +08:00
table := newUITable ( )
2022-06-29 10:55:50 +08:00
// We only include SOURCE-ADDON if there is at least one definition from an addon
if showSourceAddon {
table . AddRow ( "NAME" , "TYPE" , "NAMESPACE" , "SOURCE-ADDON" , "DESCRIPTION" )
} else {
table . AddRow ( "NAME" , "TYPE" , "NAMESPACE" , "DESCRIPTION" )
}
2021-08-03 18:00:49 +08:00
for _ , definition := range definitions {
desc := ""
if annotations := definition . GetAnnotations ( ) ; annotations != nil {
2021-12-09 20:31:19 +08:00
desc = annotations [ pkgdef . DescriptionKey ]
2021-08-03 18:00:49 +08:00
}
2022-06-29 10:55:50 +08:00
// Do not show SOURCE-ADDON column
if ! showSourceAddon {
table . AddRow ( definition . GetName ( ) , definition . GetKind ( ) , definition . GetNamespace ( ) , desc )
continue
}
sourceAddon := ""
if len ( definition . GetOwnerReferences ( ) ) > 0 {
sourceAddon = strings . TrimPrefix ( definition . GetOwnerReferences ( ) [ 0 ] . Name , "addon-" )
}
table . AddRow ( definition . GetName ( ) , definition . GetKind ( ) , definition . GetNamespace ( ) , sourceAddon , desc )
2021-08-03 18:00:49 +08:00
}
cmd . Println ( table )
return nil
} ,
}
2021-12-09 20:31:19 +08:00
cmd . Flags ( ) . StringP ( FlagType , "t" , "" , "Specify which definition type to list. If empty, all types will be searched. Valid types: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) )
2022-06-29 10:55:50 +08:00
cmd . Flags ( ) . String ( "from" , "" , "Filter definitions by which addon installed them." )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
2021-08-03 18:00:49 +08:00
return cmd
}
// NewDefinitionEditCommand create the `vela def edit` command to help user edit remote definitions
func NewDefinitionEditCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "edit NAME" ,
2022-01-12 17:43:08 +08:00
Short : "Edit X-Definition." ,
Long : "Edit X-Definition in kubernetes. If type and namespace are not specified, the command will automatically search all possible results.\n" +
2021-08-03 18:00:49 +08:00
"By default, this command will use the vi editor and can be altered by setting EDITOR environment variable." ,
Example : "# Command below will edit the ComponentDefinition (and other definitions if exists) of webservice in kubernetes\n" +
"> vela def edit webservice\n" +
"# Command below will edit the TraitDefinition of ingress in vela-system namespace\n" +
"> vela def edit ingress --type trait --namespace vela-system" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
definitionType , err := cmd . Flags ( ) . GetString ( FlagType )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagType )
}
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
2021-11-19 18:00:03 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2021-08-03 18:00:49 +08:00
}
2022-01-07 15:49:27 +08:00
config , err := c . GetConfig ( )
if err != nil {
return err
}
2021-08-03 18:00:49 +08:00
k8sClient , err := c . GetClient ( )
if err != nil {
return errors . Wrapf ( err , "failed to get k8s client" )
}
def , err := getSingleDefinition ( cmd , args [ 0 ] , k8sClient , definitionType , namespace )
if err != nil {
return err
}
cueString , err := def . ToCUEString ( )
if err != nil {
return errors . Wrapf ( err , "failed to get cue format definition" )
}
cleanup := func ( filePath string ) {
if err := os . Remove ( filePath ) ; err != nil {
cmd . PrintErrf ( "failed to remove file %s: %v" , filePath , err )
}
}
filename := fmt . Sprintf ( "vela-def-%d" , time . Now ( ) . UnixNano ( ) )
tempFilePath := filepath . Join ( os . TempDir ( ) , filename + ".cue" )
2021-09-06 18:33:42 +08:00
if err := os . WriteFile ( tempFilePath , [ ] byte ( cueString ) , 0600 ) ; err != nil {
2021-08-03 18:00:49 +08:00
return errors . Wrapf ( err , "failed to write temporary file" )
}
defer cleanup ( tempFilePath )
editor := os . Getenv ( "EDITOR" )
if editor == "" {
editor = "vi"
}
scriptFilePath := filepath . Join ( os . TempDir ( ) , filename + ".sh" )
2021-09-06 18:33:42 +08:00
if err := os . WriteFile ( scriptFilePath , [ ] byte ( editor + " " + tempFilePath ) , 0600 ) ; err != nil {
2021-08-03 18:00:49 +08:00
return errors . Wrapf ( err , "failed to write temporary script file" )
}
defer cleanup ( scriptFilePath )
editCmd := exec . Command ( "sh" , path . Clean ( scriptFilePath ) ) //nolint:gosec
editCmd . Stdin = os . Stdin
editCmd . Stdout = os . Stdout
editCmd . Stderr = os . Stderr
if err = editCmd . Run ( ) ; err != nil {
return errors . Wrapf ( err , "failed to run editor %s at path %s" , editor , scriptFilePath )
}
2021-09-06 18:33:42 +08:00
newBuf , err := os . ReadFile ( path . Clean ( tempFilePath ) )
2021-08-03 18:00:49 +08:00
if err != nil {
return errors . Wrapf ( err , "failed to read temporary file %s" , tempFilePath )
}
if cueString == string ( newBuf ) {
cmd . Printf ( "definition unchanged\n" )
return nil
}
2022-01-07 15:49:27 +08:00
if err := def . FromCUEString ( string ( newBuf ) , config ) ; err != nil {
2021-08-03 18:00:49 +08:00
return errors . Wrapf ( err , "failed to load edited cue string" )
}
if err := k8sClient . Update ( context . Background ( ) , def ) ; err != nil {
return errors . Wrapf ( err , "failed to apply changes to kubernetes" )
}
cmd . Printf ( "Definition edited successfully.\n" )
return nil
} ,
}
2021-12-09 20:31:19 +08:00
cmd . Flags ( ) . StringP ( FlagType , "t" , "" , "Specify which definition type to get. If empty, all types will be searched. Valid types: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
2021-08-03 18:00:49 +08:00
return cmd
}
2021-08-06 17:10:52 +08:00
func prettyYAMLMarshal ( obj map [ string ] interface { } ) ( string , error ) {
var b bytes . Buffer
encoder := yaml . NewEncoder ( & b )
encoder . SetIndent ( 2 )
err := encoder . Encode ( & obj )
if err != nil {
return "" , err
}
return b . String ( ) , nil
}
// NewDefinitionRenderCommand create the `vela def render` command to help user render definition cue file into k8s YAML file, if used without kubernetes environment, set IGNORE_KUBE_CONFIG=true
func NewDefinitionRenderCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "render DEFINITION.cue" ,
2022-01-12 17:43:08 +08:00
Short : "Render X-Definition." ,
Long : "Render X-Definition with cue format into kubernetes YAML format. Could be used to check whether the cue format definition is working as expected. If a directory is used as input, all cue definitions in the directory will be rendered." ,
2021-08-06 17:10:52 +08:00
Example : "# Command below will render my-webservice.cue into YAML format and print it out.\n" +
"> vela def render my-webservice.cue\n" +
"# Command below will render my-webservice.cue and save it in my-webservice.yaml.\n" +
"> vela def render my-webservice.cue -o my-webservice.yaml" +
"# Command below will render all cue format definitions in the ./defs/cue/ directory and save the YAML objects in ./defs/yaml/.\n" +
"> vela def render ./defs/cue/ -o ./defs/yaml/" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-06 17:10:52 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
output , err := cmd . Flags ( ) . GetString ( FlagOutput )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagOutput )
}
2021-08-12 19:10:51 +08:00
message , err := cmd . Flags ( ) . GetString ( FlagMessage )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagMessage )
}
2022-01-07 15:49:27 +08:00
2021-08-06 17:10:52 +08:00
render := func ( inputFilename , outputFilename string ) error {
2022-07-18 19:22:55 +08:00
cueBytes , err := utils . ReadRemoteOrLocalPath ( inputFilename , false )
2021-08-06 17:10:52 +08:00
if err != nil {
return errors . Wrapf ( err , "failed to get %s" , args [ 0 ] )
}
2022-01-07 15:49:27 +08:00
config , err := c . GetConfig ( )
if err != nil {
2023-06-01 14:15:45 +08:00
klog . Infof ( "ignore kubernetes cluster, unable to get kubeconfig: %s" , err . Error ( ) )
2022-01-07 15:49:27 +08:00
}
2021-12-09 20:31:19 +08:00
def := pkgdef . Definition { Unstructured : unstructured . Unstructured { } }
2022-01-07 15:49:27 +08:00
if err := def . FromCUEString ( string ( cueBytes ) , config ) ; err != nil {
2021-08-06 17:10:52 +08:00
return errors . Wrapf ( err , "failed to parse CUE" )
}
helmChartFormatEnv := strings . ToLower ( os . Getenv ( HelmChartFormatEnvName ) )
if helmChartFormatEnv == "true" {
def . SetNamespace ( HelmChartNamespacePlaceholder )
} else if helmChartFormatEnv == "system" {
2021-08-30 11:43:20 +08:00
def . SetNamespace ( types . DefaultKubeVelaNS )
2021-08-06 17:10:52 +08:00
}
if len ( def . GetLabels ( ) ) == 0 {
def . SetLabels ( nil )
}
s , err := prettyYAMLMarshal ( def . Object )
if err != nil {
return errors . Wrapf ( err , "failed to marshal CRD into YAML" )
}
2022-02-18 14:05:38 +08:00
s = strings . ReplaceAll ( s , "'" + HelmChartNamespacePlaceholder + "'" , "{{ include \"systemDefinitionNamespace\" . }}" ) + "\n"
2021-08-06 17:10:52 +08:00
if outputFilename == "" {
s = fmt . Sprintf ( "--- %s ---\n%s" , filepath . Base ( inputFilename ) , s )
cmd . Print ( s )
} else {
2021-08-12 19:10:51 +08:00
if message != "" {
s = "# " + strings . ReplaceAll ( message , "{{INPUT_FILENAME}}" , filepath . Base ( inputFilename ) ) + "\n" + s
}
s = "# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.\n" + s
2021-09-06 18:33:42 +08:00
if err := os . WriteFile ( outputFilename , [ ] byte ( s ) , 0600 ) ; err != nil {
2021-08-06 17:10:52 +08:00
return errors . Wrapf ( err , "failed to write YAML format definition to file %s" , outputFilename )
}
}
return nil
}
inputFilenames := [ ] string { args [ 0 ] }
outputFilenames := [ ] string { output }
fi , err := os . Stat ( args [ 0 ] )
if err != nil {
return errors . Wrapf ( err , "failed to get input %s" , args [ 0 ] )
}
if fi . IsDir ( ) {
inputFilenames = [ ] string { }
outputFilenames = [ ] string { }
2021-11-29 15:02:16 +08:00
err := filepath . Walk ( args [ 0 ] , func ( path string , info os . FileInfo , err error ) error {
filename := filepath . Base ( path )
fileSuffix := filepath . Ext ( path )
if fileSuffix != ".cue" {
return nil
2021-08-06 17:10:52 +08:00
}
2021-11-29 15:02:16 +08:00
inputFilenames = append ( inputFilenames , path )
2021-08-06 17:10:52 +08:00
if output != "" {
outputFilenames = append ( outputFilenames , filepath . Join ( output , strings . ReplaceAll ( filename , ".cue" , ".yaml" ) ) )
} else {
outputFilenames = append ( outputFilenames , "" )
}
2021-11-29 15:02:16 +08:00
return nil
} )
if err != nil {
return errors . Wrapf ( err , "failed to read directory %s" , args [ 0 ] )
2021-08-06 17:10:52 +08:00
}
}
for i , inputFilename := range inputFilenames {
if err = render ( inputFilename , outputFilenames [ i ] ) ; err != nil {
2022-09-28 10:19:28 +08:00
if _ , err = fmt . Fprintf ( cmd . ErrOrStderr ( ) , "failed to render %s, reason: %v" , inputFilename , err ) ; err != nil {
2021-08-06 17:10:52 +08:00
return errors . Wrapf ( err , "failed to write err" )
}
}
}
return nil
} ,
}
cmd . Flags ( ) . StringP ( FlagOutput , "o" , "" , "Specify the output path of the rendered definition YAML. If empty, the definition will be printed in the console. If input is a directory, the output path is expected to be a directory as well." )
2021-08-12 19:10:51 +08:00
cmd . Flags ( ) . StringP ( FlagMessage , "" , "" , "Specify the header message of the generated YAML file. For example, declaring author information." )
2021-08-06 17:10:52 +08:00
return cmd
}
2021-08-03 18:00:49 +08:00
// NewDefinitionApplyCommand create the `vela def apply` command to help user apply local definitions to k8s
2022-07-24 16:34:13 +08:00
func NewDefinitionApplyCommand ( c common . Args , streams util . IOStreams ) * cobra . Command {
2021-08-03 18:00:49 +08:00
cmd := & cobra . Command {
Use : "apply DEFINITION.cue" ,
2022-01-12 17:43:08 +08:00
Short : "Apply X-Definition." ,
Long : "Apply X-Definition from local storage to kubernetes cluster. It will apply file to vela-system namespace by default." ,
2021-08-03 18:00:49 +08:00
Example : "# Command below will apply the local my-webservice.cue file to kubernetes vela-system namespace\n" +
"> vela def apply my-webservice.cue\n" +
2022-07-24 16:34:13 +08:00
"# Apply the local directory including all files(YAML and CUE definition) to kubernetes vela-system namespace\n" +
"> vela def apply def/\n" +
2021-08-03 18:00:49 +08:00
"# Command below will apply the ./defs/my-trait.cue file to kubernetes default namespace\n" +
"> vela def apply ./defs/my-trait.cue --namespace default" +
"# Command below will convert the ./defs/my-trait.cue file to kubernetes CRD object and print it without applying it to kubernetes\n" +
2022-07-11 16:59:54 +08:00
"> vela def apply ./defs/my-trait.cue --dry-run" +
"# Apply a CUE from URL \n" +
2022-08-03 19:18:57 +08:00
"> vela def apply https://my-host-to-def/my-trait.cue --dry-run" +
2022-07-11 16:59:54 +08:00
"# Apply a CUE from stdin \n" +
"> vela def apply -" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-07-13 15:31:30 +08:00
ctx := context . Background ( )
2021-08-03 18:00:49 +08:00
dryRun , err := cmd . Flags ( ) . GetBool ( FlagDryRun )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagDryRun )
}
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
2021-11-19 18:00:03 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2021-08-03 18:00:49 +08:00
}
2022-07-24 16:34:13 +08:00
if len ( args ) < 1 {
return errors . New ( "you must specify the definition path, directory or URL" )
2021-08-03 18:00:49 +08:00
}
2022-07-24 16:34:13 +08:00
return defApplyAll ( ctx , c , streams , namespace , args [ 0 ] , dryRun )
2021-08-03 18:00:49 +08:00
} ,
}
2022-07-24 16:34:13 +08:00
2021-08-03 18:00:49 +08:00
cmd . Flags ( ) . BoolP ( FlagDryRun , "" , false , "only build definition from CUE into CRB object without applying it to kubernetes clusters" )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
2021-08-03 18:00:49 +08:00
return cmd
}
2022-07-24 16:34:13 +08:00
func defApplyAll ( ctx context . Context , c common . Args , io util . IOStreams , namespace , path string , dryRun bool ) error {
files , err := utils . LoadDataFromPath ( ctx , path , utils . IsJSONYAMLorCUEFile )
if err != nil {
return errors . Wrapf ( err , "failed to get from %s" , path )
}
for _ , f := range files {
result , err := defApplyOne ( ctx , c , namespace , f . Path , f . Data , dryRun )
if err != nil {
return err
}
io . Infonln ( result )
}
return nil
}
func defApplyOne ( ctx context . Context , c common . Args , namespace , defpath string , defBytes [ ] byte , dryRun bool ) ( string , error ) {
config , err := c . GetConfig ( )
if err != nil {
return "" , err
}
k8sClient , err := c . GetClient ( )
if err != nil {
return "" , errors . Wrapf ( err , "failed to get k8s client" )
}
def := pkgdef . Definition { Unstructured : unstructured . Unstructured { } }
switch {
case strings . HasSuffix ( defpath , ".yaml" ) || strings . HasSuffix ( defpath , ".yml" ) :
// In this case, it's not in cue format, it's a yaml
if err = def . FromYAML ( defBytes ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to parse YAML to definition" )
}
if dryRun {
return "" , errors . New ( "dry-run will render CUE to YAML, while the input is already in yaml" )
}
// YAML won't validate or format CUE schematic
op , err := utils . CreateOrUpdate ( ctx , k8sClient , & def )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "%s %s in namespace %s %s.\n" , def . GetKind ( ) , def . GetName ( ) , def . GetNamespace ( ) , op ) , nil
default :
if err := def . FromCUEString ( string ( defBytes ) , config ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to parse CUE for definition" )
}
def . SetNamespace ( namespace )
}
if dryRun {
s , err := prettyYAMLMarshal ( def . Object )
if err != nil {
return "" , errors . Wrapf ( err , "failed to marshal CRD into YAML" )
}
return s , nil
}
oldDef := pkgdef . Definition { Unstructured : unstructured . Unstructured { } }
oldDef . SetGroupVersionKind ( def . GroupVersionKind ( ) )
err = k8sClient . Get ( ctx , types2 . NamespacedName {
Namespace : def . GetNamespace ( ) ,
Name : def . GetName ( ) ,
} , & oldDef )
if err != nil {
if errors2 . IsNotFound ( err ) {
kind := def . GetKind ( )
if err = k8sClient . Create ( ctx , & def ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to create new definition in kubernetes" )
}
return fmt . Sprintf ( "%s %s created in namespace %s.\n" , kind , def . GetName ( ) , def . GetNamespace ( ) ) , nil
}
return "" , errors . Wrapf ( err , "failed to check existence of target definition in kubernetes" )
}
if err := oldDef . FromCUEString ( string ( defBytes ) , config ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to merge with existing definition" )
}
if err = k8sClient . Update ( ctx , & oldDef ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to update existing definition in kubernetes" )
}
return fmt . Sprintf ( "%s %s in namespace %s updated.\n" , oldDef . GetKind ( ) , oldDef . GetName ( ) , oldDef . GetNamespace ( ) ) , nil
}
2021-08-03 18:00:49 +08:00
// NewDefinitionDelCommand create the `vela def del` command to help user delete existing definitions conveniently
func NewDefinitionDelCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "del DEFINITION_NAME" ,
2022-01-12 17:43:08 +08:00
Short : "Delete X-Definition." ,
Long : "Delete X-Definition in kubernetes cluster." ,
2021-08-03 18:00:49 +08:00
Example : "# Command below will delete TraitDefinition of annotations in default namespace\n" +
"> vela def del annotations -t trait -n default" ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
definitionType , err := cmd . Flags ( ) . GetString ( FlagType )
if err != nil {
return errors . Wrapf ( err , "failed to get `%s`" , FlagType )
}
namespace , err := cmd . Flags ( ) . GetString ( FlagNamespace )
if err != nil {
2021-11-19 18:00:03 +08:00
return errors . Wrapf ( err , "failed to get `%s`" , Namespace )
2021-08-03 18:00:49 +08:00
}
k8sClient , err := c . GetClient ( )
if err != nil {
return errors . Wrapf ( err , "failed to get k8s client" )
}
def , err := getSingleDefinition ( cmd , args [ 0 ] , k8sClient , definitionType , namespace )
if err != nil {
return err
}
2021-12-09 20:31:19 +08:00
desc := def . GetAnnotations ( ) [ pkgdef . DescriptionKey ]
2021-08-03 18:00:49 +08:00
toDelete := false
_ , err = getPrompt ( cmd , bufio . NewReader ( cmd . InOrStdin ( ) ) ,
fmt . Sprintf ( "Are you sure to delete the following definition in namespace %s?\n" , def . GetNamespace ( ) ) +
fmt . Sprintf ( "%s %s: %s\n" , def . GetKind ( ) , def . GetName ( ) , desc ) ,
"[yes|no] > " ,
func ( resp string ) error {
switch strings . ToLower ( resp ) {
case "yes" :
toDelete = true
case "y" :
toDelete = true
case "no" :
toDelete = false
case "n" :
toDelete = false
default :
return errors . New ( "invalid input" )
}
return nil
} )
if err != nil {
return err
}
if ! toDelete {
return nil
}
if err := k8sClient . Delete ( context . Background ( ) , def ) ; err != nil {
return errors . Wrapf ( err , "failed to delete %s %s in namespace %s" , def . GetKind ( ) , def . GetName ( ) , def . GetNamespace ( ) )
}
cmd . Printf ( "%s %s in namespace %s deleted.\n" , def . GetKind ( ) , def . GetName ( ) , def . GetNamespace ( ) )
return nil
} ,
}
2021-12-09 20:31:19 +08:00
cmd . Flags ( ) . StringP ( FlagType , "t" , "" , "Specify the definition type of target. Valid types: " + strings . Join ( pkgdef . ValidDefinitionTypes ( ) , ", " ) )
2022-07-18 19:22:55 +08:00
cmd . Flags ( ) . StringP ( Namespace , "n" , types . DefaultKubeVelaNS , "Specify which namespace the definition locates." )
2021-08-03 18:00:49 +08:00
return cmd
}
// NewDefinitionValidateCommand create the `vela def vet` command to help user validate the definition
func NewDefinitionValidateCommand ( c common . Args ) * cobra . Command {
cmd := & cobra . Command {
Use : "vet DEFINITION.cue" ,
2022-01-12 17:43:08 +08:00
Short : "Validate X-Definition." ,
2021-08-03 18:00:49 +08:00
Long : "Validate definition file by checking whether it has the valid cue format with fields set correctly\n" +
"* Currently, this command only checks the cue format. This function is still working in progress and we will support more functional validation mechanism in the future." ,
Example : "# Command below will validate the my-def.cue file.\n" +
2023-04-07 14:12:37 +08:00
"> vela def vet my-def.cue\n" +
"# Validate every cue file provided\n" +
"> vela def vet my-def1.cue my-def2.cue my-def3.cue\n" +
"# Validate every cue file in the specified directories" +
"> vela def vet ./test1/ ./test2/" ,
Args : cobra . MinimumNArgs ( 1 ) ,
2021-08-03 18:00:49 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2023-04-07 14:12:37 +08:00
for _ , arg := range args {
files , err := utils . LoadDataFromPath ( cmd . Context ( ) , arg , utils . IsCUEFile )
if err != nil {
return errors . Wrapf ( err , "failed to get file from %s" , arg )
}
for _ , file := range files {
validateRes , err := validateSingleCueFile ( file . Path , file . Data , c )
if err != nil {
return err
}
fmt . Fprintf ( cmd . OutOrStdout ( ) , "%s" , validateRes )
}
2021-08-03 18:00:49 +08:00
}
return nil
} ,
}
return cmd
}
2022-04-20 18:08:24 +08:00
2023-04-07 14:12:37 +08:00
func validateSingleCueFile ( fileName string , fileData [ ] byte , c common . Args ) ( string , error ) {
def := pkgdef . Definition { Unstructured : unstructured . Unstructured { } }
config , err := c . GetConfig ( )
if err != nil {
2023-06-01 14:15:45 +08:00
klog . Infof ( "ignore kubernetes cluster, unable to get kubeconfig: %s" , err . Error ( ) )
2023-04-07 14:12:37 +08:00
}
if err := def . FromCUEString ( string ( fileData ) , config ) ; err != nil {
return "" , errors . Wrapf ( err , "failed to parse CUE: %s" , fileName )
}
return fmt . Sprintf ( "Validation %s succeed.\n" , fileName ) , nil
}
2022-04-20 18:08:24 +08:00
// NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
func NewDefinitionGenAPICommand ( c common . Args ) * cobra . Command {
2023-02-21 15:54:44 +08:00
meta := gen_sdk . GenMeta { }
2023-03-24 11:28:56 +08:00
var languageArgs [ ] string
2022-04-20 18:08:24 +08:00
cmd := & cobra . Command {
Use : "gen-api DEFINITION.cue" ,
2023-02-21 15:54:44 +08:00
Short : "Generate SDK from X-Definition." ,
Long : "Generate SDK from X-definition file.\n" +
2023-03-24 11:28:56 +08:00
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" +
2022-04-20 18:08:24 +08:00
"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet." ,
2023-02-21 15:54:44 +08:00
Example : "# Generate SDK for golang with scaffold initialized\n" +
2023-03-24 11:28:56 +08:00
"> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" +
2023-02-21 15:54:44 +08:00
"# Generate incremental definition files to existing sdk directory\n" +
2023-03-24 11:28:56 +08:00
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" +
"# Generate definitions to a sub-module\n" +
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk --submodule --api-dir path/relative/to/output --language-args arg1=val1,arg2=val2\n" ,
2022-04-20 18:08:24 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2023-03-24 11:28:56 +08:00
err := meta . Init ( c , languageArgs )
2022-04-20 18:08:24 +08:00
if err != nil {
return err
}
2023-02-21 15:54:44 +08:00
err = meta . CreateScaffold ( )
2022-04-20 18:08:24 +08:00
if err != nil {
return err
}
2023-02-21 15:54:44 +08:00
err = meta . PrepareGeneratorAndTemplate ( )
2022-04-20 18:08:24 +08:00
if err != nil {
return err
}
2023-02-21 15:54:44 +08:00
err = meta . Run ( )
2022-04-20 18:08:24 +08:00
if err != nil {
return err
}
return nil
} ,
}
2023-03-24 11:28:56 +08:00
2023-02-21 15:54:44 +08:00
cmd . Flags ( ) . StringVarP ( & meta . Output , "output" , "o" , "./apis" , "Output directory path" )
2023-03-24 11:28:56 +08:00
cmd . Flags ( ) . StringVar ( & meta . APIDirectory , "api-dir" , "" , "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis" )
cmd . Flags ( ) . BoolVar ( & meta . IsSubModule , "submodule" , false , "Whether the generated code is a submodule of the project. If set, the directory specified by `api-dir` will be treated as a submodule of the project" )
cmd . Flags ( ) . StringVarP ( & meta . Package , "package" , "p" , gen_sdk . PackagePlaceHolder , "Package name of generated code" )
cmd . Flags ( ) . StringVarP ( & meta . Lang , "language" , "g" , "go" , "Language to generate code. Valid languages: go" )
2023-02-21 15:54:44 +08:00
cmd . Flags ( ) . StringVarP ( & meta . Template , "template" , "t" , "" , "Template file path, if not specified, the default template will be used" )
cmd . Flags ( ) . StringSliceVarP ( & meta . File , "file" , "f" , nil , "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used" )
cmd . Flags ( ) . BoolVar ( & meta . InitSDK , "init" , false , "Init the whole SDK project, if not set, only the API file will be generated" )
cmd . Flags ( ) . BoolVarP ( & meta . Verbose , "verbose" , "v" , false , "Print verbose logs" )
2023-03-24 11:28:56 +08:00
var langArgsDescStr string
for lang , args := range gen_sdk . LangArgsRegistry {
langArgsDescStr += lang + ": \n"
for key , arg := range args {
langArgsDescStr += fmt . Sprintf ( "\t%s: %s(default: %s)\n" , key , arg . Name , arg . Default )
}
}
cmd . Flags ( ) . StringSliceVar ( & languageArgs , "language-args" , [ ] string { } ,
fmt . Sprintf ( "language-specific arguments to pass to the go generator, available options: \n" + langArgsDescStr ) ,
)
2023-02-21 15:54:44 +08:00
2022-04-20 18:08:24 +08:00
return cmd
}
2023-05-05 19:26:13 +08:00
2023-05-12 14:24:49 +08:00
const (
genTypeProvider = "provider"
)
2023-05-05 19:26:13 +08:00
// NewDefinitionGenCUECommand create the `vela def gen-cue` command to help user generate CUE schema from the go code
2023-05-10 14:17:16 +08:00
func NewDefinitionGenCUECommand ( _ common . Args , streams util . IOStreams ) * cobra . Command {
2023-05-05 19:26:13 +08:00
var (
typ string
typeMap map [ string ] string
nullable bool
)
cmd := & cobra . Command {
Use : "gen-cue [flags] SOURCE.go" ,
Args : cobra . ExactArgs ( 1 ) ,
Short : "Generate CUE schema from Go code." ,
Long : "Generate CUE schema from Go code.\n" +
"* This command provide a way to generate CUE schema from Go code,\n" +
"* Which can be used to keep consistency between Go code and CUE schema automatically.\n" ,
Example : "# Generate CUE schema for provider type\n" +
2023-05-10 14:17:16 +08:00
"> vela def gen-cue -t provider /path/to/myprovider.go > /path/to/myprovider.cue\n" +
2023-05-05 19:26:13 +08:00
"# Generate CUE schema for provider type with custom types\n" +
2023-05-10 14:17:16 +08:00
"> vela def gen-cue -t provider --types *k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis /path/to/myprovider.go > /path/to/myprovider.cue" ,
2023-05-05 19:26:13 +08:00
RunE : func ( cmd * cobra . Command , args [ ] string ) ( rerr error ) {
// convert map[string]string to map[string]cuegen.Type
newTypeMap := make ( map [ string ] cuegen . Type , len ( typeMap ) )
for k , v := range typeMap {
newTypeMap [ k ] = cuegen . Type ( v )
}
file := args [ 0 ]
if ! strings . HasSuffix ( file , ".go" ) {
return fmt . Errorf ( "invalid file %s, must be a go file" , file )
}
switch typ {
2023-05-12 14:24:49 +08:00
case genTypeProvider :
2023-05-05 19:26:13 +08:00
return providergen . Generate ( providergen . Options {
File : file ,
2023-05-10 14:17:16 +08:00
Writer : streams . Out ,
2023-05-05 19:26:13 +08:00
Types : newTypeMap ,
Nullable : nullable ,
} )
default :
return fmt . Errorf ( "invalid type %s" , typ )
}
} ,
}
cmd . Flags ( ) . StringVarP ( & typ , "type" , "t" , "" , "Type of the definition to generate. Valid types: [provider]" )
cmd . Flags ( ) . BoolVar ( & nullable , "nullable" , false , "Whether to generate null enum for pointer type" )
cmd . Flags ( ) . StringToStringVar ( & typeMap , "types" , map [ string ] string { } , "Special types to generate, format: <package+struct>=[any|ellipsis]. e.g. --types=*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis" )
return cmd
}
2023-05-12 14:24:49 +08:00
// NewDefinitionGenDocCommand create the `vela def gen-doc` command to generate documentation of definitions
func NewDefinitionGenDocCommand ( _ common . Args , streams util . IOStreams ) * cobra . Command {
var typ string
cmd := & cobra . Command {
Use : "gen-doc [flags] SOURCE.cue..." ,
Args : cobra . MinimumNArgs ( 1 ) ,
Short : "Generate documentation for non component, trait, policy and workflow definitions" ,
Long : "Generate documentation for non component, trait, policy and workflow definitions" ,
Example : "1. Generate documentation for provider definitions\n" +
"> vela def gen-doc -t provider provider1.cue provider2.cue > provider.md" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
readers := make ( [ ] io . Reader , 0 , len ( args ) )
for _ , arg := range args {
if ! strings . HasSuffix ( arg , ".cue" ) {
return fmt . Errorf ( "invalid file %s, must be a cue file" , arg )
}
f , err := os . ReadFile ( filepath . Clean ( arg ) )
if err != nil {
return fmt . Errorf ( "read file %s: %w" , arg , err )
}
readers = append ( readers , bytes . NewReader ( f ) )
}
switch typ {
case genTypeProvider :
return docgen . GenerateProvidersMarkdown ( cmd . Context ( ) , readers , streams . Out )
default :
return fmt . Errorf ( "invalid type %s" , typ )
}
} ,
}
cmd . Flags ( ) . StringVarP ( & typ , "type" , "t" , "" , "Type of the definition to generate. Valid types: [provider]" )
return cmd
}