mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: support vela def apply for directory (#4142)
Co-authored-by: ivyilike <pww123@cmbchina.com> Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com> Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
This commit is contained in:
co-authored by
ivyilike
Jianbo Sun
parent
573f60170c
commit
3e57246740
+96
-79
@@ -56,7 +56,7 @@ import (
|
||||
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/filters"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,7 +67,7 @@ const (
|
||||
)
|
||||
|
||||
// DefinitionCommandGroup create the command group for `vela def` command to manage definitions
|
||||
func DefinitionCommandGroup(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
func DefinitionCommandGroup(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "def",
|
||||
Short: "Manage Definitions",
|
||||
@@ -82,7 +82,7 @@ func DefinitionCommandGroup(c common.Args, order string, ioStreams cmdutil.IOStr
|
||||
NewDefinitionListCommand(c),
|
||||
NewDefinitionEditCommand(c),
|
||||
NewDefinitionRenderCommand(c),
|
||||
NewDefinitionApplyCommand(c),
|
||||
NewDefinitionApplyCommand(c, ioStreams),
|
||||
NewDefinitionDelCommand(c),
|
||||
NewDefinitionInitCommand(c),
|
||||
NewDefinitionValidateCommand(c),
|
||||
@@ -518,8 +518,8 @@ func NewDefinitionGetCommand(c common.Args) *cobra.Command {
|
||||
}
|
||||
|
||||
// NewDefinitionGenDocCommand create the `vela def doc-gen` command to generate documentation of definitions
|
||||
func NewDefinitionGenDocCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
var path, location, i18nPath string
|
||||
func NewDefinitionGenDocCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command {
|
||||
var docPath, location, i18nPath string
|
||||
cmd := &cobra.Command{
|
||||
Use: "doc-gen NAME",
|
||||
Short: "Generate documentation for definitions",
|
||||
@@ -539,11 +539,11 @@ func NewDefinitionGenDocCommand(c common.Args, ioStreams cmdutil.IOStreams) *cob
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
||||
}
|
||||
return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], path, location, i18nPath, namespace, 0)
|
||||
return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], docPath, location, i18nPath, namespace, 0)
|
||||
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&path, "path", "p", "", "Specify the path for of the doc generated from definition.")
|
||||
cmd.Flags().StringVarP(&docPath, "path", "p", "", "Specify the path for of the doc generated from definition.")
|
||||
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'. ")
|
||||
@@ -843,13 +843,15 @@ func NewDefinitionRenderCommand(c common.Args) *cobra.Command {
|
||||
}
|
||||
|
||||
// NewDefinitionApplyCommand create the `vela def apply` command to help user apply local definitions to k8s
|
||||
func NewDefinitionApplyCommand(c common.Args) *cobra.Command {
|
||||
func NewDefinitionApplyCommand(c common.Args, streams util.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply DEFINITION.cue",
|
||||
Short: "Apply X-Definition.",
|
||||
Long: "Apply X-Definition from local storage to kubernetes cluster. It will apply file to vela-system namespace by default.",
|
||||
Example: "# Command below will apply the local my-webservice.cue file to kubernetes vela-system namespace\n" +
|
||||
"> vela def apply my-webservice.cue\n" +
|
||||
"# Apply the local directory including all files(YAML and CUE definition) to kubernetes vela-system namespace\n" +
|
||||
"> vela def apply def/\n" +
|
||||
"# 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" +
|
||||
@@ -869,85 +871,100 @@ func NewDefinitionApplyCommand(c common.Args) *cobra.Command {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
||||
}
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
if len(args) < 1 {
|
||||
return errors.New("you must specify the definition path, directory or URL")
|
||||
}
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get k8s client")
|
||||
}
|
||||
defpath := args[0]
|
||||
defBytes, err := utils.ReadRemoteOrLocalPath(defpath, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get from %s", defpath)
|
||||
}
|
||||
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
|
||||
}
|
||||
cmd.Printf("%s %s in namespace %s %s.\n", def.GetKind(), def.GetName(), def.GetNamespace(), op)
|
||||
return 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")
|
||||
}
|
||||
cmd.Print(s)
|
||||
return 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")
|
||||
}
|
||||
cmd.Printf("%s %s created in namespace %s.\n", kind, def.GetName(), def.GetNamespace())
|
||||
return 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")
|
||||
}
|
||||
cmd.Printf("%s %s in namespace %s updated.\n", oldDef.GetKind(), oldDef.GetName(), oldDef.GetNamespace())
|
||||
return nil
|
||||
return defApplyAll(ctx, c, streams, namespace, args[0], dryRun)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolP(FlagDryRun, "", false, "only build definition from CUE into CRB object without applying it to kubernetes clusters")
|
||||
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewDefinitionDelCommand create the `vela def del` command to help user delete existing definitions conveniently
|
||||
func NewDefinitionDelCommand(c common.Args) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -40,7 +41,7 @@ import (
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -180,7 +181,7 @@ func removeDir(dirname string, t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewDefinitionCommandGroup(t *testing.T) {
|
||||
cmd := DefinitionCommandGroup(common2.Args{}, "", cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
|
||||
cmd := DefinitionCommandGroup(common2.Args{}, "", util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
|
||||
initCommand(cmd)
|
||||
cmd.SetArgs([]string{"-h"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
@@ -460,7 +461,7 @@ func TestNewDefinitionGetCommand(t *testing.T) {
|
||||
|
||||
func TestNewDefinitionGenDocCommand(t *testing.T) {
|
||||
c := initArgs()
|
||||
cmd := NewDefinitionGenDocCommand(c, cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
|
||||
cmd := NewDefinitionGenDocCommand(c, util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
|
||||
assert.NotNil(t, cmd.Execute())
|
||||
|
||||
cmd.SetArgs([]string{"alibaba-xxxxxxx"})
|
||||
@@ -559,8 +560,9 @@ func TestNewDefinitionRenderCommand(t *testing.T) {
|
||||
|
||||
func TestNewDefinitionApplyCommand(t *testing.T) {
|
||||
c := initArgs()
|
||||
ioStreams := util.IOStreams{In: os.Stdin, Out: bytes.NewBuffer(nil), ErrOut: bytes.NewBuffer(nil)}
|
||||
// dry-run test
|
||||
cmd := NewDefinitionApplyCommand(c)
|
||||
cmd := NewDefinitionApplyCommand(c, ioStreams)
|
||||
initCommand(cmd)
|
||||
_, traitFilename := createLocalTrait(t)
|
||||
defer removeFile(traitFilename, t)
|
||||
@@ -569,7 +571,7 @@ func TestNewDefinitionApplyCommand(t *testing.T) {
|
||||
t.Fatalf("unexpeced error when executing apply command: %v", err)
|
||||
}
|
||||
// normal test and reapply
|
||||
cmd = NewDefinitionApplyCommand(c)
|
||||
cmd = NewDefinitionApplyCommand(c, ioStreams)
|
||||
initCommand(cmd)
|
||||
cmd.SetArgs([]string{traitFilename})
|
||||
for i := 0; i < 2; i++ {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2022 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test def apply cli", func() {
|
||||
|
||||
When("test vela def apply", func() {
|
||||
|
||||
It("should not have err and applied all def files", func() {
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
ioStreams := util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer}
|
||||
|
||||
ctx := context.Background()
|
||||
c := common.Args{}
|
||||
c.SetConfig(cfg)
|
||||
c.SetClient(k8sClient)
|
||||
err := defApplyAll(ctx, c, ioStreams, types.DefaultKubeVelaNS, "./test-data/defapply", false)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
By("check component definition in YAML exist")
|
||||
cml := v1beta1.ComponentDefinition{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "testdefyaml", Namespace: types.DefaultKubeVelaNS}, &cml)).Should(BeNil())
|
||||
|
||||
By("check trait definition in CUE exist")
|
||||
traitd := v1beta1.TraitDefinition{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "testdefcue", Namespace: types.DefaultKubeVelaNS}, &traitd)).Should(BeNil())
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
testdefcue: {
|
||||
type: "trait"
|
||||
annotations: {}
|
||||
labels: {
|
||||
"ui-hidden": "true"
|
||||
}
|
||||
description: "Add host aliases on K8s pod for your workload which follows the pod spec in path 'spec.template'."
|
||||
attributes: {
|
||||
podDisruptive: false
|
||||
appliesToWorkloads: ["*"]
|
||||
}
|
||||
}
|
||||
template: {
|
||||
patch: {
|
||||
// +patchKey=ip
|
||||
spec: template: spec: hostAliases: parameter.hostAliases
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: xxx
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
type: terraform
|
||||
name: testdefyaml
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
terraform:
|
||||
configuration: https://github.com/kubevela-contrib/terraform-modules.git
|
||||
path: alibaba/vswitch
|
||||
type: remote
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: terraform.core.oam.dev/v1beta2
|
||||
kind: Configuration
|
||||
Reference in New Issue
Block a user