This repository has been archived by the owner on Jan 19, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 489
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change adds a preview of an editor for object YAML Signed-off-by: bryanl <bryanliles@gmail.com>
- Loading branch information
Showing
24 changed files
with
749 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
* Copyright (c) 2020 the Octant contributors. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package octant | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"time" | ||
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
|
||
"github.com/vmware-tanzu/octant/internal/log" | ||
"github.com/vmware-tanzu/octant/internal/util/kubernetes" | ||
"github.com/vmware-tanzu/octant/pkg/action" | ||
"github.com/vmware-tanzu/octant/pkg/store" | ||
) | ||
|
||
// ObjectUpdateFromPayload loads an object from the payload. | ||
// The object source in YAML format should exist in the `update` key. | ||
func ObjectUpdateFromPayload(payload action.Payload) (*unstructured.Unstructured, error) { | ||
s, err := payload.String("update") | ||
if err != nil { | ||
return nil, fmt.Errorf("read object source from payload: %w", err) | ||
} | ||
|
||
object, err := kubernetes.ReadObject(strings.NewReader(s)) | ||
if err != nil { | ||
return nil, fmt.Errorf("read object from payload: %w", err) | ||
} | ||
|
||
return object, nil | ||
} | ||
|
||
type ObjectUpdaterDispatcherOption func(dispatcher *ObjectUpdaterDispatcher) | ||
|
||
// ObjectUpdaterDispatcher is an action that updates an object. | ||
type ObjectUpdaterDispatcher struct { | ||
store store.Store | ||
objectFromPayload func(payload action.Payload) (*unstructured.Unstructured, error) | ||
} | ||
|
||
var _ action.Dispatcher = &ObjectUpdaterDispatcher{} | ||
|
||
// NewObjectUpdaterDispatcher creates an instance of ObjectUpdaterDispatcher. | ||
func NewObjectUpdaterDispatcher(objectStore store.Store, options ...ObjectUpdaterDispatcherOption) *ObjectUpdaterDispatcher { | ||
o := ObjectUpdaterDispatcher{ | ||
store: objectStore, | ||
objectFromPayload: ObjectUpdateFromPayload, | ||
} | ||
|
||
for _, option := range options { | ||
option(&o) | ||
} | ||
|
||
return &o | ||
} | ||
|
||
// ActionName returns the action name this dispatcher responds to. | ||
func (o ObjectUpdaterDispatcher) ActionName() string { | ||
return ActionUpdateObject | ||
} | ||
|
||
// Handle updates an object using a payload if possible. | ||
func (o ObjectUpdaterDispatcher) Handle(ctx context.Context, alerter action.Alerter, payload action.Payload) error { | ||
logger := log.From(ctx) | ||
expiration := time.Now().Add(10 * time.Second) | ||
|
||
object, err := o.objectFromPayload(payload) | ||
if err != nil { | ||
sendAlert( | ||
alerter, | ||
action.AlertTypeError, | ||
fmt.Sprintf("load object from payload: %v", err.Error()), | ||
&expiration) | ||
return nil | ||
} | ||
|
||
key, _ := store.KeyFromPayload(payload) | ||
err = o.store.Update(ctx, key, func(u *unstructured.Unstructured) error { | ||
if object.GetAPIVersion() != u.GetAPIVersion() { | ||
return fmt.Errorf("object API version cannot be updated") | ||
} | ||
if object.GetKind() != u.GetKind() { | ||
return fmt.Errorf("object kind cannot be updated") | ||
} | ||
if object.GetName() != u.GetName() { | ||
return fmt.Errorf("object name cannot be updated") | ||
} | ||
|
||
delete(object.Object, "status") | ||
|
||
for k := range object.Object { | ||
u.Object[k] = object.Object[k] | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
sendAlert( | ||
alerter, | ||
action.AlertTypeError, | ||
fmt.Sprintf("update object: %s", err.Error()), | ||
&expiration) | ||
|
||
logger.WithErr(err).Errorf("update object") | ||
return nil | ||
} | ||
|
||
successMessage := fmt.Sprintf("Updated %s (%s) %s in %s", | ||
object.GetKind(), | ||
object.GetAPIVersion(), | ||
object.GetName(), | ||
object.GetNamespace()) | ||
sendAlert(alerter, action.AlertTypeInfo, successMessage, &expiration) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* | ||
* Copyright (c) 2020 the Octant contributors. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package octant | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/require" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
|
||
"github.com/vmware-tanzu/octant/internal/testutil" | ||
"github.com/vmware-tanzu/octant/internal/util/kubernetes" | ||
"github.com/vmware-tanzu/octant/pkg/action" | ||
actionFake "github.com/vmware-tanzu/octant/pkg/action/fake" | ||
"github.com/vmware-tanzu/octant/pkg/store" | ||
storeFake "github.com/vmware-tanzu/octant/pkg/store/fake" | ||
) | ||
|
||
func TestObjectUpdateFromPayload(t *testing.T) { | ||
pod := testutil.ToUnstructured(t, testutil.CreatePod("pod")) | ||
podS, err := kubernetes.SerializeToString(pod) | ||
require.NoError(t, err) | ||
|
||
tests := []struct { | ||
name string | ||
payload action.Payload | ||
wanted *unstructured.Unstructured | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "in general", | ||
payload: action.Payload{ | ||
"update": podS, | ||
}, | ||
wanted: pod, | ||
}, | ||
{ | ||
name: "no update in payload", | ||
payload: action.Payload{}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "upload is not yaml", | ||
payload: action.Payload{ | ||
"update": "<<<", | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
actual, err := ObjectUpdateFromPayload(test.payload) | ||
if test.wantErr { | ||
require.Error(t, err) | ||
return | ||
} | ||
require.NoError(t, err) | ||
require.Equal(t, test.wanted, actual) | ||
}) | ||
} | ||
} | ||
|
||
func TestObjectUpdaterDispatcher_Handle(t *testing.T) { | ||
pod := testutil.ToUnstructured(t, testutil.CreatePod("pod")) | ||
podKey, err := store.KeyFromObject(pod) | ||
require.NoError(t, err) | ||
podPayload := action.Payload{ | ||
"namespace": pod.GetNamespace(), | ||
"apiVersion": pod.GetAPIVersion(), | ||
"kind": pod.GetKind(), | ||
"name": pod.GetName(), | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
payload action.Payload | ||
objectFromPayload func(action.Payload) (*unstructured.Unstructured, error) | ||
initStore func(ctrl *gomock.Controller) *storeFake.MockStore | ||
initAlerter func(ctrl *gomock.Controller) *actionFake.MockAlerter | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "in general", | ||
payload: podPayload, | ||
objectFromPayload: func(payload action.Payload) (*unstructured.Unstructured, error) { | ||
return pod, nil | ||
}, | ||
initStore: func(ctrl *gomock.Controller) *storeFake.MockStore { | ||
objectStore := storeFake.NewMockStore(ctrl) | ||
objectStore.EXPECT(). | ||
Update(gomock.Any(), podKey, gomock.Any()).Return(nil) | ||
|
||
return objectStore | ||
}, | ||
initAlerter: func(ctrl *gomock.Controller) *actionFake.MockAlerter { | ||
alerter := actionFake.NewMockAlerter(ctrl) | ||
alerter.EXPECT(). | ||
SendAlert(gomock.Any()). | ||
DoAndReturn(func(alert action.Alert) { | ||
require.Equal(t, action.AlertTypeInfo, alert.Type) | ||
}) | ||
return alerter | ||
}, | ||
}, | ||
{ | ||
name: "unable to load object", | ||
payload: podPayload, | ||
objectFromPayload: func(payload action.Payload) (*unstructured.Unstructured, error) { | ||
return nil, fmt.Errorf("error") | ||
}, | ||
initStore: func(ctrl *gomock.Controller) *storeFake.MockStore { | ||
objectStore := storeFake.NewMockStore(ctrl) | ||
return objectStore | ||
}, | ||
initAlerter: func(ctrl *gomock.Controller) *actionFake.MockAlerter { | ||
alerter := actionFake.NewMockAlerter(ctrl) | ||
alerter.EXPECT(). | ||
SendAlert(gomock.Any()). | ||
DoAndReturn(func(alert action.Alert) { | ||
require.Equal(t, action.AlertTypeError, alert.Type) | ||
}) | ||
return alerter | ||
}, | ||
}, | ||
{ | ||
name: "update failed", | ||
payload: podPayload, | ||
objectFromPayload: func(payload action.Payload) (*unstructured.Unstructured, error) { | ||
return pod, nil | ||
}, | ||
initStore: func(ctrl *gomock.Controller) *storeFake.MockStore { | ||
objectStore := storeFake.NewMockStore(ctrl) | ||
objectStore.EXPECT(). | ||
Update(gomock.Any(), podKey, gomock.Any()).Return(fmt.Errorf("error")) | ||
|
||
return objectStore | ||
}, | ||
initAlerter: func(ctrl *gomock.Controller) *actionFake.MockAlerter { | ||
alerter := actionFake.NewMockAlerter(ctrl) | ||
alerter.EXPECT(). | ||
SendAlert(gomock.Any()). | ||
DoAndReturn(func(alert action.Alert) { | ||
require.Equal(t, action.AlertTypeError, alert.Type) | ||
}) | ||
return alerter | ||
}, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
objectStore := test.initStore(ctrl) | ||
alerter := test.initAlerter(ctrl) | ||
|
||
o := NewObjectUpdaterDispatcher(objectStore, | ||
func(dispatcher *ObjectUpdaterDispatcher) { | ||
dispatcher.objectFromPayload = test.objectFromPayload | ||
}) | ||
err := o.Handle(ctx, alerter, test.payload) | ||
if test.wantErr { | ||
require.Error(t, err) | ||
return | ||
} | ||
require.NoError(t, err) | ||
}) | ||
} | ||
} |
Oops, something went wrong.