Skip to content

Commit

Permalink
move getKotsDownstreamOutput query to go (#895)
Browse files Browse the repository at this point in the history
* move getKotsDownstreamOutput query to go
  • Loading branch information
sgalsaleh committed Aug 1, 2020
1 parent 91a8839 commit 5ce2fdb
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 42 deletions.
3 changes: 1 addition & 2 deletions kotsadm/pkg/apiserver/server.go
Expand Up @@ -105,6 +105,7 @@ func Start() {
r.Path("/api/v1/app/{appSlug}/sequence/{sequence}/renderedcontents").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppRenderedContents)
r.Path("/api/v1/app/{appSlug}/sequence/{sequence}/contents").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppContents)
r.Path("/api/v1/app/{appSlug}/cluster/{clusterId}/dashboard").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetAppDashboard)
r.Path("/api/v1/app/{appSlug}/cluster/{clusterId}/sequence/{sequence}/downstreamoutput").Methods("OPTIONS", "GET").HandlerFunc(handlers.GetDownstreamOutput)

r.HandleFunc("/api/v1/login", handlers.Login)
r.HandleFunc("/api/v1/logout", handlers.Logout)
Expand Down Expand Up @@ -144,8 +145,6 @@ func Start() {
// Find a home snapshot routes
r.Path("/api/v1/snapshot/{backup}/logs").Methods("OPTIONS", "GET").HandlerFunc(handlers.DownloadSnapshotLogs)

// TODO

// KURL
r.HandleFunc("/api/v1/kurl", handlers.NotImplemented)
r.Path("/api/v1/kurl/generate-node-join-command-worker").Methods("OPTIONS", "POST").HandlerFunc(handlers.GenerateNodeJoinCommandWorker)
Expand Down
77 changes: 77 additions & 0 deletions kotsadm/pkg/downstream/downstream.go
Expand Up @@ -2,9 +2,11 @@ package downstream

import (
"database/sql"
"encoding/base64"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/kotsadm/pkg/downstream/types"
"github.com/replicatedhq/kots/kotsadm/pkg/logger"
"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
)

Expand Down Expand Up @@ -134,3 +136,78 @@ func SetIgnorePreflightPermissionErrors(appID string, sequence int64) error {

return nil
}

func GetDownstreamOutput(appID string, clusterID string, sequence int64) (*types.DownstreamOutput, error) {
db := persistence.MustGetPGSession()
query := `SELECT
adv.status,
adv.status_info,
ado.dryrun_stdout,
ado.dryrun_stderr,
ado.apply_stdout,
ado.apply_stderr
FROM
app_downstream_version adv
LEFT JOIN
app_downstream_output ado
ON
adv.app_id = ado.app_id AND adv.cluster_id = ado.cluster_id AND adv.sequence = ado.downstream_sequence
WHERE
adv.app_id = $1 AND
adv.cluster_id = $2 AND
adv.sequence = $3`
row := db.QueryRow(query, appID, clusterID, sequence)

var status sql.NullString
var statusInfo sql.NullString
var dryrunStdout sql.NullString
var dryrunStderr sql.NullString
var applyStdout sql.NullString
var applyStderr sql.NullString

if err := row.Scan(&status, &statusInfo, &dryrunStdout, &dryrunStderr, &applyStdout, &applyStderr); err != nil {
if err == sql.ErrNoRows {
return &types.DownstreamOutput{}, nil
}
return nil, errors.Wrap(err, "failed to select downstream")
}

renderError := ""
if status.String == "failed" {
renderError = statusInfo.String
}

dryrunStdoutDecoded, err := base64.StdEncoding.DecodeString(dryrunStdout.String)
if err != nil {
logger.Error(errors.Wrap(err, "failed to decode dryrun stdout"))
dryrunStdoutDecoded = []byte("")
}

dryrunStderrDecoded, err := base64.StdEncoding.DecodeString(dryrunStderr.String)
if err != nil {
logger.Error(errors.Wrap(err, "failed to decode dryrun stderr"))
dryrunStderrDecoded = []byte("")
}

applyStdoutDecoded, err := base64.StdEncoding.DecodeString(applyStdout.String)
if err != nil {
logger.Error(errors.Wrap(err, "failed to decode apply stdout"))
applyStdoutDecoded = []byte("")
}

applyStderrDecoded, err := base64.StdEncoding.DecodeString(applyStderr.String)
if err != nil {
logger.Error(errors.Wrap(err, "failed to decode apply stder"))
applyStderrDecoded = []byte("")
}

output := &types.DownstreamOutput{
DryrunStdout: string(dryrunStdoutDecoded),
DryrunStderr: string(dryrunStderrDecoded),
ApplyStdout: string(applyStdoutDecoded),
ApplyStderr: string(applyStderrDecoded),
RenderError: string(renderError),
}

return output, nil
}
8 changes: 8 additions & 0 deletions kotsadm/pkg/downstream/types/types.go
Expand Up @@ -5,3 +5,11 @@ type Downstream struct {
Name string
CurrentSequence int64
}

type DownstreamOutput struct {
DryrunStdout string `json:"dryrunStdout"`
DryrunStderr string `json:"dryrunStderr"`
ApplyStdout string `json:"applyStdout"`
ApplyStderr string `json:"applyStderr"`
RenderError string `json:"renderError"`
}
51 changes: 51 additions & 0 deletions kotsadm/pkg/handlers/downstream.go
@@ -0,0 +1,51 @@
package handlers

import (
"net/http"
"strconv"

"github.com/gorilla/mux"
"github.com/replicatedhq/kots/kotsadm/pkg/app"
"github.com/replicatedhq/kots/kotsadm/pkg/downstream"
"github.com/replicatedhq/kots/kotsadm/pkg/logger"
)

func GetDownstreamOutput(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(http.StatusOK)
return
}

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

appSlug := mux.Vars(r)["appSlug"]
clusterID := mux.Vars(r)["clusterId"]
sequence, err := strconv.Atoi(mux.Vars(r)["sequence"])
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

a, err := app.GetFromSlug(appSlug)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

output, err := downstream.GetDownstreamOutput(a.ID, clusterID, int64(sequence))
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

JSON(w, http.StatusOK, output)
}
65 changes: 25 additions & 40 deletions kotsadm/web/src/components/apps/AppVersionHistory.jsx
Expand Up @@ -19,7 +19,7 @@ import DownstreamWatchVersionDiff from "@src/components/watches/DownstreamWatchV
import AirgapUploadProgress from "@src/components/AirgapUploadProgress";
import UpdateCheckerModal from "@src/components/modals/UpdateCheckerModal";
import ShowDetailsModal from "@src/components/modals/ShowDetailsModal";
import { getKotsDownstreamHistory, getKotsDownstreamOutput, getUpdateDownloadStatus } from "../../queries/AppsQueries";
import { getKotsDownstreamHistory, getUpdateDownloadStatus } from "../../queries/AppsQueries";
import { Utilities, isAwaitingResults, secondsAgo, getPreflightResultState, getGitProviderDiffUrl, getCommitHashFromUrl } from "../../utilities/utilities";
import { Repeater } from "../../utilities/repeater";
import has from "lodash/has";
Expand Down Expand Up @@ -471,26 +471,30 @@ class AppVersionHistory extends Component {
}

