Skip to content

Commit

Permalink
feat: add code preview action (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
rexledesma authored Jan 4, 2022
1 parent c792abc commit 95eeb57
Show file tree
Hide file tree
Showing 13 changed files with 23,804 additions and 208 deletions.
345 changes: 240 additions & 105 deletions dist/deploy/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/deploy/index.js.map

Large diffs are not rendered by default.

22,311 changes: 22,311 additions & 0 deletions dist/preview/index.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/preview/index.js.map

Large diffs are not rendered by default.

888 changes: 888 additions & 0 deletions dist/preview/licenses.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/preview/sourcemap-register.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Builds and pushes Dagster repo Docker images and updates the corresponding Dagster Cloud repo locations.",
"scripts": {
"lint": "eslint .",
"prepare": "ncc build src/deploy-action.js -o dist/deploy --source-map --license licenses.txt",
"prepare": "ncc build src/deploy-action.js -o dist/deploy --source-map --license licenses.txt && ncc build src/preview-action.js -o dist/preview --source-map --license licenses.txt",
"test": "jest",
"all": "npm run lint && npm run prepare && npm run test"
},
Expand All @@ -27,6 +27,7 @@
"@actions/core": "^1.2.5",
"@actions/exec": "^1.1.0",
"@actions/github": "^5.0.0",
"graphql": "^16.0.1",
"graphql-request": "^3.6.0",
"yaml": "^1.10.2"
},
Expand Down
27 changes: 27 additions & 0 deletions preview/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: 'Dagster Cloud Code Preview'
description: |
'Builds the Dagster repo Docker images and creates a preview link to view the Dagster Cloud repo locations.'
branding:
icon: 'eye'
color: 'white'
inputs:
github-token:
description: 'Github API token'
required: true
dagit-url:
description: 'Dagster Cloud Dagit URL'
required: true
api-token:
description: 'Cloud Agent API token'
required: true
location-file:
description: 'Path to the locations.yaml file defining the repo locations to update'
required: true
default: 'locations.yaml'
parallel:
description: 'Whether to build and push all Docker images in parallel'
required: false
default: true
runs:
using: 'node12'
main: '../dist/preview/index.js'
27 changes: 27 additions & 0 deletions src/dagsterCloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ const UPDATE_LOCATION_MUTATION = gql`
}
`;

const CREATE_CODE_PREVIEW_MUTATION = gql`
mutation ($codePreview: CodePreviewInput!) {
createCodePreview(codePreview: $codePreview) {
__typename
... on CodePreview {
id
}
... on PythonError {
message
stack
}
}
}
`

export class DagsterCloudClient {
constructor(url, token) {
this.url = url;
Expand Down Expand Up @@ -80,4 +95,16 @@ export class DagsterCloudClient {

return result.locationName;
}

async createCodePreview(codePreview) {
const result = (await this.gqlClient.request(CREATE_CODE_PREVIEW_MUTATION, {
"codePreview": codePreview
})).createCodePreview;

if (result.__typename === "PythonError") {
throw new Error(result.message);
}

return result.id;
}
}
105 changes: 4 additions & 101 deletions src/deploy-action.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,8 @@
const core = require("@actions/core");
const exec = require("@actions/exec");
const github = require("@actions/github");
const fs = require("fs");
const os = require("os");
const path = require("path");
const util = require("util");
const YAML = require("yaml");
const { DagsterCloudClient } = require("./dagsterCloud");

const writeFileAsync = util.promisify(fs.writeFile);

async function inParallel(locations, processingFunction) {
await Promise.all(Object.entries(locations).map(processingFunction));
}

async function inSeries(locations, processingFunction) {
for (const location of locations) {
await processingFunction(location);
}
}

function tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), "dagster-cloud-ci"));
}

async function writeRequirementsDockerfile(baseImage) {
const dockerfilePath = path.join(tmpDir(), "Dockerfile");
await writeFileAsync(
dockerfilePath,
`
FROM ${baseImage}
COPY requirements.txt .
RUN pip install -r requirements.txt
WORKDIR /opt/dagster/app
COPY . /opt/dagster/app
`
);
return dockerfilePath;
}
const { getProcess, getLocations, buildDockerImages } = require("./utils");

