Skip to content

Commit

Permalink
Get app dashboard (#848)
Browse files Browse the repository at this point in the history
* move get app dashboard query to go
  • Loading branch information
sgalsaleh committed Jul 24, 2020
1 parent 827c0b5 commit 5b0c250
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 19 deletions.
1 change: 1 addition & 0 deletions kotsadm/pkg/apiserver/server.go
Expand Up @@ -99,6 +99,7 @@ func Start() {
r.Path("/api/v1/download").Methods("GET").HandlerFunc(handlers.DownloadApp)
r.Path("/api/v1/app/{appSlug}/sequence/{sequence}/renderedcontents").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppRenderedContents)
r.Path("/api/v1/app/{appSlug}/sequence/{sequence}/contents").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppContents)
r.Path("/api/v1/app/{appSlug}/cluster/{clusterId}/dashboard").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppDashboard)

r.HandleFunc("/api/v1/login", handlers.Login)
r.HandleFunc("/api/v1/logout", handlers.NotImplemented)
Expand Down
101 changes: 101 additions & 0 deletions kotsadm/pkg/appstatus/appstatus.go
@@ -0,0 +1,101 @@
package appstatus

import (
"database/sql"
"encoding/json"
"time"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
)

type AppStatus struct {
AppID string `json:"appId"`
UpdatedAt time.Time `json:"updatedAt"`
ResourceStates []ResourceState `json:"resourceStates"`
State State `json:"state"`
}

type ResourceState struct {
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
State State `json:"state"`
}

type State string

const (
StateReady State = "ready"
StateDegraded State = "degraded"
StateUnavailable State = "unavailable"
StateMissing State = "missing"
)

func Get(appID string) (*AppStatus, error) {
db := persistence.MustGetPGSession()
query := `select resource_states, updated_at from app_status where app_id = $1`
row := db.QueryRow(query, appID)

var updatedAt sql.NullTime
var resourceStatesStr sql.NullString

if err := row.Scan(&resourceStatesStr, &updatedAt); err != nil {
if err == sql.ErrNoRows {
return &AppStatus{
AppID: appID,
UpdatedAt: time.Time{},
ResourceStates: []ResourceState{},
State: StateMissing,
}, nil
}
return nil, errors.Wrap(err, "failed to scan")
}

appStatus := AppStatus{
AppID: appID,
}

if updatedAt.Valid {
appStatus.UpdatedAt = updatedAt.Time
}

if resourceStatesStr.Valid {
var resourceStates []ResourceState
if err := json.Unmarshal([]byte(resourceStatesStr.String), &resourceStates); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal resource states")
}
appStatus.ResourceStates = resourceStates
}

appStatus.State = getState(appStatus.ResourceStates)

return &appStatus, nil
}

func getState(resourceStates []ResourceState) State {
if len(resourceStates) == 0 {
return StateMissing
}
max := StateReady
for _, resourceState := range resourceStates {
max = minState(max, resourceState.State)
}
return max
}

func minState(a State, b State) State {
if a == StateMissing || b == StateMissing {
return StateMissing
}
if a == StateUnavailable || b == StateUnavailable {
return StateUnavailable
}
if a == StateDegraded || b == StateDegraded {
return StateDegraded
}
if a == StateReady || b == StateReady {
return StateReady
}
return StateMissing
}
29 changes: 29 additions & 0 deletions kotsadm/pkg/downstream/downstream.go
Expand Up @@ -36,6 +36,35 @@ func ListDownstreamsForApp(appID string) ([]*types.Downstream, error) {
return downstreams, nil
}

func GetParentSequence(appID string, clusterID string) (int64, error) {
db := persistence.MustGetPGSession()
query := `select current_sequence from app_downstream where app_id = $1 and cluster_id = $2`
row := db.QueryRow(query, appID, clusterID)

var currentSequence sql.NullInt64
if err := row.Scan(&currentSequence); err != nil {
return 0, errors.Wrap(err, "failed to scan")
}

if !currentSequence.Valid {
return -1, nil
}

query = `select parent_sequence from app_downstream_version where app_id = $1 and cluster_id = $2 and sequence = $3`
row = db.QueryRow(query, appID, clusterID, currentSequence.Int64)

var parentSequence sql.NullInt64
if err := row.Scan(&parentSequence); err != nil {
return 0, errors.Wrap(err, "failed to scan")
}

if !parentSequence.Valid {
return -1, nil
}

return parentSequence.Int64, nil
}

// SetDownstreamVersionReady sets the status for the downstream version with the given sequence and app id to "pending"
func SetDownstreamVersionReady(appID string, sequence int64) error {
db := persistence.MustGetPGSession()
Expand Down
82 changes: 82 additions & 0 deletions kotsadm/pkg/handlers/dashboard.go
@@ -0,0 +1,82 @@
package handlers

import (
"net/http"
"os"

"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/replicatedhq/kots/kotsadm/pkg/app"
"github.com/replicatedhq/kots/kotsadm/pkg/appstatus"
"github.com/replicatedhq/kots/kotsadm/pkg/downstream"
"github.com/replicatedhq/kots/kotsadm/pkg/kotsadmparams"
"github.com/replicatedhq/kots/kotsadm/pkg/logger"
"github.com/replicatedhq/kots/kotsadm/pkg/version"
)

type GetAppDashboardResponse struct {
AppStatus *appstatus.AppStatus `json:"appStatus"`
Metrics []version.MetricChart `json:"metrics"`
PrometheusAddress string `json:"prometheusAddress"`
}

func GetAppDashboard(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "content-type, origin, accept, authorization")

if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}

if err := requireValidSession(w, r); err != nil {
logger.Error(err)
return
}

appSlug := mux.Vars(r)["appSlug"]
clusterID := mux.Vars(r)["clusterId"]

a, err := app.GetFromSlug(appSlug)
if err != nil {
logger.Error(err)
w.WriteHeader(500)
return
}

appStatus, err := appstatus.Get(a.ID)
if err != nil {
logger.Error(err)
w.WriteHeader(500)
return
}

parentSequence, err := downstream.GetParentSequence(a.ID, clusterID)
if err != nil {
logger.Error(err)
w.WriteHeader(500)
return
}

metrics, err := version.GetMetricCharts(a.ID, parentSequence)
if err != nil {
logger.Error(errors.Wrap(err, "failed to get metric charts"))
metrics = []version.MetricChart{}
}

prometheusAddress, err := kotsadmparams.Get("PROMETHEUS_ADDRESS")
if err != nil {
logger.Error(errors.Wrap(err, "failed to get prometheus address from kotsadm params"))
}
if prometheusAddress == "" {
prometheusAddress = os.Getenv("PROMETHEUS_ADDRESS")
}

getAppDashboardResponse := GetAppDashboardResponse{
AppStatus: appStatus,
Metrics: metrics,
PrometheusAddress: prometheusAddress,
}

JSON(w, 200, getAppDashboardResponse)
}
24 changes: 24 additions & 0 deletions kotsadm/pkg/kotsadmparams/kotsadmparams.go
@@ -0,0 +1,24 @@
package kotsadmparams

import (
"database/sql"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
)

func Get(name string) (string, error) {
db := persistence.MustGetPGSession()
query := `select value from kotsadm_params where key = $1`
row := db.QueryRow(query, name)

var value string
if err := row.Scan(&value); err != nil {
if err == sql.ErrNoRows {
return "", nil
}
return "", errors.Wrap(err, "failed to scan")
}

return value, nil
}

0 comments on commit 5b0c250

Please sign in to comment.