用Typescript写自动化工作流

项目地址:acao

挺早之前写过一个工具帮我构建项目并发布到指定平台,主要流程就是获取项目根目录下的配置文件,然后根据配置文件在本地通过 docker 构建一个镜像然后 push 到镜像仓库,最后通过 ssh 连接指定的服务区执行发布任务

但是随着需求的增长项目就愈发臃肿,可能会存在多个镜像仓库,发布到多个平台,有可能是通过 docker-compose 管理 也有可能是 kubectl

要怎么办?

于是我在考虑有没有可能有个工具来帮助我自动化在本地或者远程执行一些任务,而我并不关心任务的内容

同时为了方便使用,需要支持继承本地或者远程的预设配置,让我可以很方便的从之前的工具过渡到新的方案上

他们都是怎么做的?

对于团队来说,可能会有现成的 jenkins 或者 git 平台的 runner,这种方案可以说并没有什么不能实现的,但是对于个人项目或者希望依赖本地环境做一些构建的情况下可能有点 太 重 了

或许也会考虑类似 GitHub Actions 的方式执行一些工作流,但是感觉对命令执行的结果不太好处理并提供给之后的流程

用 ts 写工作流

用编程的方式可能会提高一点使用门槛,但是对于程序员来说真的很方便,在这里向大家介绍一下最近开发的一个工具 acao 已经用在一些个人项目和部分公司业务代码中,基础功能应该没啥问题(欢迎 issue 讨论哦

接下来让我们看看要怎么用叭!

依赖处理 / 初始化

首先还是先安装一下,虽然也可以直接通过 npx 执行,但是对于 js 项目,使用暴露出来的一些辅助函数和预设都会比较方便

pnpm add acao -D

在项目根目录新建一个 acao.config.ts 文件,并先写一个简单的命令

import { defineConfig } from 'acao'

export default defineConfig({
	jobs: {
		ci: {
			steps: ['echo ci']
		}
	}
})

这个工作流定义了一个 名为 ci 的 job 而且只有一个步骤,执行命令 echo ci 通过 npx acao 执行预期结果将会在控制台输出 ci 字符串,相信如果有 GitHub Actions 使用经验的小伙伴看起来应该还是有点眼熟的,数据结构的部分会有参考

如果对此完全陌生的小伙伴先可以简要介绍一下,这个配置的作用是定义一个工作流,一个工作流中可能会包含多个 job 数据类型是 Record<string, AcaoJob> key 是当前这个 job 的名称,多个 job 如果没有 needs 定义的依赖关系则默认同步执行,在 job 中需要定义 steps 数组作为具体执行步骤 steps 会按顺序执行

举个栗子

纯文字看起来还是有点抽象,接下来我们来实际完成一个工作流来熟悉一下

通常对于容器化前端 csr 项目的发布流程是,先生成静态文件,然后 build 一个 docker 镜像,最后推送到镜像仓库,这些会作为构建流程

而发布流程如果是 docker compose 的话则是登陆到指定服务器,然后替换掉 docker compose 中指定 service 的 image 最后 up 一下服务(简化一下

那么我们先通过 acao 来实现一下构建流程

import { defineConfig, run } from 'acao'

const HARBOR_HOST = 'HARBOR_HOST'
const HARBOR_USERNAME = 'HARBOR_USERNAME'
const HARBOR_PASSWORD = 'HARBOR_PASSWORD'
const IMAGE = `${HARBOR_HOST}/frontend`

export default defineConfig({
  jobs: {
    ci: {
      steps: [
        'npm run build',
        run('git log --format="%ad-%h" --date=format:%Y%m%d -1', { transform: stdout => `${IMAGE}:${JSON.parse(stdout)}` }),
        run((tag) => `docker build -t ${tag} . --platform linux/amd64`)
        `docker login ${HARBOR_HOST} --username ${HARBOR_USERNAME} --password ${HARBOR_PASSWORD}`,
        run((_, ctx) => `docker push ${ctx.outputs.ci[1]}`),
      ],
    }
  },
})

在上面这个例子中,我们新引入了一个 run 函数,他同样可以和字符串一样被使用在 job steps 中来执行某个命令,在 acao 中字符串的方式是 run 函数没有特殊需求的一种简写 ‘echo ci’ 与 run(’echo ci’) 是等价的,而 run 函数提供了一些其他方法来帮助我们更好的处理命令之间的 pipeline

对于 run 的类型申明如下

type RunCmd<T = string> = T | ((prev: any, ctx: AcaoContext) => T | Promise<T>);

interface RunOptions extends ExecaOptions {
    ssh: boolean;
    transform: (stdout: string) => any | Promise<any>;
    beforeExec: (ctx: AcaoContext) => any | Promise<any>;
    afterExec: (ctx: AcaoContext) => any | Promise<any>;
}

declare function run(cmd: RunCmd, options?: Partial<RunOptions>): AcaoJobStep;

这样看起来可能有点抽象,我们结合例子来说明一下

run('git log --format="%ad-%h" --date=format:%Y%m%d -1', { transform: stdout => `${IMAGE}:${JSON.parse(stdout)}` }),

这个命令是通过 git 命令获取最近一条 commit 的 short_hash 和 日期并拼接成指定格式作为 image 的 tag 而 run 的第二个参数提供了一个 transform 参数来处理输出,拼上完整的 image 名称作为当前这个 step 的输出提供给其他 step 使用

接下来我们需要用到这条命令的输出并通过 docker 构建一个镜像

run((tag) => `docker build -t ${tag} . --platform linux/amd64`)

run 的命令支持使用一个一个返回值为字符串的函数,回调的第一个参数是上一条命令的输出,我们已经处理成完成的镜像名所以可以直接使用

接下来登陆并推送本地镜像到镜像仓库,我们主要看推送的命令

run((_, ctx) => `docker push ${ctx.outputs.ci[1]}`),

这一步我们同样需要用到处理好的镜像名称,但是对于当前这一步来说上一步的输出结果已经不是我们所期望的了,当然我们也可以通过 git 命令再获取一次,或者通过回调的第二个参数获取上下文中某个 step 的输出

ctx.outputs 中有当前不同 job 执行过所有命令的输出结果,可以通过 job 名称和 step index 获取到具体某条的结果

用一些预设

对于一些常用的命令 acao 也提供了一些预设来方便 steps 的创建,比如 volta / docker 让我们简化一下之前的 ci 流程

import { defineConfig, run } from 'acao'
import { voltaRun } from 'acao/volta'
import { dockerBuild, dockerLogin, dockerPush } from 'acao/docker'

const HARBOR_HOST = 'HARBOR_HOST'
const HARBOR_USERNAME = 'HARBOR_USERNAME'
const HARBOR_PASSWORD = 'HARBOR_PASSWORD'
const IMAGE = `${HARBOR_HOST}/frontend`

export default defineConfig({
  jobs: {
    ci: {
      steps: [
        voltaRun('npm run build', { node: '10.24.1' }),
        run('git log --format="%ad-%h" --date=format:%Y%m%d -1', { transform: stdout => `${IMAGE}:${JSON.parse(stdout)}` }),
        dockerBuild(tag => ({ tag, platform: 'linux/amd64' })),
        dockerLogin(HARBOR_HOST, { username: HARBOR_USERNAME, password: HARBOR_PASSWORD }),
        dockerPush((_, ctx) => ctx.outputs.ci[1]),
      ],
    },
})

具体预设的使用方式可以看看 文档 这里就不过多展开了,函数的方式由于提供了比较好的类型提示,所以比字符串命令的形式还是友好很多

一些远程命令

那么对于发布流程,我们不可避免的需要通过 ssh 链接一些远程服务执行一些操作,对于这种需求 acao 也提供了一种简单的方式

import { defineConfig, run } from 'acao'

export default defineConfig({
  jobs: {
    ci: {
      steps: [],
    },
    
    cd: {
	    needs: ['ci'],
	    
	    ssh: {
		    host: 'HOST',
		    username: 'USERNAME',
        password: 'PASSWORD',
	    },
	    
	    steps: [
		    run('cd /data/docker_compose && docker-compose up -d'),
	    ]
    },
  },
}

在 job 中定义了 ssh 默认 steps 中执行的都是远程命令,也可以在 run 的第二个参数中指定 ssh: false 来混合执行和远程过命令,而且同样支持所有本地的方法比如 transform 和 ctx

在例子中可以看到 needs 参数,对于没有定义依赖的 job 默认会同步执行,而 needs 参数用来定义 job 的依赖 acao 会以此进行拓扑排序,存在循环依赖的话则所有任务都不会执行

最后

acao 的命令还有一些其他用法,比如指定执行某个 job 或者忽略依赖 通过命令行参数的方式注入等等,详细使用方式可以查看 文档

这个项目最近也在持续更新中,后续也会支持更多的预设,web ui 的操作方式也在计划中

小伙伴想参与预设的开发也欢迎 pr 呀

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值