Skip to content

Commit

Permalink
Migrate cancelRestore to go api (#998)
Browse files Browse the repository at this point in the history
* Migrate cancelRestore to go api

* status codes
  • Loading branch information
emosbaugh committed Aug 26, 2020
1 parent 413549c commit f441287
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 65 deletions.
6 changes: 0 additions & 6 deletions kotsadm/api/src/kots_app/kots_app_store.ts
Expand Up @@ -1551,12 +1551,6 @@ where app_id = $1 and sequence = $2`;
await this.pool.query(q, v);
}

async updateAppRestoreReset(appId): Promise<void> {
const q = `update app set restore_in_progress_name = NULL, restore_undeploy_status = '' where id = $1`;
const v = [appId];
await this.pool.query(q, v);
}

async ignorePreflightPermissionErrors(appId: string, clusterId: string, sequence: number): Promise<void> {
const q = `UPDATE app_downstream_version
SET status = 'pending_preflight', preflight_ignore_permissions = true, preflight_result = null
Expand Down
1 change: 0 additions & 1 deletion kotsadm/api/src/schema/mutation.ts
Expand Up @@ -29,7 +29,6 @@ type Mutation {
saveSnapshotConfig(appId: String!, inputValue: Int!, inputTimeUnit: String!, schedule: String!, autoEnabled: Boolean!): Boolean
deleteSnapshot(snapshotName: String!): Boolean
cancelRestore(appId: String!): Boolean
}
`;

Expand Down
4 changes: 0 additions & 4 deletions kotsadm/api/src/snapshots/resolvers/snapshot_mutations.ts
Expand Up @@ -4,10 +4,6 @@ import { VeleroClient } from "./veleroClient";

export function SnapshotMutations(stores: Stores) {
return {
async cancelRestore(root: any, args: any, context: Context): Promise<void> {
await stores.kotsAppStore.updateAppRestoreReset(args.appId);
},

async deleteSnapshot(root: any, args: any, context: Context): Promise<void> {
context.requireSingleTenantSession();
const velero = new VeleroClient("velero"); // TODO namespace
Expand Down
1 change: 1 addition & 0 deletions kotsadm/pkg/apiserver/server.go
Expand Up @@ -155,6 +155,7 @@ func Start() {
// App snapshot routes
r.Path("/api/v1/app/{appSlug}/snapshot/backup").Methods("OPTIONS", "POST").HandlerFunc(handlers.CreateBackup)
r.Path("/api/v1/app/{appSlug}/snapshot/restore/status").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetRestoreStatus)
r.Path("/api/v1/app/{appSlug}/snapshot/restore").Methods("OPTIONS", "DELETE").HandlerFunc(handlers.CancelRestore)
r.Path("/api/v1/app/{appSlug}/snapshot/restore/{restoreName}").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetKotsadmRestore)
r.Path("/api/v1/app/{appSlug}/snapshots").Methods("OPTIONS", "GET").HandlerFunc(handlers.ListBackups)
r.Path("/api/v1/app/{appSlug}/snapshot/config").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetSnapshotConfig)
Expand Down
72 changes: 51 additions & 21 deletions kotsadm/pkg/handlers/restore.go
Expand Up @@ -50,14 +50,14 @@ func CreateRestore(w http.ResponseWriter, r *http.Request) {
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to parse authorization header"
JSON(w, 401, createRestoreResponse)
JSON(w, http.StatusUnauthorized, createRestoreResponse)
return
}

// we don't currently have roles, all valid tokens are valid sessions
if sess == nil || sess.ID == "" {
createRestoreResponse.Error = "failed to parse authorization header"
JSON(w, 401, createRestoreResponse)
JSON(w, http.StatusUnauthorized, createRestoreResponse)
return
}

Expand All @@ -67,7 +67,7 @@ func CreateRestore(w http.ResponseWriter, r *http.Request) {
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to find backup"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

Expand All @@ -79,12 +79,12 @@ func CreateRestore(w http.ResponseWriter, r *http.Request) {
if err := kotsadm.CreateRestoreJob(opts); err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to initiate restore"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

createRestoreResponse.Success = true
JSON(w, 200, createRestoreResponse)
JSON(w, http.StatusOK, createRestoreResponse)
return
}

Expand All @@ -93,60 +93,60 @@ func CreateRestore(w http.ResponseWriter, r *http.Request) {
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to parse sequence label"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

status, err := downstream.GetDownstreamVersionStatus(appID, sequence)
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to find downstream version"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

if status != "deployed" {
err := errors.Errorf("sequence %d of app %s was never deployed to this cluster", sequence, appID)
logger.Error(err)
createRestoreResponse.Error = err.Error()
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

kotsApp, err := store.GetStore().GetApp(appID)
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to get app"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

if kotsApp.RestoreInProgressName != "" {
err := errors.Errorf("restore is already in progress")
logger.Error(err)
createRestoreResponse.Error = err.Error()
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

if err := snapshot.DeleteRestore(snapshotName); err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to initiate restore"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

err = app.InitiateRestore(snapshotName, appID)
if err != nil {
logger.Error(err)
createRestoreResponse.Error = "failed to initiate restore"
JSON(w, 500, createRestoreResponse)
JSON(w, http.StatusInternalServerError, createRestoreResponse)
return
}

createRestoreResponse.Success = true

JSON(w, 200, createRestoreResponse)
JSON(w, http.StatusOK, createRestoreResponse)
}

func GetRestoreStatus(w http.ResponseWriter, r *http.Request) {
Expand All @@ -162,22 +162,22 @@ func GetRestoreStatus(w http.ResponseWriter, r *http.Request) {
if err != nil {
logger.Error(err)
response.Error = "failed to parse authorization header"
JSON(w, 401, response)
JSON(w, http.StatusUnauthorized, response)
return
}

// we don't currently have roles, all valid tokens are valid sessions
if sess == nil || sess.ID == "" {
response.Error = "failed to parse authorization header"
JSON(w, 401, response)
JSON(w, http.StatusUnauthorized, response)
return
}

foundApp, err := store.GetStore().GetAppFromSlug(mux.Vars(r)["appSlug"])
if err != nil {
logger.Error(err)
response.Error = "failed to get app from app slug"
JSON(w, 500, response)
JSON(w, http.StatusInternalServerError, response)
return
}

Expand All @@ -186,7 +186,37 @@ func GetRestoreStatus(w http.ResponseWriter, r *http.Request) {
response.Status = "running" // there is only one status right now
}

JSON(w, 200, response)
JSON(w, http.StatusOK, response)
}

func CancelRestore(w http.ResponseWriter, r *http.Request) {
if handleOptionsRequest(w, r) {
return
}

if err := requireValidSession(w, r); err != nil {
logger.Error(err)
return
}

appSlug := mux.Vars(r)["appSlug"]

foundApp, err := store.GetStore().GetAppFromSlug(appSlug)
if err != nil {
err = errors.Wrap(err, "failed to get app from app slug")
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if err := app.ResetRestore(foundApp.ID); err != nil {
err = errors.Wrap(err, "failed to reset app restore in progress name")
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusNoContent)
}

type GetKotsadmRestoreResponse struct {
Expand All @@ -211,7 +241,7 @@ func GetKotsadmRestore(w http.ResponseWriter, r *http.Request) {
if err != nil {
err = errors.Wrap(err, "failed to get app from app slug")
logger.Error(err)
w.WriteHeader(500)
w.WriteHeader(http.StatusInternalServerError)
return
}

Expand All @@ -227,7 +257,7 @@ func GetKotsadmRestore(w http.ResponseWriter, r *http.Request) {
if err := app.ResetRestore(foundApp.ID); err != nil {
err = errors.Wrap(err, "failed to reset app restore in progress name")
logger.Error(err)
w.WriteHeader(500)
w.WriteHeader(http.StatusInternalServerError)
return
}
restoreDetail = &snapshottypes.RestoreDetail{
Expand All @@ -247,11 +277,11 @@ func GetKotsadmRestore(w http.ResponseWriter, r *http.Request) {
} else if err != nil {
err = errors.Wrap(err, "failed to get restore detail")
logger.Error(err)
w.WriteHeader(500)
w.WriteHeader(http.StatusInternalServerError)
return
}

response.RestoreDetail = restoreDetail

JSON(w, 200, response)
JSON(w, http.StatusOK, response)
}
51 changes: 27 additions & 24 deletions kotsadm/web/src/components/apps/AppSnapshotRestore.jsx
Expand Up @@ -6,7 +6,6 @@ import { Line } from "rc-progress";
import Loader from "../shared/Loader";
import { Utilities } from "@src/utilities/utilities";
import { Repeater } from "@src/utilities/repeater";
import { cancelRestore } from "../../mutations/SnapshotMutations";
import "../../scss/components/snapshots/AppSnapshots.scss";

class AppSnapshotRestore extends Component {
Expand Down Expand Up @@ -94,26 +93,35 @@ class AppSnapshotRestore extends Component {
}
}

onCancelRestore = () => {
const { app } = this.props;
onCancelRestore = async () => {
this.setState({ cancelingRestore: true, cancelRestoreErr: false, cancelRestoreErrorMsg: "" });
this.props.cancelRestore(app.id)
.then(() => {
this.setState({ cancelingRestore: false });
this.props.history.push(`/app/${this.props.app?.slug}/snapshots`);
})
.catch(err => {
err.graphQLErrors.map(({ msg }) => {
this.setState({
cancelingRestore: false,
cancelRestoreErr: true,
cancelRestoreErrorMsg: msg,
});
})
})
.finally(() => {
this.setState({ cancelingRestore: false });
try {
await this.fetchCancelRestore();
this.props.history.push(`/app/${this.props.app?.slug}/snapshots`);
} catch (err) {
this.setState({
cancelRestoreErr: true,
cancelRestoreErrorMsg: err ? `${err.message}` : "Something went wrong, please try again.",
});
}
this.setState({ cancelingRestore: false });
}

fetchCancelRestore = async () => {
const { app } = this.props;
const res = await fetch(`${window.env.API_ENDPOINT}/app/${app.slug}/snapshot/restore`, {
method: "DELETE",
headers: {
"Authorization": Utilities.getToken(),
}
});
if (!res.ok) {
if (res.status === 401) {
Utilities.logoutUser();
return;
}
throw new Error(`Unexpected status code: ${res.status}`);
}
}

renderErrors = (errors) => {
Expand Down Expand Up @@ -281,9 +289,4 @@ class AppSnapshotRestore extends Component {
export default compose(
withApollo,
withRouter,
graphql(cancelRestore, {
props: ({ mutate }) => ({
cancelRestore: (appId) => mutate({ variables: { appId } })
})
})
)(AppSnapshotRestore);
10 changes: 1 addition & 9 deletions kotsadm/web/src/mutations/SnapshotMutations.js
Expand Up @@ -9,17 +9,9 @@ export const restoreSnapshotRaw = `
`;
export const restoreSnapshot = gql(restoreSnapshotRaw);

export const cancelRestoreRaw = `
mutation cancelRestore($appId: String!) {
cancelRestore(appId: $appId)
}
`;
export const cancelRestore = gql(cancelRestoreRaw);


export const saveSnapshotConfigRaw = `
mutation saveSnapshotConfig($appId: String!, $inputValue: Int!, $inputTimeUnit: String!, $schedule: String!, $autoEnabled: Boolean!) {
saveSnapshotConfig(appId: $appId, inputValue: $inputValue, inputTimeUnit: $inputTimeUnit, schedule: $schedule, autoEnabled: $autoEnabled)
}
`;
export const saveSnapshotConfig = gql(saveSnapshotConfigRaw);
export const saveSnapshotConfig = gql(saveSnapshotConfigRaw);

0 comments on commit f441287

Please sign in to comment.