Skip to content

Commit

Permalink
redirect to cluster mgmt if EC restore in progress (#4589)
Browse files Browse the repository at this point in the history
* redirect to cluster mgmt if EC restore in progress

* clean up a line

* rename field to isEmbeddedClusterWaitingForNodes
  • Loading branch information
Craig O'Donnell authored May 1, 2024
1 parent 690147f commit 2c72caa
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 7 deletions.
45 changes: 38 additions & 7 deletions pkg/handlers/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ import (
v1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
)

const (
appYamlKey = "application.yaml"
iconURI = "https://cdn2.iconfinder.com/data/icons/mixd/512/16_kubernetes-512.png"
metadataConfigMapName = "kotsadm-application-metadata"
upstreamUriKey = "upstreamUri"
defaultAppName = "the application"
appYamlKey = "application.yaml"
iconURI = "https://cdn2.iconfinder.com/data/icons/mixd/512/16_kubernetes-512.png"
metadataConfigMapName = "kotsadm-application-metadata"
upstreamUriKey = "upstreamUri"
defaultAppName = "the application"
embeddedClusterRestoreConfigMapName = "embedded-cluster-wait-for-nodes"
)

// MetadataResponse non sensitive information to be used by ui pre-login
Expand All @@ -40,8 +42,9 @@ type MetadataResponse struct {
Namespace string `json:"namespace"`
UpstreamURI string `json:"upstreamUri"`
// ConsoleFeatureFlags optional flags from application.yaml used to enable ui features
ConsoleFeatureFlags []string `json:"consoleFeatureFlags"`
AdminConsoleMetadata AdminConsoleMetadata `json:"adminConsoleMetadata"`
ConsoleFeatureFlags []string `json:"consoleFeatureFlags"`
AdminConsoleMetadata AdminConsoleMetadata `json:"adminConsoleMetadata"`
IsEmbeddedClusterWaitingForNodes bool `json:"isEmbeddedClusterWaitingForNodes"`
}

type MetadataResponseBranding struct {
Expand Down Expand Up @@ -117,6 +120,22 @@ func GetMetadataHandler(getK8sInfoFn MetadataK8sFn, kotsStore store.Store) http.
IsEmbeddedCluster: kotsadmMetadata.IsEmbeddedCluster,
}

if kotsadmMetadata.IsEmbeddedCluster {
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Error(errors.Wrap(err, "failed to get k8s clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

metadataResponse.IsEmbeddedClusterWaitingForNodes, err = isEmbeddedClusterWaitingForNodes(r.Context(), clientset)
if err != nil {
logger.Error(errors.Wrap(err, "failed to check if embedded cluster restore is in progress"))
w.WriteHeader(http.StatusInternalServerError)
return
}
}

JSON(w, http.StatusOK, metadataResponse)
}
}
Expand Down Expand Up @@ -247,3 +266,15 @@ func GetMetaDataConfig() (*v1.ConfigMap, types.Metadata, error) {
}

type MetadataK8sFn func() (*v1.ConfigMap, types.Metadata, error)

func isEmbeddedClusterWaitingForNodes(ctx context.Context, clientset kubernetes.Interface) (bool, error) {
_, err := clientset.CoreV1().ConfigMaps("embedded-cluster").Get(ctx, embeddedClusterRestoreConfigMapName, metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
return false, nil
}
return false, errors.Wrapf(err, "failed to get configmap %s", embeddedClusterRestoreConfigMapName)
}

return true, nil
}
43 changes: 43 additions & 0 deletions pkg/handlers/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
"net/http"
Expand All @@ -17,6 +18,8 @@ import (
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
)

Expand Down Expand Up @@ -705,3 +708,43 @@ func createBrandingArchiveWithFiles(files []brandingArchiveFile) (*bytes.Buffer,

return buf, nil
}

func Test_isEmbeddedClusterWaitingForNodes(t *testing.T) {
tests := []struct {
name string
clientset kubernetes.Interface
want bool
wantErr bool
}{
{
name: "no restore in progress",
clientset: fake.NewSimpleClientset(),
want: false,
wantErr: false,
},
{
name: "restore in progress",
clientset: fake.NewSimpleClientset(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: embeddedClusterRestoreConfigMapName,
Namespace: "embedded-cluster",
},
}),
want: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
got, err := isEmbeddedClusterWaitingForNodes(ctx, tt.clientset)
if (err != nil) != tt.wantErr {
t.Errorf("isEmbeddedClusterWaitingForNodes() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("isEmbeddedClusterWaitingForNodes() = %v, want %v", got, tt.want)
}
})
}
}
7 changes: 7 additions & 0 deletions web/src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type State = {
initSessionId: string | null;
selectedAppName: string | null;
snapshotInProgressApps: string[];
isEmbeddedClusterWaitingForNodes: boolean;
themeState: ThemeState;
};

Expand Down Expand Up @@ -129,6 +130,7 @@ const Root = () => {
: "",
selectedAppName: null,
snapshotInProgressApps: [],
isEmbeddedClusterWaitingForNodes: false,
themeState: {
navbarLogo: null,
},
Expand Down Expand Up @@ -272,6 +274,8 @@ const Root = () => {
adminConsoleMetadata: data.adminConsoleMetadata,
featureFlags: data.consoleFeatureFlags,
fetchingMetadata: false,
isEmbeddedClusterWaitingForNodes:
data.isEmbeddedClusterWaitingForNodes,
});
})
.catch((err) => {
Expand Down Expand Up @@ -467,6 +471,9 @@ const Root = () => {
onLoginSuccess={getAppsList}
fetchingMetadata={state.fetchingMetadata}
navigate={navigate}
isEmbeddedClusterWaitingForNodes={
state.isEmbeddedClusterWaitingForNodes
}
/>
}
/>
Expand Down
7 changes: 7 additions & 0 deletions web/src/features/Auth/components/SecureAdminConsole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Props = {
pendingApp: () => Promise<App>;
logo: string | null;
navigate: ReturnType<typeof useNavigate>;
isEmbeddedClusterWaitingForNodes: boolean;
};

type State = {
Expand Down Expand Up @@ -60,6 +61,12 @@ class SecureAdminConsole extends Component<Props, State> {
const apps = await this.props.onLoginSuccess();
const pendingApp = await this.props.pendingApp();
this.setState({ authLoading: false });

if (this.props.isEmbeddedClusterWaitingForNodes) {
this.props.navigate("/cluster/manage", { replace: true });
return loggedIn;
}

if (apps.length > 0) {
this.props.navigate(`/app/${apps[0].slug}`, { replace: true });
} else if (pendingApp?.slug && pendingApp?.needsRegistry) {
Expand Down

0 comments on commit 2c72caa

Please sign in to comment.