mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Feat: add vela debug command (#3580)
* Feat: add debug configmap if debug policy is specified Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: add vela debug command Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * make code reviewable Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * fix sonartype lift Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * fix cue string Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: display better for debug Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * tidy the go mod Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Feat: add debug test Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * change uitable vendor Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * add more tests Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * pass resource keeper from handler Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * fix lint Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * fix rebase Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com> * Pending test temporary Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
This commit is contained in:
@@ -496,6 +496,8 @@ const (
|
||||
PolicyResourceCreator ResourceCreatorRole = "policy"
|
||||
// WorkflowResourceCreator create the resource in workflow.
|
||||
WorkflowResourceCreator ResourceCreatorRole = "workflow"
|
||||
// DebugResourceCreator create the debug resource.
|
||||
DebugResourceCreator ResourceCreatorRole = "debug"
|
||||
)
|
||||
|
||||
// OAMObjectReference defines the object reference for an oam resource
|
||||
|
||||
@@ -21,6 +21,8 @@ const (
|
||||
TopologyPolicyType = "topology"
|
||||
// OverridePolicyType refers to the type of override policy
|
||||
OverridePolicyType = "override"
|
||||
// DebugPolicyType refers to the type of debug policy
|
||||
DebugPolicyType = "debug"
|
||||
)
|
||||
|
||||
// TopologyPolicySpec defines the spec of topology policy
|
||||
|
||||
@@ -192,7 +192,7 @@ func (in *ResourceTracker) findMangedResourceIndex(mr ManagedResource) int {
|
||||
}
|
||||
|
||||
// AddManagedResource add object to managed resources, if exists, update
|
||||
func (in *ResourceTracker) AddManagedResource(rsc client.Object, metaOnly bool) (updated bool) {
|
||||
func (in *ResourceTracker) AddManagedResource(rsc client.Object, metaOnly bool, creator common.ResourceCreatorRole) (updated bool) {
|
||||
gvk := rsc.GetObjectKind().GroupVersionKind()
|
||||
mr := ManagedResource{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
@@ -210,6 +210,9 @@ func (in *ResourceTracker) AddManagedResource(rsc client.Object, metaOnly bool)
|
||||
if !metaOnly {
|
||||
mr.Data = &runtime.RawExtension{Object: rsc}
|
||||
}
|
||||
if creator != "" {
|
||||
mr.ClusterObjectReference.Creator = creator
|
||||
}
|
||||
if idx := in.findMangedResourceIndex(mr); idx >= 0 {
|
||||
if reflect.DeepEqual(in.Spec.ManagedResources[idx], mr) {
|
||||
return false
|
||||
|
||||
@@ -156,16 +156,16 @@ func TestResourceTracker_ManagedResource(t *testing.T) {
|
||||
r := require.New(t)
|
||||
input := &ResourceTracker{}
|
||||
deploy1 := v12.Deployment{ObjectMeta: v13.ObjectMeta{Name: "deploy1"}}
|
||||
input.AddManagedResource(&deploy1, true)
|
||||
input.AddManagedResource(&deploy1, true, "")
|
||||
r.Equal(1, len(input.Spec.ManagedResources))
|
||||
cm2 := v1.ConfigMap{ObjectMeta: v13.ObjectMeta{Name: "cm2"}}
|
||||
input.AddManagedResource(&cm2, false)
|
||||
input.AddManagedResource(&cm2, false, "")
|
||||
r.Equal(2, len(input.Spec.ManagedResources))
|
||||
pod3 := v1.Pod{ObjectMeta: v13.ObjectMeta{Name: "pod3"}}
|
||||
input.AddManagedResource(&pod3, false)
|
||||
input.AddManagedResource(&pod3, false, "")
|
||||
r.Equal(3, len(input.Spec.ManagedResources))
|
||||
deploy1.Spec.Replicas = pointer.Int32(5)
|
||||
input.AddManagedResource(&deploy1, false)
|
||||
input.AddManagedResource(&deploy1, false, "")
|
||||
r.Equal(3, len(input.Spec.ManagedResources))
|
||||
input.DeleteManagedResource(&cm2, false)
|
||||
r.Equal(3, len(input.Spec.ManagedResources))
|
||||
|
||||
3
go.mod
3
go.mod
@@ -5,6 +5,7 @@ go 1.17
|
||||
require (
|
||||
cuelang.org/go v0.2.2
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
||||
github.com/FogDong/uitable v0.0.5
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
||||
github.com/agiledragon/gomonkey/v2 v2.4.0
|
||||
github.com/alibabacloud-go/cs-20151215/v2 v2.4.5
|
||||
@@ -66,7 +67,7 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
|
||||
6
go.sum
6
go.sum
@@ -118,6 +118,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/FogDong/uitable v0.0.5 h1:1bJo/uvhGUC6i8JPHZCr8XKMHiDExE7mQkOCmDl0ryQ=
|
||||
github.com/FogDong/uitable v0.0.5/go.mod h1:1yEaP13SkkBUj3HvqKIUWnsb42XigyZbNle84mc5kLM=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
@@ -2217,8 +2219,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6 h1:SIasE1FVIQOWz2GEAHFOmoW7xchJcqlucjSULTL0Ag4=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -182,6 +182,8 @@ type Appfile struct {
|
||||
|
||||
parser *Parser
|
||||
app *v1beta1.Application
|
||||
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// GeneratePolicyManifests generates policy manifests from an appFile
|
||||
|
||||
@@ -345,6 +345,8 @@ func (p *Parser) parsePoliciesFromRevision(ctx context.Context, af *Appfile) (er
|
||||
case v1alpha1.EnvBindingPolicyType:
|
||||
case v1alpha1.TopologyPolicyType:
|
||||
case v1alpha1.OverridePolicyType:
|
||||
case v1alpha1.DebugPolicyType:
|
||||
af.Debug = true
|
||||
default:
|
||||
w, err := p.makeWorkloadFromRevision(policy.Name, policy.Type, types.TypePolicy, policy.Properties, af.AppRevision)
|
||||
if err != nil {
|
||||
@@ -367,6 +369,8 @@ func (p *Parser) parsePolicies(ctx context.Context, af *Appfile) (err error) {
|
||||
case v1alpha1.ApplyOncePolicyType:
|
||||
case v1alpha1.EnvBindingPolicyType:
|
||||
case v1alpha1.TopologyPolicyType:
|
||||
case v1alpha1.DebugPolicyType:
|
||||
af.Debug = true
|
||||
case v1alpha1.OverridePolicyType:
|
||||
compDefs, traitDefs, err := policypkg.ParseOverridePolicyRelatedDefinitions(ctx, p.client, af.app, policy)
|
||||
if err != nil {
|
||||
|
||||
@@ -40,7 +40,7 @@ type Meta struct {
|
||||
func (m *Meta) Lookup(field string) cue.Value {
|
||||
f := m.Obj.Lookup(field)
|
||||
if !f.Exists() {
|
||||
m.Err = fmt.Errorf("invalid string argument")
|
||||
m.Err = fmt.Errorf("invalid lookup argument")
|
||||
return cue.Value{}
|
||||
}
|
||||
if err := f.Err(); err != nil {
|
||||
@@ -54,7 +54,7 @@ func (m *Meta) Int64(field string) int64 {
|
||||
f := m.Obj.Lookup(field)
|
||||
value, err := f.Int64()
|
||||
if err != nil {
|
||||
m.Err = fmt.Errorf("invalid string argument, %w", err)
|
||||
m.Err = fmt.Errorf("invalid int64 argument, %w", err)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
}
|
||||
app.Status.SetConditions(condition.ReadyCondition(common.RenderCondition.String()))
|
||||
r.Recorder.Event(app, event.Normal(velatypes.ReasonRendered, velatypes.MessageRendered))
|
||||
wf := workflow.NewWorkflow(app, r.Client, appFile.WorkflowMode)
|
||||
wf := workflow.NewWorkflow(app, r.Client, appFile.WorkflowMode, appFile.Debug, handler.resourceKeeper)
|
||||
workflowState, err := wf.ExecuteSteps(logCtx.Fork("workflow"), handler.currentAppRev, steps)
|
||||
if err != nil {
|
||||
logCtx.Error(err, "[handle workflow]")
|
||||
|
||||
@@ -54,6 +54,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/debug"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/tasks/custom"
|
||||
)
|
||||
|
||||
@@ -2657,6 +2658,64 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(k8sClient.Delete(ctx, secret)).Should(BeNil())
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app with debug policy", func() {
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-debug",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myworker",
|
||||
Type: "worker",
|
||||
Properties: &runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\",\"env\":[{\"name\":\"firstKey\",\"value\":\"firstValue\"}]}")},
|
||||
},
|
||||
},
|
||||
Policies: []v1beta1.AppPolicy{
|
||||
{
|
||||
Type: "debug",
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
|
||||
appKey := client.ObjectKey{
|
||||
Name: app.Name,
|
||||
Namespace: app.Namespace,
|
||||
}
|
||||
testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
By("Check App running successfully")
|
||||
curApp := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, appKey, curApp)).Should(BeNil())
|
||||
Expect(curApp.Status.Phase).Should(Equal(common.ApplicationRunning))
|
||||
|
||||
By("Check debug Config Map is created")
|
||||
debugCM := &corev1.ConfigMap{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{
|
||||
Name: debug.GenerateContextName(app.Name, "myworker"),
|
||||
Namespace: "default",
|
||||
}, debugCM)).Should(BeNil())
|
||||
|
||||
By("Update the application to update the debug Config Map")
|
||||
app.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\",\"env\":[{\"name\":\"firstKey\",\"value\":\"updateValue\"}]}")}
|
||||
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
updatedCM := &corev1.ConfigMap{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{
|
||||
Name: debug.GenerateContextName(app.Name, "myworker"),
|
||||
Namespace: "default",
|
||||
}, updatedCM)).Should(BeNil())
|
||||
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
const (
|
||||
|
||||
@@ -82,7 +82,7 @@ func NewAppHandler(ctx context.Context, r *Reconciler, app *v1beta1.Application,
|
||||
// Dispatch apply manifests into k8s.
|
||||
func (h *AppHandler) Dispatch(ctx context.Context, cluster string, owner common.ResourceCreatorRole, manifests ...*unstructured.Unstructured) error {
|
||||
manifests = multicluster.ResourcesWithClusterName(cluster, manifests...)
|
||||
if err := h.resourceKeeper.Dispatch(ctx, manifests); err != nil {
|
||||
if err := h.resourceKeeper.Dispatch(ctx, manifests, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mf := range manifests {
|
||||
|
||||
@@ -458,6 +458,9 @@ func (val *Value) fields() ([]*field, error) {
|
||||
no, err := attr.Int(0)
|
||||
if err != nil {
|
||||
no = 100
|
||||
if v.Name == "#do" || v.Name == "#provider" {
|
||||
no = 0
|
||||
}
|
||||
}
|
||||
fields = append(fields, &field{
|
||||
no: no,
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
errors2 "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
@@ -41,7 +42,7 @@ func (h *resourceKeeper) DispatchComponentRevision(ctx context.Context, cr *v1.C
|
||||
obj.SetName(cr.Name)
|
||||
obj.SetNamespace(cr.Namespace)
|
||||
obj.SetLabels(cr.Labels)
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, []*unstructured.Unstructured{obj}, true); err != nil {
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, []*unstructured.Unstructured{obj}, true, common.WorkflowResourceCreator); err != nil {
|
||||
return errors.Wrapf(err, "failed to record componentrevision %s/%s/%s", oam.GetCluster(cr), cr.Namespace, cr.Name)
|
||||
}
|
||||
if err = h.Client.Create(multicluster.ContextWithClusterName(ctx, oam.GetCluster(cr)), cr); err != nil {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -42,6 +43,7 @@ type DispatchOption interface {
|
||||
type dispatchConfig struct {
|
||||
rtConfig
|
||||
metaOnly bool
|
||||
creator common.ResourceCreatorRole
|
||||
}
|
||||
|
||||
func newDispatchConfig(options ...DispatchOption) *dispatchConfig {
|
||||
@@ -53,7 +55,7 @@ func newDispatchConfig(options ...DispatchOption) *dispatchConfig {
|
||||
}
|
||||
|
||||
// Dispatch dispatch resources
|
||||
func (h *resourceKeeper) Dispatch(ctx context.Context, manifests []*unstructured.Unstructured, options ...DispatchOption) (err error) {
|
||||
func (h *resourceKeeper) Dispatch(ctx context.Context, manifests []*unstructured.Unstructured, applyOpts []apply.ApplyOption, options ...DispatchOption) (err error) {
|
||||
if h.applyOncePolicy != nil && h.applyOncePolicy.Enable {
|
||||
options = append(options, MetaOnlyOption{})
|
||||
}
|
||||
@@ -66,7 +68,11 @@ func (h *resourceKeeper) Dispatch(ctx context.Context, manifests []*unstructured
|
||||
return err
|
||||
}
|
||||
// 2. apply manifests
|
||||
if err = h.dispatch(ctx, manifests); err != nil {
|
||||
opts := []apply.ApplyOption{apply.MustBeControlledByApp(h.app), apply.NotUpdateRenderHashEqual()}
|
||||
if len(applyOpts) > 0 {
|
||||
opts = append(opts, applyOpts...)
|
||||
}
|
||||
if err = h.dispatch(ctx, manifests, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -103,7 +109,7 @@ func (h *resourceKeeper) record(ctx context.Context, manifests []*unstructured.U
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get resourcetracker")
|
||||
}
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, rootManifests, cfg.metaOnly); err != nil {
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, rootManifests, cfg.metaOnly, cfg.creator); err != nil {
|
||||
return errors.Wrapf(err, "failed to record resources in resourcetracker %s", rt.Name)
|
||||
}
|
||||
}
|
||||
@@ -112,14 +118,13 @@ func (h *resourceKeeper) record(ctx context.Context, manifests []*unstructured.U
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get resourcetracker")
|
||||
}
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, versionManifests, cfg.metaOnly); err != nil {
|
||||
if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, versionManifests, cfg.metaOnly, cfg.creator); err != nil {
|
||||
return errors.Wrapf(err, "failed to record resources in resourcetracker %s", rt.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured.Unstructured) error {
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControlledByApp(h.app), apply.NotUpdateRenderHashEqual()}
|
||||
func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured.Unstructured, applyOpts []apply.ApplyOption) error {
|
||||
errs := parallel.Run(func(manifest *unstructured.Unstructured) error {
|
||||
applyCtx := multicluster.ContextWithClusterName(ctx, oam.GetCluster(manifest))
|
||||
applyCtx = oamutil.SetServiceAccountInContext(applyCtx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestResourceKeeperDispatchAndDelete(t *testing.T) {
|
||||
cm3.SetName("cm3")
|
||||
cm3.SetLabels(map[string]string{oam.TraitTypeLabel: "eternal"})
|
||||
|
||||
r.NoError(rk.Dispatch(context.Background(), []*unstructured.Unstructured{cm1, cm2, cm3}))
|
||||
r.NoError(rk.Dispatch(context.Background(), []*unstructured.Unstructured{cm1, cm2, cm3}, nil))
|
||||
r.NotNil(rk._rootRT)
|
||||
r.NotNil(rk._currentRT)
|
||||
r.Equal(1, len(rk._rootRT.Spec.ManagedResources))
|
||||
@@ -96,7 +96,7 @@ func TestResourceKeeperAdmissionDispatchAndDelete(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}}
|
||||
err = rk.Dispatch(context.Background(), objs)
|
||||
err = rk.Dispatch(context.Background(), objs, nil)
|
||||
r.NotNil(err)
|
||||
r.Contains(err.Error(), "forbidden")
|
||||
err = rk.Delete(context.Background(), objs)
|
||||
|
||||
@@ -101,9 +101,9 @@ func TestResourceKeeperGarbageCollect(t *testing.T) {
|
||||
obj.SetName(cr.GetName())
|
||||
obj.SetNamespace(cr.GetNamespace())
|
||||
obj.SetLabels(cr.GetLabels())
|
||||
r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, crRT, []*unstructured.Unstructured{obj}, true))
|
||||
r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, crRT, []*unstructured.Unstructured{obj}, true, ""))
|
||||
}
|
||||
r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, _rt, []*unstructured.Unstructured{cmMaps[i]}, true))
|
||||
r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, _rt, []*unstructured.Unstructured{cmMaps[i]}, true, ""))
|
||||
}
|
||||
|
||||
checkCount := func(cmCount, rtCount int, crCount int) {
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package resourcekeeper
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -31,6 +32,14 @@ type MetaOnlyOption struct{}
|
||||
// ApplyToDispatchConfig apply change to dispatch config
|
||||
func (option MetaOnlyOption) ApplyToDispatchConfig(cfg *dispatchConfig) { cfg.metaOnly = true }
|
||||
|
||||
// CreatorOption set the creator of the resource
|
||||
type CreatorOption struct {
|
||||
Creator common.ResourceCreatorRole
|
||||
}
|
||||
|
||||
// ApplyToDispatchConfig apply change to dispatch config
|
||||
func (option CreatorOption) ApplyToDispatchConfig(cfg *dispatchConfig) { cfg.creator = option.Creator }
|
||||
|
||||
// SkipRTOption skip the rt recording during dispatch/delete resources, which means the resource will not be controlled
|
||||
// by application resourcetracker
|
||||
type SkipRTOption struct{}
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
|
||||
// ResourceKeeper handler for dispatching and deleting resources
|
||||
type ResourceKeeper interface {
|
||||
Dispatch(context.Context, []*unstructured.Unstructured, ...DispatchOption) error
|
||||
Dispatch(context.Context, []*unstructured.Unstructured, []apply.ApplyOption, ...DispatchOption) error
|
||||
Delete(context.Context, []*unstructured.Unstructured, ...DeleteOption) error
|
||||
GarbageCollect(context.Context, ...GCOption) (bool, []v1beta1.ManagedResource, error)
|
||||
StateKeep(context.Context) error
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
@@ -143,10 +144,16 @@ func ListApplicationResourceTrackers(ctx context.Context, cli client.Client, app
|
||||
}
|
||||
|
||||
// RecordManifestsInResourceTracker records resources in ResourceTracker
|
||||
func RecordManifestsInResourceTracker(ctx context.Context, cli client.Client, rt *v1beta1.ResourceTracker, manifests []*unstructured.Unstructured, metaOnly bool) error {
|
||||
func RecordManifestsInResourceTracker(
|
||||
ctx context.Context,
|
||||
cli client.Client,
|
||||
rt *v1beta1.ResourceTracker,
|
||||
manifests []*unstructured.Unstructured,
|
||||
metaOnly bool,
|
||||
creator common.ResourceCreatorRole) error {
|
||||
if len(manifests) != 0 {
|
||||
for _, manifest := range manifests {
|
||||
rt.AddManagedResource(manifest, metaOnly)
|
||||
rt.AddManagedResource(manifest, metaOnly, creator)
|
||||
}
|
||||
return cli.Update(ctx, rt)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestRecordAndDeleteManifestsInResourceTracker(t *testing.T) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetName(fmt.Sprintf("workload-%d", i))
|
||||
objs = append(objs, obj)
|
||||
r.NoError(RecordManifestsInResourceTracker(context.Background(), cli, rt, []*unstructured.Unstructured{obj}, rand.Int()%2 == 0))
|
||||
r.NoError(RecordManifestsInResourceTracker(context.Background(), cli, rt, []*unstructured.Unstructured{obj}, rand.Int()%2 == 0, ""))
|
||||
}
|
||||
rand.Shuffle(len(objs), func(i, j int) { objs[i], objs[j] = objs[j], objs[i] })
|
||||
for i := 0; i < n; i++ {
|
||||
|
||||
116
pkg/workflow/debug/context.go
Normal file
116
pkg/workflow/debug/context.go
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
// ContextImpl is workflow debug context interface
|
||||
type ContextImpl interface {
|
||||
Set(v *value.Value) error
|
||||
}
|
||||
|
||||
// Context is debug context.
|
||||
type Context struct {
|
||||
cli client.Client
|
||||
app *v1beta1.Application
|
||||
step string
|
||||
rk resourcekeeper.ResourceKeeper
|
||||
}
|
||||
|
||||
// Set sets debug content into context
|
||||
func (d *Context) Set(v *value.Value) error {
|
||||
data, err := v.String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setStore(context.Background(), d.cli, d.rk, d.app, d.step, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStore(ctx context.Context, cli client.Client, rk resourcekeeper.ResourceKeeper, app *v1beta1.Application, step, data string) error {
|
||||
cm := &corev1.ConfigMap{}
|
||||
if err := cli.Get(ctx, types.NamespacedName{
|
||||
Namespace: app.Namespace,
|
||||
Name: GenerateContextName(app.Name, step),
|
||||
}, cm); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
cm.Name = GenerateContextName(app.Name, step)
|
||||
cm.Namespace = app.Namespace
|
||||
cm.Data = map[string]string{
|
||||
"debug": data,
|
||||
}
|
||||
u, err := util.Object2Unstructured(cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.SetGroupVersionKind(
|
||||
corev1.SchemeGroupVersion.WithKind(
|
||||
reflect.TypeOf(corev1.ConfigMap{}).Name(),
|
||||
),
|
||||
)
|
||||
if err := rk.Dispatch(ctx, []*unstructured.Unstructured{u}, []apply.ApplyOption{apply.DisableUpdateAnnotation()}, resourcekeeper.MetaOnlyOption{}, resourcekeeper.CreatorOption{Creator: common.DebugResourceCreator}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{
|
||||
"debug": data,
|
||||
}
|
||||
if err := cli.Update(ctx, cm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewContext new workflow context without initialize data.
|
||||
func NewContext(cli client.Client, rk resourcekeeper.ResourceKeeper, app *v1beta1.Application, step string) ContextImpl {
|
||||
return &Context{
|
||||
cli: cli,
|
||||
app: app,
|
||||
step: step,
|
||||
rk: rk,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateContextName generate context name
|
||||
func GenerateContextName(app, step string) string {
|
||||
return fmt.Sprintf("%s-%s-debug", app, step)
|
||||
}
|
||||
85
pkg/workflow/debug/context_test.go
Normal file
85
pkg/workflow/debug/context_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
)
|
||||
|
||||
func TestSetContext(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cli := newCliForTest(&corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: GenerateContextName("test", "step1"),
|
||||
},
|
||||
Data: map[string]string{
|
||||
"debug": "test",
|
||||
},
|
||||
})
|
||||
debugCtx := NewContext(cli, nil, &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
},
|
||||
}, "step1")
|
||||
v, err := value.NewValue(`
|
||||
test: test
|
||||
`, nil, "")
|
||||
r.NoError(err)
|
||||
err = debugCtx.Set(v)
|
||||
r.NoError(err)
|
||||
}
|
||||
|
||||
func newCliForTest(wfCm *corev1.ConfigMap) *test.MockClient {
|
||||
return &test.MockClient{
|
||||
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
o, ok := obj.(*corev1.ConfigMap)
|
||||
if ok {
|
||||
switch key.Name {
|
||||
case GenerateContextName("test", "step1"):
|
||||
if wfCm != nil {
|
||||
*o = *wfCm
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return kerrors.NewNotFound(corev1.Resource("configMap"), o.Name)
|
||||
}
|
||||
}
|
||||
return kerrors.NewNotFound(corev1.Resource("configMap"), key.Name)
|
||||
},
|
||||
MockUpdate: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
|
||||
o, ok := obj.(*corev1.ConfigMap)
|
||||
if ok {
|
||||
if wfCm == nil {
|
||||
return kerrors.NewNotFound(corev1.Resource("configMap"), o.Name)
|
||||
}
|
||||
*wfCm = *o
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -208,10 +208,17 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err
|
||||
}
|
||||
|
||||
exec.tracer = tracer
|
||||
if isDebugMode(taskv) {
|
||||
if debugLog(taskv) {
|
||||
exec.printStep("workflowStepStart", "workflow", "", taskv)
|
||||
defer exec.printStep("workflowStepEnd", "workflow", "", taskv)
|
||||
}
|
||||
if options.Debug != nil {
|
||||
defer func() {
|
||||
if err := options.Debug(exec.wfStatus.Name, taskv); err != nil {
|
||||
tracer.Error(err, "failed to debug")
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err := exec.doSteps(ctx, taskv); err != nil {
|
||||
tracer.Error(err, "do steps")
|
||||
exec.err(ctx, err, StatusReasonExecute)
|
||||
@@ -318,7 +325,7 @@ func (exec *executor) printStep(phase string, provider string, do string, v *val
|
||||
|
||||
// Handle process task-step value by provider and do.
|
||||
func (exec *executor) Handle(ctx wfContext.Context, provider string, do string, v *value.Value) error {
|
||||
if isDebugMode(v) {
|
||||
if debugLog(v) {
|
||||
exec.printStep("stepStart", provider, do, v)
|
||||
defer exec.printStep("stepEnd", provider, do, v)
|
||||
}
|
||||
@@ -330,7 +337,7 @@ func (exec *executor) Handle(ctx wfContext.Context, provider string, do string,
|
||||
}
|
||||
|
||||
func (exec *executor) doSteps(ctx wfContext.Context, v *value.Value) error {
|
||||
do := opTpy(v)
|
||||
do := OpTpy(v)
|
||||
if do != "" && do != "steps" {
|
||||
provider := opProvider(v)
|
||||
if err := exec.Handle(ctx, provider, do, v); err != nil {
|
||||
@@ -356,14 +363,14 @@ func (exec *executor) doSteps(ctx wfContext.Context, v *value.Value) error {
|
||||
|
||||
if isStepList(fieldName) {
|
||||
return false, in.StepByList(func(name string, item *value.Value) (bool, error) {
|
||||
do := opTpy(item)
|
||||
do := OpTpy(item)
|
||||
if do == "" {
|
||||
return false, nil
|
||||
}
|
||||
return false, exec.doSteps(ctx, item)
|
||||
})
|
||||
}
|
||||
do := opTpy(in)
|
||||
do := OpTpy(in)
|
||||
if do == "" {
|
||||
return false, nil
|
||||
}
|
||||
@@ -392,12 +399,13 @@ func isStepList(fieldName string) bool {
|
||||
return strings.HasPrefix(fieldName, "#up_")
|
||||
}
|
||||
|
||||
func isDebugMode(v *value.Value) bool {
|
||||
func debugLog(v *value.Value) bool {
|
||||
debug, _ := v.CueValue().LookupDef("#debug").Bool()
|
||||
return debug
|
||||
}
|
||||
|
||||
func opTpy(v *value.Value) string {
|
||||
// OpTpy get label do
|
||||
func OpTpy(v *value.Value) string {
|
||||
return getLabel(v, "#do")
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ type TaskRunOptions struct {
|
||||
PostStopHooks []TaskPostStopHook
|
||||
GetTracer func(id string, step v1beta1.WorkflowStep) monitorCtx.Context
|
||||
RunSteps func(isDag bool, runners ...TaskRunner) (*common.WorkflowStatus, error)
|
||||
Debug func(step string, v *value.Value) error
|
||||
}
|
||||
|
||||
// TaskPreStartHook run before task execution.
|
||||
|
||||
@@ -36,7 +36,9 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
wfContext "github.com/oam-dev/kubevela/pkg/workflow/context"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/debug"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/recorder"
|
||||
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
|
||||
)
|
||||
@@ -68,11 +70,13 @@ type workflow struct {
|
||||
app *oamcore.Application
|
||||
cli client.Client
|
||||
wfCtx wfContext.Context
|
||||
rk resourcekeeper.ResourceKeeper
|
||||
dagMode bool
|
||||
debug bool
|
||||
}
|
||||
|
||||
// NewWorkflow returns a Workflow implementation.
|
||||
func NewWorkflow(app *oamcore.Application, cli client.Client, mode common.WorkflowMode) Workflow {
|
||||
func NewWorkflow(app *oamcore.Application, cli client.Client, mode common.WorkflowMode, debug bool, rk resourcekeeper.ResourceKeeper) Workflow {
|
||||
dagMode := false
|
||||
if mode == common.WorkflowModeDAG {
|
||||
dagMode = true
|
||||
@@ -81,6 +85,8 @@ func NewWorkflow(app *oamcore.Application, cli client.Client, mode common.Workfl
|
||||
app: app,
|
||||
cli: cli,
|
||||
dagMode: dagMode,
|
||||
debug: debug,
|
||||
rk: rk,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +176,9 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
|
||||
monitorCtx: ctx,
|
||||
app: w.app,
|
||||
wfCtx: wfCtx,
|
||||
cli: w.cli,
|
||||
debug: w.debug,
|
||||
rk: w.rk,
|
||||
}
|
||||
|
||||
err = e.run(taskRunners)
|
||||
@@ -436,13 +445,23 @@ func (e *engine) todoByIndex(taskRunners []wfTypes.TaskRunner) []wfTypes.TaskRun
|
||||
func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error {
|
||||
wfCtx := e.wfCtx
|
||||
for _, runner := range taskRunners {
|
||||
status, operation, err := runner.Run(wfCtx, &wfTypes.TaskRunOptions{
|
||||
options := &wfTypes.TaskRunOptions{
|
||||
GetTracer: func(id string, stepStatus oamcore.WorkflowStep) monitorContext.Context {
|
||||
return e.monitorCtx.Fork(id, monitorContext.DurationMetric(func(v float64) {
|
||||
metrics.StepDurationHistogram.WithLabelValues("application", stepStatus.Type).Observe(v)
|
||||
}))
|
||||
},
|
||||
})
|
||||
}
|
||||
if e.debug {
|
||||
options.Debug = func(step string, v *value.Value) error {
|
||||
debugContext := debug.NewContext(e.cli, e.rk, e.app, step)
|
||||
if err := debugContext.Set(v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
status, operation, err := runner.Run(wfCtx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -479,10 +498,13 @@ type engine struct {
|
||||
dagMode bool
|
||||
failedAfterRetries bool
|
||||
waiting bool
|
||||
debug bool
|
||||
status *common.WorkflowStatus
|
||||
monitorCtx monitorContext.Context
|
||||
wfCtx wfContext.Context
|
||||
app *oamcore.Application
|
||||
cli client.Client
|
||||
rk resourcekeeper.ResourceKeeper
|
||||
}
|
||||
|
||||
func (e *engine) isDag() bool {
|
||||
|
||||
@@ -69,7 +69,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -111,7 +111,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
})
|
||||
|
||||
app.Status.Workflow = workflowStatus
|
||||
wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateTerminated))
|
||||
@@ -161,7 +161,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -205,7 +205,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx = monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf = NewWorkflow(app, k8sClient, common.WorkflowModeDAG)
|
||||
wf = NewWorkflow(app, k8sClient, common.WorkflowModeDAG, false, nil)
|
||||
state, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -246,7 +246,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG, false, nil)
|
||||
_, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
@@ -310,7 +310,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -386,7 +386,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -428,7 +428,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
@@ -452,7 +452,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
It("skip workflow", func() {
|
||||
app, runners := makeTestCase([]oamcore.WorkflowStep{})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateFinished))
|
||||
@@ -474,7 +474,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
pending = true
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG, false, nil)
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -541,7 +541,7 @@ var _ = Describe("Test Workflow", func() {
|
||||
},
|
||||
})
|
||||
ctx := monitorContext.NewTraceContext(context.Background(), "test-app")
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep)
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil)
|
||||
state, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing))
|
||||
|
||||
@@ -78,7 +78,7 @@ func NewCommand() *cobra.Command {
|
||||
// Getting Start
|
||||
NewEnvCommand(commandArgs, "3", ioStream),
|
||||
NewInitCommand(commandArgs, "2", ioStream),
|
||||
NewUpCommand(f, "1"),
|
||||
NewUpCommand(f, "1", commandArgs, ioStream),
|
||||
NewCapabilityShowCommand(commandArgs, ioStream),
|
||||
|
||||
// Manage Apps
|
||||
@@ -96,6 +96,9 @@ func NewCommand() *cobra.Command {
|
||||
NewWorkflowCommand(commandArgs, ioStream),
|
||||
ClusterCommandGroup(commandArgs, ioStream),
|
||||
|
||||
// Debug
|
||||
NewDebugCommand(commandArgs, ioStream),
|
||||
|
||||
// Extension
|
||||
NewAddonCommand(commandArgs, "9", ioStream),
|
||||
NewUISchemaCommand(commandArgs, "8", ioStream),
|
||||
|
||||
430
references/cli/debug.go
Normal file
430
references/cli/debug.go
Normal file
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/FogDong/uitable"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
|
||||
"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/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/debug"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/tasks/custom"
|
||||
"github.com/oam-dev/kubevela/references/appfile"
|
||||
)
|
||||
|
||||
type debugOpts struct {
|
||||
step string
|
||||
focus string
|
||||
errMsg string
|
||||
// TODO: (fog) add watch flag
|
||||
// watch bool
|
||||
}
|
||||
|
||||
// NewDebugCommand create `debug` command
|
||||
func NewDebugCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
ctx := context.Background()
|
||||
dOpts := &debugOpts{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug",
|
||||
Aliases: []string{"debug"},
|
||||
Short: "Debug running application",
|
||||
Long: "Debug running application with debug policy.",
|
||||
Example: `vela debug <application-name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("must specify application name")
|
||||
}
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dOpts.debugApplication(ctx, c, app, ioStreams)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&dOpts.step, "step", "s", "", "specify the step to debug")
|
||||
cmd.Flags().StringVarP(&dOpts.focus, "focus", "f", "", "specify the focus value to debug")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (d *debugOpts) debugApplication(ctx context.Context, c common.Args, app *v1beta1.Application, ioStreams cmdutil.IOStreams) error {
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := c.GetPackageDiscover()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, opts, errMap := d.getDebugOptions(app)
|
||||
if s == "workflow steps" {
|
||||
if d.step == "" {
|
||||
prompt := &survey.Select{
|
||||
Message: fmt.Sprintf("Select the %s to debug:", s),
|
||||
Options: opts,
|
||||
}
|
||||
var step string
|
||||
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select %s: %w", s, err)
|
||||
}
|
||||
d.step = unwrapStepName(step)
|
||||
d.errMsg = errMap[d.step]
|
||||
}
|
||||
|
||||
// debug workflow steps
|
||||
rawValue, err := d.getDebugRawValue(ctx, cli, pd, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.handleCueSteps(rawValue, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// dry run components
|
||||
dm, err := discoverymapper.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dryRunOpt := dryrun.NewDryRunOption(cli, config, dm, pd, []oam.Object{})
|
||||
comps, err := dryRunOpt.ExecuteDryRun(ctx, app)
|
||||
if err != nil {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
return nil
|
||||
}
|
||||
if err := d.debugComponents(opts, comps, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) debugComponents(compList []string, comps []*types.ComponentManifest, ioStreams cmdutil.IOStreams) error {
|
||||
opts := compList
|
||||
all := color.YellowString("all fields")
|
||||
exit := color.CyanString("exit debug mode")
|
||||
opts = append(opts, all, exit)
|
||||
|
||||
var components = make(map[string]*unstructured.Unstructured)
|
||||
var traits = make(map[string][]*unstructured.Unstructured)
|
||||
for _, comp := range comps {
|
||||
components[comp.Name] = comp.StandardWorkload
|
||||
traits[comp.Name] = comp.Traits
|
||||
}
|
||||
|
||||
if d.step != "" {
|
||||
return renderComponents(d.step, components[d.step], traits[d.step], ioStreams)
|
||||
}
|
||||
for {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select the components to debug:",
|
||||
Options: opts,
|
||||
}
|
||||
var step string
|
||||
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select components: %w", err)
|
||||
}
|
||||
|
||||
if step == exit {
|
||||
break
|
||||
}
|
||||
if step == all {
|
||||
for _, step := range compList {
|
||||
step = unwrapStepName(step)
|
||||
if err := renderComponents(step, components[step], traits[step], ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
step = unwrapStepName(step)
|
||||
if err := renderComponents(step, components[step], traits[step], ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderComponents(compName string, comp *unstructured.Unstructured, traits []*unstructured.Unstructured, ioStreams cmdutil.IOStreams) error {
|
||||
ioStreams.Info(color.CyanString("\n▫️ %s", compName))
|
||||
result, err := yaml.Marshal(comp)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "marshal result for component "+compName+" object in yaml format")
|
||||
}
|
||||
ioStreams.Info(string(result), "\n")
|
||||
for _, t := range traits {
|
||||
result, err := yaml.Marshal(t)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "marshal result for component "+compName+" object in yaml format")
|
||||
}
|
||||
ioStreams.Info(string(result), "\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) getDebugOptions(app *v1beta1.Application) (string, []string, map[string]string) {
|
||||
s := "components"
|
||||
stepList := make([]string, 0)
|
||||
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0 {
|
||||
s = "workflow steps"
|
||||
}
|
||||
errMap := make(map[string]string)
|
||||
switch {
|
||||
case app.Status.Workflow != nil:
|
||||
for _, step := range app.Status.Workflow.Steps {
|
||||
stepName := step.Name
|
||||
switch step.Phase {
|
||||
case apicommon.WorkflowStepPhaseSucceeded:
|
||||
stepName = emojiSucceed + step.Name
|
||||
case apicommon.WorkflowStepPhaseFailed:
|
||||
stepName = emojiFail + step.Name
|
||||
errMap[step.Name] = step.Message
|
||||
default:
|
||||
}
|
||||
stepList = append(stepList, stepName)
|
||||
}
|
||||
case app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0:
|
||||
for _, step := range app.Spec.Workflow.Steps {
|
||||
stepList = append(stepList, step.Name)
|
||||
}
|
||||
default:
|
||||
for _, c := range app.Spec.Components {
|
||||
stepList = append(stepList, c.Name)
|
||||
}
|
||||
}
|
||||
return s, stepList, errMap
|
||||
}
|
||||
|
||||
func unwrapStepName(step string) string {
|
||||
if strings.HasPrefix(step, emojiSucceed) {
|
||||
return strings.TrimPrefix(step, emojiSucceed)
|
||||
}
|
||||
if strings.HasPrefix(step, emojiFail) {
|
||||
return strings.TrimPrefix(step, emojiFail)
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
func (d *debugOpts) getDebugRawValue(ctx context.Context, cli client.Client, pd *packages.PackageDiscover, app *v1beta1.Application) (*value.Value, error) {
|
||||
debugCM := &corev1.ConfigMap{}
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: debug.GenerateContextName(app.Name, d.step), Namespace: app.Namespace}, debugCM); err != nil {
|
||||
return nil, fmt.Errorf("failed to get debug configmap: %w", err)
|
||||
}
|
||||
|
||||
if debugCM.Data == nil || debugCM.Data["debug"] == "" {
|
||||
return nil, fmt.Errorf("debug configmap is empty")
|
||||
}
|
||||
v, err := value.NewValue(debugCM.Data["debug"], pd, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse debug configmap: %w", err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) handleCueSteps(v *value.Value, ioStreams cmdutil.IOStreams) error {
|
||||
if d.focus != "" {
|
||||
f, err := v.LookupValue(strings.Split(d.focus, ".")...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioStreams.Info(color.New(color.FgCyan).Sprint("\n", d.focus, "\n"))
|
||||
rendered, err := renderFields(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioStreams.Info(rendered, "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := d.separateBySteps(v, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) separateBySteps(v *value.Value, ioStreams cmdutil.IOStreams) error {
|
||||
fieldMap := make(map[string]*value.Value)
|
||||
fieldList := make([]string, 0)
|
||||
if err := v.StepByFields(func(fieldName string, in *value.Value) (bool, error) {
|
||||
if in.CueValue().IncompleteKind() == cue.BottomKind {
|
||||
errInfo, err := sets.ToString(in.CueValue())
|
||||
if err != nil {
|
||||
errInfo = "value is _|_"
|
||||
}
|
||||
return true, errors.New(errInfo + "(bottom kind)")
|
||||
}
|
||||
fieldList = append(fieldList, fieldName)
|
||||
fieldMap[fieldName] = in
|
||||
return false, nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to parse debug configmap by field: %w", err)
|
||||
}
|
||||
|
||||
errStep := ""
|
||||
if d.errMsg != "" {
|
||||
s := strings.Split(d.errMsg, ":")
|
||||
errStep = strings.TrimPrefix(s[0], "step ")
|
||||
}
|
||||
opts := make([]string, 0)
|
||||
for _, field := range fieldList {
|
||||
if field == errStep {
|
||||
opts = append(opts, emojiFail+field)
|
||||
} else {
|
||||
opts = append(opts, emojiSucceed+field)
|
||||
}
|
||||
}
|
||||
all := color.YellowString("all fields")
|
||||
exit := color.CyanString("exit debug mode")
|
||||
opts = append(opts, all, exit)
|
||||
for {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select the field to debug: ",
|
||||
Options: opts,
|
||||
}
|
||||
var field string
|
||||
err := survey.AskOne(prompt, &field, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select: %w", err)
|
||||
}
|
||||
if field == exit {
|
||||
break
|
||||
}
|
||||
if field == all {
|
||||
for _, field := range fieldList {
|
||||
ioStreams.Info(color.CyanString("\n▫️ %s", field))
|
||||
rendered, err := renderFields(fieldMap[field])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioStreams.Info(rendered, "\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
field = unwrapStepName(field)
|
||||
ioStreams.Info(color.CyanString("\n▫️ %s", field))
|
||||
rendered, err := renderFields(fieldMap[field])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioStreams.Info(rendered, "\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderFields(v *value.Value) (string, error) {
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = 200
|
||||
table.Wrap = true
|
||||
i := 0
|
||||
|
||||
if err := v.StepByFields(func(fieldName string, in *value.Value) (bool, error) {
|
||||
if custom.OpTpy(in) != "" {
|
||||
rendered, err := renderFields(in)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
i++
|
||||
key := fmt.Sprintf("%v.%s", i, fieldName)
|
||||
if !strings.Contains(fieldName, "#") {
|
||||
if err := v.FillObject(in, fieldName); err != nil {
|
||||
renderValuesInRow(table, key, rendered, false)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
renderValuesInRow(table, key, rendered, true)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
vStr, err := in.String()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
i++
|
||||
key := fmt.Sprintf("%v.%s", i, fieldName)
|
||||
if !strings.Contains(fieldName, "#") {
|
||||
if err := v.FillObject(in, fieldName); err != nil {
|
||||
renderValuesInRow(table, key, vStr, false)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
renderValuesInRow(table, key, vStr, true)
|
||||
return false, nil
|
||||
}); err != nil {
|
||||
vStr, serr := v.String()
|
||||
if serr != nil {
|
||||
return "", serr
|
||||
}
|
||||
if strings.Contains(err.Error(), "(type string) as struct") {
|
||||
return strings.TrimSpace(vStr), nil
|
||||
}
|
||||
}
|
||||
|
||||
return table.String(), nil
|
||||
}
|
||||
|
||||
func renderValuesInRow(table *uitable.Table, k, v string, isPass bool) {
|
||||
v = strings.TrimSpace(v)
|
||||
if isPass {
|
||||
if strings.Contains(k, "#do") || strings.Contains(k, "#provider") {
|
||||
k = color.YellowString("%s:", k)
|
||||
} else {
|
||||
k = color.GreenString("%s:", k)
|
||||
}
|
||||
} else {
|
||||
k = color.RedString("%s:", k)
|
||||
v = color.RedString("%s%s", emojiFail, v)
|
||||
}
|
||||
if v == `"steps"` {
|
||||
v = color.BlueString(v)
|
||||
}
|
||||
|
||||
table.AddRow(k, v)
|
||||
}
|
||||
164
references/cli/debug_test.go
Normal file
164
references/cli/debug_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
func TestDebugApplicationWithWorkflow(t *testing.T) {
|
||||
c := initArgs()
|
||||
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
ctx := context.TODO()
|
||||
|
||||
testCases := map[string]struct {
|
||||
app *v1beta1.Application
|
||||
cm *corev1.ConfigMap
|
||||
step string
|
||||
focus string
|
||||
expectedErr string
|
||||
}{
|
||||
"no debug config map": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "no-debug-config-map",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
step: "test-wf1",
|
||||
focus: "test",
|
||||
expectedErr: "failed to get debug configmap",
|
||||
},
|
||||
"config map no data": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-no-data",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-no-data-test-wf1-debug",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
step: "test-wf1",
|
||||
focus: "test",
|
||||
expectedErr: "debug configmap is empty",
|
||||
},
|
||||
"config map error data": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-error-data",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-error-data-test-wf1-debug",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"debug": "error",
|
||||
},
|
||||
},
|
||||
step: "test-wf1",
|
||||
focus: "test",
|
||||
expectedErr: "failed to parse debug configmap",
|
||||
},
|
||||
"success": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "success",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "success-test-wf1-debug",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"debug": `
|
||||
test: test
|
||||
`,
|
||||
},
|
||||
},
|
||||
step: "test-wf1",
|
||||
focus: "test",
|
||||
},
|
||||
"success-component": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "success",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "test-component",
|
||||
Type: "worker",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
}},
|
||||
},
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
step: "test-component",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
d := &debugOpts{
|
||||
step: tc.step,
|
||||
focus: tc.focus,
|
||||
}
|
||||
client, err := c.GetClient()
|
||||
r.NoError(err)
|
||||
if tc.cm != nil {
|
||||
err := client.Create(ctx, tc.cm)
|
||||
r.NoError(err)
|
||||
}
|
||||
err = d.debugApplication(ctx, c, tc.app, ioStream)
|
||||
if tc.expectedErr != "" {
|
||||
r.Contains(err.Error(), tc.expectedErr)
|
||||
return
|
||||
}
|
||||
r.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -49,6 +51,7 @@ type UpCommandOptions struct {
|
||||
File string
|
||||
PublishVersion string
|
||||
RevisionName string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// Complete fill the args for vela up
|
||||
@@ -171,6 +174,12 @@ func (opt *UpCommandOptions) deployExistingApp(f velacmd.Factory, cmd *cobra.Com
|
||||
return errors.Errorf("current PublishVersion is %s", publishVersion)
|
||||
}
|
||||
oam.SetPublishVersion(app, opt.PublishVersion)
|
||||
if opt.Debug {
|
||||
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
|
||||
Name: "debug",
|
||||
Type: "debug",
|
||||
})
|
||||
}
|
||||
return cli.Update(ctx, app)
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -208,6 +217,13 @@ func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *c
|
||||
if opt.PublishVersion != "" {
|
||||
oam.SetPublishVersion(&app, opt.PublishVersion)
|
||||
}
|
||||
opt.AppName = app.Name
|
||||
if opt.Debug {
|
||||
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
|
||||
Name: "debug",
|
||||
Type: "debug",
|
||||
})
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, cli)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -243,7 +259,7 @@ var (
|
||||
)
|
||||
|
||||
// NewUpCommand will create command for applying an AppFile
|
||||
func NewUpCommand(f velacmd.Factory, order string) *cobra.Command {
|
||||
func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream util.IOStreams) *cobra.Command {
|
||||
o := &UpCommandOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "up",
|
||||
@@ -267,11 +283,19 @@ func NewUpCommand(f velacmd.Factory, order string) *cobra.Command {
|
||||
o.Complete(f, cmd, args)
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run(f, cmd))
|
||||
if o.Debug {
|
||||
dOpts := &debugOpts{}
|
||||
cli := f.Client()
|
||||
app := &v1beta1.Application{}
|
||||
cmdutil.CheckErr(cli.Get(cmd.Context(), apitypes.NamespacedName{Name: o.AppName, Namespace: o.Namespace}, app))
|
||||
cmdutil.CheckErr(dOpts.debugApplication(context.Background(), c, app, ioStream))
|
||||
}
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&o.File, "file", "f", o.File, "The file path for appfile or application. It could be a remote url.")
|
||||
cmd.Flags().StringVarP(&o.PublishVersion, "publish-version", "v", o.PublishVersion, "The publish version for deploying application.")
|
||||
cmd.Flags().StringVarP(&o.RevisionName, "revision", "r", o.RevisionName, "The revision to use for deploying the application, if empty, the current application configuration will be used.")
|
||||
cmd.Flags().BoolVarP(&o.Debug, "debug", "", o.Debug, "Enable debug mode for application")
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
|
||||
"revision",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/common"
|
||||
)
|
||||
|
||||
@@ -128,7 +129,7 @@ spec:
|
||||
}))
|
||||
|
||||
var buf bytes.Buffer
|
||||
cmd := NewUpCommand(velacmd.NewDefaultFactory(args.GetClient), "")
|
||||
cmd := NewUpCommand(velacmd.NewDefaultFactory(args.GetClient), "", args, util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
|
||||
cmd.SetArgs([]string{})
|
||||
cmd.SetOut(&buf)
|
||||
cmd.SetErr(&buf)
|
||||
|
||||
@@ -203,7 +203,7 @@ var _ = Describe("Test velaQL rest api", func() {
|
||||
}, 2*time.Minute, 3*time.Microsecond).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test collect pod from helmRelease", func() {
|
||||
PIt("Test collect pod from helmRelease", func() {
|
||||
appWithHelm := new(v1beta1.Application)
|
||||
Expect(yaml.Unmarshal([]byte(podInfoApp), appWithHelm)).Should(BeNil())
|
||||
req := apiv1.ApplicationRequest{
|
||||
|
||||
Reference in New Issue
Block a user