Skip to content

Commit

Permalink
/app/status API that returns detailed app status (#231)
Browse files Browse the repository at this point in the history
* /app/status API that returns detailed app status

* Support new appStatus field in mock data
  • Loading branch information
divolgin authored Dec 20, 2024
1 parent eeb5cac commit 2a305d5
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 63 deletions.
1 change: 1 addition & 0 deletions pkg/apiserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func Start(params APIServerParams) {

// app
r.HandleFunc("/api/v1/app/info", handlers.GetCurrentAppInfo).Methods("GET")
r.HandleFunc("/api/v1/app/status", handlers.GetCurrentAppStatus).Methods("GET")
r.HandleFunc("/api/v1/app/updates", handlers.GetAppUpdates).Methods("GET")
r.HandleFunc("/api/v1/app/history", handlers.GetAppHistory).Methods("GET")
cachedRouter.HandleFunc("/api/v1/app/custom-metrics", handlers.SendCustomAppMetrics).Methods("POST", "PATCH")
Expand Down
18 changes: 9 additions & 9 deletions pkg/appstate/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ func (s StatusInformerString) Parse() (i StatusInformer, err error) {
}

type AppStatus struct {
AppSlug string `json:"appSlug"`
ResourceStates ResourceStates `json:"resourceStates" hash:"set"`
UpdatedAt time.Time `json:"updatedAt" hash:"ignore"`
State State `json:"state"`
Sequence int64 `json:"sequence"`
AppSlug string `json:"appSlug" yaml:"appSlug"`
ResourceStates ResourceStates `json:"resourceStates" yaml:"resourceStates" hash:"set"`
UpdatedAt time.Time `json:"updatedAt" yaml:"updatedAt" hash:"ignore"`
State State `json:"state" yaml:"state"`
Sequence int64 `json:"sequence" yaml:"sequence"`
}

type ResourceStates []ResourceState

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

type State string
Expand Down
97 changes: 90 additions & 7 deletions pkg/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type GetCurrentAppInfoResponse struct {
ReleaseSequence int64 `json:"releaseSequence"`
}

type GetCurrentAppStatusResponse struct {
AppStatus appstatetypes.AppStatus `json:"appStatus"`
}

type GetAppHistoryResponse struct {
Releases []AppRelease `json:"releases"`
}
Expand Down Expand Up @@ -102,11 +106,21 @@ func GetCurrentAppInfo(w http.ResponseWriter, r *http.Request) {
return
}

response.AppStatus = mockData.AppStatus
response.HelmChartURL = mockData.HelmChartURL

if mockData.CurrentRelease != nil {
response.CurrentRelease = mockReleaseToAppRelease(*mockData.CurrentRelease)
switch mockData := mockData.(type) {
case *integrationtypes.MockDataV1:
response.AppStatus = mockData.AppStatus
response.HelmChartURL = mockData.HelmChartURL
if mockData.CurrentRelease != nil {
response.CurrentRelease = mockReleaseToAppRelease(*mockData.CurrentRelease)
}
case *integrationtypes.MockDataV2:
response.AppStatus = mockData.AppStatus.State
response.HelmChartURL = mockData.HelmChartURL
if mockData.CurrentRelease != nil {
response.CurrentRelease = mockReleaseToAppRelease(*mockData.CurrentRelease)
}
default:
logger.Errorf("unknown mock data type: %T", mockData)
}

JSON(w, http.StatusOK, response)
Expand Down Expand Up @@ -149,6 +163,53 @@ func GetCurrentAppInfo(w http.ResponseWriter, r *http.Request) {
JSON(w, http.StatusOK, response)
}

func GetCurrentAppStatus(w http.ResponseWriter, r *http.Request) {
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if isIntegrationModeEnabled {
response := GetCurrentAppStatusResponse{}

mockData, err := integration.GetMockData(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get mock data: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

switch mockData := mockData.(type) {
case *integrationtypes.MockDataV1:
logger.Errorf("app status is not supported in v1 mock data")
w.WriteHeader(http.StatusInternalServerError)
return
case *integrationtypes.MockDataV2:
response.AppStatus = mockData.AppStatus
default:
logger.Errorf("unknown mock data type: %T", mockData)
}

JSON(w, http.StatusOK, response)
return
}

response := GetCurrentAppStatusResponse{
AppStatus: store.GetStore().GetAppStatus(),
}

JSON(w, http.StatusOK, response)
}

func GetAppUpdates(w http.ResponseWriter, r *http.Request) {
if util.IsAirgap() {
JSON(w, http.StatusOK, []upstreamtypes.ChannelRelease{})
Expand Down Expand Up @@ -178,7 +239,18 @@ func GetAppUpdates(w http.ResponseWriter, r *http.Request) {
}

response := []upstreamtypes.ChannelRelease{}
for _, mockRelease := range mockData.AvailableReleases {
var avalableReleases []integrationtypes.MockRelease

switch mockData := mockData.(type) {
case *integrationtypes.MockDataV1:
avalableReleases = mockData.AvailableReleases
case *integrationtypes.MockDataV2:
avalableReleases = mockData.AvailableReleases
default:
logger.Errorf("unknown mock data type: %T", mockData)
}

for _, mockRelease := range avalableReleases {
response = append(response, upstreamtypes.ChannelRelease{
VersionLabel: mockRelease.VersionLabel,
CreatedAt: mockRelease.CreatedAt,
Expand Down Expand Up @@ -244,10 +316,21 @@ func GetAppHistory(w http.ResponseWriter, r *http.Request) {
return
}

var deployedReleases []integrationtypes.MockRelease

switch mockData := mockData.(type) {
case *integrationtypes.MockDataV1:
deployedReleases = mockData.DeployedReleases
case *integrationtypes.MockDataV2:
deployedReleases = mockData.DeployedReleases
default:
logger.Errorf("unknown mock data type: %T", mockData)
}

response := GetAppHistoryResponse{
Releases: []AppRelease{},
}
for _, mockRelease := range mockData.DeployedReleases {
for _, mockRelease := range deployedReleases {
response.Releases = append(response.Releases, mockReleaseToAppRelease(mockRelease))
}

Expand Down
16 changes: 11 additions & 5 deletions pkg/handlers/mock.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package handlers

import (
"encoding/json"
"io"
"net/http"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated-sdk/pkg/integration"
integrationtypes "github.com/replicatedhq/replicated-sdk/pkg/integration/types"
"github.com/replicatedhq/replicated-sdk/pkg/k8sutil"
"github.com/replicatedhq/replicated-sdk/pkg/logger"
"github.com/replicatedhq/replicated-sdk/pkg/store"
Expand Down Expand Up @@ -36,9 +35,16 @@ func PostIntegrationMockData(w http.ResponseWriter, r *http.Request) {
return
}

mockDataRequest := integrationtypes.MockData{}
if err := json.NewDecoder(r.Body).Decode(&mockDataRequest); err != nil {
logger.Error(err)
body, err := io.ReadAll(r.Body)
if err != nil {
logger.Errorf("failed to read request body: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

mockDataRequest, err := integration.UnmarshalJSON(body)
if err != nil {
logger.Errorf("failed to read request body: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/integration/data/default_mock_data.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
appStatus: ready
version: v2
appStatus:
resourceStates:
- kind: deployment
name: dev-app
namespace: default
state: ready
state: ready
helmChartURL: oci://registry.replicated.com/dev-app/dev-channel/dev-parent-chart
currentRelease:
versionLabel: 0.1.3
Expand Down
File renamed without changes.
41 changes: 41 additions & 0 deletions pkg/integration/data/test_mock_data_v2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
version: v2
appStatus:
resourceStates:
- kind: deployment
name: dev-app
namespace: default
state: ready
state: ready
helmChartURL: oci://custom.registry.com/my-app
currentRelease:
versionLabel: custom-label
releaseNotes: "custom release notes"
createdAt: 2023-05-23T20:58:07Z
deployedAt: 2023-05-23T21:58:07Z
helmReleaseName: custom-helm-release-name
helmReleaseRevision: 4
helmReleaseNamespace: default
deployedReleases:
- versionLabel: custom-version-label-1
releaseNotes: "custom release notes 1"
createdAt: 2023-05-21T20:58:07Z
deployedAt: 2023-05-21T21:58:07Z
helmReleaseName: custom-helm-release-name-1
helmReleaseRevision: 2
helmReleaseNamespace: default
- versionLabel: custom-version-label-2
releaseNotes: "custom release notes 2"
createdAt: 2023-05-22T20:58:07Z
deployedAt: 2023-05-22T21:58:07Z
helmReleaseName: custom-helm-release-name-2
helmReleaseRevision: 3
helmReleaseNamespace: default
availableReleases:
- versionLabel: custom-label-1
releaseNotes: "custom release notes 1"
createdAt: 2023-05-24T20:58:07Z
deployedAt: 2023-05-24T21:58:07Z
- versionLabel: custom-label-2
releaseNotes: "custom release notes 2"
createdAt: 2023-06-01T20:58:07Z
deployedAt: 2023-06-01T21:58:07Z
71 changes: 63 additions & 8 deletions pkg/integration/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package integration
import (
"context"
_ "embed"
"encoding/json"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated-sdk/pkg/integration/types"
Expand All @@ -18,7 +19,7 @@ var (
defaultMockDataYAML []byte
)

func GetMockData(ctx context.Context, clientset kubernetes.Interface, namespace string) (*types.MockData, error) {
func GetMockData(ctx context.Context, clientset kubernetes.Interface, namespace string) (types.MockData, error) {
replicatedSecretLock.Lock()
defer replicatedSecretLock.Unlock()

Expand All @@ -29,23 +30,23 @@ func GetMockData(ctx context.Context, clientset kubernetes.Interface, namespace
if err == nil {
b := secret.Data[integrationMockDataKey]
if len(b) != 0 {
var mockData types.MockData
if err := yaml.Unmarshal(b, &mockData); err != nil {
mockData, err := UnmarshalYAML(b)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data")
}
return &mockData, nil
return mockData, nil
}
}

return GetDefaultMockData(ctx)
}

func GetDefaultMockData(ctx context.Context) (*types.MockData, error) {
var mockData types.MockData
if err := yaml.Unmarshal(defaultMockDataYAML, &mockData); err != nil {
func GetDefaultMockData(ctx context.Context) (types.MockData, error) {
mockData, err := UnmarshalYAML(defaultMockDataYAML)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal default mock data")
}
return &mockData, nil
return mockData, nil
}

func SetMockData(ctx context.Context, clientset kubernetes.Interface, namespace string, mockData types.MockData) error {
Expand Down Expand Up @@ -74,3 +75,57 @@ func SetMockData(ctx context.Context, clientset kubernetes.Interface, namespace

return nil
}

func UnmarshalJSON(b []byte) (types.MockData, error) {
version := types.MockDataVersion{}
err := json.Unmarshal(b, &version)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data version")
}

switch version.Version {
case "v1", "":
mockData := &types.MockDataV1{}
err = json.Unmarshal(b, &mockData)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data v1")
}
return mockData, nil
case "v2":
mockData := &types.MockDataV2{}
err = json.Unmarshal(b, &mockData)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data v2")
}
return mockData, nil
default:
return nil, errors.Errorf("unknown mock data version: %s", version.Version)
}
}

func UnmarshalYAML(b []byte) (types.MockData, error) {
version := types.MockDataVersion{}
err := yaml.Unmarshal(b, &version)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data version")
}

switch version.Version {
case "v1", "":
mockData := &types.MockDataV1{}
err = yaml.Unmarshal(b, &mockData)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data v1")
}
return mockData, nil
case "v2":
mockData := &types.MockDataV2{}
err = yaml.Unmarshal(b, &mockData)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal mock data v2")
}
return mockData, nil
default:
return nil, errors.Errorf("unknown mock data version: %s", version.Version)
}
}
Loading

0 comments on commit 2a305d5

Please sign in to comment.