Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Commit

Permalink
Fixes for kustomize steps (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob0h authored Aug 16, 2018
1 parent 6916d44 commit 26bd1e1
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 55 deletions.
37 changes: 33 additions & 4 deletions pkg/lifecycle/daemon/routes_navcycle_kustomize.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package daemon

import (
"bytes"
"net/http"

"github.com/ghodss/yaml"
"github.com/gin-gonic/gin"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/pkg/errors"
"github.com/replicatedhq/ship/pkg/api"
"github.com/replicatedhq/ship/pkg/state"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
)

func (d *NavcycleRoutes) kustomizeSaveOverlay(c *gin.Context) {
Expand Down Expand Up @@ -81,6 +86,28 @@ func (d *NavcycleRoutes) kustomizeSaveOverlay(c *gin.Context) {
c.JSON(200, map[string]string{"status": "success"})
}

// TODO(Robert): duped logic in filetree
func isSupported(file []byte) bool {
var out unstructured.Unstructured

fileJSON, err := yaml.YAMLToJSON(file)
if err != nil {
return false
}

decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(fileJSON), 1024)
if err := decoder.Decode(&out); err != nil {
return false
}

r := resource.NewResourceFromUnstruct(out)
if r.GetKind() == "CustomResourceDefinition" {
return false
}

return true
}

func (d *NavcycleRoutes) kustomizeGetFile(c *gin.Context) {
debug := level.Debug(log.With(d.Logger, "method", "kustomizeGetFile"))
debug.Log()
Expand All @@ -97,8 +124,9 @@ func (d *NavcycleRoutes) kustomizeGetFile(c *gin.Context) {
}

type Response struct {
Base string `json:"base"`
Overlay string `json:"overlay"`
Base string `json:"base"`
IsSupported bool `json:"isSupported"`
Overlay string `json:"overlay"`
}

step, ok := d.getKustomizeStepOrAbort(c) // todo this should fetch by step ID
Expand All @@ -121,8 +149,9 @@ func (d *NavcycleRoutes) kustomizeGetFile(c *gin.Context) {
}

c.JSON(200, Response{
Base: base,
Overlay: savedState.CurrentKustomizeOverlay(request.Path),
Base: base,
Overlay: savedState.CurrentKustomizeOverlay(request.Path),
IsSupported: isSupported([]byte(base)),
})
}

Expand Down
2 changes: 1 addition & 1 deletion web/src/components/kustomize/HelmValuesEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default class HelmValuesEditor extends React.Component {
} else {
submitAction = find(actions, ["sort", 1]);
}
this.props.handleAction(submitAction);
this.props.handleAction(submitAction, true);
}

