Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

/app/status API that returns detailed app status #231

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading