Resource status display (#6)

* Start experiments

* Going to get resources

* Start to show resources

* Working on statuses

* Displaying statuses

* Fix complexity?
This commit is contained in:
Andrey Pokhilko
2022-09-07 22:37:50 +01:00
committed by GitHub
parent 967d499742
commit a3fb04fbae
8 changed files with 1215 additions and 68 deletions

View File

@@ -10,9 +10,12 @@ import (
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"os"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"time"
@@ -24,11 +27,11 @@ type DataLayer struct {
Kubectl string
}
func (l *DataLayer) runCommand(cmd ...string) (string, error) {
func (d *DataLayer) runCommand(cmd ...string) (string, error) {
log.Debugf("Starting command: %s", cmd)
prog := exec.Command(cmd[0], cmd[1:]...)
prog.Env = os.Environ()
prog.Env = append(prog.Env, "HELM_KUBECONTEXT="+l.KubeContext)
prog.Env = append(prog.Env, "HELM_KUBECONTEXT="+d.KubeContext)
var stdout bytes.Buffer
prog.Stdout = &stdout
@@ -55,35 +58,36 @@ func (l *DataLayer) runCommand(cmd ...string) (string, error) {
return string(sout), nil
}
func (l *DataLayer) runCommandHelm(cmd ...string) (string, error) {
if l.Helm == "" {
l.Helm = "helm"
func (d *DataLayer) runCommandHelm(cmd ...string) (string, error) {
if d.Helm == "" {
d.Helm = "helm"
}
cmd = append([]string{l.Helm}, cmd...)
if l.KubeContext != "" {
cmd = append(cmd, "--kube-context", l.KubeContext)
cmd = append([]string{d.Helm}, cmd...)
if d.KubeContext != "" {
cmd = append(cmd, "--kube-context", d.KubeContext)
}
return l.runCommand(cmd...)
return d.runCommand(cmd...)
}
func (l *DataLayer) runCommandKubectl(cmd ...string) (string, error) {
if l.Kubectl == "" {
l.Kubectl = "kubectl"
func (d *DataLayer) runCommandKubectl(cmd ...string) (string, error) {
// TODO: migrate into using kubectl "k8s.io/kubectl/pkg/cmd" and kube API
if d.Kubectl == "" {
d.Kubectl = "kubectl"
}
cmd = append([]string{l.Kubectl}, cmd...)
cmd = append([]string{d.Kubectl}, cmd...)
if l.KubeContext != "" {
cmd = append(cmd, "--context", l.KubeContext)
if d.KubeContext != "" {
cmd = append(cmd, "--context", d.KubeContext)
}
return l.runCommand(cmd...)
return d.runCommand(cmd...)
}
func (l *DataLayer) CheckConnectivity() error {
contexts, err := l.ListContexts()
func (d *DataLayer) CheckConnectivity() error {
contexts, err := d.ListContexts()
if err != nil {
return err
}
@@ -93,7 +97,7 @@ func (l *DataLayer) CheckConnectivity() error {
}
/*
_, err = l.runCommandHelm("env") // no point in doing is, since the default context may be invalid
_, err = d.runCommandHelm("env") // no point in doing is, since the default context may be invalid
if err != nil {
return err
}
@@ -110,8 +114,8 @@ type KubeContext struct {
Namespace string
}
func (l *DataLayer) ListContexts() (res []KubeContext, err error) {
out, err := l.runCommandKubectl("config", "get-contexts")
func (d *DataLayer) ListContexts() (res []KubeContext, err error) {
out, err := d.runCommandKubectl("config", "get-contexts")
if err != nil {
return nil, err
}
@@ -144,8 +148,8 @@ func (l *DataLayer) ListContexts() (res []KubeContext, err error) {
return res, nil
}
func (l *DataLayer) ListInstalled() (res []releaseElement, err error) {
out, err := l.runCommandHelm("ls", "--all", "--all-namespaces", "--output", "json", "--time-format", time.RFC3339)
func (d *DataLayer) ListInstalled() (res []releaseElement, err error) {
out, err := d.runCommandHelm("ls", "--all", "--all-namespaces", "--output", "json", "--time-format", time.RFC3339)
if err != nil {
return nil, err
}
@@ -157,9 +161,9 @@ func (l *DataLayer) ListInstalled() (res []releaseElement, err error) {
return res, nil
}
func (l *DataLayer) ChartHistory(namespace string, chartName string) (res []*historyElement, err error) {
func (d *DataLayer) ChartHistory(namespace string, chartName string) (res []*historyElement, err error) {
// TODO: there is `max` but there is no `offset`
out, err := l.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json", "--max", "18")
out, err := d.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json", "--max", "18")
if err != nil {
return nil, err
}
@@ -209,8 +213,8 @@ func (l *DataLayer) ChartHistory(namespace string, chartName string) (res []*his
return res, nil
}
func (l *DataLayer) ChartRepoVersions(chartName string) (res []repoChartElement, err error) {
out, err := l.runCommandHelm("search", "repo", "--regexp", "/"+chartName+"\v", "--versions", "--output", "json")
func (d *DataLayer) ChartRepoVersions(chartName string) (res []repoChartElement, err error) {
out, err := d.runCommandHelm("search", "repo", "--regexp", "/"+chartName+"\v", "--versions", "--output", "json")
if err != nil {
return nil, err
}
@@ -222,36 +226,97 @@ func (l *DataLayer) ChartRepoVersions(chartName string) (res []repoChartElement,
return res, nil
}
type SectionFn = func(string, string, int, bool) (string, error)
type SectionFn = func(string, string, int, bool) (string, error) // TODO: rework it into struct-based argument?
func (l *DataLayer) RevisionManifests(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := l.runCommandHelm("get", "manifest", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
func (d *DataLayer) RevisionManifests(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := d.runCommandHelm("get", "manifest", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
if err != nil {
return "", err
}
return out, nil
}
func (l *DataLayer) RevisionNotes(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := l.runCommandHelm("get", "notes", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string, revision int) ([]*GenericResource, error) {
out, err := d.RevisionManifests(namespace, chartName, revision, false)
if err != nil {
return nil, err
}
dec := yaml.NewDecoder(bytes.NewReader([]byte(out)))
res := make([]*GenericResource, 0)
var tmp interface{}
for dec.Decode(&tmp) == nil {
// k8s libs uses only JSON tags defined, say hello to https://github.com/go-yaml/yaml/issues/424
// bug we can juggle it
jsoned, err := json.Marshal(tmp)
if err != nil {
return nil, err
}
var doc GenericResource
err = json.Unmarshal(jsoned, &doc)
if err != nil {
return nil, err
}
res = append(res, &doc)
}
return res, nil
}
func (d *DataLayer) RevisionNotes(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := d.runCommandHelm("get", "notes", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
if err != nil {
return "", err
}
return out, nil
}
func (l *DataLayer) RevisionValues(namespace string, chartName string, revision int, onlyUserDefined bool) (res string, err error) {
func (d *DataLayer) RevisionValues(namespace string, chartName string, revision int, onlyUserDefined bool) (res string, err error) {
cmd := []string{"get", "values", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision), "--output", "yaml"}
if !onlyUserDefined {
cmd = append(cmd, "--all")
}
out, err := l.runCommandHelm(cmd...)
out, err := d.runCommandHelm(cmd...)
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) GetResource(namespace string, def *GenericResource) (*GenericResource, error) {
out, err := d.runCommandKubectl("get", strings.ToLower(def.Kind), def.Name, "--namespace", namespace, "--output", "json")
if err != nil {
if strings.HasSuffix(strings.TrimSpace(err.Error()), " not found") {
return &GenericResource{
Status: v1.CarpStatus{
Phase: "NotFound",
Message: err.Error(),
Reason: "not found",
},
}, nil
} else {
return nil, err
}
}
var res GenericResource
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
sort.Slice(res.Status.Conditions, func(i, j int) bool {
t1 := res.Status.Conditions[i].LastTransitionTime
t2 := res.Status.Conditions[j].LastTransitionTime
return t1.Time.Before(t2.Time)
})
return &res, nil
}
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
if revision1 == 0 || revision2 == 0 {
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
@@ -274,3 +339,5 @@ func RevisionDiff(functor SectionFn, ext string, namespace string, name string,
log.Debugf("The diff is: %s", diff)
return diff, nil
}
type GenericResource = v1.Carp