You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2020/12/27 14:02:07 UTC

[airflow-checks-action] 05/27: First draft

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airflow-checks-action.git

commit d3d705842bf1b5a37ab2362b9cedf5959cdd5db9
Author: Louis Brunner <lo...@gmail.com>
AuthorDate: Sat Feb 29 21:09:12 2020 +0000

    First draft
---
 .eslintrc.json                            |   1 +
 .github/workflows/{test.yml => build.yml} |  12 +-
 .github/workflows/examples.yml            | 252 ++++++++++++++++++++++++++++++
 .prettierrc.json                          |   4 +-
 __tests__/main.test.ts                    |  16 +-
 action.yml                                |  23 +++
 dist/index.js                             |   2 +-
 jest.config.js                            |   2 +-
 package.json                              |   4 +-
 src/checks.ts                             |  69 ++++++++
 src/inputs.ts                             |  51 ++++++
 src/main.ts                               |  60 +++----
 src/namespaces/Inputs.ts                  |  38 +++++
 13 files changed, 479 insertions(+), 55 deletions(-)

diff --git a/.eslintrc.json b/.eslintrc.json
index ef9fcf4..528d8da 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -14,6 +14,7 @@
     "project": "./tsconfig.eslint.json"
   },
   "rules": {
+    "@typescript-eslint/camelcase": ["off"]
   },
   "env": {
     "node": true,
diff --git a/.github/workflows/test.yml b/.github/workflows/build.yml
similarity index 53%
rename from .github/workflows/test.yml
rename to .github/workflows/build.yml
index c058dd1..ce4ac4d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/build.yml
@@ -4,6 +4,7 @@ on: # rebuild any PRs and main branch changes
   push:
     branches:
       - master
+      - 'feature/*'
       - 'releases/*'
 
 jobs:
@@ -15,14 +16,3 @@ jobs:
     - run: |
         npm install
         npm run all
-
-  # make sure the action works on a clean machines without building
-  test1:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v1
-    - uses: ./
-      with:
-        token: ${{ secrets.GITHUB_TOKEN }}
-
-  # TODO: add more (race conditions, failures, etc)
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
new file mode 100644
index 0000000..1f82161
--- /dev/null
+++ b/.github/workflows/examples.yml
@@ -0,0 +1,252 @@
+name: "examples"
+on: [push]
+
+jobs:
+  # make sure the action works on a clean machines without building
+
+  ## Basic
+  test_basic_success:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Success
+        conclusion: success
+
+  test_basic_success_with_output:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Success (Implicit)
+        conclusion: success
+        output:
+          summary: Test was a success
+          text_description: |
+            This is a text description of the annotations and images
+            With more stuff
+            And more
+
+  test_basic_failure:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Failure
+        conclusion: failure
+
+  # Other codes
+  test_basic_neutral:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test 1 = Neutral
+        conclusion: neutral
+
+  test_basic_cancelled:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Cancelled
+        conclusion: cancelled
+
+  test_basic_timed_out:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Timed Out
+        conclusion: timed_out
+
+  test_basic_action_required:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Basic Action Required
+        conclusion: action_required
+
+  ## Based on command
+  test_with_annotation:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Advanced With Output
+        conclusion: success
+
+  ## With annotations
+  test_with_annotations:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Annotations
+        conclusion: success
+        output: # output.summary is required with annotations!
+          summary: Some warnings in README.md
+        annotations:
+          - path: README.md
+            annotation_level: warning
+            title: Spell Checker
+            message: Check your spelling for 'banaas'.
+            raw_details: Do you mean 'bananas' or 'banana'?
+            start_line: 1
+            end_line: 2
+
+  test_with_annotations_from_run:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - id: annotations
+      run: |
+        echo ::set-output name=value::{"path":"README.md","start_line":1,"end_line":2,"message":"Check your spelling for 'banaas'.","annotation_level":"warning"}
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Annotations From Run
+        conclusion: success
+        output: # output.summary is required with annotations!
+          summary: Some warnings in README.md
+        annotations: ${{ steps.annotations.outputs.value }}
+
+  ## With images
+  test_with_images:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Images
+        conclusion: success
+        output: # output.summary is required with images!
+          summary: Some cool pics
+        images:
+          - alt: Cool pic
+            image_url: https://via.placeholder.com/150
+            caption: Cool description
+
+  test_with_images_from_run:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - id: images
+      run: |
+        echo ::set-output name=value::{"alt":"Cool pic","image_url":"https://via.placeholder.com/150","caption":"Cool description"}
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Images From Run
+        conclusion: success
+        output: # output.summary is required with images!
+          summary: Some warnings in README.md
+        images: ${{ steps.images.outputs.value }}
+
+  ## With actions
+  # TODO: HOW DO WE SET THE WEBHOOK!
+  test_with_actions:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Actions
+        conclusion: success
+        actions:
+          - label: Click Me
+            description: Click me to get free RAM
+            identifier: sent_to_webhook
+
+  test_with_actions_from_run:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - id: actions
+      run: |
+        echo ::set-output name=value::{"label":"Click Me","description":"Click me to get free RAM","identifier":"sent_to_webhook"}
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Actions From Run
+        conclusion: success
+        # output.summary is required with actions!
+        output:
+          summary: Some warnings in README.md
+        actions: ${{ steps.actions.outputs.value }}
+
+  ## With init
+  test_with_init:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Init
+        status: in_progress
+    - run: sleep 30
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        status: completed
+        conclusion: failure
+
+  test_with_init_implicit:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test With Init (Implicit)
+        status: in_progress
+    - run: sleep 30
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        conclusion: failure
+
+  ## Based on job
+  test_based_job_success:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: ./
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Based On Job (Success)
+        conclusion: ${{ job }}
+
+  test_based_job_failure:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - run: false
+    - uses: ./
+      if: always()
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}
+        name: Test Based On Job (Failure)
+        conclusion: ${{ job }}
diff --git a/.prettierrc.json b/.prettierrc.json
index 9d64c21..bf0887a 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,8 +1,8 @@
 {
-  "printWidth": 80,
+  "printWidth": 120,
   "tabWidth": 2,
   "useTabs": false,
-  "semi": false,
+  "semi": true,
   "singleQuote": true,
   "trailingComma": "all",
   "bracketSpacing": false,
diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index 32cf034..bced80d 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -1,15 +1,15 @@
-import * as process from 'process'
-import * as cp from 'child_process'
-import * as path from 'path'
+import * as process from 'process';
+import * as cp from 'child_process';
+import * as path from 'path';
 
 // shows how the runner will run a javascript action with env / stdout protocol
 test('test runs', () => {
-  process.env['TOKEN'] = 'ABC'
-  const ip = path.join(__dirname, '..', 'lib', 'main.js')
+  process.env['TOKEN'] = 'ABC';
+  const ip = path.join(__dirname, '..', 'lib', 'main.js');
   const options: cp.ExecSyncOptions = {
     env: process.env,
-  }
-  console.log(cp.execSync(`node ${ip}`, options).toString())
-})
+  };
+  console.log(cp.execSync(`node ${ip}`, options).toString());
+});
 
 // TODO: add more
