Feat: show or list revisions in vela def get and vela show (#4307)

* Feat: support --version when getting definitions

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: add tests

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: add tests for vela show

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Style: format imports

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: fix tests

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: fix tests

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: fix tests

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>
This commit is contained in:
Charlie Chiang
2022-07-06 15:41:00 +08:00
committed by GitHub
parent be317c412a
commit da5282a15d
11 changed files with 705 additions and 13 deletions
+120 -2
View File
@@ -23,8 +23,6 @@ import (
"fmt"
"strings"
"github.com/oam-dev/kubevela/pkg/utils/filters"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/format"
@@ -33,15 +31,19 @@ import (
"cuelang.org/go/tools/fix"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/model/sets"
"github.com/oam-dev/kubevela/pkg/cue/model/value"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/filters"
)
const (
@@ -65,6 +67,24 @@ var (
"scope": v1beta1.ScopeDefinitionKind,
"workflow-step": v1beta1.WorkflowStepDefinitionKind,
}
// StringToDefinitionType converts user input to DefinitionType used in DefinitionRevisions
StringToDefinitionType = map[string]common.DefinitionType{
// component
"component": common.ComponentType,
// trait
"trait": common.TraitType,
// policy
"policy": common.PolicyType,
// workflow-step
"workflow-step": common.WorkflowStepType,
}
// DefinitionKindToNameLabel records DefinitionRevision types and labels to search its name
DefinitionKindToNameLabel = map[common.DefinitionType]string{
common.ComponentType: oam.LabelComponentDefinitionName,
common.TraitType: oam.LabelTraitDefinitionName,
common.PolicyType: oam.LabelPolicyDefinitionName,
common.WorkflowStepType: oam.LabelWorkflowStepDefinitionName,
}
)
// Definition the general struct for handling all kinds of definitions like ComponentDefinition or TraitDefinition
@@ -425,6 +445,104 @@ func SearchDefinition(c client.Client, definitionType, namespace string, additio
return definitions, nil
}
// SearchDefinitionRevisions finds DefinitionRevisions.
// Use defName to filter DefinitionRevisions using the name of the underlying Definition.
// Empty defName will keep everything.
// Use defType to only keep DefinitionRevisions of the specified DefinitionType.
// Empty defType will search every possible type.
// Use rev to only keep the revision you want. rev=0 will keep every revision.
func SearchDefinitionRevisions(ctx context.Context, c client.Client, namespace string,
defName string, defType common.DefinitionType, rev int64) ([]v1beta1.DefinitionRevision, error) {
var nameLabels []string
if defName == "" {
// defName="" means we don't care about the underlying definition names.
// So, no need to add name labels, just use anything to let the loop run once.
nameLabels = append(nameLabels, "")
} else {
// Since different definitions have different labels for its name, we need to
// find the corresponding label for definition names, to match names later.
// Empty defType will give all possible name labels of DefinitionRevisions,
// so that we can search for DefinitionRevisions of all Definition types.
for k, v := range DefinitionKindToNameLabel {
if defType != "" && defType != k {
continue
}
nameLabels = append(nameLabels, v)
}
}
var defRev []v1beta1.DefinitionRevision
// Search DefinitionRevisions using each possible label
for _, l := range nameLabels {
var listOptions []client.ListOption
if namespace != "" {
listOptions = append(listOptions, client.InNamespace(namespace))
}
// Using name label to find DefinitionRevisions with specified name.
if defName != "" {
listOptions = append(listOptions, client.MatchingLabels{
l: defName,
})
}
objs := v1beta1.DefinitionRevisionList{}
objs.SetGroupVersionKind(schema.GroupVersionKind{
Group: v1beta1.Group,
Version: v1beta1.Version,
Kind: v1beta1.DefinitionRevisionKind,
})
// Search for DefinitionRevisions
if err := c.List(ctx, &objs, listOptions...); err != nil {
return nil, errors.Wrapf(err, "failed to list DefinitionRevisions of %s", defName)
}
for _, dr := range objs.Items {
// Keep only the specified type
if defType != "" && defType != dr.Spec.DefinitionType {
continue
}
// Only give the revision that the user wants
if rev != 0 && rev != dr.Spec.Revision {
continue
}
defRev = append(defRev, dr)
}
}
return defRev, nil
}
// GetDefinitionFromDefinitionRevision will extract the underlying Definition from a DefinitionRevision.
func GetDefinitionFromDefinitionRevision(rev *v1beta1.DefinitionRevision) (*Definition, error) {
var def *Definition
var u map[string]interface{}
var err error
switch rev.Spec.DefinitionType {
case common.ComponentType:
u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rev.Spec.ComponentDefinition)
case common.TraitType:
u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rev.Spec.TraitDefinition)
case common.PolicyType:
u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rev.Spec.PolicyDefinition)
case common.WorkflowStepType:
u, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&rev.Spec.WorkflowStepDefinition)
default:
return nil, fmt.Errorf("unsupported definition type: %s", rev.Spec.DefinitionType)
}
if err != nil {
return nil, err
}
def = &Definition{Unstructured: unstructured.Unstructured{Object: u}}
return def, nil
}
// GetDefinitionDefaultSpec returns the default spec of Definition with given kind. This may be implemented with cue in the future.
func GetDefinitionDefaultSpec(kind string) map[string]interface{} {
switch kind {
+78 -3
View File
@@ -19,19 +19,22 @@ package definition
import (
"context"
"encoding/json"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/oam-dev/kubevela/pkg/utils/filters"
"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml"
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
addonutils "github.com/oam-dev/kubevela/pkg/utils/addon"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/filters"
)
func TestDefinitionBasicFunctions(t *testing.T) {
@@ -143,3 +146,75 @@ func TestDefinitionBasicFunctions(t *testing.T) {
t.Fatalf("failed to search definition with addon filter applied: %s", "too many results returned")
}
}
func TestDefinitionRevisionSearch(t *testing.T) {
c := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
var err error
// Load test DefinitionRevisions files into client
testFiles, err := ioutil.ReadDir("testdata")
assert.NoError(t, err, "read testdata failed")
for _, file := range testFiles {
if !strings.HasSuffix(file.Name(), ".yaml") {
continue
}
content, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
assert.NoError(t, err)
def := &v1beta1.DefinitionRevision{}
err = yaml.Unmarshal(content, def)
assert.NoError(t, err)
err = c.Create(context.TODO(), def)
assert.NoError(t, err, "cannot create "+file.Name())
}
var defrevs []v1beta1.DefinitionRevision
// Read with no conditions, should at least have 4 defrevs
defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "", "", "", 0)
assert.NoError(t, err)
assert.Equal(t, true, len(defrevs) >= 4)
// Restrict namespace
defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-custom-ns", "", "", 0)
assert.NoError(t, err)
assert.Equal(t, 1, len(defrevs))
// Restrict type
defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "", common2.ComponentType, 0)
assert.NoError(t, err)
assert.Equal(t, 2, len(defrevs))
// Restrict revision
defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "", "", 1)
assert.NoError(t, err)
assert.Equal(t, 2, len(defrevs))
// Restrict name
defrevs, err = SearchDefinitionRevisions(context.TODO(), c, "rev-test-ns", "webservice", "", 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(defrevs))
// Test GetDefinitionFromDefinitionRevision
defrev := defrevs[0]
// Simulate ComponentDefinition
defrev.Spec.DefinitionType = common2.ComponentType
_, err = GetDefinitionFromDefinitionRevision(&defrev)
assert.NoError(t, err)
// Simulate TraitDefinition
defrev.Spec.DefinitionType = common2.TraitType
_, err = GetDefinitionFromDefinitionRevision(&defrev)
assert.NoError(t, err)
// Simulate PolicyDefinition
defrev.Spec.DefinitionType = common2.PolicyType
_, err = GetDefinitionFromDefinitionRevision(&defrev)
assert.NoError(t, err)
// Simulate WorkflowStepDefinition
defrev.Spec.DefinitionType = common2.WorkflowStepType
_, err = GetDefinitionFromDefinitionRevision(&defrev)
assert.NoError(t, err)
}
+59
View File
@@ -0,0 +1,59 @@
apiVersion: core.oam.dev/v1beta1
kind: DefinitionRevision
metadata:
labels:
app.kubernetes.io/managed-by: Helm
custom.definition.oam.dev/ui-hidden: 'true'
trait.oam.dev/name: affinity
name: affinity-v1
namespace: rev-test-custom-ns
spec:
componentDefinition:
metadata: {}
spec:
workload:
definition:
apiVersion: ''
kind: ''
status: {}
definitionType: Trait
policyDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
revision: 1
revisionHash: 9db54dd8da7e4bd5
traitDefinition:
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: >-
affinity specify affinity and tolerationon K8s pod for your workload
which follows the pod spec in path 'spec.template'.
name: affinity
namespace: rev-test-custom-ns
spec:
appliesToWorkloads:
- '*'
definitionRef:
name: ''
podDisruptive: true
schematic:
cue:
template: |
parameter: {
// +usage=Specify the number of workload
replicas: *1 | int
}
// +patchStrategy=retainKeys
patch: spec: replicas: parameter.replicas
status: {}
workflowStepDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
@@ -0,0 +1,62 @@
apiVersion: core.oam.dev/v1beta1
kind: DefinitionRevision
metadata:
labels:
app.kubernetes.io/managed-by: Helm
componentdefinition.oam.dev/name: webservice
name: webservice-v1
namespace: rev-test-ns
spec:
componentDefinition:
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: >-
Describes long-running, scalable, containerized services that have a
stable network endpoint to receive external network traffic from
customers.
name: webservice
namespace: rev-test-ns
spec:
schematic:
cue:
template: |
output: parameter.objects[0]
outputs: {
for i, v in parameter.objects {
if i > 0 {
"objects-\(i)": v
}
}
}
parameter: {
objects: [...{}]
}
workload:
definition:
apiVersion: ""
kind: ""
type: autodetects.core.oam.dev
status: {}
definitionType: Component
policyDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
revision: 1
revisionHash: dfa072dac5088ed8
traitDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
workflowStepDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
@@ -0,0 +1,62 @@
apiVersion: core.oam.dev/v1beta1
kind: DefinitionRevision
metadata:
labels:
app.kubernetes.io/managed-by: Helm
componentdefinition.oam.dev/name: webservice
name: webservice-v2
namespace: rev-test-ns
spec:
componentDefinition:
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: >-
Describes long-running, scalable, containerized services that have a
stable network endpoint to receive external network traffic from
customers.
name: webservice
namespace: rev-test-ns
spec:
schematic:
cue:
template: |
output: parameter.objects[0]
outputs: {
for i, v in parameter.objects {
if i > 0 {
"objects-\(i)": v
}
}
}
parameter: {
objects: [...{}]
}
workload:
definition:
apiVersion: ""
kind: ""
type: autodetects.core.oam.dev
status: {}
definitionType: Component
policyDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
revision: 2
revisionHash: c0a0c0591d0bc6
traitDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
workflowStepDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
+64
View File
@@ -0,0 +1,64 @@
apiVersion: core.oam.dev/v1beta1
kind: DefinitionRevision
metadata:
labels:
app.kubernetes.io/managed-by: Helm
custom.definition.oam.dev/ui-hidden: 'true'
trait.oam.dev/name: affinity
name: affinity-v1
namespace: rev-test-ns
spec:
componentDefinition:
metadata: {}
spec:
workload:
definition:
apiVersion: ''
kind: ''
status: {}
definitionType: Trait
policyDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
revision: 1
revisionHash: 9db54dd8d5314bd5
traitDefinition:
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: >-
affinity specify affinity and tolerationon K8s pod for your workload
which follows the pod spec in path 'spec.template'.
meta.helm.sh/release-name: kubevela
meta.helm.sh/release-namespace: rev-test-ns
labels:
app.kubernetes.io/managed-by: Helm
custom.definition.oam.dev/ui-hidden: 'true'
name: affinity
namespace: rev-test-ns
spec:
appliesToWorkloads:
- '*'
definitionRef:
name: ''
podDisruptive: true
schematic:
cue:
template: |
parameter: {
// +usage=Specify the number of workload
replicas: *1 | int
}
// +patchStrategy=retainKeys
patch: spec: replicas: parameter.replicas
status: {}
workflowStepDefinition:
metadata: {}
spec:
definitionRef:
name: ''
status: {}
+73 -3
View File
@@ -26,7 +26,9 @@ import (
"os/exec"
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
@@ -409,8 +411,39 @@ func getSingleDefinition(cmd *cobra.Command, definitionName string, client clien
return &pkgdef.Definition{Unstructured: definitions[0]}, nil
}
// getDefRevs will search for DefinitionRevisions with specified conditions.
// Check SearchDefinitionRevisions for details.
func getDefRevs(ctx context.Context, client client.Client, ns, defTypeStr, defName string, rev int64) ([]v1beta1.DefinitionRevision, error) {
defType, ok := pkgdef.StringToDefinitionType[defTypeStr]
// Empty definition type is intentionally allowed, to allow the user to match all definition types
if defTypeStr != "" && !ok {
return nil, fmt.Errorf("%s is not a valid type. Valid types are %v", defTypeStr, reflect.ValueOf(pkgdef.StringToDefinitionType).MapKeys())
}
return pkgdef.SearchDefinitionRevisions(ctx, client, ns, defName, defType, rev)
}
// printDefRevs will print DefinitionRevisions
func printDefRevs(ctx context.Context, cmd *cobra.Command, client client.Client, ns, defTypeStr, defName string) error {
revs, err := getDefRevs(ctx, client, ns, defTypeStr, defName, 0)
if err != nil {
return err
}
table := newUITable()
table.AddRow("NAME", "REVISION", "TYPE", "HASH")
for _, rev := range revs {
table.AddRow(defName, rev.Spec.Revision, rev.Spec.DefinitionType, rev.Spec.RevisionHash)
}
cmd.Println(table)
return nil
}
// NewDefinitionGetCommand create the `vela def get` command to get definition from k8s
func NewDefinitionGetCommand(c common.Args) *cobra.Command {
var listRevisions bool
var targetRevision string
cmd := &cobra.Command{
Use: "get NAME",
Short: "Get definition",
@@ -433,10 +466,45 @@ func NewDefinitionGetCommand(c common.Args) *cobra.Command {
if err != nil {
return errors.Wrapf(err, "failed to get k8s client")
}
def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
if err != nil {
return err
if listRevisions {
return printDefRevs(context.Background(), cmd, k8sClient, namespace, definitionType, args[0])
}
var def *pkgdef.Definition
// Get history Definition from DefinitionRevisions
if targetRevision != "" {
// "v1", "1", both need to work
targetRevision = strings.TrimPrefix(targetRevision, "v")
ver, err := strconv.Atoi(targetRevision)
if err != nil {
return fmt.Errorf("invalid version: %w", err)
}
// Get the user-specified revision.
revs, err := getDefRevs(context.Background(), k8sClient, namespace, definitionType, args[0], int64(ver))
if err != nil {
return err
}
if len(revs) == 0 {
return fmt.Errorf("no %s with revision %s found in namespace %s", args[0], targetRevision, namespace)
}
// Now we have at least one DefinitionRevision (typically it will only be one).
// They all fit user's conditions. We will use the first one.
// Extract Definition from DefinitionRevision that we just got.
def, err = pkgdef.GetDefinitionFromDefinitionRevision(&revs[0])
if err != nil {
return err
}
} else {
def, err = getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
if err != nil {
return err
}
}
cueString, err := def.ToCUEString()
if err != nil {
return errors.Wrapf(err, "failed to get cue format definition")
@@ -449,6 +517,8 @@ func NewDefinitionGetCommand(c common.Args) *cobra.Command {
}
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
cmd.Flags().StringP(Namespace, "n", "", "Specify which namespace to get. If empty, all namespaces will be searched.")
cmd.Flags().BoolVarP(&listRevisions, "revisions", "", false, "List revisions of the specified definition.")
cmd.Flags().StringVarP(&targetRevision, "revision", "r", "", "Get the specified version of a definition.")
return cmd
}
+42
View File
@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -32,6 +33,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml"
common3 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
@@ -413,6 +415,46 @@ func TestNewDefinitionGetCommand(t *testing.T) {
if err := cmd.Execute(); err == nil {
t.Fatalf("expect found no trait error, but not found")
}
// Load test DefinitionRevisions files into client
dir := filepath.Join("..", "..", "pkg", "definition", "testdata")
testFiles, err := ioutil.ReadDir(dir)
assert.NoError(t, err, "read testdata failed")
for _, file := range testFiles {
if !strings.HasSuffix(file.Name(), ".yaml") {
continue
}
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
assert.NoError(t, err)
def := &v1beta1.DefinitionRevision{}
err = yaml.Unmarshal(content, def)
assert.NoError(t, err)
client, err := c.GetClient()
assert.NoError(t, err)
err = client.Create(context.TODO(), def)
assert.NoError(t, err, "cannot create "+file.Name())
}
// test get revision list
cmd = NewDefinitionGetCommand(c)
initCommand(cmd)
cmd.SetArgs([]string{"webservice", "--revisions", "--namespace=rev-test-ns"})
err = cmd.Execute()
assert.NoError(t, err)
// test get a non-existent revision
cmd = NewDefinitionGetCommand(c)
initCommand(cmd)
cmd.SetArgs([]string{"webservice", "--revision=3"})
err = cmd.Execute()
assert.NotNil(t, err, "should have not found error")
// test get a revision
cmd = NewDefinitionGetCommand(c)
initCommand(cmd)
cmd.SetArgs([]string{"webservice", "--revision=1", "--namespace=rev-test-ns"})
err = cmd.Execute()
assert.NoError(t, err)
}
func TestNewDefinitionGenDocCommand(t *testing.T) {
+33 -5
View File
@@ -26,6 +26,8 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@@ -61,6 +63,7 @@ var webSite bool
// NewCapabilityShowCommand shows the reference doc for a component type or trait
func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
var revision string
cmd := &cobra.Command{
Use: "show",
Short: "Show the reference doc for a component, trait or workflow.",
@@ -76,10 +79,24 @@ func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra
if err != nil {
return err
}
if revision == "" {
if webSite {
return startReferenceDocsSite(ctx, namespace, c, ioStreams, capabilityName)
}
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace, 0)
}
// v1, 1, both need to work
version := strings.TrimPrefix(revision, "v")
ver, err := strconv.Atoi(version)
if err != nil {
return fmt.Errorf("invalid revision: %w", err)
}
if webSite {
return startReferenceDocsSite(ctx, namespace, c, ioStreams, capabilityName)
}
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace)
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace, int64(ver))
},
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
@@ -87,6 +104,7 @@ func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra
}
cmd.Flags().BoolVarP(&webSite, "web", "", false, " start web doc site")
cmd.Flags().StringVarP(&revision, "revision", "r", "", "Get the specified revision of a definition. Use def get to list revisions.")
addNamespaceAndEnvArg(cmd)
cmd.SetOut(ioStreams.Out)
@@ -384,7 +402,7 @@ func getDefinitions(capabilities []types.Capability) ([]string, []string, []stri
}
// ShowReferenceConsole will show capability reference in console
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string) error {
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string, rev int64) error {
config, err := c.GetConfig()
if err != nil {
return err
@@ -393,9 +411,19 @@ func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.
if err != nil {
return err
}
capability, err := plugins.GetCapabilityByName(ctx, c, capabilityName, ns, pd)
if err != nil {
return err
var capability *types.Capability
if rev == 0 {
capability, err = plugins.GetCapabilityByName(ctx, c, capabilityName, ns, pd)
if err != nil {
return err
}
} else {
capability, err = plugins.GetCapabilityFromDefinitionRevision(ctx, c, pd, ns, capabilityName, rev)
if err != nil {
return err
}
}
cli, err := c.GetClient()
+58
View File
@@ -24,6 +24,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
@@ -453,6 +455,62 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri
return nil, fmt.Errorf("could not find %s in namespace %s, or %s", capabilityName, ns, types.DefaultKubeVelaNS)
}
// GetCapabilityFromDefinitionRevision gets capabilities from the underlying Definition in DefinitionRevisions
func GetCapabilityFromDefinitionRevision(ctx context.Context, c common.Args, pd *packages.PackageDiscover, ns, defName string, r int64) (*types.Capability, error) {
k8sClient, err := c.GetClient()
if err != nil {
return nil, err
}
revs, err := definition.SearchDefinitionRevisions(ctx, k8sClient, ns, defName, "", r)
if err != nil {
return nil, err
}
// `ns` defaults to `default` in `vela show`, if user doesn't specify anything,
// which often is not the desired behavior.
// So we need to search again in the vela-system namespace, if no revisions found.
// This behavior is consistent with the code above in GetCapabilityByName(), which also does double-search.
if len(revs) == 0 && ns == "default" {
revs, err = definition.SearchDefinitionRevisions(ctx, k8sClient, types.DefaultKubeVelaNS, defName, "", r)
if err != nil {
return nil, err
}
}
if len(revs) == 0 {
return nil, fmt.Errorf("no %s with revision %d found in namespace %s or %s", defName, r, ns, types.DefaultKubeVelaNS)
}
rev := revs[0]
switch rev.Spec.DefinitionType {
case commontypes.ComponentType:
var refName string
componentDef := rev.Spec.ComponentDefinition
// if workload type of ComponentDefinition is unclear,
// set the DefinitionReference's Name to AutoDetectWorkloadDefinition
if componentDef.Spec.Workload.Type == types.AutoDetectWorkloadDefinition {
refName = types.AutoDetectWorkloadDefinition
} else {
dm, err := c.GetDiscoveryMapper()
if err != nil {
return nil, err
}
ref, err := util.ConvertWorkloadGVK2Definition(dm, componentDef.Spec.Workload.Definition)
if err != nil {
return nil, err
}
refName = ref.Name
}
return GetCapabilityByComponentDefinitionObject(componentDef, refName)
case commontypes.TraitType:
return GetCapabilityByTraitDefinitionObject(rev.Spec.TraitDefinition)
case commontypes.WorkflowStepType:
return GetCapabilityByWorkflowStepDefinitionObject(rev.Spec.WorkflowStepDefinition, pd)
default:
return nil, fmt.Errorf("unsupported type %s", rev.Spec.DefinitionType)
}
}
// GetCapabilityByComponentDefinitionObject gets capability by ComponentDefinition object
func GetCapabilityByComponentDefinitionObject(componentDef v1beta1.ComponentDefinition, referenceName string) (*types.Capability, error) {
capability, err := HandleDefinition(componentDef.Name, referenceName, componentDef.Annotations, componentDef.Labels,
+54
View File
@@ -19,7 +19,10 @@ package plugins
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"cuelang.org/go/cue"
"github.com/google/go-cmp/cmp"
@@ -344,3 +347,54 @@ var _ = Describe("test GetNamespacedCapabilitiesFromCluster", func() {
})
})
var _ = Describe("test GetCapabilityFromDefinitionRevision", func() {
var (
ctx context.Context
c common.Args
)
BeforeEach(func() {
c = common.Args{}
c.SetClient(k8sClient)
ctx = context.Background()
By("create namespace")
Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "rev-test-custom-ns"}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "rev-test-ns"}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
// Load test DefinitionRevisions files into client
dir := filepath.Join("..", "..", "pkg", "definition", "testdata")
testFiles, err := ioutil.ReadDir(dir)
Expect(err).Should(Succeed())
for _, file := range testFiles {
if !strings.HasSuffix(file.Name(), ".yaml") {
continue
}
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
Expect(err).Should(Succeed())
def := &corev1beta1.DefinitionRevision{}
err = yaml.Unmarshal(content, def)
Expect(err).Should(Succeed())
client, err := c.GetClient()
Expect(err).Should(Succeed())
err = client.Create(context.TODO(), def)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
}
})
It("non-existent defrev", func() {
_, err := GetCapabilityFromDefinitionRevision(ctx, c, nil, "rev-test-custom-ns", "not-a-name", 0)
Expect(err).ShouldNot(Succeed())
})
It("component type", func() {
_, err := GetCapabilityFromDefinitionRevision(ctx, c, nil, "rev-test-ns", "webservice", 0)
Expect(err).Should(Succeed())
})
It("trait type", func() {
_, err := GetCapabilityFromDefinitionRevision(ctx, c, nil, "rev-test-custom-ns", "affinity", 0)
Expect(err).Should(Succeed())
})
})