Add printers for events
It will allow users to see what's happening during phase execution. Change-Id: I31c4f98ccbd811ac88c7eabdf5efbe6ffaae6a41 Relates-To: #339
This commit is contained in:
parent
d649d9d450
commit
feadd70f34
@ -15,6 +15,7 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
applyevent "sigs.k8s.io/cli-utils/pkg/apply/event"
|
applyevent "sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||||
@ -56,6 +57,87 @@ type Event struct {
|
|||||||
GenericContainerEvent GenericContainerEvent
|
GenericContainerEvent GenericContainerEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GenericEvent generalized type for custom events
|
||||||
|
type GenericEvent struct {
|
||||||
|
Type string
|
||||||
|
Operation string
|
||||||
|
Message string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapTypeToEvent = map[Type]string{
|
||||||
|
ClusterctlType: "ClusterctlEvent",
|
||||||
|
IsogenType: "IsogenEvent",
|
||||||
|
BootstrapType: "BootstrapEvent",
|
||||||
|
GenericContainerType: "GenericContainerEvent",
|
||||||
|
}
|
||||||
|
|
||||||
|
var unknownEventType = map[Type]string{
|
||||||
|
ApplierType: "ApplierType",
|
||||||
|
ErrorType: "ErrorType",
|
||||||
|
StatusPollerType: "StatusPollerType",
|
||||||
|
WaitType: "WaitType",
|
||||||
|
}
|
||||||
|
|
||||||
|
var isogenOperationToString = map[IsogenOperation]string{
|
||||||
|
IsogenStart: "IsogenStart",
|
||||||
|
IsogenValidation: "IsogenValidation",
|
||||||
|
IsogenEnd: "IsogenEnd",
|
||||||
|
}
|
||||||
|
|
||||||
|
var clusterctlOperationToString = map[ClusterctlOperation]string{
|
||||||
|
ClusterctlInitStart: "ClusterctlInitStart",
|
||||||
|
ClusterctlInitEnd: "ClusterctlInitEnd",
|
||||||
|
ClusterctlMoveStart: "ClusterctlMoveStart",
|
||||||
|
ClusterctlMoveEnd: "ClusterctlMoveEnd",
|
||||||
|
}
|
||||||
|
|
||||||
|
var bootstrapOperationToString = map[BootstrapOperation]string{
|
||||||
|
BootstrapStart: "BootstrapStart",
|
||||||
|
BootstrapDryRun: "BootstrapDryRun",
|
||||||
|
BootstrapValidation: "BootstrapValidation",
|
||||||
|
BootstrapRun: "BootstrapRun",
|
||||||
|
BootstrapEnd: "BootstrapEnd",
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericContainerOperationToString = map[GenericContainerOperation]string{
|
||||||
|
GenericContainerStart: "GenericContainerStart",
|
||||||
|
GenericContainerStop: "GenericContainerStop",
|
||||||
|
}
|
||||||
|
|
||||||
|
//Normalize cast Event to GenericEvent type
|
||||||
|
func Normalize(e Event) GenericEvent {
|
||||||
|
var eventType string
|
||||||
|
if t, exists := mapTypeToEvent[e.Type]; exists {
|
||||||
|
eventType = t
|
||||||
|
} else {
|
||||||
|
eventType = fmt.Sprintf("Unknown event type: %v", unknownEventType[e.Type])
|
||||||
|
}
|
||||||
|
|
||||||
|
var operation, message string
|
||||||
|
switch e.Type {
|
||||||
|
case ClusterctlType:
|
||||||
|
operation = clusterctlOperationToString[e.ClusterctlEvent.Operation]
|
||||||
|
message = e.ClusterctlEvent.Message
|
||||||
|
case IsogenType:
|
||||||
|
operation = isogenOperationToString[e.IsogenEvent.Operation]
|
||||||
|
message = e.IsogenEvent.Message
|
||||||
|
case BootstrapType:
|
||||||
|
operation = bootstrapOperationToString[e.BootstrapEvent.Operation]
|
||||||
|
message = e.BootstrapEvent.Message
|
||||||
|
case GenericContainerType:
|
||||||
|
operation = genericContainerOperationToString[e.GenericContainerEvent.Operation]
|
||||||
|
message = e.GenericContainerEvent.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericEvent{
|
||||||
|
Type: eventType,
|
||||||
|
Operation: operation,
|
||||||
|
Message: message,
|
||||||
|
Timestamp: e.Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewEvent create new event with timestamp
|
// NewEvent create new event with timestamp
|
||||||
func NewEvent() Event {
|
func NewEvent() Event {
|
||||||
return Event{
|
return Event{
|
||||||
|
61
pkg/events/events_test.go
Normal file
61
pkg/events/events_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 events_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sourceEvent events.Event
|
||||||
|
expectedEvent events.GenericEvent
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Unknow event type",
|
||||||
|
sourceEvent: events.NewEvent().WithErrorEvent(events.ErrorEvent{}),
|
||||||
|
expectedEvent: events.GenericEvent{
|
||||||
|
Type: "Unknown event type: ErrorType",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Clusterctl event type",
|
||||||
|
sourceEvent: events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{
|
||||||
|
Operation: events.ClusterctlInitStart,
|
||||||
|
Message: "Clusterctl init start",
|
||||||
|
}),
|
||||||
|
expectedEvent: events.GenericEvent{
|
||||||
|
Type: "ClusterctlEvent",
|
||||||
|
Message: "Clusterctl init start",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ge := events.Normalize(tt.sourceEvent)
|
||||||
|
assert.Equal(t, tt.expectedEvent.Type, ge.Type)
|
||||||
|
if tt.expectedEvent.Type != "Unknown event type: ErrorType" {
|
||||||
|
assert.Equal(t, tt.expectedEvent.Message, ge.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
69
pkg/events/printers.go
Normal file
69
pkg/events/printers.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// YAMLPrinter format for event printer output
|
||||||
|
YAMLPrinter = "yaml"
|
||||||
|
// JSONPrinter format for event printer output
|
||||||
|
JSONPrinter = "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGenericPrinter returns event printer
|
||||||
|
func NewGenericPrinter(writer io.Writer, formatterType string) GenericPrinter {
|
||||||
|
var formatter func(o interface{}) ([]byte, error)
|
||||||
|
switch formatterType {
|
||||||
|
case YAMLPrinter:
|
||||||
|
formatter = yaml.Marshal
|
||||||
|
case JSONPrinter:
|
||||||
|
formatter = json.Marshal
|
||||||
|
default:
|
||||||
|
log.Fatal("Event printer received wrong type of event formatter")
|
||||||
|
}
|
||||||
|
return GenericPrinter{
|
||||||
|
formatter: formatter,
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericPrinter object represents event printer
|
||||||
|
type GenericPrinter struct {
|
||||||
|
formatter func(interface{}) ([]byte, error)
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintEvent write event details
|
||||||
|
func (p GenericPrinter) PrintEvent(ge GenericEvent) error {
|
||||||
|
data, err := p.formatter(map[string]interface{}{
|
||||||
|
"Type": ge.Type,
|
||||||
|
"Operation": ge.Operation,
|
||||||
|
"Message": ge.Message,
|
||||||
|
"Timestamp": ge.Timestamp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = p.writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
69
pkg/events/printers_test.go
Normal file
69
pkg/events/printers_test.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
https://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 events_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeWriter struct {
|
||||||
|
writeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.Writer = fakeWriter{}
|
||||||
|
|
||||||
|
func (f fakeWriter) Write(p []byte) (n int, err error) {
|
||||||
|
return 0, f.writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintEvent(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
formatterType string
|
||||||
|
errString string
|
||||||
|
writer io.Writer
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Fail on formatter type",
|
||||||
|
formatterType: events.YAMLPrinter,
|
||||||
|
errString: "Error on write",
|
||||||
|
writer: fakeWriter{
|
||||||
|
writeErr: fmt.Errorf("Error on write"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := events.NewGenericPrinter(tt.writer, tt.formatterType)
|
||||||
|
e := events.NewEvent().WithIsogenEvent(events.IsogenEvent{
|
||||||
|
Operation: events.IsogenStart,
|
||||||
|
Message: "starting ISO generation",
|
||||||
|
})
|
||||||
|
ge := events.Normalize(e)
|
||||||
|
err := p.PrintEvent(ge)
|
||||||
|
if tt.errString != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.errString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -34,15 +34,19 @@ type EventProcessor interface {
|
|||||||
type DefaultProcessor struct {
|
type DefaultProcessor struct {
|
||||||
errors []error
|
errors []error
|
||||||
applierChan chan<- applyevent.Event
|
applierChan chan<- applyevent.Event
|
||||||
|
genericPrinter GenericPrinter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultProcessor returns instance of DefaultProcessor as interface Implementation
|
// NewDefaultProcessor returns instance of DefaultProcessor as interface Implementation
|
||||||
func NewDefaultProcessor(streams genericclioptions.IOStreams) EventProcessor {
|
func NewDefaultProcessor(streams genericclioptions.IOStreams) EventProcessor {
|
||||||
applyCh := make(chan applyevent.Event)
|
applyCh := make(chan applyevent.Event)
|
||||||
go printers.GetPrinter(printers.EventsPrinter, streams).Print(applyCh, common.DryRunNone)
|
go printers.GetPrinter(printers.EventsPrinter, streams).Print(applyCh, common.DryRunNone)
|
||||||
|
// printer for custom airshipctl events
|
||||||
|
genericPrinter := NewGenericPrinter(log.Writer(), JSONPrinter)
|
||||||
return &DefaultProcessor{
|
return &DefaultProcessor{
|
||||||
errors: []error{},
|
errors: []error{},
|
||||||
applierChan: applyCh,
|
applierChan: applyCh,
|
||||||
|
genericPrinter: genericPrinter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,19 +59,16 @@ func (p *DefaultProcessor) Process(ch <-chan Event) error {
|
|||||||
case ErrorType:
|
case ErrorType:
|
||||||
log.Printf("Received error on event channel %v", e.ErrorEvent)
|
log.Printf("Received error on event channel %v", e.ErrorEvent)
|
||||||
p.errors = append(p.errors, e.ErrorEvent.Error)
|
p.errors = append(p.errors, e.ErrorEvent.Error)
|
||||||
case ClusterctlType, IsogenType, GenericContainerType:
|
|
||||||
// TODO each event needs to be interface that allows us to print it for example
|
|
||||||
// Stringer interface or AsYAML for further processing.
|
|
||||||
// For now we print the event object as is
|
|
||||||
log.Printf("Received event: %v", e)
|
|
||||||
case BootstrapType:
|
|
||||||
log.Printf("%s", e.BootstrapEvent.Message)
|
|
||||||
case StatusPollerType:
|
case StatusPollerType:
|
||||||
log.Fatalf("Processing for status poller events are not yet implemented")
|
log.Fatalf("Processing for status poller events are not yet implemented")
|
||||||
case WaitType:
|
case WaitType:
|
||||||
log.Fatalf("Processing for wait events are not yet implemented")
|
log.Fatalf("Processing for wait events are not yet implemented")
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown event type received: %d", e.Type)
|
ge := Normalize(e)
|
||||||
|
err := p.genericPrinter.PrintEvent(ge)
|
||||||
|
if err != nil {
|
||||||
|
p.errors = append(p.errors, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return checkErrors(p.errors)
|
return checkErrors(p.errors)
|
||||||
|
Loading…
Reference in New Issue
Block a user