handleViewLogs = async version => {
const { match, app } = this.props;
const clusterSlug = app.downstreams?.length && app.downstreams[0].cluster?.slug;
if (clusterSlug) {
try {
const { app } = this.props;
const clusterId = app.downstreams?.length && app.downstreams[0].cluster?.id;

this.setState({ logsLoading: true, showLogsModal: true });
this.props.client.query({
query: getKotsDownstreamOutput,
fetchPolicy: "no-cache",
variables: {
appSlug: match.params.slug,
clusterSlug: clusterSlug,
sequence: version.sequence
}
}).then(result => {
const logs = result.data.getKotsDownstreamOutput;

const res = await fetch(`${window.env.API_ENDPOINT}/app/${app?.slug}/cluster/${clusterId}/sequence/${version?.sequence}/downstreamoutput`, {
headers: {
"Authorization": Utilities.getToken(),
"Content-Type": "application/json",
},
method: "GET",
});
if (res.ok && res.status === 200) {
const logs = await res.json();
const selectedTab = Object.keys(logs)[0];
this.setState({ logs, selectedTab, logsLoading: false });
}).catch(err => {
console.log(err);
} else {
console.log("failed to view logs, unexpected status code", res.status);
this.setState({ logsLoading: false });
});
}
} catch(err) {
console.log(err);
this.setState({ logsLoading: false });
}
}

Expand Down Expand Up @@ -1091,11 +1095,12 @@ class AppVersionHistory extends Component {
</div>
) : (
<div className="flex-column flex1">
{logs.renderError ?
<div className="flex-column flex1">
{!logs.renderError && this.renderLogsTabs()}
<div className="flex-column flex1 u-border--gray monaco-editor-wrapper">
<MonacoEditor
language="json"
value={logs.renderError}
value={logs.renderError || logs[selectedTab]}
height="100%"
width="100%"
options={{
Expand All @@ -1108,27 +1113,7 @@ class AppVersionHistory extends Component {
}}
/>
</div>
:
<div className="flex-column flex1">
{this.renderLogsTabs()}
<div className="flex-column flex1 u-border--gray monaco-editor-wrapper">
<MonacoEditor
language="json"
value={logs[selectedTab]}
height="100%"
width="100%"
options={{
readOnly: true,
contextmenu: false,
minimap: {
enabled: false
},
scrollBeyondLastLine: false,
}}
/>
</div>
</div>
}
</div>
<div className="u-marginTop--20 flex">
<button type="button" className="btn primary" onClick={this.hideLogsModal}>Ok, got it!</button>
</div>
Expand Down

0 comments on commit 5ce2fdb

Please sign in to comment.