github.com/Datadog/cnab-go@v0.3.3-beta1.0.20191007143216-bba4b7e723d0/brigade.js (about)

     1  const { events, Job } = require("brigadier");
     2  
     3  const projectOrg = "deislabs";
     4  const projectName = "cnab-go";
     5  
     6  const goImg = "golang:1.11";
     7  const gopath = "/go";
     8  const localPath = gopath + `/src/github.com/${projectOrg}/${projectName}`;
     9  
    10  const releaseTagRegex = /^refs\/tags\/(v[0-9]+(?:\.[0-9]+)*(?:\-.+)?)$/;
    11  
    12  // **********************************************
    13  // Event Handlers
    14  // **********************************************
    15  
    16  events.on("exec", (e, p) => {
    17    return test(e, p).run();
    18  })
    19  
    20  events.on("push", (e, p) => {
    21    let matchStr = e.revision.ref.match(releaseTagRegex);
    22  
    23    if (matchStr) {
    24      // This is an official release with a semantically versioned tag
    25      let matchTokens = Array.from(matchStr);
    26      let version = matchTokens[1];
    27      return test(e, p).run()
    28        .then(() => {
    29          githubRelease(p, version).run();
    30        });
    31    }
    32  })
    33  
    34  events.on("check_suite:requested", runSuite);
    35  events.on("check_suite:rerequested", runSuite);
    36  events.on("check_run:rerequested", checkRequested);
    37  events.on("issue_comment:created", handleIssueComment);
    38  events.on("issue_comment:edited", handleIssueComment);
    39  
    40  // **********************************************
    41  // Actions
    42  // **********************************************
    43  
    44  function test(e, project) {
    45    var test = new Job("tests", goImg);
    46  
    47    // Add a bit of set up for golang build
    48    test.env = {
    49      DEST_PATH: localPath,
    50      GOPATH: gopath
    51    };
    52  
    53    test.tasks = [
    54      // Need to move the source into GOPATH so vendor/ works as desired.
    55      `mkdir -p ${localPath}`,
    56      `cp -a /src/* ${localPath}`,
    57      `cp -a /src/.git ${localPath}`,
    58      `cd ${localPath}`,
    59      "make bootstrap",
    60      "make build",
    61      "make test",
    62      "make lint",
    63    ];
    64  
    65    return test;
    66  }
    67  
    68  // Here we can add additional Check Runs, which will run in parallel and
    69  // report their results independently to GitHub
    70  function runSuite(e, p) {
    71    // For now, this is the one-stop shop running build, lint and test targets
    72    return runTests(e, p);
    73  }
    74  
    75  // runTests is a Check Run that is ran as part of a Checks Suite
    76  function runTests(e, p) {
    77    console.log("Check requested");
    78  
    79    // Create Notification object (which is just a Job to update GH using the Checks API)
    80    var note = new Notification(`tests`, e, p);
    81    note.conclusion = "";
    82    note.title = "Run Tests";
    83    note.summary = "Running the test targets for " + e.revision.commit;
    84    note.text = "This test will ensure build, linting and tests all pass."
    85  
    86    // Send notification, then run, then send pass/fail notification
    87    return notificationWrap(test(e, p), note);
    88  }
    89  
    90  // handleIssueComment handles an issue_comment event, parsing the comment
    91  // text and determining whether or not to trigger a corresponding action
    92  function handleIssueComment(e, p) {
    93    if (e.payload) {
    94      payload = JSON.parse(e.payload);
    95  
    96      // Extract the comment body and trim whitespace
    97      comment = payload.body.comment.body.trim();
    98  
    99      // Here we determine if a comment should provoke an action
   100      switch(comment) {
   101      case "/brig run":
   102        return runTests(e, p);
   103      default:
   104        console.log(`No applicable action found for comment: ${comment}`);
   105      }
   106    }
   107  }
   108  
   109  // checkRequested is the default function invoked on a check_run:* event
   110  //
   111  // It determines which check is being requested (from the payload body)
   112  // and runs this particular check, or else throws an error if the check
   113  // is not found
   114  function checkRequested(e, p) {
   115    if (e.payload) {
   116      payload = JSON.parse(e.payload);
   117  
   118      // Extract the check name
   119      name = payload.body.check_run.name;
   120  
   121      // Determine which check to run
   122      switch(name) {
   123      case "tests":
   124        return runTests(e, p);
   125      default:
   126        throw new Error(`No check found with name: ${name}`);
   127      }
   128    }
   129  }
   130  
   131  // githubRelease creates a new release on GitHub, named by the provided tag
   132  function githubRelease(p, tag) {
   133    if (!p.secrets.ghToken) {
   134      throw new Error("Project must have 'secrets.ghToken' set");
   135    }
   136  
   137    var job = new Job("release", goImg);
   138    job.mountPath = localPath;
   139    parts = p.repo.name.split("/", 2);
   140  
   141    job.env = {
   142      "GITHUB_USER": parts[0],
   143      "GITHUB_REPO": parts[1],
   144      "GITHUB_TOKEN": p.secrets.ghToken,
   145    };
   146  
   147    job.tasks = [
   148      "go get github.com/aktau/github-release",
   149      `cd ${localPath}`,
   150      `last_tag=$(git describe --tags ${tag}^ --abbrev=0 --always)`,
   151      `github-release release \
   152        -t ${tag} \
   153        -n "${parts[1]} ${tag}" \
   154        -d "$(git log --no-merges --pretty=format:'- %s %H (%aN)' HEAD ^$last_tag)" \
   155        || echo "release ${tag} exists"`
   156    ];
   157  
   158    console.log(job.tasks);
   159    console.log(`release at https://github.com/${p.repo.name}/releases/tag/${tag}`);
   160  
   161    return job;
   162  }
   163  
   164  
   165  // **********************************************
   166  // Classes/Helpers
   167  // **********************************************
   168  
   169  // A GitHub Check Suite notification
   170  class Notification {
   171    constructor(name, e, p) {
   172      this.proj = p;
   173      this.payload = e.payload;
   174      this.name = name;
   175      this.externalID = e.buildID;
   176      this.detailsURL = `https://brigadecore.github.io/kashti/builds/${ e.buildID }`;
   177      this.title = "running check";
   178      this.text = "";
   179      this.summary = "";
   180  
   181      // count allows us to send the notification multiple times, with a distinct pod name
   182      // each time.
   183      this.count = 0;
   184  
   185      // One of: "success", "failure", "neutral", "cancelled", or "timed_out".
   186      this.conclusion = "neutral";
   187    }
   188  
   189    // Send a new notification, and return a Promise<result>.
   190    run() {
   191      this.count++;
   192      var job = new Job(`${ this.name }-notification-${ this.count }`, "brigadecore/brigade-github-check-run:v0.1.0");
   193      job.imageForcePull = true;
   194      job.env = {
   195        "CHECK_CONCLUSION": this.conclusion,
   196        "CHECK_NAME": this.name,
   197        "CHECK_TITLE": this.title,
   198        "CHECK_PAYLOAD": this.payload,
   199        "CHECK_SUMMARY": this.summary,
   200        "CHECK_TEXT": this.text,
   201        "CHECK_DETAILS_URL": this.detailsURL,
   202        "CHECK_EXTERNAL_ID": this.externalID
   203      };
   204      return job.run();
   205    }
   206  }
   207  
   208  // Helper to wrap a job execution between two notifications.
   209  async function notificationWrap(job, note) {
   210    await note.run();
   211    try {
   212      let res = await job.run();
   213      const logs = await job.logs();
   214      note.conclusion = "success";
   215      note.summary = `Task "${ job.name }" passed`;
   216      note.text = "```" + res.toString() + "```\nTest Complete";
   217      return await note.run();
   218    } catch (e) {
   219      const logs = await job.logs();
   220      note.conclusion = "failure";
   221      note.summary = `Task "${ job.name }" failed for ${ e.buildID }`;
   222      note.text = "```" + logs + "```\nFailed with error: " + e.toString();
   223      try {
   224        await note.run();
   225      } catch (e2) {
   226        console.error("failed to send notification: " + e2.toString());
   227        console.error("original error: " + e.toString());
   228      }
   229      throw e;
   230    }
   231  }