Skip to content

Commit

Permalink
refactor: add observability
Browse files Browse the repository at this point in the history
  • Loading branch information
jgillich committed Jan 25, 2024
1 parent ebed77f commit ef9338d
Show file tree
Hide file tree
Showing 27 changed files with 965 additions and 869 deletions.
21 changes: 21 additions & 0 deletions behavior/behavior.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package behavior

import (
"github.com/imkira/go-observer/v2"
)

type Behavior struct {
Preferences observer.Property[Preferences]
}

func NewBehavior() (*Behavior, error) {
prefs, err := LoadPreferences()
if err != nil {
return nil, err
}
prefs.Defaults()

return &Behavior{
Preferences: observer.NewProperty(*prefs),
}, nil
}
64 changes: 47 additions & 17 deletions state/cluster.go → behavior/cluster_behavior.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package state
package behavior

import (
"context"
"sort"

"github.com/go-logr/logr"
"github.com/imkira/go-observer/v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
eventsv1 "k8s.io/api/events/v1"
Expand All @@ -23,21 +24,34 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

type Cluster struct {
client.Client
Clientset *kubernetes.Clientset
Dynamic *dynamic.DynamicClient
Preferences *ClusterPreferences
Scheme *runtime.Scheme
Resources []metav1.APIResource
type ClusterBehavior struct {
*Behavior

client client.Client
clientset *kubernetes.Clientset
dynamic *dynamic.DynamicClient
scheme *runtime.Scheme

ClusterPreferences observer.Property[ClusterPreferences]

metrics *Metrics

Resources []metav1.APIResource
Namespaces observer.Property[[]corev1.Namespace]

SelectedResource observer.Property[*metav1.APIResource]
SelectedObject observer.Property[client.Object]

SearchText observer.Property[string]
SearchFilter observer.Property[SearchFilter]
}

func NewCluster(ctx context.Context, prefs *ClusterPreferences) (*Cluster, error) {
func (b *Behavior) WithCluster(ctx context.Context, clusterPrefs observer.Property[ClusterPreferences]) (*ClusterBehavior, error) {
logf.SetLogger(logr.Discard())

config := &rest.Config{
Host: prefs.Host,
TLSClientConfig: prefs.TLS,
Host: clusterPrefs.Value().Host,
TLSClientConfig: clusterPrefs.Value().TLS,
}

discovery, err := discovery.NewDiscoveryClientForConfig(config)
Expand Down Expand Up @@ -71,12 +85,28 @@ func NewCluster(ctx context.Context, prefs *ClusterPreferences) (*Cluster, error
return nil, err
}

cluster := Cluster{
Client: rclient,
Clientset: clientset,
Preferences: prefs,
Scheme: scheme,
Dynamic: dynamicClient,
var namespaces corev1.NamespaceList
if err := rclient.List(context.TODO(), &namespaces); err != nil {
return nil, err
}

cluster := ClusterBehavior{
Behavior: b,
client: rclient,
clientset: clientset,
scheme: scheme,
ClusterPreferences: clusterPrefs,
dynamic: dynamicClient,
Namespaces: observer.NewProperty(namespaces.Items),
SelectedObject: observer.NewProperty[client.Object](nil),
SelectedResource: observer.NewProperty[*metav1.APIResource](nil),
SearchText: observer.NewProperty(""),
SearchFilter: observer.NewProperty(SearchFilter{}),
}

cluster.metrics, err = cluster.newMetrics(&cluster)
if err != nil {
// metrics disabled
}

resources, err := discovery.ServerPreferredResources()
Expand Down
197 changes: 197 additions & 0 deletions behavior/detail_behavior.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package behavior

import (
"context"
"fmt"
"io"
"math"
"strings"

"github.com/dustin/go-humanize"
"github.com/getseabird/seabird/util"
"github.com/imkira/go-observer/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type DetailBehaviour struct {
*ClusterBehavior

Yaml observer.Property[string]
Properties observer.Property[[]ObjectProperty]
}

func (b *ClusterBehavior) NewDetailBehavior() *DetailBehaviour {
d := DetailBehaviour{
ClusterBehavior: b,
Yaml: observer.NewProperty[string](""),
Properties: observer.NewProperty[[]ObjectProperty](nil),
}

onChange(d.SelectedObject, d.onObjectChange)

return &d
}

func (b *DetailBehaviour) onObjectChange(object client.Object) {
codec := unstructured.NewJSONFallbackEncoder(serializer.NewCodecFactory(b.scheme).LegacyCodec(b.scheme.PreferredVersionAllGroups()...))
encoded, err := runtime.Encode(codec, object)
if err != nil {
b.Yaml.Update(fmt.Sprintf("error: %v", err))
} else {
yaml, err := util.JsonToYaml(encoded)
if err != nil {
b.Yaml.Update(fmt.Sprintf("error: %v", err))
} else {
b.Yaml.Update(string(yaml))
}
}

var labels []ObjectProperty
for key, value := range object.GetLabels() {
labels = append(labels, ObjectProperty{Name: key, Value: value})
}
var annotations []ObjectProperty
for key, value := range object.GetAnnotations() {
annotations = append(annotations, ObjectProperty{Name: key, Value: value})
}
var owners []ObjectProperty
for _, ref := range object.GetOwnerReferences() {
owners = append(owners, ObjectProperty{Name: fmt.Sprintf("%s %s", ref.APIVersion, ref.Kind), Value: ref.Name})
}

var properties []ObjectProperty
properties = append(properties,
ObjectProperty{
Name: "Metadata",
Children: []ObjectProperty{
ObjectProperty{
Name: "Name",
Value: object.GetName(),
},
ObjectProperty{
Name: "Namespace",
Value: object.GetNamespace(),
},
ObjectProperty{
Name: "Labels",
Children: labels,
},
ObjectProperty{
Name: "Annotations",
Children: annotations,
},
ObjectProperty{
Name: "Owners",
Children: owners,
},
},
})

switch object := object.(type) {
case *corev1.Pod:
var containers []ObjectProperty

var podMetrics *metricsv1beta1.PodMetrics
if b.metrics != nil {
podMetrics = b.metrics.PodValue(types.NamespacedName{Name: object.Name, Namespace: object.Namespace})
}

for _, container := range object.Spec.Containers {
var props []ObjectProperty
var status corev1.ContainerStatus
for _, s := range object.Status.ContainerStatuses {
if s.Name == container.Name {
status = s
break
}
}

var metrics *metricsv1beta1.ContainerMetrics
if podMetrics != nil {
for _, m := range podMetrics.Containers {
if m.Name == container.Name {
metrics = &m
break
}
}
}

var state string
if status.State.Running != nil {
state = "Running"
} else if status.State.Terminated != nil {
message := status.State.Terminated.Message
if len(message) == 0 {
message = status.State.Waiting.Reason
}
state = fmt.Sprintf("Terminated: %s", message)
} else if status.State.Waiting != nil {
message := status.State.Waiting.Message
if len(message) == 0 {
message = status.State.Waiting.Reason
}
state = fmt.Sprintf("Waiting: %s", message)
}
props = append(props, ObjectProperty{Name: "State", Value: state})

props = append(props, ObjectProperty{Name: "Image", Value: container.Image})

if len(container.Command) > 0 {
props = append(props, ObjectProperty{Name: "Command", Value: strings.Join(container.Command, " ")})
}

// TODO env

if metrics != nil {
if cpu := metrics.Usage.Cpu(); cpu != nil {
props = append(props, ObjectProperty{Name: "CPU", Value: fmt.Sprintf("%v%%", math.Round(cpu.AsApproximateFloat64()*10000)/10000)})
}
if mem := metrics.Usage.Memory(); mem != nil {
bytes, _ := mem.AsInt64()
props = append(props, ObjectProperty{Name: "Memory", Value: humanize.Bytes(uint64(bytes))})
}
}

containers = append(containers, ObjectProperty{Name: container.Name, Children: props})
}

properties = append(properties, ObjectProperty{Name: "Containers", Children: containers})
case *corev1.ConfigMap:
var data []ObjectProperty
for key, value := range object.Data {
data = append(data, ObjectProperty{Name: key, Value: value})
}
properties = append(properties, ObjectProperty{Name: "Data", Children: data})
case *corev1.Secret:
var data []ObjectProperty
for key, value := range object.Data {
data = append(data, ObjectProperty{Name: key, Value: string(value)})
}
properties = append(properties, ObjectProperty{Name: "Data", Children: data})
}

b.Properties.Update(properties)

}

type ObjectProperty struct {
Name string
Value string
Children []ObjectProperty
}

func (b *DetailBehaviour) PodLogs() ([]byte, error) {
pod := b.SelectedObject.Value().(*corev1.Pod)
req := b.clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{})
r, err := req.Stream(context.TODO())
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
Loading

0 comments on commit ef9338d

Please sign in to comment.