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 support for viewing and migrate Tiller releases #330

Merged
merged 11 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from 9 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
36 changes: 35 additions & 1 deletion dashboard/src/actions/apps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Dispatch } from "redux";
import { createAction, getReturnOfExpression } from "typesafe-actions";

import { App } from "../shared/App";
import Chart from "../shared/Chart";
import { HelmRelease } from "../shared/HelmRelease";
import { IApp, IChartVersion, IStoreState } from "../shared/types";
import { AppConflict, IApp, IChartVersion, IStoreState, MissingChart } from "../shared/types";

export const requestApps = createAction("REQUEST_APPS");
export const receiveApps = createAction("RECEIVE_APPS", (apps: IApp[]) => {
Expand Down Expand Up @@ -47,6 +49,7 @@ export function deleteApp(releaseName: string, namespace: string) {
return async (dispatch: Dispatch<IStoreState>): Promise<boolean> => {
try {
await HelmRelease.delete(releaseName, namespace);
await App.waitForDeletion(releaseName);
return true;
} catch (e) {
dispatch(errorDeleteApp(e));
Expand Down Expand Up @@ -82,6 +85,11 @@ export function deployChart(
if (resourceVersion) {
await HelmRelease.upgrade(releaseName, namespace, chartVersion, values);
} else {
const releaseExists = await App.exists(releaseName);
if (releaseExists) {
dispatch(errorApps(new AppConflict("Already exists")));
return false;
}
await HelmRelease.create(releaseName, namespace, chartVersion, values);
}
return true;
Expand All @@ -91,3 +99,29 @@ export function deployChart(
}
};
}

export function migrateApp(
chartVersion: IChartVersion,
releaseName: string,
namespace: string,
values?: string,
) {
return async (dispatch: Dispatch<IStoreState>): Promise<boolean> => {
try {
const chartExists = await Chart.exists(
chartVersion.relationships.chart.data.name,
chartVersion.attributes.version,
chartVersion.relationships.chart.data.repo.name,
);
if (!chartExists) {
dispatch(errorApps(new MissingChart("Not found")));
return false;
}
await HelmRelease.create(releaseName, namespace, chartVersion, values);
return true;
} catch (e) {
dispatch(errorApps(e));
return false;
}
};
}
71 changes: 71 additions & 0 deletions dashboard/src/components/AppMigrate/AppMigrate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from "react";

import { RouterAction } from "react-router-redux";
import { IApp, IChartState, IChartVersion } from "../../shared/types";
import { IAppRepository } from "../../shared/types";

import MigrateForm from "../../components/MigrateForm";

interface IAppMigrateProps {
app: IApp;
error: Error | undefined;
namespace: string;
releaseName: string;
repos: IAppRepository[];
selected: IChartState["selected"];
migrateApp: (
version: IChartVersion,
releaseName: string,
namespace: string,
values?: string,
) => Promise<boolean>;
getApp: (releaseName: string, namespace: string) => Promise<void>;
push: (location: string) => RouterAction;
fetchRepositories: () => Promise<void>;
}

class AppMigrate extends React.Component<IAppMigrateProps> {
public componentDidMount() {
const { fetchRepositories, releaseName, getApp, namespace } = this.props;
getApp(releaseName, namespace);
fetchRepositories();
}

public componentWillReceiveProps(nextProps: IAppMigrateProps) {
const { releaseName, getApp, namespace } = this.props;
if (nextProps.namespace !== namespace) {
getApp(releaseName, nextProps.namespace);
}
}

public render() {
const { app, repos } = this.props;
if (
!repos ||
!app ||
!app.data ||
!app.data.chart ||
!app.data.chart.metadata ||
!app.data.chart.metadata.version ||
!app.data.chart.values
) {
return <div>Loading</div>;
}
return (
<div>
<MigrateForm
{...this.props}
chartID={app.data.name}
chartVersion={app.data.chart.metadata.version}
chartValues={app.data.chart.values.raw}
chartName={app.data.chart.metadata.name || ""}
chartRepoName=""
chartRepoURL=""
chartRepoAuth=""
/>
</div>
);
}
}

export default AppMigrate;
3 changes: 3 additions & 0 deletions dashboard/src/components/AppMigrate/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AppMigrate from "./AppMigrate";

export default AppMigrate;
46 changes: 34 additions & 12 deletions dashboard/src/components/AppView/AppControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,52 @@ interface IAppControlsProps {
}

interface IAppControlsState {
migrate: boolean;
modalIsOpen: boolean;
redirectToAppList: boolean;
upgrade: boolean;
deleting: boolean;
}

class AppControls extends React.Component<IAppControlsProps, IAppControlsState> {
public state: IAppControlsState = {
deleting: false,
migrate: false,
modalIsOpen: false,
redirectToAppList: false,
upgrade: false,
};

public render() {
const { name, namespace } = this.props.app.data;
if (this.props.app.hr && this.props.app.hr.metadata) {
return (
<div className="AppControls">
<button className="button" onClick={this.handleUpgradeClick}>
Upgrade
</button>
{this.state.upgrade && <Redirect push={true} to={`/apps/ns/${namespace}/edit/${name}`} />}
<button className="button button-danger" onClick={this.openModel}>
Delete
</button>
<ConfirmDialog
onConfirm={this.handleDeleteClick}
modalIsOpen={this.state.modalIsOpen}
loading={this.state.deleting}
closeModal={this.closeModal}
/>
{this.state.redirectToAppList && <Redirect to={`/apps/ns/${namespace}`} />}
</div>
);
}
return (
<div className="AppControls">
<button className="button" onClick={this.handleUpgradeClick}>
Upgrade
</button>
{this.state.upgrade && <Redirect push={true} to={`/apps/ns/${namespace}/edit/${name}`} />}
<button className="button button-danger" onClick={this.openModel}>
Delete
<button className="button" onClick={this.handleMigrateClick}>
Migrate
Copy link
Contributor

Choose a reason for hiding this comment

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

should we call this button Import App?

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 set this back to Migrate since there is now a banner that explains it in detail

</button>
<ConfirmDialog
onConfirm={this.handleDeleteClick}
modalIsOpen={this.state.modalIsOpen}
closeModal={this.closeModal}
/>
{this.state.redirectToAppList && <Redirect to={`/apps/ns/${namespace}`} />}
{this.state.migrate && (
<Redirect push={true} to={`/apps/ns/${namespace}/migrate/${name}`} />
)}
</div>
);
}
Expand All @@ -47,6 +64,10 @@ class AppControls extends React.Component<IAppControlsProps, IAppControlsState>
this.setState({ upgrade: true });
};

public handleMigrateClick = () => {
this.setState({ migrate: true });
};

public openModel = () => {
this.setState({
modalIsOpen: true,
Expand All @@ -60,6 +81,7 @@ class AppControls extends React.Component<IAppControlsProps, IAppControlsState>
};

public handleDeleteClick = async () => {
this.setState({ deleting: true });
const deleted = await this.props.deleteApp();
const s: Partial<IAppControlsState> = { modalIsOpen: false };
if (deleted) {
Expand Down
14 changes: 14 additions & 0 deletions dashboard/src/components/AppView/AppView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class AppView extends React.Component<IAppViewProps, IAppViewState> {
<main>
<div className="container">
{this.props.deleteError && this.renderError(this.props.deleteError, "delete")}
{!this.props.app.hr && this.renderMigrationNeeded()}
<div className="row collapse-b-tablet">
<div className="col-3">
<ChartInfo app={app} />
Expand Down Expand Up @@ -198,6 +199,19 @@ class AppView extends React.Component<IAppViewProps, IAppViewState> {
);
}

private renderMigrationNeeded() {
return (
<div className="banner">
<div className="container container-small text-c">
<p className="margin-t-small">
This release is not being managed by Kubeapps. To be able to upgrade or delete this
release <strong>click on the "Migrate" button below</strong>.
</p>
</div>
</div>
);
}

private renderError(error: Error, action: string = "view") {
const { namespace, releaseName } = this.props;
switch (error.constructor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class AppRepoListItem extends React.Component<IAppRepoListItemProps, IApp
<ConfirmDialog
onConfirm={this.handleDeleteClick(repo.metadata.name)}
modalIsOpen={this.state.modalIsOpen}
loading={false}
closeModal={this.closeModal}
/>

Expand Down
31 changes: 18 additions & 13 deletions dashboard/src/components/ConfirmDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Modal from "react-modal";

interface IConfirmDialogProps {
modalIsOpen: boolean;
loading: boolean;
onConfirm: () => Promise<any>;
closeModal: () => Promise<any>;
}
Expand Down Expand Up @@ -39,19 +40,23 @@ class ConfirmDialog extends React.Component<IConfirmDialogProps, IConfirmDialogS
{this.state.error && (
<div className="padding-big margin-b-big bg-action">{this.state.error}</div>
)}
<div>Are you sure you want to delete this?</div>
<div>
<button className="button" onClick={this.props.closeModal}>
Cancel
</button>
<button
className="button button-primary button-danger"
type="submit"
onClick={this.props.onConfirm}
>
Delete
</button>
</div>
{this.props.loading === true ? (
Copy link
Contributor Author

@andresmgot andresmgot May 25, 2018

Choose a reason for hiding this comment

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

this is needed to wait until the Tiller release is deleted, if not, after clicking "Delete" it goes directly to the Applications view and the deleted application is still there.

<div> Loading ... </div>
) : (
<div>
<div> Are you sure you want to delete this? </div>
<button className="button" onClick={this.props.closeModal}>
Cancel
</button>
<button
className="button button-primary button-danger"
type="submit"
onClick={this.props.onConfirm}
>
Delete
</button>
</div>
)}
</Modal>
</div>
);
Expand Down
11 changes: 9 additions & 2 deletions dashboard/src/components/DeploymentForm/DeploymentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RouterAction } from "react-router-redux";

import { IServiceBinding } from "../../shared/ServiceBinding";
import {
AppConflict,
ForbiddenError,
IChartState,
IChartVersion,
Expand Down Expand Up @@ -89,7 +90,7 @@ class DeploymentForm extends React.Component<IDeploymentFormProps, IDeploymentFo
namespace = hr.metadata.namespace;
this.setState({
namespace,
releaseName: hr.metadata.name,
releaseName: hr.spec.releaseName,
});
} else {
this.setState({
Expand Down Expand Up @@ -291,7 +292,7 @@ class DeploymentForm extends React.Component<IDeploymentFormProps, IDeploymentFo
resourceVersion,
);
if (deployed) {
push(`/apps/ns/${namespace}/${namespace}-${releaseName}`);
push(`/apps/ns/${namespace}/${releaseName}`);
} else {
this.setState({ isDeploying: false });
}
Expand Down Expand Up @@ -326,6 +327,12 @@ class DeploymentForm extends React.Component<IDeploymentFormProps, IDeploymentFo
roles[0].verbs = ["create"];
}
switch (error && error.constructor) {
case AppConflict:
return (
<NotFoundErrorAlert
header={`The given release name already exists in the cluster. Choose a different one`}
/>
);
case ForbiddenError:
return (
<PermissionsErrorAlert
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/DeprovisionButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class DeprovisionButton extends React.Component<IDeprovisionButtonProps, IDeprov
<ConfirmDialog
onConfirm={this.handleDeprovision}
modalIsOpen={this.state.modalIsOpen}
loading={false}
closeModal={this.closeModal}
/>

Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/FunctionView/FunctionControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class FunctionControls extends React.Component<IFunctionControlsProps, IFunction
</button>
<ConfirmDialog
onConfirm={this.handleDeleteClick}
loading={false}
modalIsOpen={this.state.modalIsOpen}
closeModal={this.closeModal}
/>
Expand Down
Loading