async function run() {
try {
Expand All @@ -49,71 +11,12 @@ async function run() {

const locationFile = core.getInput("location-file");

const locations = await core
.group("Read locations.yaml", async () => {
const locationsFile = fs.readFileSync(locationFile, "utf8");
return YAML.parse(locationsFile).locations;
})
.catch((error) => {
core.error(`Error reading locations.yaml: ${error}`, {
file: locations,
});
});
const locations = await getLocations(locationFile);

const parallel = core.getBooleanInput("parallel");
const process = parallel ? inParallel : inSeries;
const process = getProcess(parallel);

await core.group("Build Docker images", async () => {
await process(locations, async ([_, location]) => {
const basePath = path.parse(locationFile).dir;
const buildPath = path.join(basePath, location["build"]);

let dockerfile = path.join(buildPath, "Dockerfile");
const baseImage = location["base_image"];

if (!fs.existsSync(dockerfile)) {
const requirementsFile = path.join(buildPath, "requirements.txt");

if (!fs.existsSync(requirementsFile) || !baseImage) {
core.error(
"Supplied build path must either contain Dockerfile, or requirements.txt with base_image"
);
}

dockerfile = await writeRequirementsDockerfile(baseImage);
} else {
if (baseImage) {
core.error(
"No need to specify base_image for location if build path contains Dockerfile"
);
}

dockerfile = "./Dockerfile";
}

const imageName = `${location["registry"]}:${imageTag}`;

let dockerArguments = [
"build",
".",
"--label",
`sha=${github.context.sha}`,
"-f",
dockerfile,
"-t",
imageName,
];

if (location["target"]) {
dockerArguments = dockerArguments.concat([
"--target",
location["target"],
]);
}

await exec.exec("docker", dockerArguments, { cwd: buildPath });
});
});
await buildDockerImages(process, locationFile, locations, imageTag);

await core.group("Push Docker image", async () => {
await process(locations, async ([_, location]) => {
Expand Down
107 changes: 107 additions & 0 deletions src/preview-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const core = require("@actions/core");
const exec = require("@actions/exec");
const github = require("@actions/github");
const { DagsterCloudClient } = require("./dagsterCloud");
const {
getProcess,
getLocations,
buildDockerImages,
createCommentOnCommit,
} = require("./utils");

async function run() {
try {
const imageTag = github.context.sha.substring(0, 6);

const locationFile = core.getInput("location-file");

const locations = await getLocations(locationFile);

const parallel = core.getBooleanInput("parallel");
const process = getProcess(parallel);

await buildDockerImages(process, locationFile, locations, imageTag);

await core.group("Create code preview", async () => {
const dagitUrl = core.getInput("dagit-url");
const endpoint = `${dagitUrl}/graphql`;

const apiToken = core.getInput("api-token");

const client = new DagsterCloudClient(endpoint, apiToken);

core.info(github.context.pull_request);

const commitSha = github.context.payload.pull_request.head.sha;
const codePreview = {
commitMessage: github.context.payload.pull_request.head.label,
branchName: github.context.payload.pull_request.head.ref,
branchUrl: github.context.payload.pull_request.html_url,
commitSha: commitSha,
commitUrl: `${github.context.payload.pull_request.html_url}/commits/${commitSha}`,
};

const codePreviewId = await client.createCodePreview(codePreview);

await process(locations, async ([locationName, location]) => {
const pythonFile = location["python_file"];
const packageName = location["package_name"];
const moduleName = location["module_name"];
const workingDirectory = location["working_directory"];
const executablePath = location["executable_path"];
const attribute = location["attribute"];

if (
[pythonFile, packageName, moduleName].filter((x) => !!x).length != 1
) {
core.error(
`Must provide exactly one of python_file, package_name, or module_name on location ${locationName}.`
);
}

const imageName = `${location["registry"]}:${imageTag}`;
const pythonFileArg = pythonFile ? ["--python-file", pythonFile] : [];
const packageNameArg = packageName ? ["--package-name", packageName] : [];
const moduleNameArg = moduleName ? ["--module-name", moduleName] : [];
const workingDirectoryArg = workingDirectory ? ["--working-directory", workingDirectory]: [];
const executablePathArg = executablePath ? ["--executable-path", executablePath] : [];
const attributeArg = attribute ? ["--attribute", attribute] : [];

await exec.exec(
"docker",
[
"run",
imageName,
"dagster-cloud",
"workspace",
"snapshot",
locationName,
codePreviewId,
"--url",
dagitUrl,
"--api-token",
apiToken,
"--image",
imageName,
]
.concat(pythonFileArg)
.concat(packageNameArg)
.concat(moduleNameArg)
.concat(workingDirectoryArg)
.concat(executablePathArg)
.concat(attributeArg)
);

core.info(
`Successfully created repository location for code preview ${codePreviewId}`
);

await createCommentOnCommit(codePreviewId);
});
});
} catch (error) {
core.setFailed(error.message);
}
}

run();
Loading

0 comments on commit 95eeb57

Please sign in to comment.