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:
ivyilike
2022-07-24 16:34:13 +08:00
committed by GitHub
co-authored by ivyilike Jianbo Sun
parent 573f60170c
commit 3e57246740
6 changed files with 208 additions and 84 deletions
+96 -79
View File
@@ -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{
+7 -5
View File
@@ -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++ {
+60
View File
@@ -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