Merge pull request #1174 from wonderflow/cli

align cli to the application object
This commit is contained in:
Jianbo Sun
2021-03-12 16:42:06 +08:00
committed by GitHub
53 changed files with 450 additions and 1449 deletions
+20
View File
@@ -3,6 +3,7 @@ package types
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
@@ -10,6 +11,7 @@ import (
type Args struct {
Config *rest.Config
Schema *runtime.Scheme
Client client.Client
}
// SetConfig insert kubeconfig into Args
@@ -21,3 +23,21 @@ func (a *Args) SetConfig() error {
a.Config = restConf
return nil
}
// GetClient get client if exist
func (a *Args) GetClient() (client.Client, error) {
if a.Config == nil {
if err := a.SetConfig(); err != nil {
return nil, err
}
}
if a.Client != nil {
return a.Client, nil
}
newClient, err := client.New(a.Config, client.Options{Scheme: a.Schema})
if err != nil {
return nil, err
}
a.Client = newClient
return a.Client, nil
}
+3 -1
View File
@@ -43,7 +43,6 @@ type Capability struct {
CueTemplate string `json:"template,omitempty"`
CueTemplateURI string `json:"templateURI,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
DefinitionPath string `json:"definition"`
CrdName string `json:"crdName,omitempty"`
Center string `json:"center,omitempty"`
Status string `json:"status,omitempty"`
@@ -52,6 +51,9 @@ type Capability struct {
// trait only
AppliesTo []string `json:"appliesTo,omitempty"`
// Namespace represents it's a system-level or user-level capability.
Namespace string `json:"namespace,omitempty"`
// Plugin Source
Source *Source `json:"source,omitempty"`
Install *Installation `json:"install,omitempty"`
+1 -1
View File
@@ -136,7 +136,7 @@ var (
ginkgo.It("should list all applications", func() {
output, err := Exec("vela ls")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("SERVICE"))
gomega.Expect(output).To(gomega.ContainSubstring("COMPONENT"))
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
gomega.Expect(output).To(gomega.ContainSubstring(workloadType))
if traitAlias != "" {
@@ -83,7 +83,6 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
appParser := appfile.NewApplicationParser(r.Client, r.dm)
ctx = oamutil.SetNamespaceInCtx(ctx, app.Namespace)
appfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
if err != nil {
applog.Error(err, "[Handle Parse]")
+2 -8
View File
@@ -3,8 +3,6 @@ package cue
import (
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"cuelang.org/go/cue"
@@ -16,13 +14,9 @@ import (
const specValue = "parameter"
// GetParameters get parameter from cue template
func GetParameters(templatePath string) ([]types.Parameter, error) {
func GetParameters(templateStr string) ([]types.Parameter, error) {
r := cue.Runtime{}
b, err := ioutil.ReadFile(filepath.Clean(templatePath))
if err != nil {
return nil, err
}
template, err := r.Compile("", string(b)+BaseTemplate)
template, err := r.Compile("", templateStr+BaseTemplate)
if err != nil {
return nil, err
}
+9 -5
View File
@@ -1,6 +1,7 @@
package cue
import (
"io/ioutil"
"testing"
"cuelang.org/go/cue"
@@ -10,7 +11,8 @@ import (
)
func TestGetParameter(t *testing.T) {
params, err := GetParameters("testdata/workloads/metrics.cue")
data, _ := ioutil.ReadFile("testdata/workloads/metrics.cue")
params, err := GetParameters(string(data))
assert.NoError(t, err)
assert.Equal(t, params, []types.Parameter{
{Name: "format", Required: false, Default: "prometheus", Usage: "format of the metrics, " +
@@ -19,8 +21,8 @@ func TestGetParameter(t *testing.T) {
{Name: "port", Required: false, Default: int64(8080), Type: cue.IntKind},
{Name: "selector", Required: false, Usage: "the label selector for the pods, default is the workload labels", Type: cue.StructKind},
})
params, err = GetParameters("testdata/workloads/deployment.cue")
data, _ = ioutil.ReadFile("testdata/workloads/deployment.cue")
params, err = GetParameters(string(data))
assert.NoError(t, err)
assert.Equal(t, []types.Parameter{
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
@@ -31,7 +33,8 @@ func TestGetParameter(t *testing.T) {
{Name: "cpu", Short: "", Required: false, Usage: "", Default: "", Type: cue.StringKind}},
params)
params, err = GetParameters("testdata/workloads/test-param.cue")
data, _ = ioutil.ReadFile("testdata/workloads/test-param.cue")
params, err = GetParameters(string(data))
assert.NoError(t, err)
assert.Equal(t, []types.Parameter{
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
@@ -41,7 +44,8 @@ func TestGetParameter(t *testing.T) {
{Name: "enable", Default: false, Type: cue.BoolKind},
{Name: "fval", Default: 64.3, Type: cue.FloatKind},
{Name: "nval", Default: float64(0), Required: true, Type: cue.NumberKind}}, params)
params, err = GetParameters("testdata/workloads/empty.cue")
data, _ = ioutil.ReadFile("testdata/workloads/empty.cue")
params, err = GetParameters(string(data))
assert.NoError(t, err)
var exp []types.Parameter
assert.Equal(t, exp, params)
+2 -4
View File
@@ -7,8 +7,6 @@ import (
"os"
"strings"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
@@ -17,9 +15,9 @@ import (
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/repo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
@@ -39,7 +37,7 @@ func Install(ioStreams cmdutil.IOStreams, repoName, repoURL, chartName, version,
if err != nil {
return err
}
kubeClient, err := client.New(args.Config, client.Options{Scheme: args.Schema})
kubeClient, err := args.GetClient()
if err != nil {
return err
}
+3 -1
View File
@@ -18,11 +18,12 @@ type APIServer struct {
server *http.Server
KubeClient client.Client
dm discoverymapper.DiscoveryMapper
c types.Args
}
// New will create APIServer
func New(c types.Args, port, staticPath string) (*APIServer, error) {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return nil, err
}
@@ -33,6 +34,7 @@ func New(c types.Args, port, staticPath string) (*APIServer, error) {
s := &APIServer{
KubeClient: newClient,
dm: dm,
c: c,
}
server := &http.Server{
Addr: port,
+1 -1
View File
@@ -107,7 +107,7 @@ func (s *APIServer) CreateApplication(c *gin.Context) {
IO: ioStream,
Env: env,
}
buildResult, data, err := o.ExportFromAppFile(&body, false)
buildResult, data, err := o.ExportFromAppFile(&body, env.Namespace, false, s.c)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
+3 -2
View File
@@ -63,7 +63,8 @@ func (s *APIServer) DeleteCapabilityCenter(c *gin.Context) {
// RemoveCapabilityFromCluster remove a specific capability from cluster
func (s *APIServer) RemoveCapabilityFromCluster(c *gin.Context) {
capabilityCenterName := c.Param("capabilityName")
msg, err := common.RemoveCapabilityFromCluster(s.KubeClient, capabilityCenterName)
// TODO get namespace from env
msg, err := common.RemoveCapabilityFromCluster("default", s.c, s.KubeClient, capabilityCenterName)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
@@ -74,7 +75,7 @@ func (s *APIServer) RemoveCapabilityFromCluster(c *gin.Context) {
// ListCapabilities lists capabilities of a capability center
func (s *APIServer) ListCapabilities(c *gin.Context) {
capabilityCenterName := c.Param("capabilityName")
capabilityList, err := common.ListCapabilities(capabilityCenterName)
capabilityList, err := common.ListCapabilities("default", s.c, capabilityCenterName)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
+5 -92
View File
@@ -1,22 +1,12 @@
package apiserver
import (
"context"
"os"
"strconv"
"github.com/gin-gonic/gin"
"github.com/spf13/pflag"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/oam-dev/kubevela/apis/types"
env2 "github.com/oam-dev/kubevela/pkg/utils/env"
util2 "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/apiserver/apis"
"github.com/oam-dev/kubevela/references/apiserver/util"
"github.com/oam-dev/kubevela/references/appfile/api"
"github.com/oam-dev/kubevela/references/common"
"github.com/oam-dev/kubevela/references/plugins"
)
// AttachTrait attaches a trait to a component
@@ -30,13 +20,8 @@ func (s *APIServer) AttachTrait(c *gin.Context) {
util.HandleError(c, util.InvalidArgument, "the trait attach request body is invalid")
return
}
ctrl.Log.Info("request parameters body:", "body", body)
msg, err := s.DoAttachTrait(c, body)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
}
util.AssembleResponse(c, msg, nil)
util.AssembleResponse(c, "deprecated, please use appfile to update", nil)
}
// GetTrait gets a trait by name
@@ -46,7 +31,7 @@ func (s *APIServer) GetTrait(c *gin.Context) {
var capability types.Capability
var err error
if capability, err = common.GetTraitDefinition(&workloadType, traitType); err != nil {
if capability, err = common.GetTraitDefinition("default", s.c, &workloadType, traitType); err != nil {
util.HandleError(c, util.StatusInternalServerError, err)
return
}
@@ -58,7 +43,7 @@ func (s *APIServer) ListTrait(c *gin.Context) {
var traitList []types.Capability
var workloadName string
var err error
if traitList, err = common.ListTraitDefinitions(&workloadName); err != nil {
if traitList, err = common.ListTraitDefinitions("default", s.c, &workloadName); err != nil {
util.HandleError(c, util.StatusInternalServerError, err)
return
}
@@ -67,77 +52,5 @@ func (s *APIServer) ListTrait(c *gin.Context) {
// DetachTrait detaches a trait from a component
func (s *APIServer) DetachTrait(c *gin.Context) {
envName := c.Param("envName")
traitType := c.Param("traitName")
componentName := c.Param("compName")
applicationName := c.Param("appName")
var staging = false
var err error
if stagingStr := c.Param("staging"); stagingStr != "" {
if staging, err = strconv.ParseBool(stagingStr); err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
}
}
msg, err := s.DoDetachTrait(c, envName, traitType, componentName, applicationName, staging)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
}
util.AssembleResponse(c, msg, nil)
}
// DoAttachTrait executes attaching trait operation
func (s *APIServer) DoAttachTrait(c context.Context, body apis.TraitBody) (string, error) {
// Prepare
var appObj *api.Application
fs := pflag.NewFlagSet("trait", pflag.ContinueOnError)
for _, f := range body.Flags {
fs.String(f.Name, f.Value, "")
}
var staging = false
var err error
if body.Staging != "" {
staging, err = strconv.ParseBool(body.Staging)
if err != nil {
return "", err
}
}
traitAlias := body.Name
template, err := plugins.GetInstalledCapabilityWithCapName(types.TypeTrait, traitAlias)
if err != nil {
return "", err
}
// Run step
env, err := env2.GetEnvByName(body.EnvName)
if err != nil {
return "", err
}
appObj, err = common.AddOrUpdateTrait(env, body.AppName, body.ComponentName, fs, template)
if err != nil {
return "", err
}
io := util2.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
return common.TraitOperationRun(c, s.KubeClient, env, appObj, staging, io)
}
// DoDetachTrait executes detaching trait operation
func (s *APIServer) DoDetachTrait(c context.Context, envName string, traitType string, componentName string, appName string, staging bool) (string, error) {
var appObj *api.Application
var err error
if appName == "" {
appName = componentName
}
if appObj, err = common.PrepareDetachTrait(envName, traitType, componentName, appName); err != nil {
return "", err
}
// Run
env, err := env2.GetEnvByName(envName)
if err != nil {
return "", err
}
io := util2.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
return common.TraitOperationRun(c, s.KubeClient, env, appObj, staging, io)
util.AssembleResponse(c, "deprecated, please use appfile to update", nil)
}
+1 -1
View File
@@ -29,7 +29,7 @@ func (s *APIServer) GetWorkload(c *gin.Context) {
// ListWorkload lists all workloads in the cluster
func (s *APIServer) ListWorkload(c *gin.Context) {
var workloadDefinitionList []apis.WorkloadMeta
workloads, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
workloads, err := plugins.LoadInstalledCapabilityWithType("default", s.c, types.TypeWorkload)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err)
return
+4 -2
View File
@@ -19,6 +19,7 @@ import (
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
util2 "github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
)
@@ -36,8 +37,9 @@ func ApplyTerraform(app *v1alpha2.Application, k8sClient client.Client, ioStream
var nativeVelaComponents []v1alpha2.ApplicationComponent
// parse template
appParser := appfile.NewApplicationParser(k8sClient, dm)
// TODO(wangyike) this context only for compiling success, lately mabey surport setting sysNs and appNs in api-server or cli
appFile, err := appParser.GenerateAppFile(context.TODO(), app.Name, app)
ctx := util2.SetNamespaceInCtx(context.Background(), namespace)
appFile, err := appParser.GenerateAppFile(ctx, app.Name, app)
if err != nil {
return nil, fmt.Errorf("failed to parse appfile: %w", err)
}
-4
View File
@@ -2,14 +2,10 @@ package api
// Driver is mutli implement interface
type Driver interface {
// List applications
List(envName string) ([]*Application, error)
// Save application
Save(app *Application, envName string) error
// Delete application
Delete(envName, appName string) error
// Get application
Get(envName, appName string) (*Application, error)
// Name of storage driver
Name() string
}
+27 -72
View File
@@ -2,6 +2,7 @@ package appfile
import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
@@ -16,8 +17,8 @@ import (
)
// NewEmptyApplication new empty application, only set tm
func NewEmptyApplication() (*api.Application, error) {
tm, err := template.Load()
func NewEmptyApplication(namespace string, c types.Args) (*api.Application, error) {
tm, err := template.Load(namespace, c)
if err != nil {
return nil, err
}
@@ -52,19 +53,17 @@ func Validate(app *api.Application) error {
return nil
}
// IsNotFound is application not found error
func IsNotFound(appName string, err error) bool {
return err != nil && err.Error() == fmt.Sprintf(`application "%s" not found`, appName)
}
// LoadApplication will load application with env and name from default vela home dir.
func LoadApplication(envName, appName string) (*api.Application, error) {
app, err := GetStorage().Get(envName, appName)
// LoadApplication will load application from cluster.
func LoadApplication(namespace, appName string, c types.Args) (*v1alpha2.Application, error) {
newClient, err := c.GetClient()
if err != nil {
return nil, err
}
err = Validate(app)
return app, err
app := &v1alpha2.Application{}
if err := newClient.Get(context.TODO(), client.ObjectKey{Namespace: namespace, Name: appName}, app); err != nil {
return nil, err
}
return app, nil
}
// Delete will delete an app along with it's appfile.
@@ -72,50 +71,16 @@ func Delete(envName, appName string) error {
return GetStorage().Delete(envName, appName)
}
// List will list all apps
func List(envName string) ([]*api.Application, error) {
respApps, err := GetStorage().List(envName)
if err != nil {
return nil, err
}
var apps []*api.Application
for _, resp := range respApps {
app := NewApplication(resp.AppFile, resp.Tm)
err := Validate(app)
if err != nil {
return nil, err
}
apps = append(apps, app)
}
return apps, nil
}
// MatchAppByComp will get application with componentName without AppName.
func MatchAppByComp(envName, compName string) (*api.Application, error) {
apps, err := List(envName)
if err != nil {
return nil, err
}
for _, subapp := range apps {
for _, v := range GetComponents(subapp) {
if v == compName {
return subapp, nil
}
}
}
return nil, fmt.Errorf("no app found contains %s in env %s", compName, envName)
}
// Save will save appfile into default dir.
func Save(app *api.Application, envName string) error {
return GetStorage().Save(app, envName)
}
// GetComponents will get oam components from Appfile.
func GetComponents(app *api.Application) []string {
func GetComponents(app *v1alpha2.Application) []string {
var components []string
for name := range app.Services {
components = append(components, name)
for _, cmp := range app.Spec.Components {
components = append(components, cmp.Name)
}
sort.Strings(components)
return components
@@ -130,6 +95,18 @@ func GetServiceConfig(app *api.Application, componentName string) (string, map[s
return svc.GetType(), svc.GetApplicationConfig()
}
// GetApplicationSettings will get service type and it's configuration
func GetApplicationSettings(app *v1alpha2.Application, componentName string) (string, map[string]interface{}) {
for _, comp := range app.Spec.Components {
if comp.Name == componentName {
data := map[string]interface{}{}
_ = json.Unmarshal(comp.Settings.Raw, &data)
return comp.WorkloadType, data
}
}
return "", make(map[string]interface{})
}
// GetWorkload will get workload type and it's configuration
func GetWorkload(app *api.Application, componentName string) (string, map[string]interface{}) {
svcType, config := GetServiceConfig(app, componentName)
@@ -163,33 +140,11 @@ func GetTraits(app *api.Application, componentName string) (map[string]map[strin
return traitsData, nil
}
// GetTraitsByType will get trait configuration with specified component and trait type, we assume one type of trait can only attach to a component once.
func GetTraitsByType(app *api.Application, componentName, traitType string) (map[string]interface{}, error) {
service, ok := app.Services[componentName]
if !ok {
return nil, fmt.Errorf("service name (%s) doesn't exist", componentName)
}
t, ok := service[traitType]
if !ok {
return make(map[string]interface{}), nil
}
return t.(map[string]interface{}), nil
}
// GetAppConfig will get AppConfig from K8s cluster.
func GetAppConfig(ctx context.Context, c client.Client, app *api.Application, env *types.EnvMeta) (*v1alpha2.ApplicationConfiguration, error) {
func GetAppConfig(ctx context.Context, c client.Client, app *v1alpha2.Application, env *types.EnvMeta) (*v1alpha2.ApplicationConfiguration, error) {
appConfig := &v1alpha2.ApplicationConfiguration{}
if err := c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, appConfig); err != nil {
return nil, err
}
return appConfig, nil
}
// GetApplication will get Application from K8s cluster.
func GetApplication(ctx context.Context, c client.Client, app *api.Application, env *types.EnvMeta) (*v1alpha2.Application, error) {
appl := &v1alpha2.Application{}
if err := c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, appl); err != nil {
return nil, err
}
return appl, nil
}
-17
View File
@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"testing"
"time"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
@@ -128,7 +127,6 @@ services:
continue
}
assert.Equal(t, c.ExpName, app.Name, caseName)
assert.Equal(t, c.ExpComponents, GetComponents(app), caseName)
workloadType, workload := GetWorkload(app, c.WantWorkload)
assert.Equal(t, c.ExpWorkload, workload, caseName)
assert.Equal(t, c.ExpWorkloadType, workloadType, caseName)
@@ -137,18 +135,3 @@ services:
assert.Equal(t, c.ExpTraits, traits, caseName)
}
}
func TestLoadNotExistsApplication(t *testing.T) {
caseName := "load not exists application"
now := time.Now().Unix()
appName := fmt.Sprintf("test-app-%d", now)
app, err := LoadApplication(types.DefaultEnvName, appName)
assert.Nil(t, app, caseName)
assert.Error(t, err, caseName)
errString := fmt.Sprintf(`application "%s" not found`, appName)
assert.EqualError(t, err, errString, caseName)
}
-66
View File
@@ -5,7 +5,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/ghodss/yaml"
@@ -13,7 +12,6 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/env"
"github.com/oam-dev/kubevela/pkg/utils/system"
"github.com/oam-dev/kubevela/references/appfile/api"
"github.com/oam-dev/kubevela/references/appfile/template"
)
// LocalDriverName is local storage driver name
@@ -34,33 +32,6 @@ func (l *Local) Name() string {
return LocalDriverName
}
// List applications from local storage
func (l *Local) List(envName string) ([]*api.Application, error) {
appDir, err := getApplicationDir(envName)
if err != nil {
return nil, err
}
files, err := ioutil.ReadDir(appDir)
if err != nil {
return nil, fmt.Errorf("list apps from %s err %w", appDir, err)
}
var apps []*api.Application
for _, f := range files {
if f.IsDir() {
continue
}
if !strings.HasSuffix(f.Name(), ".yaml") {
continue
}
app, err := loadFromFile(filepath.Join(appDir, f.Name()))
if err != nil {
return nil, fmt.Errorf("load application err %w", err)
}
apps = append(apps, app)
}
return apps, nil
}
// Save application from local storage
func (l *Local) Save(app *api.Application, envName string) error {
appDir, err := getApplicationDir(envName)
@@ -88,24 +59,6 @@ func (l *Local) Delete(envName, appName string) error {
return os.Remove(filepath.Join(appDir, appName+".yaml"))
}
// Get application from local storage
func (l *Local) Get(envName, appName string) (*api.Application, error) {
appDir, err := getApplicationDir(envName)
if err != nil {
return nil, err
}
app, err := loadFromFile(filepath.Join(appDir, appName+".yaml"))
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf(`application "%s" not found`, appName)
}
return nil, err
}
return app, nil
}
func getApplicationDir(envName string) (string, error) {
appDir := filepath.Join(env.GetEnvDirByName(envName), "applications")
_, err := system.CreateIfNotExist(appDir)
@@ -114,22 +67,3 @@ func getApplicationDir(envName string) (string, error) {
}
return appDir, err
}
// LoadFromFile will load application from file
func loadFromFile(fileName string) (*api.Application, error) {
tm, err := template.Load()
if err != nil {
return nil, err
}
_, err = os.Stat(fileName)
if err != nil {
return nil, err
}
f, err := api.LoadFromFile(fileName)
if err != nil {
return nil, err
}
app := &api.Application{AppFile: f, Tm: tm}
return app, nil
}
-86
View File
@@ -10,18 +10,15 @@ import (
"github.com/ghodss/yaml"
"github.com/oam-dev/kubevela/references/appfile/api"
"github.com/oam-dev/kubevela/references/appfile/template"
)
var dir string
var tm template.Manager
var afile *api.AppFile
var appName = "testsvc"
var envName = "default"
func init() {
dir, _ = getApplicationDir(envName)
tm, _ = template.Load()
afile = api.NewAppFile()
afile.Name = appName
svcs := make(map[string]api.Service)
@@ -37,34 +34,6 @@ func init() {
_ = ioutil.WriteFile(filepath.Join(dir, appName+".yaml"), out, 0644)
}
func TestLocal_Get(t *testing.T) {
type args struct {
envName string
appName string
}
tests := []struct {
name string
args args
want *api.Application
wantErr bool
}{
{"TestLocal_Get1", args{envName: envName, appName: appName}, &api.Application{AppFile: afile, Tm: tm}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &Local{}
got, err := l.Get(tt.args.envName, tt.args.appName)
if (err != nil) != tt.wantErr {
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Get() got = %v, want %v", got, tt.want)
}
})
}
}
func TestLocal_Delete(t *testing.T) {
type args struct {
envName string
@@ -110,35 +79,6 @@ func TestLocal_Save(t *testing.T) {
}
}
func TestLocal_List(t *testing.T) {
type args struct {
envName string
}
want := make([]*api.Application, 0)
want = append(want, &api.Application{AppFile: afile, Tm: tm})
tests := []struct {
name string
args args
want []*api.Application
wantErr bool
}{
{"TestLocal_List1", args{envName}, want, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &Local{}
got, err := l.List(tt.args.envName)
if (err != nil) != tt.wantErr {
t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) == 0 {
t.Errorf("List() got = %v, want %v", got, tt.want)
}
})
}
}
func TestLocal_Name(t *testing.T) {
tests := []struct {
name string
@@ -197,29 +137,3 @@ func Test_getApplicationDir(t *testing.T) {
})
}
}
func Test_loadFromFile(t *testing.T) {
type args struct {
fileName string
}
tests := []struct {
name string
args args
want *api.Application
wantErr bool
}{
{"testRespApp", args{fileName: filepath.Join(dir, appName+".yaml")}, &api.Application{AppFile: afile, Tm: tm}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loadFromFile(tt.args.fileName)
if (err != nil) != tt.wantErr {
t.Errorf("loadFromFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got.Name != tt.want.Name {
t.Errorf("loadFromFile() got = %v, want %v", got, tt.want)
}
})
}
}
+39 -19
View File
@@ -1,8 +1,13 @@
package appfile
import (
"encoding/json"
"errors"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/references/appfile/api"
)
@@ -25,30 +30,39 @@ func SetWorkload(app *api.Application, componentName, workloadType string, workl
}
// SetTrait will set user trait for Appfile
func SetTrait(app *api.Application, componentName, traitType string, traitData map[string]interface{}) error {
func SetTrait(app *v1alpha2.Application, componentName, traitType string, traitData map[string]interface{}) error {
if app == nil {
return errors.New("app is nil pointer")
}
if traitData == nil {
traitData = make(map[string]interface{})
}
s, ok := app.Services[componentName]
if !ok {
s = api.Service{}
data, err := json.Marshal(traitData)
if err != nil {
return fmt.Errorf("fail to marshal trait data %w", err)
}
t, ok := s[traitType]
if !ok {
t = make(map[string]interface{})
var foundComp bool
for idx, comp := range app.Spec.Components {
if comp.Name != componentName {
continue
}
foundComp = true
var added bool
for j, tr := range app.Spec.Components[idx].Traits {
if tr.Name != traitType {
continue
}
added = true
app.Spec.Components[idx].Traits[j].Properties.Raw = data
}
if !added {
app.Spec.Components[idx].Traits = append(app.Spec.Components[idx].Traits, v1alpha2.ApplicationTrait{Name: traitType, Properties: runtime.RawExtension{Raw: data}})
}
}
tm := t.(map[string]interface{})
for k, v := range traitData {
tm[k] = v
if !foundComp {
return errors.New(componentName + " not found in app " + app.Name)
}
s[traitType] = t
app.Services[componentName] = s
return Validate(app)
return nil
}
// RemoveTrait will remove a trait from Appfile
@@ -65,12 +79,18 @@ func RemoveTrait(app *api.Application, componentName, traitType string) error {
return nil
}
// RemoveComponent will remove component from Appfile
func RemoveComponent(app *api.Application, componentName string) error {
// RemoveComponent will remove component from Application
func RemoveComponent(app *v1alpha2.Application, componentName string) error {
if app == nil {
return errors.New("app is nil pointer")
}
delete(app.Services, componentName)
var newComps []v1alpha2.ApplicationComponent
for _, comp := range app.Spec.Components {
if comp.Name == componentName {
continue
}
newComps = append(newComps, comp)
}
app.Spec.Components = newComps
return nil
}
-10
View File
@@ -33,11 +33,6 @@ func GetStorage() *Storage {
return store
}
// List applications storage common implement
func (s *Storage) List(envName string) ([]*api.Application, error) {
return s.Driver.List(envName)
}
// Save application storage common implement
func (s *Storage) Save(app *api.Application, envName string) error {
return s.Driver.Save(app, envName)
@@ -47,8 +42,3 @@ func (s *Storage) Save(app *api.Application, envName string) error {
func (s *Storage) Delete(envName, appName string) error {
return s.Driver.Delete(envName, appName)
}
// Get application storage common implement
func (s *Storage) Get(envName, appName string) (*api.Application, error) {
return s.Driver.Get(envName, appName)
}
+2 -2
View File
@@ -12,8 +12,8 @@ type Manager interface {
}
// Load will load all installed capabilities and create a manager
func Load() (Manager, error) {
caps, err := plugins.LoadAllInstalledCapability()
func Load(namespace string, c types.Args) (Manager, error) {
caps, err := plugins.LoadAllInstalledCapability(namespace, c)
if err != nil {
return nil, err
}
+14 -7
View File
@@ -6,7 +6,6 @@ import (
"strings"
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
@@ -29,7 +28,7 @@ func CapabilityCommandGroup(c types.Args, ioStream cmdutil.IOStreams) *cobra.Com
}
cmd.AddCommand(
NewCenterCommand(ioStream),
NewCapListCommand(ioStream),
NewCapListCommand(c, ioStream),
NewCapInstallCommand(c, ioStream),
NewCapUninstallCommand(c, ioStream),
)
@@ -94,7 +93,7 @@ func NewCapInstallCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comm
if argsLength < 1 {
return errors.New("you must specify <center>/<name> for capability you want to install")
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
@@ -126,7 +125,7 @@ func NewCapUninstallCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Co
if len(args) < 1 {
return errors.New("you must specify <name> for capability you want to uninstall")
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
@@ -138,7 +137,11 @@ func NewCapUninstallCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Co
}
name = l[1]
}
return common.RemoveCapability(newClient, name, ioStreams)
env, err := GetEnv(cmd)
if err != nil {
return err
}
return common.RemoveCapability(env.Namespace, c, newClient, name, ioStreams)
},
}
cmd.PersistentFlags().StringP("token", "t", "", "Github Repo token")
@@ -168,7 +171,7 @@ func NewCapCenterSyncCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
}
// NewCapListCommand List capabilities from cap-center
func NewCapListCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
func NewCapListCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "ls [cap-center]",
Short: "List capabilities from cap-center",
@@ -179,7 +182,11 @@ func NewCapListCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
if len(args) > 0 {
repoName = args[0]
}
capabilityList, err := common.ListCapabilities(repoName)
env, err := GetEnv(cmd)
if err != nil {
return err
}
capabilityList, err := common.ListCapabilities(env.Namespace, c, repoName)
if err != nil {
return err
}
+3 -2
View File
@@ -18,6 +18,7 @@ package cli
// constants used in `svc` command
const (
App = "app"
Service = "svc"
App = "app"
Service = "svc"
Namespace = "namespace"
)
+1 -1
View File
@@ -45,7 +45,7 @@ func NewDashboardCommand(c types.Args, ioStreams cmdutil.IOStreams, frontendSour
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
+4 -3
View File
@@ -5,7 +5,6 @@ import (
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -30,11 +29,13 @@ func NewDeleteCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
cmd.SetOut(ioStreams.Out)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
o := &common.DeleteOptions{}
o := &common.DeleteOptions{
C: c,
}
o.Client = newClient
o.Env, err = GetEnv(cmd)
if err != nil {
+8 -3
View File
@@ -8,7 +8,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
@@ -37,7 +36,7 @@ func NewDryRunCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
@@ -54,7 +53,13 @@ func NewDryRunCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
parser := appfile.NewApplicationParser(newClient, dm)
ctx := oamutil.SetNamespaceInCtx(context.Background(), app.Namespace)
velaEnv, err := GetEnv(cmd)
if err != nil {
return err
}
ctx := oamutil.SetNamespaceInCtx(context.Background(), velaEnv.Namespace)
appFile, err := parser.GenerateAppFile(ctx, app.Name, app)
if err != nil {
return errors.WithMessage(err, "generate appFile")
+1 -5
View File
@@ -64,15 +64,11 @@ func NewEnvInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
if err := RefreshDefinitions(ctx, c, ioStreams, true, true); err != nil {
return err
}
return CreateOrUpdateEnv(ctx, newClient, &envArgs, args, ioStreams)
},
Annotations: map[string]string{
+7 -5
View File
@@ -14,12 +14,12 @@ import (
cmdexec "k8s.io/kubectl/pkg/cmd/exec"
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/appfile/api"
)
const (
@@ -40,7 +40,7 @@ type VelaExecOptions struct {
context.Context
VelaC types.Args
Env *types.EnvMeta
App *api.Application
App *v1alpha2.Application
f k8scmdutil.Factory
kcExecOptions *cmdexec.ExecOptions
@@ -122,7 +122,7 @@ func (o *VelaExecOptions) Init(ctx context.Context, c *cobra.Command, argsIn []s
return err
}
o.Env = env
app, err := appfile.LoadApplication(env.Name, o.Args[0])
app, err := appfile.LoadApplication(env.Namespace, o.Args[0], o.VelaC)
if err != nil {
return err
}
@@ -167,8 +167,10 @@ func (o *VelaExecOptions) getComponentName() (string, error) {
svcName := o.ServiceName
if svcName != "" {
if _, exist := o.App.Services[svcName]; exist {
return svcName, nil
for _, cc := range o.App.Spec.Components {
if cc.Name == svcName {
return svcName, nil
}
}
o.Cmd.Printf("The service name '%s' is not valid\n", svcName)
}
-101
View File
@@ -1,101 +0,0 @@
package cli
import (
"context"
"fmt"
"os"
"testing"
"github.com/oam-dev/kubevela/references/appfile/api"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/kubectl/pkg/cmd/exec"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
func TestExecCommand(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{
Config: tf.ClientConfigVal,
}
cmd := NewExecCommand(fakeC, io)
cmd.PersistentFlags().StringP("env", "e", "", "")
o := &VelaExecOptions{
kcExecOptions: &exec.ExecOptions{},
f: tf,
ClientSet: fake.NewSimpleClientset(&corev1.PodList{
Items: []corev1.Pod{
{
ObjectMeta: v1.ObjectMeta{
Name: "fakePod",
Namespace: "default",
Labels: map[string]string{
oam.LabelAppName: "fakeApp",
oam.LabelAppComponent: "fakeComp",
}},
},
},
}),
}
err := o.Init(context.Background(), cmd, []string{"fakeApp"})
errString := fmt.Sprintf(`application "%s" not found`, "fakeApp")
assert.EqualError(t, err, errString)
fakeApp := &api.Application{
AppFile: &api.AppFile{
Name: "fakeApp",
Services: map[string]api.Service{
"fakeComp": map[string]interface{}{},
},
},
}
o.App = fakeApp
cf := genericclioptions.NewConfigFlags(true)
cf.Namespace = &o.Env.Namespace
o.f = k8scmdutil.NewFactory(k8scmdutil.NewMatchVersionFlags(cf))
err = o.Complete()
assert.NoError(t, err)
}
func TestExecCommandPersistentPreRunE(t *testing.T) {
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{}
cmd := NewExecCommand(fakeC, io)
assert.Nil(t, cmd.PersistentPreRunE(new(cobra.Command), []string{}))
}
func TestGetComponent(t *testing.T) {
o := &VelaExecOptions{
App: &api.Application{
AppFile: &api.AppFile{
Name: "fakeApp",
Services: map[string]api.Service{
"fakeComp1": map[string]interface{}{},
"fakeComp2": map[string]interface{}{},
},
},
},
}
o.ServiceName = "fakeComp1"
svcName, err := o.getComponentName()
assert.NoError(t, err)
assert.Equal(t, o.ServiceName, svcName)
o.ServiceName = "fakeComp2"
svcName, err = o.getComponentName()
assert.NoError(t, err)
assert.Equal(t, o.ServiceName, svcName)
}
+6 -2
View File
@@ -19,15 +19,19 @@ func NewExportCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
types.TagCommandType: types.TypeStart,
},
RunE: func(cmd *cobra.Command, args []string) error {
velaEnv, err := GetEnv(cmd)
if err != nil {
return err
}
o := &common.AppfileOptions{
IO: ioStream,
Env: &types.EnvMeta{},
Env: velaEnv,
}
filePath, err := cmd.Flags().GetString(appFilePath)
if err != nil {
return err
}
_, data, err := o.Export(filePath, true)
_, data, err := o.Export(filePath, velaEnv.Namespace, true, c)
if err != nil {
return err
}
+7 -42
View File
@@ -28,6 +28,7 @@ type appInitOptions struct {
client client.Client
cmdutil.IOStreams
Env *types.EnvMeta
c types.Args
app *api.Application
appName string
@@ -38,7 +39,7 @@ type appInitOptions struct {
// NewInitCommand creates `init` command
func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
o := &appInitOptions{IOStreams: ioStreams}
o := &appInitOptions{IOStreams: ioStreams, c: c}
cmd := &cobra.Command{
Use: "init",
DisableFlagsInUseLine: true,
@@ -49,7 +50,7 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
@@ -69,9 +70,6 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
if err = o.Workload(); err != nil {
return err
}
if err = o.Traits(); err != nil {
return err
}
if err := appfile.Validate(o.app); err != nil {
return err
@@ -96,14 +94,14 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
if err != nil {
return err
}
deployStatus, err := printTrackingDeployStatus(ctx, o.client, o.IOStreams, o.workloadName, o.appName, o.Env)
deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Env)
if err != nil {
return err
}
if deployStatus != compStatusDeployed {
return nil
}
return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Env, cmd)
return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Env, cmd, c)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
@@ -184,7 +182,7 @@ func formatAndGetUsage(p *types.Parameter) string {
// Workload asks user to choose workload type from installed workloads
func (o *appInitOptions) Workload() error {
workloads, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
workloads, err := plugins.LoadInstalledCapabilityWithType(o.Env.Namespace, o.c, types.TypeWorkload)
if err != nil {
return err
}
@@ -306,7 +304,7 @@ func (o *appInitOptions) Workload() error {
// other type not supported
}
}
o.app, err = common.BaseComplete(o.Env.Name, o.workloadName, o.appName, fs, o.workloadType)
o.app, err = common.BaseComplete(o.Env, o.c, o.workloadName, o.appName, fs, o.workloadType)
return err
}
@@ -319,36 +317,3 @@ func GetCapabilityByName(name string, workloads []types.Capability) (types.Capab
}
return types.Capability{}, fmt.Errorf("%s not found", name)
}
// Traits attaches specific trait to service
func (o *appInitOptions) Traits() error {
traits, err := plugins.LoadInstalledCapabilityWithType(types.TypeTrait)
if err != nil {
return err
}
switch o.workloadType {
case "webservice":
// TODO(wonderflow) this should get from workload definition to know which trait should be suggestions
var suggestTraits = []string{}
if o.Env.Domain != "" {
suggestTraits = append(suggestTraits, "route")
}
for _, tr := range suggestTraits {
trait, err := GetCapabilityByName(tr, traits)
if err != nil {
continue
}
tflags := pflag.NewFlagSet("trait", pflag.ContinueOnError)
for _, pa := range trait.Parameters {
types.SetFlagBy(tflags, pa)
}
// TODO(wonderflow): give a way to add parameter for trait
o.app, err = common.AddOrUpdateTrait(o.Env, o.appName, o.workloadName, tflags, trait)
if err != nil {
return err
}
}
default:
}
return nil
}
+3 -3
View File
@@ -15,11 +15,11 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/appfile/api"
)
// NewLogsCommand creates `logs` command to tail logs of application
@@ -45,7 +45,7 @@ func NewLogsCommand(c types.Args, ioStreams util.IOStreams) *cobra.Command {
if err != nil {
return err
}
app, err := appfile.LoadApplication(env.Name, args[0])
app, err := appfile.LoadApplication(env.Namespace, args[0], c)
if err != nil {
return err
}
@@ -69,7 +69,7 @@ type Args struct {
Output string
Env *types.EnvMeta
C types.Args
App *api.Application
App *v1alpha2.Application
}
// Run refer to the implementation at https://github.com/oam-dev/stern/blob/master/stern/main.go
+45 -81
View File
@@ -5,14 +5,12 @@ import (
"strings"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/apiserver/apis"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/common"
)
// NewListCommand creates `ls` command and its nested children command
@@ -22,8 +20,8 @@ func NewListCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
Use: "ls",
Aliases: []string{"list"},
DisableFlagsInUseLine: true,
Short: "List services",
Long: "List services of all applications",
Short: "List applications",
Long: "List all applications in cluster",
Example: `vela ls`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return c.SetConfig()
@@ -33,98 +31,64 @@ func NewListCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
if err != nil {
return err
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
appName, err := cmd.Flags().GetString(App)
namespace, err := cmd.Flags().GetString(Namespace)
if err != nil {
return err
}
printComponentList(ctx, newClient, appName, env, ioStreams)
return nil
if namespace == "" {
namespace = env.Namespace
}
return printApplicationList(ctx, newClient, namespace, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
}
cmd.PersistentFlags().StringP(App, "", "", "specify the name of application")
cmd.PersistentFlags().StringP(Namespace, "n", "", "specify the namespace the application want to list, default is the current env namespace")
return cmd
}
func printComponentList(ctx context.Context, c client.Reader, appName string, env *types.EnvMeta,
ioStreams cmdutil.IOStreams) {
deployedComponentList, err := common.ListComponents(ctx, c, common.Option{
AppName: appName,
Namespace: env.Namespace,
})
if err != nil {
ioStreams.Infof("listing services: %s\n", err)
return
func printApplicationList(ctx context.Context, c client.Reader, namespace string, ioStreams cmdutil.IOStreams) error {
table := newUITable()
table.AddRow("APP", "COMPONENT", "TYPE", "TRAITS", "PHASE", "HEALTHY", "STATUS", "CREATED-TIME")
applist := v1alpha2.ApplicationList{}
if err := c.List(ctx, &applist, client.InNamespace(namespace)); err != nil {
if apierrors.IsNotFound(err) {
ioStreams.Info(table.String())
return nil
}
return err
}
fetcher := func(name string) (*v1alpha2.Application, error) {
var app = new(v1alpha2.Application)
err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: env.Namespace}, app)
return app, err
}
all := mergeStagingComponents(deployedComponentList, env, ioStreams, fetcher)
table := newUITable()
table.AddRow("SERVICE", "APP", "TYPE", "TRAITS", "STATUS", "CREATED-TIME")
for _, a := range all {
traitAlias := strings.Join(a.TraitNames, ",")
table.AddRow(a.Name, a.App, a.WorkloadName, traitAlias, a.Status, a.CreatedTime)
for _, a := range applist.Items {
for idx, cmp := range a.Spec.Components {
var appName = a.Name
if idx > 0 {
appName = "├─"
if idx == len(a.Spec.Components)-1 {
appName = "└─"
}
}
var healthy, status string
if len(a.Status.Services) > idx {
if a.Status.Services[idx].Healthy {
healthy = "healthy"
} else {
healthy = "unhealthy"
}
status = a.Status.Services[idx].Message
}
var traits []string
for _, tr := range cmp.Traits {
traits = append(traits, tr.Name)
}
table.AddRow(appName, cmp.Name, cmp.WorkloadType, strings.Join(traits, ","), a.Status.Phase, healthy, status, a.CreationTimestamp)
}
}
ioStreams.Info(table.String())
}
func mergeStagingComponents(deployed []apis.ComponentMeta, env *types.EnvMeta, ioStreams cmdutil.IOStreams, fetcher func(name string) (*v1alpha2.Application, error)) []apis.ComponentMeta {
localApps, err := appfile.List(env.Name)
if err != nil {
ioStreams.Error("list application err", err)
return deployed
}
var all []apis.ComponentMeta
for _, app := range localApps {
appl, err := fetcher(app.Name)
if err != nil {
ioStreams.Errorf("fetch app %s err %v\n", app.Name, err)
continue
}
for _, c := range appl.Spec.Components {
traits := []string{}
for _, t := range c.Traits {
traits = append(traits, t.Name)
}
compMeta, exist := GetCompMeta(deployed, app.Name, c.Name)
if !exist {
all = append(all, apis.ComponentMeta{
Name: c.Name,
App: app.Name,
WorkloadName: c.WorkloadType,
TraitNames: traits,
Status: types.StatusStaging,
CreatedTime: app.CreateTime.String(),
})
continue
}
compMeta.TraitNames = traits
compMeta.WorkloadName = c.WorkloadType
if appl.Status.Phase != v1alpha2.ApplicationRunning {
compMeta.Status = types.StatusStaging
}
all = append(all, compMeta)
}
}
return all
}
// GetCompMeta gets meta of a component
func GetCompMeta(deployed []apis.ComponentMeta, appName, compName string) (apis.ComponentMeta, bool) {
for _, v := range deployed {
if v.Name == compName && v.App == appName {
return v, true
}
}
return apis.ComponentMeta{}, false
return nil
}
+4 -5
View File
@@ -28,7 +28,6 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/appfile/api"
)
// VelaPortForwardOptions for vela port-forward
@@ -40,7 +39,7 @@ type VelaPortForwardOptions struct {
context.Context
VelaC types.Args
Env *types.EnvMeta
App *api.Application
App *v1alpha2.Application
f k8scmdutil.Factory
kcPortForwardOptions *cmdpf.PortForwardOptions
@@ -74,7 +73,7 @@ func NewPortForwardCommand(c types.Args, ioStreams util.IOStreams) *cobra.Comman
ioStreams.Error("Please specify application name.")
return nil
}
newClient, err := client.New(o.VelaC.Config, client.Options{Scheme: o.VelaC.Schema})
newClient, err := o.VelaC.GetClient()
if err != nil {
return err
}
@@ -114,7 +113,7 @@ func (o *VelaPortForwardOptions) Init(ctx context.Context, cmd *cobra.Command, a
}
o.Env = env
app, err := appfile.LoadApplication(env.Name, o.Args[0])
app, err := appfile.LoadApplication(env.Namespace, o.Args[0], o.VelaC)
if err != nil {
return err
}
@@ -191,7 +190,7 @@ func (o *VelaPortForwardOptions) Complete() error {
}
if len(o.Args) < 2 {
var found bool
_, configs := appfile.GetServiceConfig(o.App, svcName)
_, configs := appfile.GetApplicationSettings(o.App, svcName)
for k, v := range configs {
if k == "port" {
var val string
-62
View File
@@ -1,62 +0,0 @@
package cli
import (
"context"
"fmt"
"os"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/kubectl/pkg/cmd/portforward"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
func TestPortForwardCommand(t *testing.T) {
fakePod := corev1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: "fakePod",
Namespace: "default",
ResourceVersion: "10",
Labels: map[string]string{
oam.LabelAppComponent: "fakeComp",
}},
}
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{
Config: tf.ClientConfigVal,
}
cmd := NewPortForwardCommand(fakeC, io)
cmd.PersistentFlags().StringP("env", "e", "", "")
fakeClientSet := k8sfake.NewSimpleClientset(&corev1.PodList{
Items: []corev1.Pod{fakePod},
})
o := &VelaPortForwardOptions{
ioStreams: io,
kcPortForwardOptions: &portforward.PortForwardOptions{},
f: tf,
ClientSet: fakeClientSet,
VelaC: fakeC,
}
err := o.Init(context.Background(), cmd, []string{"fakeApp", "8081:8080"})
errString := fmt.Sprintf(`application "%s" not found`, "fakeApp")
assert.EqualError(t, err, errString)
}
func TestNewPortForwardCommandPersistentPreRunE(t *testing.T) {
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{}
cmd := NewPortForwardCommand(fakeC, io)
assert.Nil(t, cmd.PersistentPreRunE(new(cobra.Command), []string{}))
}
-285
View File
@@ -1,285 +0,0 @@
package cli
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"time"
"github.com/fatih/color"
"github.com/gosuri/uitable"
"github.com/mitchellh/hashstructure/v2"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/system"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/plugins"
)
type refreshStatus string
const (
added refreshStatus = "Added"
updated refreshStatus = "Updated"
unchanged refreshStatus = "Unchanged"
deleted refreshStatus = "Deleted"
)
const (
refreshInterval = 5 * time.Minute
)
// RefreshDefinitions will sync local capabilities with cluster installed ones
func RefreshDefinitions(ctx context.Context, c types.Args, ioStreams cmdutil.IOStreams, silentOutput, enforceRefresh bool) error {
dir, _ := system.GetCapabilityDir()
oldCaps, err := plugins.LoadAllInstalledCapability()
if err != nil {
return err
}
useCached, err := useCacheInsteadRefresh(dir, refreshInterval)
if err != nil {
return err
}
if !enforceRefresh && useCached {
// use local capabilities instead of fetching from cluster
printRefreshReport(nil, oldCaps, ioStreams, silentOutput, true)
return nil
}
syncedTemplates, warnings, err := plugins.SyncDefinitionsToLocal(ctx, c, dir)
if err != nil {
return err
}
for _, w := range warnings {
ioStreams.Infof(w)
}
plugins.RemoveLegacyTemps(syncedTemplates, dir)
printRefreshReport(syncedTemplates, oldCaps, ioStreams, silentOutput, false)
return nil
}
// silent indicates whether output existing caps if no change occurs. If false, output all existing caps.
func printRefreshReport(newCaps, oldCaps []types.Capability, io cmdutil.IOStreams, silent, useCached bool) {
var report map[refreshStatus][]types.Capability
if useCached {
report = map[refreshStatus][]types.Capability{
added: make([]types.Capability, 0),
updated: make([]types.Capability, 0),
unchanged: oldCaps,
deleted: make([]types.Capability, 0),
}
} else {
report = refreshResultReport(newCaps, oldCaps)
}
table := newUITable()
table.MaxColWidth = 80
table.AddRow("TYPE", "CATEGORY", "DESCRIPTION")
if len(report[added]) == 0 && len(report[updated]) == 0 && len(report[deleted]) == 0 {
// no change occurs, just show all existing caps
// always show workload at first
for _, cap := range report[unchanged] {
if cap.Type == types.TypeWorkload {
table.AddRow(cap.Name, cap.Type, cap.Description)
}
}
for _, cap := range report[unchanged] {
if cap.Type == types.TypeTrait {
table.AddRow(cap.Name, cap.Type, cap.Description)
}
}
if !silent {
io.Infof("Automatically discover capabilities successfully %s(no changes)\n\n", emojiSucceed)
io.Info(table.String())
}
return
}
io.Infof("Automatically discover capabilities successfully %sAdd(%s) Update(%s) Delete(%s)\n\n",
emojiSucceed,
green.Sprint(len(report[added])),
yellow.Sprint(len(report[updated])),
red.Sprint(len(report[deleted])))
// show added/updated/deleted cpas
addStsRow(added, report, table)
addStsRow(updated, report, table)
addStsRow(deleted, report, table)
io.Info(table.String())
io.Info()
}
func addStsRow(sts refreshStatus, report map[refreshStatus][]types.Capability, t *uitable.Table) {
caps := report[sts]
if len(caps) == 0 {
return
}
var stsIcon string
var stsColor *color.Color
switch sts {
case added:
stsIcon = "+"
stsColor = green
case updated:
stsIcon = "*"
stsColor = yellow
case deleted:
stsIcon = "-"
stsColor = red
case unchanged:
// normal color display
}
for _, cap := range caps {
t.AddRow(
// color.New(color.Bold).Sprint(stsColor.Sprint(stsIcon)),
stsColor.Sprintf("%s%s", stsIcon, cap.Name),
stsColor.Sprint(cap.Type),
stsColor.Sprint(cap.Description))
}
}
func refreshResultReport(newCaps, oldCaps []types.Capability) map[refreshStatus][]types.Capability {
dir, _ := system.GetCapabilityDir()
cachedHash := readCapDefHashFromLocal(dir)
newHash := map[string]string{}
for _, newCap := range newCaps {
h, err := hashstructure.Hash(newCap, hashstructure.FormatV2, nil)
if err != nil {
continue
}
newHash[newCap.Name] = strconv.FormatUint(h, 10)
}
report := map[refreshStatus][]types.Capability{
added: make([]types.Capability, 0),
updated: make([]types.Capability, 0),
unchanged: make([]types.Capability, 0),
deleted: make([]types.Capability, 0),
}
for _, newCap := range newCaps {
found := false
for _, oldCap := range oldCaps {
if newCap.Name == oldCap.Name {
found = true
break
}
}
if !found {
report[added] = append(report[added], newCap)
}
}
for _, oldCap := range oldCaps {
found := false
for _, newCap := range newCaps {
if oldCap.Name == newCap.Name {
found = true
// use cached hash to determine whether the cap is changed
if h, ok := cachedHash[newCap.Name]; ok {
if h == newHash[newCap.Name] {
report[unchanged] = append(report[unchanged], newCap)
} else {
report[updated] = append(report[updated], newCap)
}
break
}
// in case of missing cache, use Equal func to compare
if types.EqualCapability(oldCap, newCap) {
report[unchanged] = append(report[unchanged], newCap)
} else {
report[updated] = append(report[updated], newCap)
}
break
}
}
if !found {
report[deleted] = append(report[deleted], oldCap)
}
}
_ = writeCapDefHashIntoLocal(dir, newHash)
return report
}
// useCacheInsteadRefresh checks whether use cached capabilities instead of refresh from cluster
// a timestamp records the time when refresh from cluster last time
// if duration since last time refresh DOES NOT exceed `cacheExpiredDuration`
// use cached capabilities instead of refresh from cluster
// else refresh from cluster and refresh the timestamp
func useCacheInsteadRefresh(capDir string, cacheExpiredDuration time.Duration) (bool, error) {
currentTimestamp := strconv.FormatInt(time.Now().Unix(), 10)
tmpDir := filepath.Join(capDir, ".tmp")
timeFilePath := filepath.Join(tmpDir, ".lasttimerefresh")
exist, _ := system.CreateIfNotExist(tmpDir)
if !exist {
// file saving timestamp is not created yet, create and refresh the timestamp
if err := ioutil.WriteFile(timeFilePath, []byte(currentTimestamp), 0600); err != nil {
return false, err
}
return false, nil
}
r, err := ioutil.ReadFile(filepath.Clean(timeFilePath))
if err != nil {
if os.IsNotExist(err) {
// tmpDir exists but `.lasttimerefresh` file doesn't
if err := ioutil.WriteFile(timeFilePath, []byte(currentTimestamp), 0600); err != nil {
return false, err
}
return false, nil
}
return false, err
}
i, err := strconv.ParseInt(string(r), 10, 64)
if err != nil {
return false, err
}
lt := time.Unix(i, 0)
if time.Since(lt) > cacheExpiredDuration {
// cache is expired, refresh the timestamp
if err := ioutil.WriteFile(timeFilePath, []byte(currentTimestamp), 0600); err != nil {
return false, err
}
return false, nil
}
// cache is not expired
return true, nil
}
// each capability has a hash value cached in local capability dir
// hash value is used to compare local capability with one from cluster
// refresh report will show all changed capabilities
func readCapDefHashFromLocal(capDir string) map[string]string {
r := map[string]string{}
tmpDir := filepath.Join(capDir, ".tmp")
hashFilePath := filepath.Join(tmpDir, ".capabilityhash")
if exist, err := system.CreateIfNotExist(tmpDir); !exist || err != nil {
return r
}
if _, err := os.Stat(hashFilePath); os.IsNotExist(err) {
return r
}
hashData, err := ioutil.ReadFile(filepath.Clean(hashFilePath))
if err != nil {
return r
}
if err := json.Unmarshal(hashData, &r); err != nil {
return r
}
return r
}
func writeCapDefHashIntoLocal(capDir string, hashData map[string]string) error {
tmpDir := filepath.Join(capDir, ".tmp")
hashFilePath := filepath.Join(tmpDir, ".capabilityhash")
if _, err := system.CreateIfNotExist(tmpDir); err != nil {
return err
}
data, err := json.Marshal(hashData)
if err != nil {
return err
}
if err := ioutil.WriteFile(hashFilePath, data, 0600); err != nil {
return err
}
return nil
}
-49
View File
@@ -1,49 +0,0 @@
package cli
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCheckAndUpdateRefreshInterval(t *testing.T) {
testdir := "testdir-refresh-interval"
testMaxInterval := time.Second
err := os.MkdirAll(testdir, 0755)
assert.NoError(t, err)
defer os.RemoveAll(testdir)
r, err := useCacheInsteadRefresh(testdir, testMaxInterval)
assert.Equal(t, r, false, "should not use cache for tmp file is not created")
assert.NoError(t, err)
r, err = useCacheInsteadRefresh(testdir, testMaxInterval)
assert.Equal(t, r, true, "should use cache for interval is not expired")
assert.NoError(t, err)
time.Sleep(2 * testMaxInterval)
r, err = useCacheInsteadRefresh(testdir, testMaxInterval)
assert.Equal(t, r, false, "should not use cache for interval is already expired")
assert.NoError(t, err)
}
func TestWriteAndReadLocalCapHash(t *testing.T) {
testdir := "testdir-caphash"
err := os.MkdirAll(testdir, 0755)
assert.NoError(t, err)
defer os.RemoveAll(testdir)
result := readCapDefHashFromLocal(testdir)
assert.Equal(t, result, map[string]string{}, "capability hash data should be empty")
fakeHashData := map[string]string{
"a": "test1",
"b": "test2",
}
err = writeCapDefHashIntoLocal(testdir, fakeHashData)
assert.NoError(t, err, "write new hash data successfully")
result = readCapDefHashFromLocal(testdir)
assert.Equal(t, result, fakeHashData, "read hash data successfully")
}
+10 -30
View File
@@ -92,11 +92,11 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
ioStreams.Errorf("Error: failed to get Env: %s", err)
return err
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd)
return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd, c)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
@@ -107,8 +107,8 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
return cmd
}
func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error {
app, err := appfile.LoadApplication(env.Name, appName)
func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command, velaC types.Args) error {
app, err := appfile.LoadApplication(env.Namespace, appName, velaC)
if err != nil {
return err
}
@@ -118,8 +118,7 @@ func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOSt
table := newUITable()
table.AddRow(" Name:", appName)
table.AddRow(" Namespace:", namespace)
table.AddRow(" Created at:", app.CreateTime.String())
table.AddRow(" Updated at:", app.UpdateTime.String())
table.AddRow(" Created at:", app.CreationTimestamp.String())
cmd.Printf("%s\n\n", table.String())
cmd.Printf("Services:\n\n")
@@ -216,14 +215,14 @@ HealthCheckLoop:
return healthStatus, healthInfo, nil
}
func printTrackingDeployStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) (CompStatus, error) {
func printTrackingDeployStatus(c types.Args, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta) (CompStatus, error) {
sDeploy := newTrackingSpinnerWithDelay("Checking Status ...", trackingInterval)
sDeploy.Start()
defer sDeploy.Stop()
TrackDeployLoop:
for {
time.Sleep(trackingInterval)
deployStatus, failMsg, err := TrackDeployStatus(ctx, c, compName, appName, env)
deployStatus, failMsg, err := TrackDeployStatus(c, appName, env)
if err != nil {
return compStatusUnknown, err
}
@@ -245,12 +244,12 @@ TrackDeployLoop:
}
// TrackDeployStatus will only check AppConfig is deployed successfully,
func TrackDeployStatus(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (CompStatus, string, error) {
app, appObj, err := getApp(ctx, c, compName, appName, env)
func TrackDeployStatus(c types.Args, appName string, env *types.EnvMeta) (CompStatus, string, error) {
appObj, err := appfile.LoadApplication(env.Namespace, appName, c)
if err != nil {
return compStatusUnknown, "", err
}
if app == nil || appObj == nil {
if appObj == nil {
return compStatusUnknown, "", errors.New(ErrNotLoadAppConfig)
}
condition := appObj.Status.Conditions
@@ -319,25 +318,6 @@ func trackHealthCheckingStatus(ctx context.Context, c client.Client, compName, a
return compStatusHealthCheckDone, HealthStatusNotDiagnosed, "", nil
}
func getApp(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (*api.Application, *v1alpha2.Application, error) {
var app *api.Application
var err error
if appName != "" {
app, err = appfile.LoadApplication(env.Name, appName)
} else {
app, err = appfile.MatchAppByComp(env.Name, compName)
}
if err != nil {
return nil, nil, err
}
appObj, err := appfile.GetApplication(ctx, c, app, env)
if err != nil {
return nil, nil, err
}
return app, appObj, nil
}
func getWorkloadStatusFromApp(app *v1alpha2.Application, compName string) (v1alpha2.ApplicationComponentStatus, bool) {
foundWlStatus := false
wlStatus := v1alpha2.ApplicationComponentStatus{}
+2 -5
View File
@@ -111,7 +111,7 @@ func NewInstallCommand(c types.Args, chartContent string, ioStreams cmdutil.IOSt
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return err
}
@@ -172,9 +172,6 @@ func (i *initCmd) run(ioStreams cmdutil.IOStreams, chartSource string) error {
"try running 'vela workloads' or 'vela traits' to check after a while, details: %v", err)
return nil
}
if err := RefreshDefinitions(context.Background(), i.c, ioStreams, false, true); err != nil {
return err
}
ioStreams.Info("- Finished successfully.")
if waitDuration > 0 {
@@ -204,7 +201,7 @@ func CheckCapabilityReady(ctx context.Context, c types.Args, timeout time.Durati
defer spiner.Stop()
for {
_, err = plugins.GetCapabilitiesFromCluster(ctx, types.DefaultKubeVelaNS, c, tmpdir, nil)
_, err = plugins.GetCapabilitiesFromCluster(ctx, types.DefaultKubeVelaNS, c, nil)
if err == nil {
return nil
}
+18
View File
@@ -0,0 +1,18 @@
name: first-vela-app
services:
testsvc:
type: webservice
image: crccheck/hello-world
port: 8000
ingress:
domain: testsvc.example.com
http:
"/": 8000
abc:
type: webservice
image: crccheck/hello-world
port: 8000
ingress:
domain: testsvc.example.com
http:
"/": 8000
+10 -16
View File
@@ -1,7 +1,6 @@
package cli
import (
"context"
"strings"
"github.com/spf13/cobra"
@@ -9,15 +8,13 @@ import (
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/common"
"github.com/oam-dev/kubevela/references/plugins"
)
// NewTraitsCommand creates `traits` command
func NewTraitsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
var workloadName string
var enforceRefresh bool
ctx := context.Background()
cmd := &cobra.Command{
Use: "traits [--apply-to WORKLOAD_NAME]",
Use: "traits",
DisableFlagsInUseLine: true,
Short: "List traits",
Long: "List traits",
@@ -26,12 +23,11 @@ func NewTraitsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := RefreshDefinitions(ctx, c, ioStreams, true, enforceRefresh); err != nil {
env, err := GetEnv(cmd)
if err != nil {
return err
}
return printTraitList(&workloadName, ioStreams)
return printTraitList(env.Namespace, c, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeCap,
@@ -39,22 +35,20 @@ func NewTraitsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
}
cmd.SetOut(ioStreams.Out)
cmd.Flags().StringVar(&workloadName, "apply-to", "", "Workload name")
cmd.Flags().BoolVarP(&enforceRefresh, "", "r", false, "Enforce refresh from cluster even if cache is not expired")
return cmd
}
func printTraitList(workloadName *string, ioStreams cmdutil.IOStreams) error {
func printTraitList(userNamespace string, c types.Args, ioStreams cmdutil.IOStreams) error {
table := newUITable()
table.MaxColWidth = 120
table.Wrap = true
traitDefinitionList, err := common.ListTraitDefinitions(workloadName)
traitDefinitionList, err := common.ListRawTraitDefinitions(userNamespace, c)
if err != nil {
return err
}
table.AddRow("NAME", "DESCRIPTION", "APPLIES TO")
table.AddRow("NAME", "NAMESPACE", "APPLIES-TO", "CONFLICTS-WITH", "DESCRIPTION")
for _, t := range traitDefinitionList {
table.AddRow(t.Name, t.Description, strings.Join(t.AppliesTo, "\n"))
table.AddRow(t.Name, t.Namespace, strings.Join(t.Spec.AppliesToWorkloads, ","), strings.Join(t.Spec.ConflictsWith, ","), plugins.GetDescription(t.Annotations))
}
ioStreams.Info(table.String())
return nil
-80
View File
@@ -1,11 +1,9 @@
package cli
import (
"bytes"
"os"
"testing"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
@@ -14,84 +12,6 @@ import (
"github.com/oam-dev/kubevela/references/common"
)
func Test_printTraitList(t *testing.T) {
traits := []types.Capability{
{
Name: "route",
CrdName: "routes.oam.dev",
// This format is currently OAM spec standard
AppliesTo: []string{"apps/v1.Deployment", "alibaba/v1.Clonset"},
},
{
Name: "scaler",
CrdName: "scaler.oam.dev",
// This format is also reasonable, it's align with oam definition name, so we also support here
AppliesTo: []string{"deployments.apps"},
},
}
workloads := []types.Capability{
{
Name: "deployment",
CrdName: "deployments.apps",
},
{
Name: "clonset",
CrdName: "clonsets.alibaba",
},
}
newTable := func() *uitable.Table {
table := newUITable()
table.AddRow("NAME", "DEFINITION", "APPLIES TO")
return table
}
tb1 := newTable()
tb1.AddRow("route", "routes.oam.dev", "deployment")
tb1.AddRow("", "", "clonset")
tb1.AddRow("scaler", "scaler.oam.dev", "deployment")
tb2 := newTable()
tb2.AddRow("route", "routes.oam.dev", "deployment")
tb2.AddRow("scaler", "scaler.oam.dev", "deployment")
tb3 := newTable()
tb3.AddRow("route", "routes.oam.dev", "clonset")
cases := map[string]struct {
traits []types.Capability
workloads []types.Capability
workloadName string
iostream cmdutil.IOStreams
ExpectedString string
}{
"All Workloads": {
traits: traits,
workloads: workloads,
ExpectedString: tb1.String() + "\n",
},
"Specify Workload Name deployment": {
traits: traits,
workloads: workloads,
workloadName: "deployment",
ExpectedString: tb2.String() + "\n",
},
"Specify Workload Name clonset": {
traits: traits,
workloads: workloads,
workloadName: "clonset",
ExpectedString: tb3.String() + "\n",
},
}
// TODO(zzxwill) As the old `func printTraitList(traits, workloads []types.Capability, workloadName *string, ioStreams cmdutil.IOStreams)`
// doesn't exist any more, comment this unit-test for now
//for cname, c := range cases {
for _, c := range cases {
b := bytes.Buffer{}
iostream := cmdutil.IOStreams{Out: &b}
nn := c.workloadName
assert.NoError(t, printTraitList(&nn, iostream))
}
}
func TestNewTraitsCommandPersistentPreRunE(t *testing.T) {
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{}
+2 -3
View File
@@ -2,7 +2,6 @@ package cli
import (
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -31,7 +30,7 @@ func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
if err != nil {
return err
}
kubecli, err := client.New(c.Config, client.Options{Scheme: c.Schema})
kubecli, err := c.GetClient()
if err != nil {
return err
}
@@ -45,7 +44,7 @@ func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
if err != nil {
return err
}
return o.Run(filePath, c.Config)
return o.Run(filePath, velaEnv.Namespace, c)
},
}
cmd.SetOut(ioStream.Out)
+11 -17
View File
@@ -1,19 +1,16 @@
package cli
import (
"context"
"github.com/spf13/cobra"
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/common"
"github.com/oam-dev/kubevela/references/plugins"
)
// NewWorkloadsCommand creates `workloads` command
func NewWorkloadsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
var enforceRefresh bool
ctx := context.Background()
cmd := &cobra.Command{
Use: "workloads",
DisableFlagsInUseLine: true,
@@ -24,32 +21,29 @@ func NewWorkloadsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := RefreshDefinitions(ctx, c, ioStreams, true, enforceRefresh); err != nil {
return err
}
workloads, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
env, err := GetEnv(cmd)
if err != nil {
return err
}
return printWorkloadList(workloads, ioStreams)
return printWorkloadList(env.Namespace, c, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeCap,
},
}
cmd.SetOut(ioStreams.Out)
cmd.Flags().BoolVarP(&enforceRefresh, "", "r", false, "Enforce refresh from cluster even if cache is not expired")
return cmd
}
func printWorkloadList(workloadList []types.Capability, ioStreams cmdutil.IOStreams) error {
func printWorkloadList(userNamespace string, c types.Args, ioStreams cmdutil.IOStreams) error {
def, err := common.ListRawWorkloadDefinitions(userNamespace, c)
if err != nil {
return err
}
table := newUITable()
table.MaxColWidth = 120
table.AddRow("NAME", "DESCRIPTION")
for _, r := range workloadList {
table.AddRow(r.Name, r.Description)
table.AddRow("NAME", "NAMESPACE", "WORKLOAD", "DESCRIPTION")
for _, r := range def {
table.AddRow(r.Name, r.Namespace, r.Spec.Reference.Name, plugins.GetDescription(r.Annotations))
}
ioStreams.Info(table.String())
return nil
+29 -29
View File
@@ -19,7 +19,6 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
@@ -95,6 +94,7 @@ type DeleteOptions struct {
CompName string
Client client.Client
Env *types.EnvMeta
C types.Args
}
// ListApplications lists all applications
@@ -224,19 +224,31 @@ func (o *DeleteOptions) DeleteApp() (string, error) {
return "", fmt.Errorf("delete application err: %w", err)
}
// TODO(wonderflow): delete the default health scope here
for _, cmp := range app.Spec.Components {
healthScopeName, ok := cmp.Scopes[api.DefaultHealthScopeKey]
if ok {
var healthScope corev1alpha2.HealthScope
if err := o.Client.Get(ctx, client.ObjectKey{Namespace: o.Env.Namespace, Name: healthScopeName}, &healthScope); err != nil {
if apierrors.IsNotFound(err) {
continue
}
return "", fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
}
if err = o.Client.Delete(ctx, &healthScope); err != nil {
return "", fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
}
}
}
return fmt.Sprintf("app \"%s\" deleted from env \"%s\"", o.AppName, o.Env.Name), nil
}
// DeleteComponent will delete one component including server side.
func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) (string, error) {
var app *api.Application
var err error
if o.AppName != "" {
app, err = appfile.LoadApplication(o.Env.Name, o.AppName)
} else {
app, err = appfile.MatchAppByComp(o.Env.Name, o.CompName)
if o.AppName == "" {
return "", errors.New("app name is required")
}
app, err := appfile.LoadApplication(o.Env.Namespace, o.AppName, o.C)
if err != nil {
return "", err
}
@@ -249,24 +261,15 @@ func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) (string, error) {
if err := appfile.RemoveComponent(app, o.CompName); err != nil {
return "", err
}
if err := appfile.Save(app, o.Env.Name); err != nil {
return "", err
}
// Remove component from appConfig in k8s cluster
ctx := context.Background()
if err := BuildRun(ctx, app, o.Client, o.Env, io); err != nil {
if err := o.Client.Update(ctx, app); err != nil {
return "", err
}
// Remove component in k8s cluster
var c corev1alpha2.Component
c.Name = o.CompName
c.Namespace = o.Env.Namespace
err = o.Client.Delete(context.Background(), &c)
if err != nil && !apierrors.IsNotFound(err) {
return "", fmt.Errorf("delete component err: %w", err)
}
// It's the server responsibility to GC component
return fmt.Sprintf("component \"%s\" deleted from \"%s\"", o.CompName, o.AppName), nil
}
@@ -359,8 +362,8 @@ func saveAndLoadRemoteAppfile(url string) (*api.AppFile, error) {
}
// ExportFromAppFile exports Application from appfile object
func (o *AppfileOptions) ExportFromAppFile(app *api.AppFile, quiet bool) (*BuildResult, []byte, error) {
tm, err := template.Load()
func (o *AppfileOptions) ExportFromAppFile(app *api.AppFile, namespace string, quiet bool, c types.Args) (*BuildResult, []byte, error) {
tm, err := template.Load(namespace, c)
if err != nil {
return nil, nil, err
}
@@ -401,7 +404,7 @@ func (o *AppfileOptions) ExportFromAppFile(app *api.AppFile, quiet bool) (*Build
}
// Export export Application object from the path of Appfile
func (o *AppfileOptions) Export(filePath string, quiet bool) (*BuildResult, []byte, error) {
func (o *AppfileOptions) Export(filePath, namespace string, quiet bool, c types.Args) (*BuildResult, []byte, error) {
var app *api.AppFile
var err error
if !quiet {
@@ -410,9 +413,6 @@ func (o *AppfileOptions) Export(filePath string, quiet bool) (*BuildResult, []by
if filePath != "" {
if strings.HasPrefix(filePath, "https://") || strings.HasPrefix(filePath, "http://") {
app, err = saveAndLoadRemoteAppfile(filePath)
if err != nil {
return nil, nil, err
}
} else {
app, err = api.LoadFromFile(filePath)
}
@@ -426,16 +426,16 @@ func (o *AppfileOptions) Export(filePath string, quiet bool) (*BuildResult, []by
if !quiet {
o.IO.Info("Load Template ...")
}
return o.ExportFromAppFile(app, quiet)
return o.ExportFromAppFile(app, namespace, quiet, c)
}
// Run starts an application according to Appfile
func (o *AppfileOptions) Run(filePath string, config *rest.Config) error {
result, data, err := o.Export(filePath, false)
func (o *AppfileOptions) Run(filePath, namespace string, c types.Args) error {
result, data, err := o.Export(filePath, namespace, false, c)
if err != nil {
return err
}
dm, err := discoverymapper.New(config)
dm, err := discoverymapper.New(c.Config)
if err != nil {
return err
}
+14 -14
View File
@@ -259,9 +259,9 @@ func SyncCapabilityCenter(capabilityCenterName string) error {
// RemoveCapabilityFromCluster will remove a capability from cluster.
// 1. remove definition 2. uninstall chart 3. remove local files
func RemoveCapabilityFromCluster(client client.Client, capabilityName string) (string, error) {
func RemoveCapabilityFromCluster(userNamespace string, c types.Args, client client.Client, capabilityName string) (string, error) {
ioStreams := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
if err := RemoveCapability(client, capabilityName, ioStreams); err != nil {
if err := RemoveCapability(userNamespace, c, client, capabilityName, ioStreams); err != nil {
return "", err
}
msg := fmt.Sprintf("%s removed successfully", capabilityName)
@@ -270,9 +270,9 @@ func RemoveCapabilityFromCluster(client client.Client, capabilityName string) (s
// RemoveCapability will remove a capability from cluster.
// 1. remove definition 2. uninstall chart 3. remove local files
func RemoveCapability(client client.Client, capabilityName string, ioStreams cmdutil.IOStreams) error {
func RemoveCapability(userNamespace string, c types.Args, client client.Client, capabilityName string, ioStreams cmdutil.IOStreams) error {
// TODO(wonderflow): make sure no apps is using this capability
caps, err := plugins.LoadAllInstalledCapability()
caps, err := plugins.LoadAllInstalledCapability(userNamespace, c)
if err != nil {
return err
}
@@ -329,14 +329,14 @@ func uninstallCap(client client.Client, cap types.Capability, ioStreams cmdutil.
}
// ListCapabilities will list all caps from specified center
func ListCapabilities(capabilityCenterName string) ([]types.Capability, error) {
func ListCapabilities(userNamespace string, c types.Args, capabilityCenterName string) ([]types.Capability, error) {
var capabilityList []types.Capability
dir, err := system.GetCapCenterDir()
if err != nil {
return capabilityList, err
}
if capabilityCenterName != "" {
return listCenterCapabilities(filepath.Join(dir, capabilityCenterName))
return listCenterCapabilities(userNamespace, c, filepath.Join(dir, capabilityCenterName))
}
dirs, err := ioutil.ReadDir(dir)
if err != nil {
@@ -346,7 +346,7 @@ func ListCapabilities(capabilityCenterName string) ([]types.Capability, error) {
if !dd.IsDir() {
continue
}
caps, err := listCenterCapabilities(filepath.Join(dir, dd.Name()))
caps, err := listCenterCapabilities(userNamespace, c, filepath.Join(dir, dd.Name()))
if err != nil {
return capabilityList, err
}
@@ -355,7 +355,7 @@ func ListCapabilities(capabilityCenterName string) ([]types.Capability, error) {
return capabilityList, nil
}
func listCenterCapabilities(repoDir string) ([]types.Capability, error) {
func listCenterCapabilities(userNamespace string, c types.Args, repoDir string) ([]types.Capability, error) {
templates, err := plugins.LoadCapabilityFromSyncedCenter(repoDir)
if err != nil {
return templates, err
@@ -364,9 +364,9 @@ func listCenterCapabilities(repoDir string) ([]types.Capability, error) {
return templates, nil
}
baseDir := filepath.Base(repoDir)
workloads := gatherWorkloads(templates)
workloads := gatherWorkloads(userNamespace, c, templates)
for i, p := range templates {
status := checkInstallStatus(baseDir, p)
status := checkInstallStatus(userNamespace, c, baseDir, p)
convertedApplyTo := ConvertApplyTo(p.AppliesTo, workloads)
templates[i].Center = baseDir
templates[i].Status = status
@@ -409,8 +409,8 @@ func RemoveCapabilityCenter(centerName string) (string, error) {
return message, err
}
func gatherWorkloads(templates []types.Capability) []types.Capability {
workloads, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
func gatherWorkloads(userNamespace string, c types.Args, templates []types.Capability) []types.Capability {
workloads, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeWorkload)
if err != nil {
workloads = make([]types.Capability, 0)
}
@@ -422,9 +422,9 @@ func gatherWorkloads(templates []types.Capability) []types.Capability {
return workloads
}
func checkInstallStatus(repoName string, tmp types.Capability) string {
func checkInstallStatus(userNamespace string, c types.Args, repoName string, tmp types.Capability) string {
var status = "uninstalled"
installed, _ := plugins.LoadInstalledCapabilityWithType(tmp.Type)
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 {
return "installed"
+48 -123
View File
@@ -5,45 +5,79 @@ import (
"fmt"
"strings"
"cuelang.org/go/cue"
plur "github.com/gertd/go-pluralize"
"github.com/spf13/pflag"
"sigs.k8s.io/controller-runtime/pkg/client"
client2 "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/appfile/api"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/references/plugins"
)
// ListTraitDefinitions will list all definition include traits and workloads
func ListTraitDefinitions(workloadName *string) ([]types.Capability, error) {
func ListTraitDefinitions(userNamespace string, c types.Args, workloadName *string) ([]types.Capability, error) {
var traitList []types.Capability
traits, err := plugins.LoadInstalledCapabilityWithType(types.TypeTrait)
traits, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeTrait)
if err != nil {
return traitList, err
}
workloads, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
workloads, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeWorkload)
if err != nil {
return traitList, err
}
traitList = convertAllAppliyToList(traits, workloads, workloadName)
traitList = convertAllApplyToList(traits, workloads, workloadName)
return traitList, nil
}
// ListRawTraitDefinitions will list raw definition
func ListRawTraitDefinitions(userNamespace string, c types.Args) ([]v1alpha2.TraitDefinition, error) {
client, err := c.GetClient()
if err != nil {
return nil, err
}
ctx := util.SetNamespaceInCtx(context.Background(), userNamespace)
traitList := v1alpha2.TraitDefinitionList{}
if err = client.List(ctx, &traitList, client2.InNamespace(userNamespace)); err != nil {
return nil, err
}
sysTraitList := v1alpha2.TraitDefinitionList{}
if err = client.List(ctx, &sysTraitList, client2.InNamespace(oam.SystemDefinitonNamespace)); err != nil {
return nil, err
}
return append(traitList.Items, sysTraitList.Items...), nil
}
// ListRawWorkloadDefinitions will list raw definition
func ListRawWorkloadDefinitions(userNamespace string, c types.Args) ([]v1alpha2.WorkloadDefinition, error) {
client, err := c.GetClient()
if err != nil {
return nil, err
}
ctx := util.SetNamespaceInCtx(context.Background(), userNamespace)
workloadList := v1alpha2.WorkloadDefinitionList{}
if err = client.List(ctx, &workloadList); err != nil {
return nil, err
}
sysWorkloadList := v1alpha2.WorkloadDefinitionList{}
if err = client.List(ctx, &sysWorkloadList, client2.InNamespace(oam.SystemDefinitonNamespace)); err != nil {
return nil, err
}
return append(workloadList.Items, sysWorkloadList.Items...), nil
}
// GetTraitDefinition will get trait capability with applyTo converted
func GetTraitDefinition(workloadName *string, traitType string) (types.Capability, error) {
func GetTraitDefinition(userNamespace string, c types.Args, workloadName *string, traitType string) (types.Capability, error) {
var traitDef types.Capability
traitCap, err := plugins.GetInstalledCapabilityWithCapName(types.TypeTrait, traitType)
if err != nil {
return traitDef, err
}
workloadsCap, err := plugins.LoadInstalledCapabilityWithType(types.TypeWorkload)
workloadsCap, err := plugins.LoadInstalledCapabilityWithType(userNamespace, c, types.TypeWorkload)
if err != nil {
return traitDef, err
}
traitList := convertAllAppliyToList([]types.Capability{traitCap}, workloadsCap, workloadName)
traitList := convertAllApplyToList([]types.Capability{traitCap}, workloadsCap, workloadName)
if len(traitList) != 1 {
return traitDef, fmt.Errorf("could not get installed capability by %s", traitType)
}
@@ -51,7 +85,7 @@ func GetTraitDefinition(workloadName *string, traitType string) (types.Capabilit
return traitDef, nil
}
func convertAllAppliyToList(traits []types.Capability, workloads []types.Capability, workloadName *string) []types.Capability {
func convertAllApplyToList(traits []types.Capability, workloads []types.Capability, workloadName *string) []types.Capability {
var traitList []types.Capability
for _, t := range traits {
convertedApplyTo := ConvertApplyTo(t.AppliesTo, workloads)
@@ -118,112 +152,3 @@ func Parse(applyTo string) string {
}
return plur.NewClient().Plural(strings.ToLower(l[1])) + "." + apigroup
}
// ValidateAndMutateForCore was built in validate and mutate function for core workloads and traits
func ValidateAndMutateForCore(traitType, workloadName string, flags *pflag.FlagSet, env *types.EnvMeta) error {
switch traitType {
case "route":
domain, _ := flags.GetString("domain")
if domain == "" {
if env.Domain == "" {
return fmt.Errorf("--domain is required if not contain in environment")
}
if strings.HasPrefix(env.Domain, "https://") {
env.Domain = strings.TrimPrefix(env.Domain, "https://")
}
if strings.HasPrefix(env.Domain, "http://") {
env.Domain = strings.TrimPrefix(env.Domain, "http://")
}
if err := flags.Set("domain", workloadName+"."+env.Domain); err != nil {
return fmt.Errorf("set flag for vela-core trait('route') err %w, please make sure your template is right", err)
}
}
issuer, _ := flags.GetString("issuer")
if issuer == "" && env.Issuer != "" {
if err := flags.Set("issuer", env.Issuer); err != nil {
return fmt.Errorf("set flag for vela-core trait('route') err %w, please make sure your template is right", err)
}
}
default:
// extend other trait here in the future
}
return nil
}
// AddOrUpdateTrait attach trait to workload
func AddOrUpdateTrait(env *types.EnvMeta, appName string, componentName string, flagSet *pflag.FlagSet, template types.Capability) (*api.Application, error) {
err := ValidateAndMutateForCore(template.Name, componentName, flagSet, env)
if err != nil {
return nil, err
}
if appName == "" {
appName = componentName
}
app, err := appfile.LoadApplication(env.Name, appName)
if err != nil {
return app, err
}
traitAlias := template.Name
traitData, err := appfile.GetTraitsByType(app, componentName, traitAlias)
if err != nil {
return app, err
}
for _, v := range template.Parameters {
name := v.Name
if v.Alias != "" {
name = v.Alias
}
// nolint:exhaustive
switch v.Type {
case cue.IntKind:
traitData[v.Name], err = flagSet.GetInt64(name)
case cue.StringKind:
traitData[v.Name], err = flagSet.GetString(name)
case cue.BoolKind:
traitData[v.Name], err = flagSet.GetBool(name)
case cue.NumberKind, cue.FloatKind:
traitData[v.Name], err = flagSet.GetFloat64(name)
default:
// Currently we don't support get value from complex type
continue
}
if err != nil {
return nil, fmt.Errorf("get flag(s) \"%s\" err %w", name, err)
}
}
if err = appfile.SetTrait(app, componentName, traitAlias, traitData); err != nil {
return app, err
}
return app, appfile.Save(app, env.Name)
}
// TraitOperationRun will check if it's a stage operation before run
func TraitOperationRun(ctx context.Context, c client.Client, env *types.EnvMeta, appObj *api.Application,
staging bool, io cmdutil.IOStreams) (string, error) {
if staging {
return "Staging saved", nil
}
err := BuildRun(ctx, appObj, c, env, io)
if err != nil {
return "", err
}
return "Deployed!", nil
}
// PrepareDetachTrait will detach trait in local AppFile
func PrepareDetachTrait(envName string, traitType string, componentName string, appName string) (*api.Application, error) {
var appObj *api.Application
var err error
if appName == "" {
appName = componentName
}
if appObj, err = appfile.LoadApplication(envName, appName); err != nil {
return appObj, err
}
if err = appfile.RemoveTrait(appObj, componentName, traitType); err != nil {
return appObj, err
}
return appObj, appfile.Save(appObj, envName)
}
+11 -31
View File
@@ -1,7 +1,6 @@
package common
import (
"context"
"fmt"
"strconv"
"strings"
@@ -28,27 +27,20 @@ type RunOptions struct {
util.IOStreams
}
// LoadIfExist will load Application from local dir
func LoadIfExist(envName string, workloadName string, appGroup string) (*api.Application, error) {
// InitApplication will load Application from cluster
func InitApplication(env *types.EnvMeta, c types.Args, workloadName string, appGroup string) (*api.Application, error) {
var appName string
if appGroup != "" {
appName = appGroup
} else {
appName = workloadName
}
app, err := appfile.LoadApplication(envName, appName)
// can't handle
if err != nil && !appfile.IsNotFound(appName, err) {
return nil, err
}
// TODO(wonderflow): we should load the existing application from cluster and convert to appfile
// app, err := appfile.LoadApplication(env.Namespace, appName, c)
// compatible application not found
if app == nil {
app, err = appfile.NewEmptyApplication()
if err != nil {
return nil, err
}
app, err := appfile.NewEmptyApplication(env.Namespace, c)
if err != nil {
return nil, err
}
app.Name = appName
@@ -56,8 +48,8 @@ func LoadIfExist(envName string, workloadName string, appGroup string) (*api.App
}
// BaseComplete will construct an Application from cli parameters.
func BaseComplete(envName string, workloadName string, appName string, flagSet *pflag.FlagSet, workloadType string) (*api.Application, error) {
app, err := LoadIfExist(envName, workloadName, appName)
func BaseComplete(env *types.EnvMeta, c types.Args, workloadName string, appName string, flagSet *pflag.FlagSet, workloadType string) (*api.Application, error) {
app, err := InitApplication(env, c, workloadName, appName)
if err != nil {
return nil, err
}
@@ -69,7 +61,7 @@ func BaseComplete(envName string, workloadName string, appName string, flagSet *
// Not exist
tp = workloadType
}
template, err := plugins.LoadCapabilityByName(tp)
template, err := plugins.LoadCapabilityByName(tp, env.Namespace, c)
if err != nil {
return nil, err
}
@@ -129,17 +121,5 @@ func BaseComplete(envName string, workloadName string, appName string, flagSet *
if err = appfile.SetWorkload(app, workloadName, tp, workloadData); err != nil {
return app, err
}
return app, appfile.Save(app, envName)
}
// BaseRun will check if it's a stating operation before run
func BaseRun(staging bool, app *api.Application, kubeClient client.Client, env *types.EnvMeta, io util.IOStreams) (string, error) {
if staging {
return "Staging saved", nil
}
if err := BuildRun(context.Background(), app, kubeClient, env, io); err != nil {
err = fmt.Errorf("create app err: %w", err)
return "", err
}
return fmt.Sprintf("App %s deployed", app.Name), nil
return app, appfile.Save(app, env.Name)
}
+4 -4
View File
@@ -162,7 +162,7 @@ func StoreRepos(repos []CapCenterConfig) error {
}
// ParseAndSyncCapability will convert config from remote center to capability
func ParseAndSyncCapability(data []byte, syncDir string) (types.Capability, error) {
func ParseAndSyncCapability(data []byte) (types.Capability, error) {
var obj = unstructured.Unstructured{Object: make(map[string]interface{})}
err := yaml.Unmarshal(data, &obj.Object)
if err != nil {
@@ -175,14 +175,14 @@ func ParseAndSyncCapability(data []byte, syncDir string) (types.Capability, erro
if err != nil {
return types.Capability{}, err
}
return HandleDefinition(rd.Name, syncDir, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil, rd.Spec.Schematic)
return HandleDefinition(rd.Name, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil, rd.Spec.Schematic)
case "TraitDefinition":
var td v1alpha2.TraitDefinition
err = yaml.Unmarshal(data, &td)
if err != nil {
return types.Capability{}, err
}
return HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
return HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
case "ScopeDefinition":
// TODO(wonderflow): support scope definition here.
}
@@ -241,7 +241,7 @@ func (g *GithubCenter) SyncCapabilityFromCenter() error {
return fmt.Errorf("decode github content %s err %w", *fileContent.Path, err)
}
}
tmp, err := ParseAndSyncCapability(data, filepath.Join(dir, ".tmp"))
tmp, err := ParseAndSyncCapability(data)
if err != nil {
fmt.Printf("parse definition of %s err %v\n", *fileContent.Name, err)
continue
+21 -26
View File
@@ -3,9 +3,7 @@ package plugins
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
@@ -20,7 +18,6 @@ import (
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
"github.com/oam-dev/kubevela/pkg/utils/system"
util2 "github.com/oam-dev/kubevela/pkg/utils/util"
)
@@ -28,12 +25,12 @@ import (
const DescriptionUndefined = "description not defined"
// GetCapabilitiesFromCluster will get capability from K8s cluster
func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c types.Args, syncDir string, selector labels.Selector) ([]types.Capability, error) {
workloads, _, err := GetWorkloadsFromCluster(ctx, namespace, c, syncDir, selector)
func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c types.Args, selector labels.Selector) ([]types.Capability, error) {
workloads, _, err := GetWorkloadsFromCluster(ctx, namespace, c, selector)
if err != nil {
return nil, err
}
traits, _, err := GetTraitsFromCluster(ctx, namespace, c, syncDir, selector)
traits, _, err := GetTraitsFromCluster(ctx, namespace, c, selector)
if err != nil {
return nil, err
}
@@ -42,8 +39,8 @@ func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c types.A
}
// GetWorkloadsFromCluster will get capability from K8s cluster
func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args, syncDir string, selector labels.Selector) ([]types.Capability, []error, error) {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args, selector labels.Selector) ([]types.Capability, []error, error) {
newClient, err := c.GetClient()
if err != nil {
return nil, nil, err
}
@@ -61,11 +58,12 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args
var templateErrors []error
for _, wd := range workloadDefs.Items {
tmp, err := HandleDefinition(wd.Name, syncDir, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil, wd.Spec.Schematic)
tmp, err := HandleDefinition(wd.Name, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil, wd.Spec.Schematic)
if err != nil {
templateErrors = append(templateErrors, errors.Wrapf(err, "handle workload template `%s` failed", wd.Name))
continue
}
tmp.Namespace = namespace
if tmp, err = validateCapabilities(tmp, dm, wd.Name, wd.Spec.Reference); err != nil {
return nil, nil, err
}
@@ -75,8 +73,8 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args
}
// GetTraitsFromCluster will get capability from K8s cluster
func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, syncDir string, selector labels.Selector) ([]types.Capability, []error, error) {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, selector labels.Selector) ([]types.Capability, []error, error) {
newClient, err := c.GetClient()
if err != nil {
return nil, nil, err
}
@@ -93,11 +91,12 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, s
var templateErrors []error
for _, td := range traitDefs.Items {
tmp, err := HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
tmp, err := HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
if err != nil {
templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name))
continue
}
tmp.Namespace = namespace
if tmp, err = validateCapabilities(tmp, dm, td.Name, td.Spec.Reference); err != nil {
return nil, nil, err
}
@@ -134,9 +133,9 @@ func validateCapabilities(tmp types.Capability, dm discoverymapper.DiscoveryMapp
}
// HandleDefinition will handle definition to capability
func HandleDefinition(name, syncDir, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string, schematic *corev1alpha2.Schematic) (types.Capability, error) {
func HandleDefinition(name, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string, schematic *corev1alpha2.Schematic) (types.Capability, error) {
var tmp types.Capability
tmp, err := HandleTemplate(extension, schematic, name, syncDir)
tmp, err := HandleTemplate(extension, schematic, name)
if err != nil {
return types.Capability{}, err
}
@@ -158,11 +157,12 @@ func GetDescription(annotation map[string]string) string {
if !ok {
return DescriptionUndefined
}
desc = strings.ReplaceAll(desc, "\n", " ")
return desc
}
// HandleTemplate will handle definition template to capability
func HandleTemplate(in *runtime.RawExtension, schematic *corev1alpha2.Schematic, name, syncDir string) (types.Capability, error) {
func HandleTemplate(in *runtime.RawExtension, schematic *corev1alpha2.Schematic, name string) (types.Capability, error) {
tmp, err := util.ConvertTemplateJSON2Object(name, in, schematic)
if err != nil {
return types.Capability{}, err
@@ -183,15 +183,10 @@ func HandleTemplate(in *runtime.RawExtension, schematic *corev1alpha2.Schematic,
if tmp.CueTemplate == "" {
return types.Capability{}, errors.New("template not exist in definition")
}
_, _ = system.CreateIfNotExist(syncDir)
filePath := filepath.Join(syncDir, name+".cue")
//nolint:gosec
err = ioutil.WriteFile(filePath, []byte(tmp.CueTemplate), 0644)
if err != nil {
return types.Capability{}, err
}
tmp.DefinitionPath = filePath
tmp.Parameters, err = cue.GetParameters(filePath)
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate)
if err != nil {
return types.Capability{}, err
}
@@ -203,7 +198,7 @@ func SyncDefinitionsToLocal(ctx context.Context, c types.Args, localDefinitionDi
var syncedTemplates []types.Capability
var warnings []string
templates, templateErrors, err := GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, nil)
templates, templateErrors, err := GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, nil)
if err != nil {
return nil, nil, err
}
@@ -215,7 +210,7 @@ func SyncDefinitionsToLocal(ctx context.Context, c types.Args, localDefinitionDi
syncedTemplates = append(syncedTemplates, templates...)
SinkTemp2Local(templates, localDefinitionDir)
templates, templateErrors, err = GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, nil)
templates, templateErrors, err = GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, nil)
if err != nil {
return nil, warnings, err
}
@@ -233,7 +228,7 @@ func SyncDefinitionsToLocal(ctx context.Context, c types.Args, localDefinitionDi
func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir string, capabilityName string) (*types.Capability, error) {
var foundCapability bool
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
newClient, err := c.GetClient()
if err != nil {
return nil, err
}
@@ -244,7 +239,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir
foundCapability = true
}
if foundCapability {
template, err := HandleDefinition(capabilityName, localDefinitionDir, workloadDef.Spec.Reference.Name,
template, err := HandleDefinition(capabilityName, workloadDef.Spec.Reference.Name,
workloadDef.Annotations, workloadDef.Spec.Extension, types.TypeWorkload, nil, workloadDef.Spec.Schematic)
if err == nil {
return &template, nil
@@ -258,7 +253,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir
foundCapability = true
}
if foundCapability {
template, err := HandleDefinition(capabilityName, localDefinitionDir, traitDef.Spec.Reference.Name,
template, err := HandleDefinition(capabilityName, traitDef.Spec.Reference.Name,
traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, workloadDef.Spec.Schematic)
if err == nil {
return &template, nil
+3 -6
View File
@@ -108,7 +108,7 @@ var _ = Describe("DefinitionFiles", func() {
// Notice!! DefinitionPath Object is Cluster Scope object
// which means objects created in other DefinitionNamespace will also affect here.
It("gettrait", func() {
traitDefs, _, err := GetTraitsFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, definitionDir, selector)
traitDefs, _, err := GetTraitsFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, selector)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting trait definitions %v", traitDefs))
for i := range traitDefs {
@@ -116,7 +116,6 @@ var _ = Describe("DefinitionFiles", func() {
By("check CueTemplate is fulfilled")
Expect(traitDefs[i].CueTemplate).ShouldNot(BeEmpty())
traitDefs[i].CueTemplate = ""
traitDefs[i].DefinitionPath = ""
}
Expect(traitDefs).Should(Equal([]types.Capability{route}))
})
@@ -124,7 +123,7 @@ var _ = Describe("DefinitionFiles", func() {
// Notice!! DefinitionPath Object is Cluster Scope object
// which means objects created in other DefinitionNamespace will also affect here.
It("getworkload", func() {
workloadDefs, _, err := GetWorkloadsFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, definitionDir, selector)
workloadDefs, _, err := GetWorkloadsFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, selector)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting workload definitions %v", workloadDefs))
for i := range workloadDefs {
@@ -132,17 +131,15 @@ var _ = Describe("DefinitionFiles", func() {
By("check CueTemplate is fulfilled")
Expect(workloadDefs[i].CueTemplate).ShouldNot(BeEmpty())
workloadDefs[i].CueTemplate = ""
workloadDefs[i].DefinitionPath = ""
}
Expect(workloadDefs).Should(Equal([]types.Capability{deployment, websvc}))
})
It("getall", func() {
alldef, err := GetCapabilitiesFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, definitionDir, selector)
alldef, err := GetCapabilitiesFromCluster(context.Background(), DefinitionNamespace, types.Args{Config: cfg, Schema: scheme}, selector)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting all definitions %v", alldef))
for i := range alldef {
alldef[i].CueTemplate = ""
alldef[i].DefinitionPath = ""
}
Expect(alldef).Should(Equal([]types.Capability{deployment, websvc, route}))
})
+37 -13
View File
@@ -2,6 +2,7 @@ package plugins
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -14,8 +15,8 @@ import (
)
// LoadCapabilityByName will load capability from local by name
func LoadCapabilityByName(name string) (types.Capability, error) {
caps, err := LoadAllInstalledCapability()
func LoadCapabilityByName(name string, userNamespace string, c types.Args) (types.Capability, error) {
caps, err := LoadAllInstalledCapability(userNamespace, c)
if err != nil {
return types.Capability{}, err
}
@@ -28,26 +29,49 @@ func LoadCapabilityByName(name string) (types.Capability, error) {
}
// LoadAllInstalledCapability will list all capability
func LoadAllInstalledCapability() ([]types.Capability, error) {
workloads, err := LoadInstalledCapabilityWithType(types.TypeWorkload)
func LoadAllInstalledCapability(userNamespace string, c types.Args) ([]types.Capability, error) {
caps, err := GetCapabilitiesFromCluster(context.TODO(), userNamespace, c, nil)
if err != nil {
return nil, err
}
traits, err := LoadInstalledCapabilityWithType(types.TypeTrait)
systemCaps, err := GetCapabilitiesFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
if err != nil {
return nil, err
}
workloads = append(workloads, traits...)
return workloads, nil
caps = append(caps, systemCaps...)
return caps, nil
}
// LoadInstalledCapabilityWithType will load cap list by type
func LoadInstalledCapabilityWithType(capT types.CapType) ([]types.Capability, error) {
dir, err := system.GetCapabilityDir()
if err != nil {
return nil, err
func LoadInstalledCapabilityWithType(userNamespace string, c types.Args, capT types.CapType) ([]types.Capability, error) {
switch capT {
case types.TypeWorkload:
caps, _, err := GetWorkloadsFromCluster(context.TODO(), userNamespace, c, nil)
if err != nil {
return nil, err
}
systemCaps, _, err := GetWorkloadsFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
if err != nil {
return nil, err
}
caps = append(caps, systemCaps...)
return caps, nil
case types.TypeTrait:
caps, _, err := GetTraitsFromCluster(context.TODO(), userNamespace, c, nil)
if err != nil {
return nil, err
}
systemCaps, _, err := GetTraitsFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
if err != nil {
return nil, err
}
caps = append(caps, systemCaps...)
return caps, nil
case types.TypeScope:
}
return loadInstalledCapabilityWithType(dir, capT)
return nil, nil
}
// GetInstalledCapabilityWithCapName will get cap by alias
@@ -210,7 +234,7 @@ func LoadCapabilityFromSyncedCenter(dir string) ([]types.Capability, error) {
fmt.Printf("read file %s err %v\n", f.Name(), err)
continue
}
tmp, err := ParseAndSyncCapability(data, filepath.Join(dir, ".tmp"))
tmp, err := ParseAndSyncCapability(data)
if err != nil {
fmt.Printf("get definition of %s err %v\n", f.Name(), err)
continue
+5 -1
View File
@@ -183,7 +183,11 @@ func setDisplayFormat(format string) {
// GenerateReferenceDocs generates reference docs
func (ref *MarkdownReference) GenerateReferenceDocs(baseRefPath string) error {
caps, err := LoadAllInstalledCapability()
c, err := common.InitBaseRestConfig()
if err != nil {
return err
}
caps, err := LoadAllInstalledCapability("default", c)
if err != nil {
return fmt.Errorf("failed to generate reference docs for all capabilities: %w", err)
}