From d5b5a96d915a4ea7fc3611029bd70374aae43b82 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 21 Feb 2024 22:36:18 +0000 Subject: [PATCH 1/5] add pending_cluster_management status for embedded clusters --- ...dded_cluster_confirm_cluster_management.go | 95 +++++++++++++++++++ pkg/handlers/handlers.go | 2 + pkg/handlers/interface.go | 1 + pkg/handlers/mock/mock.go | 12 +++ pkg/online/online.go | 10 ++ pkg/store/kotsstore/version_store.go | 5 +- pkg/store/types/constants.go | 17 ++-- web/src/components/apps/AppDetailPage.tsx | 6 +- .../apps/EmbeddedClusterManagement.tsx | 60 ++++++++++-- web/src/types/index.ts | 1 + 10 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 pkg/handlers/embedded_cluster_confirm_cluster_management.go diff --git a/pkg/handlers/embedded_cluster_confirm_cluster_management.go b/pkg/handlers/embedded_cluster_confirm_cluster_management.go new file mode 100644 index 0000000000..7c11b95716 --- /dev/null +++ b/pkg/handlers/embedded_cluster_confirm_cluster_management.go @@ -0,0 +1,95 @@ +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" +) + +type ConfirmEmbeddedClusterManagementResponse struct { + VersionStatus string `json:"versionStatus"` +} + +func (h *Handler) ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *http.Request) { + 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 { + 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 + } + } + pendingVersion.Status = downstreamVersionStatus + + if err := store.GetStore().SetDownstreamVersionStatus(app.ID, pendingVersion.Sequence, pendingVersion.Status, ""); 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(pendingVersion.Status), + }) +} diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index 0e7d51b55e..38797e113f 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -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("GenerateEmbeddedClusterNodeJoinCommand").Path("/api/v1/embedded-cluster/confirm").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"). diff --git a/pkg/handlers/interface.go b/pkg/handlers/interface.go index d5d02d9e1d..f974be4eae 100644 --- a/pkg/handlers/interface.go +++ b/pkg/handlers/interface.go @@ -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) diff --git a/pkg/handlers/mock/mock.go b/pkg/handlers/mock/mock.go index 75532830f1..1c56f5c7ba 100644 --- a/pkg/handlers/mock/mock.go +++ b/pkg/handlers/mock/mock.go @@ -178,6 +178,18 @@ func (mr *MockKOTSHandlerMockRecorder) ConfigureIdentityService(w, r interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigureIdentityService", reflect.TypeOf((*MockKOTSHandler)(nil).ConfigureIdentityService), w, r) } +// ConfirmEmbeddedClusterManagement mocks base method. +func (m *MockKOTSHandler) ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *http.Request) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ConfirmEmbeddedClusterManagement", w, r) +} + +// ConfirmEmbeddedClusterManagement indicates an expected call of ConfirmEmbeddedClusterManagement. +func (mr *MockKOTSHandlerMockRecorder) ConfirmEmbeddedClusterManagement(w, r interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfirmEmbeddedClusterManagement", reflect.TypeOf((*MockKOTSHandler)(nil).ConfirmEmbeddedClusterManagement), w, r) +} + // CreateAppFromAirgap mocks base method. func (m *MockKOTSHandler) CreateAppFromAirgap(w http.ResponseWriter, r *http.Request) { m.ctrl.T.Helper() diff --git a/pkg/online/online.go b/pkg/online/online.go index 6075d02ae8..f22152d721 100644 --- a/pkg/online/online.go +++ b/pkg/online/online.go @@ -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") diff --git a/pkg/store/kotsstore/version_store.go b/pkg/store/kotsstore/version_store.go index b6baea220f..37ffdf6956 100644 --- a/pkg/store/kotsstore/version_store.go +++ b/pkg/store/kotsstore/version_store.go @@ -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 diff --git a/pkg/store/types/constants.go b/pkg/store/types/constants.go index 1ce8b655d7..a80755c4d7 100644 --- a/pkg/store/types/constants.go +++ b/pkg/store/types/constants.go @@ -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 ) diff --git a/web/src/components/apps/AppDetailPage.tsx b/web/src/components/apps/AppDetailPage.tsx index 6135256c2b..914966f6b5 100644 --- a/web/src/components/apps/AppDetailPage.tsx +++ b/web/src/components/apps/AppDetailPage.tsx @@ -337,15 +337,11 @@ function AppDetailPage(props: Props) { const firstVersion = downstream.pendingVersions.find( (version: Version) => version?.sequence === 0 ); - if (firstVersion?.status === "unknown" && props.isEmbeddedCluster) { + if ((firstVersion?.status === "unknown" || 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; } diff --git a/web/src/components/apps/EmbeddedClusterManagement.tsx b/web/src/components/apps/EmbeddedClusterManagement.tsx index 24bd117c0e..2886dce658 100644 --- a/web/src/components/apps/EmbeddedClusterManagement.tsx +++ b/web/src/components/apps/EmbeddedClusterManagement.tsx @@ -3,7 +3,7 @@ import classNames from "classnames"; import MaterialReactTable, { MRT_ColumnDef } from "material-react-table"; import { ChangeEvent, useEffect, useMemo, useReducer, useState } from "react"; import Modal from "react-modal"; -import { Link, useParams } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; import { KotsPageTitle } from "@components/Head"; import { useApps } from "@features/App"; @@ -13,6 +13,7 @@ import Icon from "../Icon"; import CodeSnippet from "../shared/CodeSnippet"; import "@src/scss/components/apps/EmbeddedClusterManagement.scss"; +import { on } from "events"; const testData = { nodes: undefined, @@ -50,12 +51,17 @@ const EmbeddedClusterManagement = ({ ); const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); - const { data: appsData } = useApps(); + const { + data: appsData, + refetch: refetchApps + } = useApps(); // we grab the first app because embeddedcluster users should only ever have one app const app = appsData?.apps?.[0]; const { slug } = useParams(); + const navigate = useNavigate(); + // #region queries type NodesResponse = { ha: boolean; @@ -360,6 +366,48 @@ const EmbeddedClusterManagement = ({ }, [nodesData?.nodes?.toString()]); // #endregion + const onContinueClick = async () => { + const res = await fetch( + `${process.env.API_ENDPOINT}/embedded-cluster/confirm`, + { + headers: { + Accept: "application/json", + }, + credentials: "include", + method: "POST", + } + ); + if (!res.ok) { + if (res.status === 401) { + Utilities.logoutUser(); + } + console.log( + "failed to update cluster management, unexpected status code", + res.status + ); + try { + const error = await res.json(); + throw new Error( + error?.error?.message || error?.error || error?.message + ); + } catch (err) { + throw new Error("Unable to update cluster management, please try again later."); + } + } + + await refetchApps(); + + const data = await res.json(); + + if (data.versionStatus === "pending_config") { + navigate(`/${app?.slug}/config`); + } else if (data.versionStatus === "pending_preflight") { + navigate(`/${app?.slug}/preflight`); + } else { + navigate(`/app/${app?.slug}`); + } + }; + return (
@@ -436,14 +484,12 @@ const EmbeddedClusterManagement = ({ )}
{fromLicenseFlow && ( - onContinueClick()} > Continue - + )} {/* MODALS */} diff --git a/web/src/types/index.ts b/web/src/types/index.ts index 8d63e68bf5..d42ecd4a18 100644 --- a/web/src/types/index.ts +++ b/web/src/types/index.ts @@ -257,6 +257,7 @@ export type VersionStatus = | "deploying" | "failed" | "pending" + | "pending_cluster_management" | "pending_config" | "pending_download" | "pending_preflight" From 56afe861c052f29193ab920d2d556a54b854422a Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 21 Feb 2024 22:57:28 +0000 Subject: [PATCH 2/5] yarn format:fix --- web/src/components/apps/AppDetailPage.tsx | 6 +++++- .../components/apps/EmbeddedClusterManagement.tsx | 12 +++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/web/src/components/apps/AppDetailPage.tsx b/web/src/components/apps/AppDetailPage.tsx index 914966f6b5..41678921d7 100644 --- a/web/src/components/apps/AppDetailPage.tsx +++ b/web/src/components/apps/AppDetailPage.tsx @@ -337,7 +337,11 @@ function AppDetailPage(props: Props) { const firstVersion = downstream.pendingVersions.find( (version: Version) => version?.sequence === 0 ); - if ((firstVersion?.status === "unknown" || firstVersion?.status === "pending_cluster_management") && props.isEmbeddedCluster) { + if ( + (firstVersion?.status === "unknown" || + firstVersion?.status === "pending_cluster_management") && + props.isEmbeddedCluster + ) { navigate(`/${appNeedsConfiguration.slug}/cluster/manage`); return; } diff --git a/web/src/components/apps/EmbeddedClusterManagement.tsx b/web/src/components/apps/EmbeddedClusterManagement.tsx index 2886dce658..1e189ebfdf 100644 --- a/web/src/components/apps/EmbeddedClusterManagement.tsx +++ b/web/src/components/apps/EmbeddedClusterManagement.tsx @@ -13,7 +13,6 @@ import Icon from "../Icon"; import CodeSnippet from "../shared/CodeSnippet"; import "@src/scss/components/apps/EmbeddedClusterManagement.scss"; -import { on } from "events"; const testData = { nodes: undefined, @@ -51,10 +50,7 @@ const EmbeddedClusterManagement = ({ ); const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); - const { - data: appsData, - refetch: refetchApps - } = useApps(); + const { data: appsData, refetch: refetchApps } = useApps(); // we grab the first app because embeddedcluster users should only ever have one app const app = appsData?.apps?.[0]; @@ -382,7 +378,7 @@ const EmbeddedClusterManagement = ({ Utilities.logoutUser(); } console.log( - "failed to update cluster management, unexpected status code", + "failed to confirm cluster management, unexpected status code", res.status ); try { @@ -391,7 +387,9 @@ const EmbeddedClusterManagement = ({ error?.error?.message || error?.error || error?.message ); } catch (err) { - throw new Error("Unable to update cluster management, please try again later."); + throw new Error( + "Unable to confirm cluster management, please try again later." + ); } } From 6548c6252b68804c5e1c0c74f301205c3b611584 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 21 Feb 2024 23:14:17 +0000 Subject: [PATCH 3/5] fix route name --- pkg/handlers/handlers.go | 2 +- pkg/handlers/handlers_test.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index 38797e113f..0e6e05940a 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -277,7 +277,7 @@ func RegisterSessionAuthRoutes(r *mux.Router, kotsStore store.Store, handler KOT // Embedded Cluster r.Name("EmbeddedCluster").Path("/api/v1/embedded-cluster").HandlerFunc(NotImplemented) - r.Name("GenerateEmbeddedClusterNodeJoinCommand").Path("/api/v1/embedded-cluster/confirm").Methods("POST"). + r.Name("ConfirmEmbeddedClusterManagement").Path("/api/v1/embedded-cluster/confirm").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)) diff --git a/pkg/handlers/handlers_test.go b/pkg/handlers/handlers_test.go index d5f3542abe..a12dadc083 100644 --- a/pkg/handlers/handlers_test.go +++ b/pkg/handlers/handlers_test.go @@ -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}, From ac5c2f714d0661760690c7ab7acb4356877a88f3 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 21 Feb 2024 23:30:35 +0000 Subject: [PATCH 4/5] address pr feedback --- ...dded_cluster_confirm_cluster_management.go | 80 +++++++++++-------- pkg/handlers/embedded_cluster_delete_node.go | 7 ++ pkg/handlers/embedded_cluster_drain_node.go | 7 ++ pkg/handlers/embedded_cluster_get.go | 19 +++++ .../embedded_cluster_node_join_command.go | 13 +++ 5 files changed, 91 insertions(+), 35 deletions(-) diff --git a/pkg/handlers/embedded_cluster_confirm_cluster_management.go b/pkg/handlers/embedded_cluster_confirm_cluster_management.go index 7c11b95716..36e6b27975 100644 --- a/pkg/handlers/embedded_cluster_confirm_cluster_management.go +++ b/pkg/handlers/embedded_cluster_confirm_cluster_management.go @@ -11,6 +11,7 @@ import ( "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 { @@ -18,6 +19,12 @@ type ConfirmEmbeddedClusterManagementResponse struct { } func (h *Handler) ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *http.Request) { + 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)) @@ -46,50 +53,53 @@ func (h *Handler) ConfirmEmbeddedClusterManagement(w http.ResponseWriter, r *htt } pendingVersion := downstreamVersions.PendingVersions[0] - if pendingVersion.Status == storetypes.VersionPendingClusterManagement { - 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) + if pendingVersion.Status != storetypes.VersionPendingClusterManagement { + logger.Error(fmt.Errorf("pending version is not in pending_cluster_management status")) + w.WriteHeader(http.StatusBadRequest) + return + } - 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 - } + 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) - kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir) - if err != nil { - logger.Error(fmt.Errorf("failed to load kots kinds: %w", err)) - w.WriteHeader(http.StatusInternalServerError) - return - } + 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 + } - 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 - } - } - pendingVersion.Status = downstreamVersionStatus + kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir) + if err != nil { + logger.Error(fmt.Errorf("failed to load kots kinds: %w", err)) + w.WriteHeader(http.StatusInternalServerError) + return + } - if err := store.GetStore().SetDownstreamVersionStatus(app.ID, pendingVersion.Sequence, pendingVersion.Status, ""); err != nil { - logger.Error(fmt.Errorf("failed to set downstream version status: %w", err)) + 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(pendingVersion.Status), + VersionStatus: string(downstreamVersionStatus), }) } diff --git a/pkg/handlers/embedded_cluster_delete_node.go b/pkg/handlers/embedded_cluster_delete_node.go index ea55401654..7851e0d0f5 100644 --- a/pkg/handlers/embedded_cluster_delete_node.go +++ b/pkg/handlers/embedded_cluster_delete_node.go @@ -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) diff --git a/pkg/handlers/embedded_cluster_drain_node.go b/pkg/handlers/embedded_cluster_drain_node.go index ae59dc5071..8d1ef4d399 100644 --- a/pkg/handlers/embedded_cluster_drain_node.go +++ b/pkg/handlers/embedded_cluster_drain_node.go @@ -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) diff --git a/pkg/handlers/embedded_cluster_get.go b/pkg/handlers/embedded_cluster_get.go index 078dddfc5e..f8725702e1 100644 --- a/pkg/handlers/embedded_cluster_get.go +++ b/pkg/handlers/embedded_cluster_get.go @@ -7,6 +7,7 @@ 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 { @@ -14,6 +15,12 @@ type GetEmbeddedClusterRolesResponse struct { } 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) @@ -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) @@ -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) diff --git a/pkg/handlers/embedded_cluster_node_join_command.go b/pkg/handlers/embedded_cluster_node_join_command.go index 1d67f780d8..12cb8f1b49 100644 --- a/pkg/handlers/embedded_cluster_node_join_command.go +++ b/pkg/handlers/embedded_cluster_node_join_command.go @@ -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 { @@ -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)) @@ -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) From fc214414c6752a28314f3dcfae98243c6ca1e4a4 Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Wed, 21 Feb 2024 23:35:24 +0000 Subject: [PATCH 5/5] additional FE feedback --- pkg/handlers/handlers.go | 2 +- web/src/components/apps/AppDetailPage.tsx | 3 +-- web/src/components/apps/EmbeddedClusterManagement.tsx | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index 0e6e05940a..582e9ed8cc 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -277,7 +277,7 @@ 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/confirm").Methods("POST"). + 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)) diff --git a/web/src/components/apps/AppDetailPage.tsx b/web/src/components/apps/AppDetailPage.tsx index 41678921d7..8697faaeb6 100644 --- a/web/src/components/apps/AppDetailPage.tsx +++ b/web/src/components/apps/AppDetailPage.tsx @@ -338,8 +338,7 @@ function AppDetailPage(props: Props) { (version: Version) => version?.sequence === 0 ); if ( - (firstVersion?.status === "unknown" || - firstVersion?.status === "pending_cluster_management") && + firstVersion?.status === "pending_cluster_management" && props.isEmbeddedCluster ) { navigate(`/${appNeedsConfiguration.slug}/cluster/manage`); diff --git a/web/src/components/apps/EmbeddedClusterManagement.tsx b/web/src/components/apps/EmbeddedClusterManagement.tsx index 1e189ebfdf..28ee11bb16 100644 --- a/web/src/components/apps/EmbeddedClusterManagement.tsx +++ b/web/src/components/apps/EmbeddedClusterManagement.tsx @@ -364,7 +364,7 @@ const EmbeddedClusterManagement = ({ const onContinueClick = async () => { const res = await fetch( - `${process.env.API_ENDPOINT}/embedded-cluster/confirm`, + `${process.env.API_ENDPOINT}/embedded-cluster/management`, { headers: { Accept: "application/json",