mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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: {}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user