Skip to content

Commit

Permalink
Add user custom init script feature
Browse files Browse the repository at this point in the history
  • Loading branch information
9Ninety committed Jun 16, 2020
1 parent b35066b commit 6656aef
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 59 deletions.
28 changes: 21 additions & 7 deletions images/remote-workspace/initialize/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
// @ts-check

const ChildProcess = require('child_process');
const {createHash} = require('crypto');
const { createHash } = require('crypto');
const Path = require('path');

require('villa/platform/node');

const FSE = require('fs-extra');
const {main} = require('main-function');
const { main } = require('main-function');
const stripJSONComments = require('strip-json-comments');
const v = require('villa');

/** @type {import('../../../bld/shared').WorkspaceMetadata} */
// @ts-ignore
const {projects} = require('/root/workspace/metadata.json');
const {
projects,
customInitScript,
} = require('/root/workspace/metadata.json');

main(async () => {
// prepare projects

console.info('Checking project hosts...');

// #region SSH initialize
let hostSet = new Set(
projects
.map(project => (project.git.url.match(/@(.+?):/) || [])[1])
Expand All @@ -28,7 +32,7 @@ main(async () => {

let unknownHosts = await v.filter(Array.from(hostSet), async host => {
try {
await spawn('ssh-keygen', ['-F', host], {stdio: 'ignore'});
await spawn('ssh-keygen', ['-F', host], { stdio: 'ignore' });
return false;
} catch {
return true;
Expand Down Expand Up @@ -59,10 +63,19 @@ main(async () => {
...process.env,
GIT_SSH_COMMAND: 'ssh -i /root/.ssh/initialize-identity',
};
// #endregion

if (customInitScript) {
let scriptPath = `/root/workspace/custom-init-script.sh`;
console.info('Running user custom init script...');
FSE.writeFileSync(scriptPath, customInitScript);
await spawn('bash', [scriptPath], { cwd: '/root/workspace/' });
}

// #region Per project initialize
for (let {
name,
git: {url, branch = 'master', newBranch = branch},
git: { url, branch = 'master', newBranch = branch },
scripts = {},
} of projects) {
let urlHash = createHash('md5')
Expand Down Expand Up @@ -100,7 +113,7 @@ main(async () => {
url,
projectPath,
],
{env: gitCloneEnv},
{ env: gitCloneEnv },
);

console.info(`Checking out branch "${newBranch}" from "${branch}"...`);
Expand All @@ -114,7 +127,7 @@ main(async () => {
`origin/${branch}`,
newBranch !== branch ? '--no-track' : undefined,
],
{cwd: projectPath},
{ cwd: projectPath },
);

let inPlaceProjectConfigPath = Path.join(
Expand Down Expand Up @@ -142,6 +155,7 @@ main(async () => {
}).catch(console.error);
}
}
// #endregion
});

/**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"ts-node": "^8.4.1",
"tslib": "^1.10.0",
"tslint": "^5.20.0",
"typescript": "^3.7.2",
"typescript": "^3.7.5",
"typescript-tslint-plugin": "^0.5.4"
}
}
66 changes: 34 additions & 32 deletions src/client/@components/workspace-form.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {Button, Checkbox, Descriptions, Input, Radio, message} from 'antd';
import {CheckboxOptionType} from 'antd/lib/checkbox';
import {RadioChangeEvent} from 'antd/lib/radio';
import { Button, Checkbox, Descriptions, Input, Radio, message } from 'antd';
import { CheckboxOptionType } from 'antd/lib/checkbox';
import { RadioChangeEvent } from 'antd/lib/radio';
import _ from 'lodash';
import {computed, observable, runInAction, when} from 'mobx';
import {observer} from 'mobx-react';
import React, {ChangeEvent, Component, ReactNode} from 'react';
import {Dict, OmitValueOfKey} from 'tslang';
import { computed, observable, runInAction, when } from 'mobx';
import { observer } from 'mobx-react';
import React, { ChangeEvent, Component, ReactNode } from 'react';
import { Dict, OmitValueOfKey } from 'tslang';

import {
CreateWorkspaceOptions,
Expand All @@ -23,6 +23,7 @@ export interface WorkspaceFormProps {
workspace: WorkspaceMetadata | undefined;
defaultWorkspaceName?: string;
defaultParams?: Dict<string>;
customInitScript?: string;
onSubmitSuccess(data: CreateWorkspaceOptions): void;
}

Expand All @@ -49,10 +50,10 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
constructor(props: WorkspaceFormProps) {
super(props);

let {workspace} = props;
let { workspace } = props;

if (workspace) {
let {displayName, owner, image, projects, services} = workspace;
let { displayName, owner, image, projects, services } = workspace;

let options: OmitValueOfKey<RawWorkspace, 'id' | 'params'> = {
displayName,
Expand All @@ -61,7 +62,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
projects: projects.map(
({
name,
git: {url, branch, newBranch},
git: { url, branch, newBranch },
scripts,
ssh,
}): RawWorkspaceProject => {
Expand Down Expand Up @@ -97,7 +98,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {

@computed
private get paramDict(): Dict<string | undefined> {
let {workspace, defaultParams: defaultParamDict = {}} = this.props;
let { workspace, defaultParams: defaultParamDict = {} } = this.props;

return _.pick(
(workspace && workspace.params) || {
Expand All @@ -124,7 +125,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
| RawTemplateWorkspaceConfig
| undefined {
let {
templates: {workspaces},
templates: { workspaces },
} = this.props;

let selectedWorkspaceName = this.selectedWorkspaceName;
Expand All @@ -137,7 +138,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get selectedProjectTemplates(): RawTemplateProjectConfig[] {
let {
templates: {projects = []},
templates: { projects = [] },
} = this.props;

let selectedProjectNameSet = new Set(this.selectedProjectNames);
Expand All @@ -153,7 +154,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get requiredProjectNames(): string[] {
let {
templates: {workspaces},
templates: { workspaces },
} = this.props;

let workspaceName = this.selectedWorkspaceName;
Expand All @@ -162,7 +163,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
return [];
}

let {projects = []} = workspaces.find(
let { projects = [] } = workspaces.find(
workspace => workspace.name === workspaceName,
)!;

Expand All @@ -174,7 +175,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get selectedServiceTemplates(): RawTemplateServiceConfig[] {
let {
templates: {services = []},
templates: { services = [] },
} = this.props;

let selectedServiceNameSet = new Set(this.selectedServiceNames);
Expand All @@ -190,7 +191,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get requiredServiceNames(): string[] {
let {
templates: {workspaces},
templates: { workspaces },
} = this.props;

let workspaceName = this.selectedWorkspaceName;
Expand All @@ -199,7 +200,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
return [];
}

let {services = []} = workspaces.find(
let { services = [] } = workspaces.find(
workspace => workspace.name === workspaceName,
)!;

Expand Down Expand Up @@ -227,15 +228,16 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {

let paramDict = this.paramDict;

let {displayName = ''} = this.selectedWorkspaceTemplate || {
let { displayName = '' } = this.selectedWorkspaceTemplate || {
name: '',
};

let options: CreateWorkspaceOptions = {
displayName,
owner: localStorage.email,
projects: this.selectedProjectTemplates.map(({params, ...rest}) => rest),
services: this.selectedServiceTemplates.map(({params, ...rest}) => rest),
projects: this.selectedProjectTemplates.map(({ params, ...rest }) => rest),
services: this.selectedServiceTemplates.map(({ params, ...rest }) => rest),
customInitScript: this.props.customInitScript,
};

options = _.cloneDeepWith(options, value => {
Expand All @@ -255,7 +257,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get workspaceTemplatesRendering(): ReactNode {
let {
templates: {workspaces},
templates: { workspaces },
} = this.props;

if (!workspaces) {
Expand Down Expand Up @@ -283,7 +285,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get projectTemplatesRendering(): ReactNode {
let {
templates: {projects},
templates: { projects },
} = this.props;

if (!projects) {
Expand All @@ -297,7 +299,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
<Descriptions.Item label="Project Templates">
<Checkbox.Group
options={projects.map(
({name}): CheckboxOptionType => {
({ name }): CheckboxOptionType => {
return {
label: name,
value: name,
Expand All @@ -316,7 +318,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
@computed
private get serviceTemplatesRendering(): ReactNode {
let {
templates: {services},
templates: { services },
} = this.props;

if (!services) {
Expand All @@ -330,7 +332,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
<Descriptions.Item label="Service Templates">
<Checkbox.Group
options={services.map(
({name}): CheckboxOptionType => {
({ name }): CheckboxOptionType => {
return {
label: name,
value: name,
Expand Down Expand Up @@ -372,7 +374,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {

@computed
private get optionsJSONRendering(): ReactNode {
let {workspace} = this.props;
let { workspace } = this.props;

return (
<Descriptions.Item label="Options">
Expand Down Expand Up @@ -417,13 +419,13 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
() => !!this.props.templates.workspaces?.length,
() => {
let {
templates: {workspaces},
templates: { workspaces },
defaultWorkspaceName,
} = this.props;

let _defaultWorkspaceName =
defaultWorkspaceName &&
workspaces?.some(({name}) => name === defaultWorkspaceName)
workspaces?.some(({ name }) => name === defaultWorkspaceName)
? defaultWorkspaceName
: undefined;

Expand Down Expand Up @@ -470,7 +472,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
}

private async submit(json: string): Promise<void> {
let {workspace, onSubmitSuccess} = this.props;
let { workspace, onSubmitSuccess } = this.props;

let data = JSON.parse(json);

Expand All @@ -480,7 +482,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
let method: string;

if (workspace) {
let {id, port} = workspace;
let { id, port } = workspace;

url = `/api/workspaces/${id}`;
method = 'PUT';
Expand Down Expand Up @@ -512,7 +514,7 @@ export class WorkspaceForm extends Component<WorkspaceFormProps> {
body: json,
});

let {error} = await response.json();
let { error } = await response.json();

if (error) {
message.error(error);
Expand Down
1 change: 1 addition & 0 deletions src/client/@utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './localStorage';
14 changes: 14 additions & 0 deletions src/client/@utils/localStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Save customize init script, set to empty or null to delete
*/
export function saveCustomInitScript(content: string): void {
if (content === null || content === '') {
localStorage.removeItem('customInitScript');
} else {
localStorage.setItem('customInitScript', content);
}
}

export function loadCustomInitScript(): string {
return localStorage.getItem('customInitScript') || '';
}
Loading

0 comments on commit 6656aef

Please sign in to comment.