Skip to content

Commit

Permalink
Merge pull request #871 from replicatedhq/move-get-airgap-install-sta…
Browse files Browse the repository at this point in the history
…tus-to-go

move getAirgapInstallStatus query to go
  • Loading branch information
sgalsaleh committed Jul 28, 2020
2 parents 83f6b65 + 2ebabca commit dffd59a
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 23 deletions.
29 changes: 29 additions & 0 deletions kotsadm/pkg/airgap/airgap.go
Expand Up @@ -4,6 +4,7 @@ import (
"archive/tar"
"bufio"
"compress/gzip"
"database/sql"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -32,6 +33,11 @@ type PendingApp struct {
LicenseData string
}

type InstallStatus struct {
InstallStatus string `json:"installStatus"`
CurrentMessage string `json:"currentMessage"`
}

func GetPendingAirgapUploadApp() (*PendingApp, error) {
db := persistence.MustGetPGSession()
query := `select id from app where install_state in ('airgap_upload_pending', 'airgap_upload_in_progress', 'airgap_upload_error') order by created_at desc limit 1`
Expand All @@ -53,6 +59,29 @@ func GetPendingAirgapUploadApp() (*PendingApp, error) {
return &pendingApp, nil
}

func GetInstallStatus() (*InstallStatus, error) {
db := persistence.MustGetPGSession()
query := `SELECT install_state from app ORDER BY created_at DESC LIMIT 1`
row := db.QueryRow(query)

var installState sql.NullString
if err := row.Scan(&installState); err != nil {
return nil, errors.Wrap(err, "failed to scan")
}

_, message, err := task.GetTaskStatus("airgap-install")
if err != nil {
return nil, errors.Wrap(err, "failed to get task status")
}

status := &InstallStatus{
InstallStatus: installState.String,
CurrentMessage: message,
}

return status, nil
}

// CreateAppFromAirgap does a lot. Maybe too much. Definitely too much.
// This function assumes that there's an app in the database that doesn't have a version
// After execution, there will be a sequence 0 of the app, and all clusters in the database
Expand Down
3 changes: 2 additions & 1 deletion kotsadm/pkg/apiserver/server.go
Expand Up @@ -90,8 +90,9 @@ func Start() {
// proxy for license/titled api
r.Path("/license/v1/license").Methods("GET").HandlerFunc(handlers.NodeProxy(upstream))

// Airgap upload and update
// Airgap
r.Path("/api/v1/app/airgap").Methods("OPTIONS", "POST", "PUT").HandlerFunc(handlers.UploadAirgapBundle)
r.Path("/api/v1/app/airgap/status").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAirgapInstallStatus)

// Implemented handlers
r.Path("/api/v1/license/platform").Methods("OPTIONS", "POST").HandlerFunc(handlers.ExchangePlatformLicense)
Expand Down
24 changes: 24 additions & 0 deletions kotsadm/pkg/handlers/airgap.go
Expand Up @@ -23,6 +23,30 @@ type CreateAppFromAirgapResponse struct {
type UpdateAppFromAirgapResponse struct {
}

func GetAirgapInstallStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "content-type, origin, accept, authorization")

if r.Method == "OPTIONS" {
w.WriteHeader(200)
return
}

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

status, err := airgap.GetInstallStatus()
if err != nil {
logger.Error(err)
w.WriteHeader(500)
return
}

JSON(w, 200, status)
}

func UploadAirgapBundle(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "content-type, origin, accept, authorization")
Expand Down
69 changes: 47 additions & 22 deletions kotsadm/web/src/components/AirgapUploadProgress.jsx
@@ -1,26 +1,61 @@
import React from "react"
import { withRouter } from "react-router-dom";
import { compose, withApollo, graphql } from "react-apollo";
import Loader from "./shared/Loader";
import { getAirgapInstallStatus } from "../queries/AppsQueries";
import { formatByteSize, calculateTimeDifference } from "@src/utilities/utilities";
import { formatByteSize, calculateTimeDifference, Utilities } from "@src/utilities/utilities";
import { Repeater } from "@src/utilities/repeater";
import "@src/scss/components/AirgapUploadProgress.scss";
import get from "lodash/get";
let processingImages = null;

class AirgapUploadProgress extends React.Component {
constructor(props) {
super(props);

this.state = {
installStatus: "",
currentMessage: "",
getAirgapInstallStatusJob: new Repeater(),
};
}

componentDidMount() {
processingImages = null;
this.getAirgapInstallStatus();
}

componentWillUnmount() {
this.state.getAirgapInstallStatusJob.stop();
}

getAirgapInstallStatus = async () => {
try {
const res = await fetch(`${window.env.API_ENDPOINT}/app/airgap/status`, {
headers: {
"Authorization": Utilities.getToken(),
"Content-Type": "application/json",
},
method: "GET",
});

const response = await res.json();

this.setState({
installStatus: response.installStatus,
currentMessage: response.currentMessage,
});
} catch(err) {
console.log(err);
}
}

render() {
const { total, sent, onProgressError, onProgressSuccess, smallSize } = this.props;
const { getAirgapInstallStatus } = this.props.data;
const { installStatus, currentMessage } = this.state;

if (getAirgapInstallStatus?.installStatus === "installed") {
if (installStatus === "installed") {
// this conditional is really awkward but im keeping the functionality the same
if (!smallSize) {
this.props.data?.stopPolling();
this.state.getAirgapInstallStatusJob.stop();
}
if (onProgressSuccess) {
onProgressSuccess();
Expand All @@ -30,11 +65,11 @@ class AirgapUploadProgress extends React.Component {
}
}

const hasError = getAirgapInstallStatus?.installStatus === "airgap_upload_error";
const hasError = installStatus === "airgap_upload_error";

if (hasError) {
this.props.data?.stopPolling();
onProgressError(getAirgapInstallStatus?.currentMessage);
this.state.getAirgapInstallStatusJob.stop();
onProgressError(currentMessage);
return null;
}

Expand Down Expand Up @@ -70,9 +105,9 @@ class AirgapUploadProgress extends React.Component {
);
}

this.props.data?.startPolling(1000);
this.state.getAirgapInstallStatusJob.start(this.getAirgapInstallStatus, 1000);

let statusMsg = getAirgapInstallStatus?.currentMessage;
let statusMsg = currentMessage;
try {
// Some of these messages will be JSON formatted progress reports.
const jsonMessage = JSON.parse(statusMsg);
Expand Down Expand Up @@ -186,14 +221,4 @@ AirgapUploadProgress.defaultProps = {
sent: 0
};

export default compose(
withRouter,
withApollo,
graphql(getAirgapInstallStatus, {
options: () => {
return {
fetchPolicy: "network-only"
};
}
})
)(AirgapUploadProgress);
export default withRouter(AirgapUploadProgress);
7 changes: 7 additions & 0 deletions kotsadm/web/src/utilities/repeater.js
Expand Up @@ -4,6 +4,9 @@ export class Repeater {
}

start = (handlerFunc, sleepMs) => {
if (this.isRunning()) {
return;
}
this.handlerFunc = handlerFunc;
this.sleepMs = sleepMs;
this.doNotRun = false;
Expand All @@ -14,6 +17,10 @@ export class Repeater {
this.doNotRun = true;
}

isRunning = () => {
return !this.doNotRun;
}

repeat = () => {
if (this.doNotRun) {
return
Expand Down

0 comments on commit dffd59a

Please sign in to comment.