在过去的两到四个月里,我开始管理一个新项目,幸运的是,我能够应用我从课程和阅读材料中学到的很多东西,同时牢记我所在团队成员的经验一起工作让事情变得简单,但同时也有点挑战性,所以我可以鼓励他们学习新事物或加强他们目前拥有的知识。

在项目的前两周,我们必须交付一个 MVP,因此我们决定将其托管在Heroku中,在那里我为多环境创建了一个管道,现在我认为这是一个矫枉过正 😅 因为它是只是一个MVP。

继续前进,我希望能够将我的 Docker 镜像推送到 Heroku 注册表,所以合并的每一小段代码我都手动构建了镜像并将其推送到 Heroku。

到目前为止一切顺利,但我厌倦了一遍又一遍地做同样的事情,所以我记得我可以使用 GitHub Actions 来自动化这个过程💡。我在 GitHub Marketplace 上搜索允许我构建 docker 图像并将其推送到 Heroku 的东西,我找到了一些东西,但这不是我想要的。所以我做了工程师会做的任何事情,创造它的行动😎。

阅读文档!

由于我从未使用过 Action,我不得不去阅读文档,我发现它是一个有据可查的功能。

引起我注意的是,人们可以为一些常见的编程语言(如 JavaScript、Python 和 Java)编写动作。您可以在此处阅读有关支持的语言和框架的更多信息。

现在我知道我可以为我的项目编写一个动作,然后我继续前进并登陆创建动作页面,在这里我注意到您可以使用 JavaScript 或 Bash 编写动作,这对我来说很酷😉。

建设行动

我决定像往常一样使用 JavaScript 来编写我的操作,为您的项目创建一个文件夹:

mkdir my-action && cd my-action

进入全屏模式 退出全屏模式

添加action.yml

使用您最喜欢的 IDE 或代码编辑器打开您的项目目录并创建一个名为action.yml的新文件。该文件是您要定义操作元数据的位置,并且应该具有以下结构:

name: # Name of your action
description: # Some Fancy description explaining what this does
inputs: # User input for you action
  id_of_your_input:
    description: # What is this input about
    required: # Set this to true if the input is required or set it to fall if otherwise
    default: # Some default value
outputs:
  time: # id of output
    description: 'The time we greeted you'
runs:
  using: 'node12'
  main: 'index.js'

进入全屏模式 退出全屏模式

所以我创建了我的action.yml,它看起来像这样:

name: 'Deploy Docker Image to Heroku App'
author: 'Jean Carlos Taveras'
description: 'A simple action to build, push and Deploy a Docker Image to your Heroku app.'
inputs:
  email:
    description: 'Email Linked to your Heroku Account'
    required: true
  api_key:
    description: 'Your Heroku API Key'
    required: true
  app_name:
    description: 'Your Heroku App Name'
    required: true
  dockerfile_path:
    description: 'Dokerfile path'
    required: true
  options:
    description: 'Optional build parameters'
    required: false
runs:
  using: 'node12'
  main: 'dist/index.js'

进入全屏模式 退出全屏模式

安装依赖

在开始编码之前,您需要安装两个依赖项

  • @actions/core

  • @actions/github

您需要@actions/core才能从action.yml中提取声明的输入和输出变量等。另一方面,@actions/github用于获取有关操作上下文等的信息。

npm install -s @actions/core @actions/github

进入全屏模式 退出全屏模式

写动作的核心

创建一个index.js文件,让我们导入依赖项:

const core = require('@actions/core');
const github = require('@actions/github'); // In case you need it

进入全屏模式 退出全屏模式

由于我需要执行 docker 和 Heroku 命令,我需要添加child_processutil模块,并从后者获取promisify函数。

...
const { promisify } = require('util');

const exec = promisify(require('child_process').exec);

进入全屏模式 退出全屏模式

好的!现在我必须创建一个函数来允许对 Heroku Registry 进行身份验证。

...

async function loginHeroku() {
  const login = core.getInput('email');
  const password = core.getInput('api_key');

  try { 
    await exec(`echo ${password} | docker login --username=${login} registry.heroku.com --password-stdin`); 
    console.log('Logged in succefully ✅');    
  } catch (error) { 
    core.setFailed(`Authentication process faild. Error: ${error.message}`);    
  } 
}

进入全屏模式 退出全屏模式

好的!现在我需要构建 Docker 镜像,将其推送到 Heroku Registry 并将其部署到 Heroku App

...

async function buildPushAndDeploy() {
  const appName = core.getInput('app_name');
  const dockerFilePath = core.getInput('dockerfile_path');
  const buildOptions = core.getInput('options') || '';
  const herokuAction = herokuActionSetUp(appName);

  try {
    await exec(`cd ${dockerFilePath}`);

    await exec(`docker build . --file Dockerfile ${buildOptions} --tag registry.heroku.com/${appName}/web`);
    console.log('Image built 🛠');

    await exec(herokuAction('push'));
    console.log('Container pushed to Heroku Container Registry ⏫');

    await exec(herokuAction('release'));
    console.log('App Deployed successfully 🚀');
  } catch (error) {
    core.setFailed(`Something went wrong building your image. Error: ${error.message}`);
  } 
}

进入全屏模式 退出全屏模式

现在我看到了这个,我需要重构这个函数😅。当我说让我们写下我们行动的核心时,我想我太认真了。

您可能会注意到有一个名为herokuActionSetUp的函数,它只是一个返回 Heroku 操作(推送或释放)的辅助函数。

...

/**
 * 
 * @param {string} appName - Heroku App Name
 * @returns {function}
 */
function herokuActionSetUp(appName) {
  /**
   * @typedef {'push' | 'release'} Actions
   * @param {Actions} action - Action to be performed
   * @returns {string}
   */
  return function herokuAction(action) {
    const HEROKU_API_KEY = core.getInput('api_key');
    const exportKey = `HEROKU_API_KEY=${HEROKU_API_KEY}`;

    return `${exportKey} heroku container:${action} web --app ${appName}` 
  }
}

进入全屏模式 退出全屏模式

我们快完成了。我们只需要调用我们的函数,因为这些函数是异步的,所以我们可以将它们链接在一起,如下所示:

...

loginHeroku()
  .then(() => buildPushAndDeploy())
  .catch((error) => {
    console.log({ message: error.message });
    core.setFailed(error.message);
  })

进入全屏模式 退出全屏模式

捆绑您的代码

为了防止提交您的node_modules/文件夹,您可以运行:

npx zeit/ncc build index.js

进入全屏模式 退出全屏模式

这将创建一个dist文件夹,其中包含一个包index.js文件,请记住,您必须更改action.yml文件中的runs部分以指向捆绑的 JavaScript 文件:

runs:
  using: 'node12'
  main: 'dist/index.js'

进入全屏模式 退出全屏模式

添加自述文件

您应该添加一个README.md文件以让用户了解如何使用您的操作。

测试您的操作

您可以按照 GitHub 文档在工作流中测试您的操作中的说明进行操作。但是,我发现这种测试方法非常痛苦,因为每次进行更改时都必须推送代码。然后您可以做的是使用nektos/act在本地运行您的操作(https://github.com/nektos/act)是一个文档齐全且易于使用的工具。


就是这样,这就是使用 JavaScript 创建动作所需的全部知识。这篇文章比我想象的要长一点,因为这是我的第一篇文章。

感谢并在 GitHub MarketplaceDeploy Docker Image to Heroku App以及jctaveras/heroku-deploy的 repo 中检查此操作。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