diff --git a/action.yml b/action.yml
index e7a778d..125221d 100644
--- a/action.yml
+++ b/action.yml
@@ -4,6 +4,29 @@ author: 'Louis Brunner'
 inputs:
   token:
     description: 'your GITHUB_TOKEN'
+    required: true
+  name:
+    description: 'the name of your check'
+    required: true
+  conclusion:
+    description: 'the conclusion of your check'
+    required: true
+  status:
+    description: 'the status of your check'
+    required: false
+    default: completed
+  output:
+    description: 'the output of your check'
+    required: false
+  annotations:
+    description: 'the annotations of your check'
+    required: false
+  images:
+    description: 'the images of your check'
+    required: false
+  actions:
+    description: 'the actions of your check'
+    required: false
 runs:
   using: 'node12'
   main: 'dist/index.js'
diff --git a/dist/index.js b/dist/index.js
index 24aa44e..37237e3 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1 +1 @@
-module.exports=function(e,t){"use strict";var r={};function __webpack_require__(t){if(r[t]){return r[t].exports}var n=r[t]={i:t,l:false,exports:{}};e[t].call(n.exports,n,n.exports,__webpack_require__);n.l=true;return n.exports}__webpack_require__.ab=__dirname+"/";function startup(){return __webpack_require__(429)}return startup()}({87:function(e){e.exports=require("os")},429:function(e,t,r){e.exports=function(e,t){"use strict";var r={};function __webpack_require__(t){if(r[t]){return r[t] [...]
\ No newline at end of file
+module.exports=function(e,t){"use strict";var r={};function __webpack_require__(t){if(r[t]){return r[t].exports}var n=r[t]={i:t,l:false,exports:{}};e[t].call(n.exports,n,n.exports,__webpack_require__);n.l=true;return n.exports}__webpack_require__.ab=__dirname+"/";function startup(){return __webpack_require__(429)}return startup()}({87:function(e){e.exports=require("os")},429:function(e,t,r){e.exports=function(e,t){"use strict";var r={};function __webpack_require__(t){if(r[t]){return r[t] [...]
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index 9bac3cc..f3a27fa 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -8,4 +8,4 @@ module.exports = {
     '^.+\\.ts$': 'ts-jest',
   },
   verbose: true,
-}
+};
diff --git a/package.json b/package.json
index dfd516b..381c53c 100644
--- a/package.json
+++ b/package.json
@@ -6,10 +6,10 @@
   "main": "dist/index.js",
   "scripts": {
     "build": "tsc",
-    "lint": "eslint *.js **/*.ts",
+    "lint": "eslint '**/*.js' '**/*.ts'",
     "pack": "ncc build -m",
     "test": "jest",
-    "format": "prettier --write *.js **/*.ts",
+    "format": "prettier --write '**/*.js' '**/*.ts'",
     "all": "npm run build && npm run lint && npm run pack && npm test"
   },
   "repository": {
diff --git a/src/checks.ts b/src/checks.ts
new file mode 100644
index 0000000..8271217
--- /dev/null
+++ b/src/checks.ts
@@ -0,0 +1,69 @@
+import {GitHub} from '@actions/github';
+import * as Inputs from './namespaces/Inputs';
+
+type Ownership = {
+  owner: string;
+  repo: string;
+};
+
+type CreateOptions = {
+  completed: boolean;
+};
+
+const unpackInputs = (inputs: Inputs.Args): object => {
+  let output;
+  if (inputs.output) {
+    output = {
+      summary: inputs.output.summary,
+      text: inputs.output.text_description,
+      actions: inputs.actions,
+      images: inputs.images,
+    };
+  }
+  return {
+    status: inputs.status.toString(),
+    conclusion: inputs.conclusion.toString(),
+    output,
+    actions: inputs.actions,
+  };
+};
+
+const formatDate = (): string => {
+  return new Date().toISOString();
+};
+
+export const createRun = async (
+  octokit: GitHub,
+  sha: string,
+  ownership: Ownership,
+  inputs: Inputs.Args,
+  options?: CreateOptions,
+): Promise<number> => {
+  const dates: {completed_at?: string} = {};
+  if (!options || options.completed) {
+    dates.completed_at = formatDate();
+  }
+  const {data} = await octokit.checks.create({
+    ...ownership,
+    head_sha: sha,
+    name: inputs.name,
+    started_at: formatDate(),
+    ...dates,
+    ...unpackInputs(inputs),
+  });
+  return data.id;
+};
+
+export const updateRun = async (
+  octokit: GitHub,
+  id: number,
+  ownership: Ownership,
+  inputs: Inputs.Args,
+): Promise<void> => {
+  await octokit.checks.update({
+    ...ownership,
+    check_run_id: id,
+    completed_at: formatDate(),
+    ...unpackInputs(inputs),
+  });
+};
diff --git a/src/inputs.ts b/src/inputs.ts
new file mode 100644
index 0000000..6b5ed0d
--- /dev/null
+++ b/src/inputs.ts
@@ -0,0 +1,51 @@
+import {InputOptions} from '@actions/core';
+import * as Inputs from './namespaces/Inputs';
+
+type GetInput = (name: string, options?: InputOptions | undefined) => string;
+
+const parseJSON = <T>(getInput: GetInput, property: string): T | undefined => {
+  const value = getInput(property);
+  if (!value) {
+    return;
+  }
+  try {
+    const obj = JSON.parse(value);
+    return obj as T;
+  } catch (e) {
+    throw new Error(`invalid format for '${property}: ${e.toString()}`);
+  }
+};
+
+export const parseInputs = (getInput: GetInput): Inputs.Args => {
+  const token = getInput('token', {required: true});
+  const name = getInput('name', {required: true});
+  const statusStr = getInput('status', {required: true});
+  const conclusionStr = getInput('conclusion', {required: true});
+
+  if (!(statusStr in Inputs.Status)) {
+    throw new Error(`invalid value for 'status': '${statusStr}'`);
+  }
+  const status = statusStr as Inputs.Status;
+
+  if (!(conclusionStr in Inputs.Conclusion)) {
+    throw new Error(`invalid value for 'conclusion': '${conclusionStr}'`);
+  }
+  const conclusion = conclusionStr as Inputs.Conclusion;
+
+  const output = parseJSON<Inputs.Output>(getInput, 'output');
+  const annotations = parseJSON<Inputs.Annotations>(getInput, 'annotations');
+  const images = parseJSON<Inputs.Images>(getInput, 'images');
+  const actions = parseJSON<Inputs.Actions>(getInput, 'actions');
+
+  return {
+    name,
+    token,
+    status,
+    conclusion,
+
+    output,
+    annotations,
+    images,
+    actions,
+  };
+};
diff --git a/src/main.ts b/src/main.ts
index 0208cac..2a5ff19 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,44 +1,44 @@
-import * as core from '@actions/core'
-import * as github from '@actions/github'
+import * as core from '@actions/core';
+import * as github from '@actions/github';
+import {parseInputs} from './inputs';
+import {createRun, updateRun} from './checks';
+
+const stateID = 'checkID';
 
-// eslint-disable-next-line @typescript-eslint/require-await
 async function run(): Promise<void> {
   try {
-    const token: string = core.getInput('token')
+    core.debug(`Parsing inputs`);
+    const inputs = parseInputs(core.getInput);
 
-    core.debug(`Setting up OctoKit`)
-    const octokit = new github.GitHub(token)
+    core.debug(`Setting up OctoKit`);
+    const octokit = new github.GitHub(inputs.token);
 
     const ownership = {
       owner: github.context.repo.owner,
       repo: github.context.repo.repo,
-    }
-
-    const info = { // TODO: from argument
-    }
-
-    const { data } = await octokit.checks.listForRef({
-      ...ownership,
-      ref: github.context.sha,
-    })
+    };
+    const sha = github.context.sha;
 
-    if (data.check_runs.length > 0) {
-      octokit.checks.update({
-        ...ownership,
-        check_run_id: data.check_runs[0].id,
-        ...info,
-      })
-    } else {
-      octokit.checks.create({
-        ...ownership,
-        head_sha: github.context.sha,
-        name: 'Check Run Test', // TODO: from argument
-        ...info,
-      })
+    switch (inputs.status) {
+      case 'in_progress':
+      case 'queued': {
+        const id = await createRun(octokit, sha, ownership, inputs, {completed: false});
+        core.saveState(stateID, id.toString());
+        break;
+      }
+      case 'completed': {
+        const id = core.getState(stateID);
+        if (id) {
+          updateRun(octokit, parseInt(id), ownership, inputs);
+        } else {
+          createRun(octokit, sha, ownership, inputs);
+        }
+        break;
+      }
     }
   } catch (error) {
-    core.setFailed(error.message)
+    core.setFailed(error.message);
   }
 }
 
-run()
+run();
diff --git a/src/namespaces/Inputs.ts b/src/namespaces/Inputs.ts
new file mode 100644
index 0000000..9bfcba3
--- /dev/null
+++ b/src/namespaces/Inputs.ts
@@ -0,0 +1,38 @@
+import {Octokit} from '@octokit/rest';
+
+export type Args = {
+  name: string;
+  token: string;
+  conclusion: Conclusion;
+  status: Status;
+  output?: Output;
+  annotations?: Annotations;
+  images?: Images;
+  actions?: Actions;
+};
+
+export type Annotations = Octokit.ChecksCreateParamsOutputAnnotations[];
+
+export type Images = Octokit.ChecksCreateParamsOutputImages[];
+
+export type Actions = Octokit.ChecksCreateParamsActions[];
+
+export type Output = {
+  summary: string;
+  text_description?: string;
+};
+
+export enum Conclusion {
+  Success = 'success',
+  Failure = 'failure',
+  Neutral = 'neutral',
+  Cancelled = 'cancelled',
+  TimedOut = 'timed_out',
+  ActionRequired = 'action_required',
+}
+
+export enum Status {
+  Queued = 'queued',
+  InProgress = 'in_progress',
+  Completed = 'completed',
+}