Align vela cli and kubectl vela client tools (#1827)

* allow vela CLI to specify NS

* vela up support application yaml

* fix

* add default cap center to vela CLI

* add alias for `vela components`(`vela comp`,or `vela component`) and `vela traits`(`vela trait`)

* fix cap ls STATUS fields are always "uninstalled"

* fix vela up process

* Revert "allow vela CLI to specify NS"

This reverts commit 33f27362

will refactor to use Initializer

* add --discover for vela CLI

* * rfc capcenter to reuse registry type
* change default cap center to oss

* judge if application file in advance

* fix CI

* try CI

* fix error check
This commit is contained in:
chival
2021-06-29 15:52:43 +08:00
committed by GitHub
parent 2b4d12fbdd
commit 22d014d91a
9 changed files with 220 additions and 110 deletions
+21 -8
View File
@@ -203,17 +203,11 @@ func NewCapListCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
if err != nil {
return err
}
capabilityList, err := common.ListCapabilities(env.Namespace, c, repoName)
err = printCenterCapabilities(env.Namespace, repoName, c, ioStreams, nil)
if err != nil {
return err
}
table := newUITable()
table.AddRow("NAME", "CENTER", "TYPE", "DEFINITION", "STATUS", "APPLIES-TO")
for _, c := range capabilityList {
table.AddRow(c.Name, c.Center, c.Type, c.CrdName, c.Status, c.AppliesTo)
}
ioStreams.Info(table.String())
return nil
},
}
@@ -274,3 +268,22 @@ func removeCapCenter(args []string, ioStreams cmdutil.IOStreams) error {
}
return err
}
func printCenterCapabilities(namespace, repoName string, args common2.Args, ioStreams cmdutil.IOStreams, option *types.CapType) error {
capabilityList, err := common.ListCapabilities(namespace, args, repoName)
if err != nil {
return err
}
table := newUITable()
table.AddRow("NAME", "CENTER", "TYPE", "DEFINITION", "STATUS", "APPLIES-TO")
for _, c := range capabilityList {
if option == nil {
table.AddRow(c.Name, c.Center, c.Type, c.CrdName, c.Status, c.AppliesTo)
}
if option != nil && c.Type == *option {
table.AddRow(c.Name, c.Center, c.Type, c.CrdName, c.Status, c.AppliesTo)
}
}
ioStreams.Info(table.String())
return nil
}
+13 -1
View File
@@ -40,6 +40,7 @@ import (
func NewComponentsCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "components",
Aliases: []string{"comp", "component"},
DisableFlagsInUseLine: true,
Short: "List components",
Long: "List components",
@@ -48,16 +49,27 @@ func NewComponentsCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Co
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
isDiscover, _ := cmd.Flags().GetBool("discover")
env, err := GetEnv(cmd)
if err != nil {
return err
}
return printComponentList(env.Namespace, c, ioStreams)
if !isDiscover {
return printComponentList(env.Namespace, c, ioStreams)
}
option := types.TypeComponentDefinition
err = printCenterCapabilities(env.Namespace, "", c, ioStreams, &option)
if err != nil {
return err
}
return nil
},
Annotations: map[string]string{
types.TagCommandType: types.TypeCap,
},
}
cmd.Flags().Bool("discover", false, "discover traits in capability centers")
cmd.SetOut(ioStreams.Out)
return cmd
}
+13 -2
View File
@@ -40,6 +40,7 @@ import (
func NewTraitsCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "traits",
Aliases: []string{"trait"},
DisableFlagsInUseLine: true,
Short: "List traits",
Long: "List traits",
@@ -48,17 +49,27 @@ func NewTraitsCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Comman
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
isDiscover, _ := cmd.Flags().GetBool("discover")
env, err := GetEnv(cmd)
if err != nil {
return err
}
return printTraitList(env.Namespace, c, ioStreams)
if !isDiscover {
return printTraitList(env.Namespace, c, ioStreams)
}
option := types.TypeTrait
err = printCenterCapabilities(env.Namespace, "", c, ioStreams, &option)
if err != nil {
return err
}
return nil
},
Annotations: map[string]string{
types.TagCommandType: types.TypeCap,
},
}
cmd.Flags().Bool("discover", false, "discover traits in capability centers")
cmd.SetOut(ioStreams.Out)
return cmd
}
+29 -8
View File
@@ -17,8 +17,14 @@ limitations under the License.
package cli
import (
"io/ioutil"
"path/filepath"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/spf13/cobra"
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -51,21 +57,36 @@ func NewUpCommand(c common2.Args, ioStream cmdutil.IOStreams) *cobra.Command {
if err != nil {
return err
}
o := &common.AppfileOptions{
Kubecli: kubecli,
IO: ioStream,
Env: velaEnv,
}
filePath, err := cmd.Flags().GetString(appFilePath)
if err != nil {
return err
}
return o.Run(filePath, velaEnv.Namespace, c)
fileContent, err := ioutil.ReadFile(filepath.Clean(filePath))
if err != nil {
return err
}
var app corev1beta1.Application
err = yaml.Unmarshal(fileContent, &app)
if err != nil {
return errors.Wrap(err, "File format is illegal")
}
if app.APIVersion != "" && app.Kind != "" {
err = common.ApplyApplication(app, ioStream, kubecli)
if err != nil {
return err
}
} else {
o := &common.AppfileOptions{
Kubecli: kubecli,
IO: ioStream,
Env: velaEnv,
}
return o.Run(filePath, velaEnv.Namespace, c)
}
return nil
},
}
cmd.SetOut(ioStream.Out)
cmd.Flags().StringP(appFilePath, "f", "", "specify file path for appfile")
return cmd
}
+22
View File
@@ -42,6 +42,7 @@ import (
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/apply"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/apiserver/apis"
@@ -520,3 +521,24 @@ func (o *AppfileOptions) Info(app *corev1beta1.Application) string {
}
return appUpMessage
}
// ApplyApplication will apply an application file in K8s GVK format
func ApplyApplication(app corev1beta1.Application, ioStream cmdutil.IOStreams, clt client.Client) error {
if app.Namespace == "" {
app.Namespace = types.DefaultAppNamespace
}
_, err := ioStream.Out.Write([]byte("Applying an application in K8S format...\n"))
if err != nil {
return err
}
applicator := apply.NewAPIApplicator(clt)
err = applicator.Apply(context.Background(), &app)
if err != nil {
return err
}
_, err = ioStream.Out.Write([]byte("Successfully apply application"))
if err != nil {
return err
}
return nil
}
+16 -17
View File
@@ -170,7 +170,7 @@ func InstallComponentDefinition(client client.Client, workloadData []byte, ioStr
}
// InstallTraitDefinition will add a trait into K8s cluster and install it's controller
func InstallTraitDefinition(client client.Client, mapper discoverymapper.DiscoveryMapper, traitdata []byte, ioStreams cmdutil.IOStreams, tp *types.Capability) error {
func InstallTraitDefinition(client client.Client, mapper discoverymapper.DiscoveryMapper, traitdata []byte, ioStreams cmdutil.IOStreams, cap *types.Capability) error {
var td v1beta1.TraitDefinition
var err error
if err = yaml.Unmarshal(traitdata, &td); err != nil {
@@ -178,24 +178,24 @@ func InstallTraitDefinition(client client.Client, mapper discoverymapper.Discove
}
td.Namespace = types.DefaultKubeVelaNS
ioStreams.Info("Installing trait capability " + td.Name)
if tp.Install != nil {
tp.Source.ChartName = tp.Install.Helm.Name
if err = helm.InstallHelmChart(ioStreams, tp.Install.Helm); err != nil {
if cap.Install != nil {
cap.Source.ChartName = cap.Install.Helm.Name
if err = helm.InstallHelmChart(ioStreams, cap.Install.Helm); err != nil {
return err
}
err = addSourceIntoExtension(td.Spec.Extension, tp.Source)
err = addSourceIntoExtension(td.Spec.Extension, cap.Source)
if err != nil {
return err
}
}
if err = HackForStandardTrait(*tp, client); err != nil {
if err = HackForStandardTrait(*cap, client); err != nil {
return err
}
gvk, err := util.GetGVKFromDefinition(mapper, td.Spec.Reference)
if err != nil {
return err
}
tp.CrdInfo = &types.CRDInfo{
cap.CrdInfo = &types.CRDInfo{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
}
@@ -400,7 +400,6 @@ func ListCapabilities(userNamespace string, c common.Args, capabilityCenterName
}
return capabilityList, nil
}
func listCenterCapabilities(userNamespace string, c common.Args, repoDir string) ([]types.Capability, error) {
dm, err := c.GetDiscoveryMapper()
if err != nil {
@@ -414,10 +413,10 @@ func listCenterCapabilities(userNamespace string, c common.Args, repoDir string)
return templates, nil
}
baseDir := filepath.Base(repoDir)
workloads := gatherComponents(userNamespace, c, templates)
components := gatherComponents(userNamespace, c, templates)
for i, p := range templates {
status := checkInstallStatus(userNamespace, c, baseDir, p)
convertedApplyTo := ConvertApplyTo(p.AppliesTo, workloads)
status := checkInstallStatus(userNamespace, c, p)
convertedApplyTo := ConvertApplyTo(p.AppliesTo, components)
templates[i].Center = baseDir
templates[i].Status = status
templates[i].AppliesTo = convertedApplyTo
@@ -460,23 +459,23 @@ func RemoveCapabilityCenter(centerName string) (string, error) {
}
func gatherComponents(userNamespace string, c common.Args, templates []types.Capability) []types.Capability {
workloads, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeComponentDefinition)
components, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeComponentDefinition)
if err != nil {
workloads = make([]types.Capability, 0)
components = make([]types.Capability, 0)
}
for _, t := range templates {
if t.Type == types.TypeComponentDefinition {
workloads = append(workloads, t)
components = append(components, t)
}
}
return workloads
return components
}
func checkInstallStatus(userNamespace string, c common.Args, repoName string, tmp types.Capability) string {
func checkInstallStatus(userNamespace string, c common.Args, tmp types.Capability) string {
var status = "uninstalled"
installed, _ := plugins.LoadInstalledCapabilityWithType(userNamespace, c, tmp.Type)
for _, i := range installed {
if i.Source != nil && i.Source.RepoName == repoName && i.Name == tmp.Name && i.CrdName == tmp.CrdName {
if i.Name == tmp.Name && i.CrdName == tmp.CrdName {
return "installed"
}
}
+59 -56
View File
@@ -18,7 +18,6 @@ package plugins
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
@@ -40,7 +39,7 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/system"
)
// Content contains different type of content needed when building Registry or GithubCenter
// Content contains different type of content needed when building Registry
type Content struct {
OssContent
GithubContent
@@ -86,6 +85,8 @@ func NewCenterClient(ctx context.Context, name, address, token string) (CenterCl
switch Type {
case TypeGithub:
return NewGithubCenter(ctx, token, name, &cfg.GithubContent)
case TypeOss:
return NewOssCenter(fmt.Sprintf("https://%s/", cfg.BucketURL), name), nil
default:
}
return nil, errors.New("we only support github as repository now")
@@ -178,22 +179,12 @@ func Parse(addr string) (string, *Content, error) {
return TypeUnknown, nil, nil
}
// RemoteCapability defines the capability discovered from remote cap center
type RemoteCapability struct {
// Name MUST be xxx.yaml
Name string `json:"name"`
URL string `json:"downloadUrl"`
Sha string `json:"sha"`
// Type MUST be file
Type string `json:"type"`
}
// RemoteCapabilities is slice of cap center
type RemoteCapabilities []RemoteCapability
// LoadRepos will load all cap center repos
// TODO(wonderflow): we can make default(built-in) repo configurable, then we should make default inside the answer
func LoadRepos() ([]CapCenterConfig, error) {
defaultRepo := CapCenterConfig{
Name: "default-cap-center",
Address: "oss://registry.kubevela.net/",
}
config, err := system.GetRepoConfig()
if err != nil {
return nil, err
@@ -201,7 +192,7 @@ func LoadRepos() ([]CapCenterConfig, error) {
data, err := ioutil.ReadFile(filepath.Clean(config))
if err != nil {
if os.IsNotExist(err) {
return []CapCenterConfig{}, nil
return []CapCenterConfig{defaultRepo}, nil
}
return nil, err
}
@@ -209,6 +200,16 @@ func LoadRepos() ([]CapCenterConfig, error) {
if err = yaml.Unmarshal(data, &repos); err != nil {
return nil, err
}
haveDefault := false
for _, repo := range repos {
if repo.Address == defaultRepo.Address {
haveDefault = true
break
}
}
if !haveDefault {
repos = append(repos, defaultRepo)
}
return repos, nil
}
@@ -226,8 +227,8 @@ func StoreRepos(repos []CapCenterConfig) error {
return ioutil.WriteFile(config, data, 0644)
}
// ParseAndSyncCapability will convert config from remote center to capability
func ParseAndSyncCapability(mapper discoverymapper.DiscoveryMapper, data []byte) (types.Capability, error) {
// ParseCapability will convert config from remote center to capability
func ParseCapability(mapper discoverymapper.DiscoveryMapper, data []byte) (types.Capability, error) {
var obj = unstructured.Unstructured{Object: make(map[string]interface{})}
err := yaml.Unmarshal(data, &obj.Object)
if err != nil {
@@ -258,18 +259,8 @@ func ParseAndSyncCapability(mapper discoverymapper.DiscoveryMapper, data []byte)
return types.Capability{}, fmt.Errorf("unknown definition Type %s", obj.GetKind())
}
// GithubCenter implementation of cap center
type GithubCenter struct {
client *github.Client
cfg *GithubContent
centerName string
ctx context.Context
}
var _ CenterClient = &GithubCenter{}
// NewGithubCenter will create client by github center implementation
func NewGithubCenter(ctx context.Context, token, centerName string, r *GithubContent) (*GithubCenter, error) {
func NewGithubCenter(ctx context.Context, token, centerName string, r *GithubContent) (*GithubRegistry, error) {
var tc *http.Client
if token != "" {
ts := oauth2.StaticTokenSource(
@@ -277,17 +268,18 @@ func NewGithubCenter(ctx context.Context, token, centerName string, r *GithubCon
)
tc = oauth2.NewClient(ctx, ts)
}
return &GithubCenter{client: github.NewClient(tc), cfg: r, centerName: centerName, ctx: ctx}, nil
return &GithubRegistry{client: github.NewClient(tc), cfg: r, centerName: centerName, ctx: ctx}, nil
}
// SyncCapabilityFromCenter will sync capability from github cap center
// SyncCapabilityFromCenter will sync capability from github registry
// TODO(wonderflow): currently we only sync by create, we also need to delete which not exist remotely.
func (g *GithubCenter) SyncCapabilityFromCenter() error {
func (g *GithubRegistry) SyncCapabilityFromCenter() error {
dir, err := system.GetCapCenterDir()
if err != nil {
return err
}
repoDir := filepath.Join(dir, g.centerName)
_, _ = system.CreateIfNotExist(repoDir)
var success int
items, err := g.getRepoFile()
if err != nil {
@@ -296,7 +288,8 @@ func (g *GithubCenter) SyncCapabilityFromCenter() error {
for _, item := range items {
addon, err := item.toAddon()
if err != nil {
return err
fmt.Printf("[INFO] CRD for %s not found\n", item.name)
continue
}
//nolint:gosec
err = ioutil.WriteFile(filepath.Join(repoDir, addon.Name+".yaml"), item.data, 0644)
@@ -310,33 +303,43 @@ func (g *GithubCenter) SyncCapabilityFromCenter() error {
return nil
}
func (g *GithubCenter) getRepoFile() ([]RegistryFile, error) {
var items []RegistryFile
_, dirs, _, err := g.client.Repositories.GetContents(g.ctx, g.cfg.Owner, g.cfg.Repo, g.cfg.Path, &github.RepositoryContentGetOptions{Ref: g.cfg.Ref})
if err != nil {
return []RegistryFile{}, err
// NewOssCenter will create OSS center implementation
func NewOssCenter(bucketURL string, centerName string) *OssRegistry {
var tc http.Client
return &OssRegistry{
Client: &tc,
bucketURL: bucketURL,
centerName: centerName,
}
for _, repoItem := range dirs {
if *repoItem.Type != "file" {
}
// SyncCapabilityFromCenter will sync capability from oss registry
func (o *OssRegistry) SyncCapabilityFromCenter() error {
dir, err := system.GetCapCenterDir()
if err != nil {
return err
}
repoDir := filepath.Join(dir, o.centerName)
_, _ = system.CreateIfNotExist(repoDir)
var success int
items, err := o.getRegFiles()
if err != nil {
return err
}
for _, item := range items {
addon, err := item.toAddon()
if err != nil {
fmt.Printf("[INFO] CRD for %s not found\n", item.name)
continue
}
fileContent, _, _, err := g.client.Repositories.GetContents(g.ctx, g.cfg.Owner, g.cfg.Repo, *repoItem.Path, &github.RepositoryContentGetOptions{Ref: g.cfg.Ref})
//nolint:gosec
err = ioutil.WriteFile(filepath.Join(repoDir, addon.Name+".yaml"), item.data, 0644)
if err != nil {
fmt.Printf("Getting content URL %s error: %s\n", repoItem.GetURL(), err)
fmt.Printf("write definition %s to %s err %v\n", addon.Name+".yaml", repoDir, err)
continue
}
var data []byte
if *fileContent.Encoding == "base64" {
data, err = base64.StdEncoding.DecodeString(*fileContent.Content)
if err != nil {
fmt.Printf("decode github content %s err %s\n", fileContent.GetPath(), err)
continue
}
}
items = append(items, RegistryFile{
data: data,
name: *fileContent.Name,
})
success++
}
return items, nil
fmt.Printf("successfully sync %d from %s remote center\n", success, o.centerName)
return nil
}
+1 -1
View File
@@ -259,7 +259,7 @@ func LoadCapabilityFromSyncedCenter(mapper discoverymapper.DiscoveryMapper, dir
fmt.Printf("read file %s err %v\n", f.Name(), err)
continue
}
tmp, err := ParseAndSyncCapability(mapper, data)
tmp, err := ParseCapability(mapper, data)
if err != nil {
fmt.Printf("get definition of %s err %v\n", f.Name(), err)
continue
+46 -17
View File
@@ -26,9 +26,9 @@ import (
"os"
"path"
"path/filepath"
"strings"
"github.com/google/go-github/v32/github"
"github.com/pkg/errors"
"golang.org/x/oauth2"
"github.com/oam-dev/kubevela/apis/types"
@@ -43,10 +43,10 @@ type Registry interface {
// GithubRegistry is Registry's implementation treat github url as resource
type GithubRegistry struct {
client *github.Client
cfg *GithubContent
ctx context.Context
name string // to be used to cache registry
client *github.Client
cfg *GithubContent
ctx context.Context
centerName string // to be used to cache registry
}
// NewRegistry will create a registry implementation
@@ -64,7 +64,7 @@ func NewRegistry(ctx context.Context, token, registryName string, regURL string)
)
tc = oauth2.NewClient(ctx, ts)
}
return GithubRegistry{client: github.NewClient(tc), cfg: &cfg.GithubContent, ctx: ctx, name: registryName}, nil
return GithubRegistry{client: github.NewClient(tc), cfg: &cfg.GithubContent, ctx: ctx, centerName: registryName}, nil
case TypeOss:
var tc http.Client
return OssRegistry{
@@ -161,7 +161,8 @@ func (g *GithubRegistry) getRepoFile() ([]RegistryFile, error) {
// OssRegistry is Registry's implementation treat OSS url as resource
type OssRegistry struct {
*http.Client
bucketURL string
bucketURL string
centerName string
}
// GetCap return capability object and raw data specified by cap name
@@ -196,6 +197,22 @@ func (o OssRegistry) GetCap(addonName string) (types.Capability, []byte, error)
// ListCaps list all capabilities of registry
func (o OssRegistry) ListCaps() ([]types.Capability, error) {
rfs, err := o.getRegFiles()
if err != nil {
return []types.Capability{}, errors.Wrap(err, "Get raw files fail")
}
capas := make([]types.Capability, 0)
for _, rf := range rfs {
capa, err := rf.toAddon()
if err != nil {
fmt.Printf("[WARN] Parse file %s fail\n", rf.name)
}
capas = append(capas, capa)
}
return capas, nil
}
func (o OssRegistry) getRegFiles() ([]RegistryFile, error) {
req, _ := http.NewRequestWithContext(
context.Background(),
http.MethodGet,
@@ -204,30 +221,42 @@ func (o OssRegistry) ListCaps() ([]types.Capability, error) {
)
resp, err := o.Client.Do(req)
if err != nil {
return []types.Capability{}, err
return []RegistryFile{}, err
}
data, err := ioutil.ReadAll(resp.Body)
_ = resp.Body.Close()
if err != nil {
return []types.Capability{}, err
return []RegistryFile{}, err
}
list := &ListBucketResult{}
err = xml.Unmarshal(data, list)
if err != nil {
return []types.Capability{}, err
return []RegistryFile{}, err
}
capas := make([]types.Capability, 0)
rfs := make([]RegistryFile, 0)
for _, fileName := range list.File {
addonName := strings.Split(fileName, ".")[0]
capa, _, err := o.GetCap(addonName)
req, _ := http.NewRequestWithContext(
context.Background(),
http.MethodGet,
o.bucketURL+fileName,
nil,
)
resp, err := o.Client.Do(req)
if err != nil {
fmt.Printf("Get %s err: %s\n", fileName, err)
fmt.Printf("[WARN] %s download fail\n", fileName)
continue
}
capas = append(capas, capa)
data, _ := ioutil.ReadAll(resp.Body)
_ = resp.Body.Close()
rf := RegistryFile{
data: data,
name: fileName,
}
rfs = append(rfs, rf)
}
return capas, nil
return rfs, nil
}
// LocalRegistry is Registry's implementation treat local url as resource
@@ -282,7 +311,7 @@ func (item RegistryFile) toAddon() (types.Capability, error) {
if err != nil {
return types.Capability{}, err
}
capability, err := ParseAndSyncCapability(dm, item.data)
capability, err := ParseCapability(dm, item.data)
if err != nil {
return types.Capability{}, err
}