onValuesSaved() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class AceEditorHOC extends React.Component {
componentDidUpdate(prevProps) {
const { fileToView } = this.props;
if (fileToView !== prevProps.fileToView) {
if (fileToView.baseContent) {
if (fileToView.baseContent && fileToView.isSupported) {
const markers = this.createMarkers(fileToView);
this.setState({ markers });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { mount } from "enzyme";
import { AceEditorHOC } from "./AceEditorHOC";

const validYaml = `
const testYaml = `
apiVersion: v1
kind: Service
metadata:
Expand All @@ -20,14 +20,24 @@ spec:
`;

describe("AceEditorHOC", () => {
describe("provided a valid yaml", () => {
describe("provided a valid yaml that is supported", () => {
const wrapper = mount(<AceEditorHOC fileToView={{ baseContent: "" }} />);
it("creates markers for all values", () => {
wrapper.setProps({
fileToView: { baseContent: validYaml },
fileToView: { baseContent: testYaml, isSupported: true },
});
const markers = wrapper.state().markers;
expect(markers).toHaveLength(9);
});
});
describe("provided an unsupported yaml", () => {
const wrapper = mount(<AceEditorHOC fileToView={{ baseContent: "" }} />);
it("creates markers for all values", () => {
wrapper.setProps({
fileToView: { baseContent: testYaml, isSupported: false },
});
const markers = wrapper.state().markers;
expect(markers).toHaveLength(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from "react";
import RenderActions from "../../shared/RenderActions";

import "../../../scss/components/kustomize/KustomizeEmpty.scss";

export default class KustomizeEmpty extends React.Component {
render() {
const { actions, handleAction} = this.props;
return (
<div className="flex1 flex-column u-paddingLeft--20 u-paddingRight--20 u-paddingTop--30 u-paddingBottom--30 u-overflow--auto EmmptyState--wrapper">
<div className="KustomizeEmpty--wrapper flex1 flex-column u-paddingLeft--20 u-paddingRight--20 u-paddingTop--30 u-paddingBottom--30 u-overflow--auto EmmptyState--wrapper">
<p className="u-fontSize--jumbo u-color--tuna u-fontWeight--bold u-marginBottom--normal u-lineHeight--normal">Kustomize your YAML with overlays</p>
<p className="u-fontSize--normal u-fontWeight--medium u-lineHeight--more">
An overlay is a target that modifies (and thus depends on) another target. The kustomization in an overlay refers to (via file path, URI or other method)
Expand Down Expand Up @@ -43,15 +45,15 @@ export default class KustomizeEmpty extends React.Component {
</div>
</div>
</div>
<div className="action container u-width--full u-marginTop--30 flex flex1 justifyContent--flexEnd u-position--fixed u-bottom--0 u-right--0 u-left--0">
<RenderActions actions={actions} handleAction={handleAction} />
</div>
<div className="skip-wrapper">
<p className="u-fontSize--normal u-fontWeight--medium u-lineHeight--more">
You are not required to customize your YAML. We built this tool to make it easy to apply overlay values and ship customized YAML quickly and effeciently. However, if you have no need to change any of these files you can move right along to the deployment step.
</p>
<p className="u-marginTop--20 u-fontSize--normal u-fontWeight--medium u-lineHeight--more">If you’re ready to deploy your YAML simply <span onClick={this.props.skipKustomize} className="u-color--astral u-textDecoration--underlineOnHover">click here</span>.</p>
</div>
<div className="action container u-width--full u-marginTop--30 flex flex1 justifyContent--flexEnd u-position--fixed u-bottom--0 u-right--0 u-left--0">
<RenderActions actions={actions} handleAction={handleAction} />
</div>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ export default class KustomizeOverlay extends React.Component {
if (this.props.patch !== lastProps.patch) {
this.setState({ patch: this.props.patch });
}

if (this.props.finished && this.props.location.pathname === "/kustomize") {
this.props.handleAction();
}
}

componentDidMount() {
Expand Down Expand Up @@ -120,6 +116,7 @@ export default class KustomizeOverlay extends React.Component {
actions,
startPoll,
} = this.props;

if (isNavcycle) {
await finalizeStep({ action: actions[0] });
startPoll();
Expand Down Expand Up @@ -148,6 +145,7 @@ export default class KustomizeOverlay extends React.Component {
async handleKustomizeSave(closeOverlay) {
const { selectedFile } = this.state;
const contents = this.aceEditorOverlay.editor.getValue();
this.setState({ patch: contents });
const payload = {
path: selectedFile,
contents,
Expand Down Expand Up @@ -250,10 +248,10 @@ export default class KustomizeOverlay extends React.Component {
<div data-tip="create-overlay-tooltip" data-for="create-overlay-tooltip" className="overlay-toggle u-cursor--pointer" onClick={() => this.setState({ patch: fileToView.overlayContent })}>
<span className="icon clickable u-overlayViewIcon"></span>
</div>
:
<div data-tip="create-overlay-tooltip" data-for="create-overlay-tooltip" className="overlay-toggle u-cursor--pointer" onClick={this.createOverlay}>
<span className="icon clickable u-overlayCreateIcon"></span>
</div>
: fileToView && !fileToView.isSupported ? null :
<div data-tip="create-overlay-tooltip" data-for="create-overlay-tooltip" className="overlay-toggle u-cursor--pointer" onClick={this.createOverlay}>
<span className="icon clickable u-overlayCreateIcon"></span>
</div>
)
}
<ReactTooltip id="create-overlay-tooltip" effect="solid" className="replicated-tooltip">{fileToView && fileToView.overlayContent.length ? "View" : "Create"} overlay</ReactTooltip>
Expand Down
83 changes: 62 additions & 21 deletions web/src/components/shared/DetermineComponentForRoute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { withRouter } from "react-router-dom";
import autoBind from "react-autobind";
import find from "lodash/find";
import findIndex from "lodash/findIndex";
import indexOf from "lodash/indexOf";

import Loader from "./Loader";
Expand All @@ -20,8 +21,7 @@ class DetermineComponentForRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
startPoll: false,
finished: false,
maxPollReached: false,
};
autoBind(this);
}
Expand All @@ -38,36 +38,75 @@ class DetermineComponentForRoute extends React.Component {
const pollStartedButNotFinished = !this.state.finished && this.state.startPoll;
if(parsedDetail.status === "success" && pollStartedButNotFinished) {
this.setState({ finished: true, startPoll: false }, () => {
clearInterval(this.interval);
this.stopPoll();
});
}
}

async handleAction(action) {
const currRoute = find(this.props.routes, ["id", this.props.routeId]);
const currIndex = indexOf(this.props.routes, currRoute);
const nextRoute = this.props.routes[currIndex + 1];
if(action) {
await this.props.finalizeStep({action}).then(() => {
this.props.history.push(`/${nextRoute.id}`);
});
} else {
this.props.history.push(`/${nextRoute.id}`);
async handleAction(action, gotoNext) {
await this.props.finalizeStep({action});
if (gotoNext) {
this.gotoRoute();
}
}

startPoll(routeId) {
if (!this.state.startPoll) {
this.setState({ startPoll: true, finished: false });
const { finished } = this.state;
gotoRoute(route) {
let nextRoute = route;

if (!nextRoute) {
const currRoute = find(this.props.routes, ["id", this.props.routeId]);
const currIndex = indexOf(this.props.routes, currRoute);
nextRoute = this.props.routes[currIndex + 1];
}
this.props.history.push(`/${nextRoute.id}`);
}

async getCurrentStep(stepId) {
const apiEndpoint = window.env.API_ENDPOINT;
const url = `${apiEndpoint}/navcycle/step/${stepId}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Accept": "application/json",
},
});
const body = await response.json();
return body;
}

async skipKustomize() {
const kustomizeStepIndex = findIndex(this.props.routes, { phase: "kustomize" });
const kustomizeStep = this.props.routes[kustomizeStepIndex];
const stepAfterKustomize = this.props.routes[kustomizeStepIndex + 1];

const { actions } = await this.getCurrentStep(kustomizeStep.id);
this.handleAction(actions[0]);
this.startPoll(kustomizeStep.id, () => this.gotoRoute(stepAfterKustomize));
}

async startPoll(routeId, cb) {
let finished = false;

if (!this.interval) {
this.interval = setInterval(() => {
if (!finished) {
this.props.getContentForStep(routeId);
if (finished) {
clearInterval(this.interval);
cb();
} else {
this.getCurrentStep(routeId).then(({ progress }) => {
const { detail } = progress;
const parsedDetail = JSON.parse(detail);
finished = parsedDetail.status === "success";
});
}
}, 1000);
}
}

stopPoll() {
clearInterval(this.interval);
}

renderStep(phase) {
const { currentStep, progress, actions, location } = this.props;
if (!phase || !phase.length) return null;
Expand Down Expand Up @@ -102,7 +141,8 @@ class DetermineComponentForRoute extends React.Component {
case "render":
return (
<StepBuildingAssets
startPoll={() => this.startPoll(this.props.routeId)}
startPoll={() => this.startPoll(this.props.routeId, this.gotoRoute)}
routeId={this.props.routeId}
finished={this.state.finished}
handleAction={this.handleAction}
location={location}
Expand Down Expand Up @@ -141,12 +181,13 @@ class DetermineComponentForRoute extends React.Component {
<KustomizeEmpty
actions={actions}
handleAction={this.handleAction}
skipKustomize={this.skipKustomize}
/>
);
case "kustomize":
return (
<KustomizeOverlay
startPoll={() => this.startPoll(this.props.routeId)}
startPoll={() => this.startPoll(this.props.routeId, this.gotoRoute)}
finished={this.state.finished}
location={location}
actions={actions}
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/shared/RenderActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const RenderActions = ({ actions, handleAction, isLoading }) => (
<button
key={index}
className={`btn ${action.buttonType}`}
onClick={() => handleAction(action)}
onClick={() => handleAction(action, true)}
disabled={isLoading}>{isLoading ? action.loadingText : action.text}
</button>
);
Expand Down
10 changes: 3 additions & 7 deletions web/src/components/shared/StepBuildingAssets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ export default class StepBuildingAssets extends React.Component {
autoBind(this);
}

componentDidUpdate() {
if (this.props.finished && this.props.location.pathname === "/render") {
this.props.handleAction();
}
}

componentDidMount() {
this.props.startPoll();
if (this.props.location.pathname === "/render") {
this.props.startPoll();
}
}

render() {
Expand Down
1 change: 1 addition & 0 deletions web/src/redux/data/kustomizeOverlay/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function saveKustomizeOverlay(payload) {
return;
}
await response.json();
dispatch(getFileContent(payload.path));
dispatch(loadingData("saveKustomize", false));
} catch (error) {
dispatch(loadingData("saveKustomize", false));
Expand Down
12 changes: 7 additions & 5 deletions web/src/redux/data/kustomizeOverlay/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ const kustomizeState = {

function updateFileContents(currState, data) {
const nextFiles = currState.fileContents;
let newObj = {};
newObj.baseContent = data.content.base;
newObj.overlayContent = data.content.overlay;
newObj.key = data.path;
nextFiles.unshift(newObj); // add to front of array so uniqBy will keep newest version
const transformed = {
baseContent: data.content.base,
overlayContent: data.content.overlay,
key: data.path,
isSupported: data.content.isSupported,
}
nextFiles.unshift(transformed); // add to front of array so uniqBy will keep newest version
return uniqBy(nextFiles, "key");
}

Expand Down
Loading

0 comments on commit 26bd1e1

Please sign in to comment.