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

add pending_cluster_management status for embedded clusters #4461

Merged
merged 5 commits into from
Feb 22, 2024
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
105 changes: 105 additions & 0 deletions pkg/handlers/embedded_cluster_confirm_cluster_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package handlers

import (
"fmt"
"net/http"
"os"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/preflight"
"github.com/replicatedhq/kots/pkg/store"
storetypes "github.com/replicatedhq/kots/pkg/store/types"
"github.com/replicatedhq/kots/pkg/util"
)

type ConfirmEmbeddedClusterManagementResponse struct {
VersionStatus string `json:"versionStatus"`
}

func (h *Handler) ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *http.Request) {
sgalsaleh marked this conversation as resolved.
Show resolved Hide resolved
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

apps, err := store.GetStore().ListInstalledApps()
if err != nil {
logger.Error(fmt.Errorf("failed to list installed apps: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

if len(apps) == 0 {
logger.Error(fmt.Errorf("no installed apps found"))
w.WriteHeader(http.StatusInternalServerError)
return
}
app := apps[0]

downstreamVersions, err := store.GetStore().FindDownstreamVersions(app.ID, true)
if err != nil {
logger.Error(fmt.Errorf("failed to find downstream versions: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

if len(downstreamVersions.PendingVersions) == 0 {
logger.Error(fmt.Errorf("no pending versions found"))
w.WriteHeader(http.StatusInternalServerError)
return
}
pendingVersion := downstreamVersions.PendingVersions[0]

if pendingVersion.Status != storetypes.VersionPendingClusterManagement {
logger.Error(fmt.Errorf("pending version is not in pending_cluster_management status"))
w.WriteHeader(http.StatusBadRequest)
return
}

archiveDir, err := os.MkdirTemp("", "kotsadm")
if err != nil {
logger.Error(fmt.Errorf("failed to create temp dir: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
defer os.RemoveAll(archiveDir)

err = store.GetStore().GetAppVersionArchive(app.ID, pendingVersion.Sequence, archiveDir)
if err != nil {
logger.Error(fmt.Errorf("failed to get app version archive: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir)
if err != nil {
logger.Error(fmt.Errorf("failed to load kots kinds: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

downstreamVersionStatus := storetypes.VersionPending
if kotsKinds.IsConfigurable() {
downstreamVersionStatus = storetypes.VersionPendingConfig
} else if kotsKinds.HasPreflights() {
downstreamVersionStatus = storetypes.VersionPendingPreflight
if err := preflight.Run(app.ID, app.Slug, pendingVersion.Sequence, false, archiveDir); err != nil {
logger.Error(errors.Wrap(err, "failed to start preflights"))
w.WriteHeader(http.StatusInternalServerError)
return
}
}

if err := store.GetStore().SetDownstreamVersionStatus(app.ID, pendingVersion.Sequence, downstreamVersionStatus, ""); err != nil {
logger.Error(fmt.Errorf("failed to set downstream version status: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

JSON(w, http.StatusOK, ConfirmEmbeddedClusterManagementResponse{
VersionStatus: string(downstreamVersionStatus),
})
}
7 changes: 7 additions & 0 deletions pkg/handlers/embedded_cluster_delete_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ import (
"github.com/replicatedhq/kots/pkg/embeddedcluster"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/util"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (h *Handler) DeleteEmbeddedClusterNode(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
Expand Down
7 changes: 7 additions & 0 deletions pkg/handlers/embedded_cluster_drain_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ import (
"github.com/replicatedhq/kots/pkg/embeddedcluster"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/util"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (h *Handler) DrainEmbeddedClusterNode(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
Expand Down
19 changes: 19 additions & 0 deletions pkg/handlers/embedded_cluster_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import (
"github.com/replicatedhq/kots/pkg/embeddedcluster"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/util"
)

type GetEmbeddedClusterRolesResponse struct {
Roles []string `json:"roles"`
}

func (h *Handler) GetEmbeddedClusterNodes(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
Expand All @@ -31,6 +38,12 @@ func (h *Handler) GetEmbeddedClusterNodes(w http.ResponseWriter, r *http.Request
}

func (h *Handler) GetEmbeddedClusterNode(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
Expand All @@ -49,6 +62,12 @@ func (h *Handler) GetEmbeddedClusterNode(w http.ResponseWriter, r *http.Request)
}

func (h *Handler) GetEmbeddedClusterRoles(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

roles, err := embeddedcluster.GetRoles(r.Context())
if err != nil {
logger.Error(err)
Expand Down
13 changes: 13 additions & 0 deletions pkg/handlers/embedded_cluster_node_join_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/store"
"github.com/replicatedhq/kots/pkg/util"
)

type GenerateEmbeddedClusterNodeJoinCommandResponse struct {
Expand All @@ -29,6 +30,12 @@ type GenerateEmbeddedClusterNodeJoinCommandRequest struct {
}

func (h *Handler) GenerateEmbeddedClusterNodeJoinCommand(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

generateEmbeddedClusterNodeJoinCommandRequest := GenerateEmbeddedClusterNodeJoinCommandRequest{}
if err := json.NewDecoder(r.Body).Decode(&generateEmbeddedClusterNodeJoinCommandRequest); err != nil {
logger.Error(fmt.Errorf("failed to decode request body: %w", err))
Expand Down Expand Up @@ -63,6 +70,12 @@ func (h *Handler) GenerateEmbeddedClusterNodeJoinCommand(w http.ResponseWriter,

// this function relies on the token being valid for authentication
func (h *Handler) GetEmbeddedClusterNodeJoinCommand(w http.ResponseWriter, r *http.Request) {
if !util.IsEmbeddedCluster() {
logger.Errorf("not an embedded cluster")
w.WriteHeader(http.StatusBadRequest)
return
}

// read query string, ensure that the token is valid
token := r.URL.Query().Get("token")
roles, err := store.GetStore().GetEmbeddedClusterInstallCommandRoles(token)
Expand Down
2 changes: 2 additions & 0 deletions pkg/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ func RegisterSessionAuthRoutes(r *mux.Router, kotsStore store.Store, handler KOT

// Embedded Cluster
r.Name("EmbeddedCluster").Path("/api/v1/embedded-cluster").HandlerFunc(NotImplemented)
r.Name("ConfirmEmbeddedClusterManagement").Path("/api/v1/embedded-cluster/management").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.ConfirmEmbeddedClusterManagement))
r.Name("GenerateEmbeddedClusterNodeJoinCommand").Path("/api/v1/embedded-cluster/generate-node-join-command").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.GenerateEmbeddedClusterNodeJoinCommand))
r.Name("DrainEmbeddedClusterNode").Path("/api/v1/embedded-cluster/nodes/{nodeName}/drain").Methods("POST").
Expand Down
10 changes: 10 additions & 0 deletions pkg/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,16 @@ var HandlerPolicyTests = map[string][]HandlerPolicyTest{
},

"EmbeddedCluster": {}, // Not implemented
"ConfirmEmbeddedClusterManagement": {
{
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.ConfirmEmbeddedClusterManagement(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"GenerateEmbeddedClusterNodeJoinCommand": {
{
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
Expand Down
1 change: 1 addition & 0 deletions pkg/handlers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type KOTSHandler interface {
GetKurlNodes(w http.ResponseWriter, r *http.Request)

// EmbeddedCLuster
ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *http.Request)
GenerateEmbeddedClusterNodeJoinCommand(w http.ResponseWriter, r *http.Request)
DrainEmbeddedClusterNode(w http.ResponseWriter, r *http.Request)
DeleteEmbeddedClusterNode(w http.ResponseWriter, r *http.Request)
Expand Down
12 changes: 12 additions & 0 deletions pkg/handlers/mock/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions pkg/online/online.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ func CreateAppFromOnline(opts CreateOnlineAppOpts) (_ *kotsutil.KotsKinds, final
return nil, errors.Wrap(err, "failed to load kotskinds from path")
}

status, err := store.GetStore().GetDownstreamVersionStatus(opts.PendingApp.ID, newSequence)
if err != nil {
return nil, errors.Wrap(err, "failed to get downstream version status")
}

if status == storetypes.VersionPendingClusterManagement {
// if pending cluster management, we don't want to deploy the app
return kotsKinds, nil
}

hasStrictPreflights, err := store.GetStore().HasStrictPreflights(opts.PendingApp.ID, newSequence)
if err != nil {
return nil, errors.Wrap(err, "failed to check if app preflight has strict analyzers")
Expand Down
5 changes: 4 additions & 1 deletion pkg/store/kotsstore/version_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,10 @@ func (s *KOTSStore) upsertAppVersionStatements(appID string, sequence int64, bas
return nil, errors.Wrap(err, "failed to check strict preflights from spec")
}
downstreamStatus := types.VersionPending
if baseSequence == nil && kotsKinds.IsConfigurable() { // initial version should always require configuration (if exists) even if all required items are already set and have values (except for automated installs, which can override this later)
if baseSequence == nil && util.IsEmbeddedCluster() {
// embedded clusters always require cluster management on initial install
downstreamStatus = types.VersionPendingClusterManagement
} else if baseSequence == nil && kotsKinds.IsConfigurable() { // initial version should always require configuration (if exists) even if all required items are already set and have values (except for automated installs, which can override this later)
downstreamStatus = types.VersionPendingConfig
} else if kotsKinds.HasPreflights() && (!skipPreflights || hasStrictPreflights) {
downstreamStatus = types.VersionPendingPreflight
Expand Down
17 changes: 9 additions & 8 deletions pkg/store/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package types
type DownstreamVersionStatus string

const (
VersionUnknown DownstreamVersionStatus = "unknown" // we don't know
VersionPendingConfig DownstreamVersionStatus = "pending_config" // needs required configuration
VersionPendingDownload DownstreamVersionStatus = "pending_download" // needs to be downloaded from the upstream source
VersionPendingPreflight DownstreamVersionStatus = "pending_preflight" // waiting for preflights to finish
VersionPending DownstreamVersionStatus = "pending" // can be deployed, but is not yet
VersionDeploying DownstreamVersionStatus = "deploying" // is being deployed
VersionDeployed DownstreamVersionStatus = "deployed" // did deploy successfully
VersionFailed DownstreamVersionStatus = "failed" // did not deploy successfully
VersionUnknown DownstreamVersionStatus = "unknown" // we don't know
VersionPendingClusterManagement DownstreamVersionStatus = "pending_cluster_management" // needs cluster configuration
VersionPendingConfig DownstreamVersionStatus = "pending_config" // needs required configuration
VersionPendingDownload DownstreamVersionStatus = "pending_download" // needs to be downloaded from the upstream source
VersionPendingPreflight DownstreamVersionStatus = "pending_preflight" // waiting for preflights to finish
VersionPending DownstreamVersionStatus = "pending" // can be deployed, but is not yet
VersionDeploying DownstreamVersionStatus = "deploying" // is being deployed
VersionDeployed DownstreamVersionStatus = "deployed" // did deploy successfully
VersionFailed DownstreamVersionStatus = "failed" // did not deploy successfully
)
9 changes: 4 additions & 5 deletions web/src/components/apps/AppDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -337,15 +337,14 @@ function AppDetailPage(props: Props) {
const firstVersion = downstream.pendingVersions.find(
(version: Version) => version?.sequence === 0
);
if (firstVersion?.status === "unknown" && props.isEmbeddedCluster) {
if (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this ever be unknown now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we could remove that. probably left over from an early implementation?

firstVersion?.status === "pending_cluster_management" &&
props.isEmbeddedCluster
) {
navigate(`/${appNeedsConfiguration.slug}/cluster/manage`);
return;
}
if (firstVersion?.status === "pending_config") {
if (props.isEmbeddedCluster) {
navigate(`/${appNeedsConfiguration.slug}/cluster/manage`);
return;
}
navigate(`/${appNeedsConfiguration.slug}/config`);
return;
}
Expand Down
Loading
Loading