翻新。类固醇的依赖性更新

930 阅读12分钟

本文讨论了软件开发项目中的一个重要问题:保持最新的依赖关系。更新依赖关系可以关闭潜在的安全漏洞,并允许我们使用最新的功能和应用错误修复。在这里,我演示了一种使用Renovate在CI/CD环境中自动更新依赖关系的方法。

Renovate能解决什么问题?

看看你的package-lock.jsonyarn.lock 文件,你肯定会发现你每天都在处理成百上千的依赖关系。依赖关系迟早会造成问题。

  • 随着时间的推移,由于破坏性的变化、重大的更新等,维护工作会增加。
  • 在某些时候,仅仅因为每天都有这么多的依赖性更新出现,保持项目的更新可能不再可行了。
  • 安全漏洞变得更有可能

因此,一方面,你应该将依赖关系更新到最新的版本,以利用新的功能,从性能改进中受益,或弥补安全漏洞。另一方面,更新依赖关系是一项繁琐的工作,它消耗了你的团队的大量时间,拖延了他们改进产品和建立新功能的工作。

你通常会从及时的更新中受益,这些更新只涉及小的版本跳跃,因为更新很有可能不会破坏你的构建。等待时间过长意味着你的团队不得不花费大量的精力来执行批量更新,特别是在涉及到重大更新的时候。

如果你一次更新很多依赖关系,你可能会有以下问题。

  • 你的构建被破坏了--是哪个依赖关系造成的?
  • 你的构建没有问题,你已经合并了所有的依赖关系,但你的部署却坏了--是哪个依赖关系造成的?

可以说,定期进行这些手工依赖性更新是不可持续的。你需要工具支持--幸好有Renovate!

Renovate是如何帮助的?

Renovate是一个开源项目,旨在自动更新依赖关系。它扫描指定项目的包文件(例如,package.json,pom.xml ),并创建合并请求(MR)或拉动请求(PR),这取决于你的CI/CD工具的命名惯例(我今后使用MR这个术语)。

你甚至可以把游戏推得很远,以至于当CI管道是绿色的(即,构建是确定的,着色是确定的,所有测试都是成功的),你可以让MR自动合并。后者是实现持续部署的一步,这可能是你的团队的目标之一。

请注意,Renovate并不像OWASP那样对你的项目进行安全分析。但可以说,如果你保持你的依赖关系是最新的,就会对安全产生积极的影响,漏洞迟早会被消除。当然,你可以将Renovate与专门的漏洞检测工具相结合。

Renovate如何集成到你的工作流程中?

Renovate支持许多CI/CD工具语言。这篇文章描述了如何在GitHub和GitLab上使用它。

我们配置了一个Renovate "机器人",它可以由调度器手动或自动触发。机器人会扫描所有分配的项目,并根据你的配置和确定的依赖性更新,创建一个或多个MRs。Renovate提供多种方法来减少噪音--例如,通过定义组规则,将多个依赖关系合并到一个MR中,或自动合并特定的MR。

Renovate允许细粒度的配置。它的配置概念受到ESLint或Spring的启发。你可以定义全局配置,这些配置会被每个项目配置所继承。此外,你还可以定义项目特定的配置:扩展继承的基本配置和覆盖现有的设置(例如,全局的automerge被设置为false,但你在一个特定的项目配置中激活它)。

你可以在许多层面上定义规则:在项目层面,在依赖类型层面(例如,只针对开发依赖),或特定依赖(例如,忽略TypeScript >v4.2)。Renovate追求的是惯例大于配置的概念。这意味着,基本配置带有许多开箱即用的有意义的设置。此外,你可以从汇编的设置列表(设置预置和完整的配置预置)中进行选择。

正如我们接下来所看到的,Renovate直接在MR或邮件通知中提供文档,告知我们哪个配置是活动的,哪些依赖即将被更新,以及内联发布说明和提供进行的提示。

为GitHub使用Renovate应用

为GitHub设置Renovate意味着要安装相关的Renovate应用。你唯一可以配置的是Renovate机器人(即应用程序)会扫描哪些仓库。所有其他设置都是通过代码配置的。

Page To Install Renovate

在GitHub中安装Renovate应用。

安装完成后,你可以在应用程序部分找到配置设置,点击你的个人资料图片>设置>应用程序

Applications Configuration Section On Renovate

列出已安装的GitHub应用程序。

点击配置,并滚动到配置页面的底部,以改变之后对你的存储库的访问。

入职培训

不用担心 - Renovate还没有更新依赖关系。你首先会在你授予Renovate访问权的每个资源库中收到一个入职的MR。在我的例子中,机器人分析了单一配置的版本库,并描述了接下来会发生什么,所以不会有什么意外。

正如你在下面的截图中看到的,Renovate已经创建了一个标题为 "配置Renovate "的入职MR。

Configure Renovate Being Opened By Renovate

Renovate提交了一个入职公关。

如果你打开PR,你会看到一个非常详细的描述,说明合并后会发生什么。

首先,你会被通知Renovate已经检测到一个package.json 文件。然后Renovate会应用默认的配置预设,并列出具体的配置。为此,Renovate将创建一个项目专用的配置文件(renovate.json)。正如已经提到的,我们可以在以后改变配置。

List Of Renovates Onboarding PR Steps

入职公关的细节。

在 "期待什么 "部分,Renovate详细描述了哪些依赖被更新以及如何更新。

Renovate's Detailed List Of Pin Dependencies And Update Dependencies

入职公关中的 "期待什么 "部分。

这实际上是说,我们使用Renovate提供的默认配置(config:base)。Renovate提供了默认的配置预设(例如,:automergeDisabled ),我们可以在我们的配置中使用,我们很快就会看到。此外,它将多个预设分组为完整的配置预设config:baseconfig:semverAllMonthly 是这种完整配置预设的例子。

让我们合并一下,为我们的项目激活Renovate。

第一个依赖性更新

正如入职时的MR所描述的,又有两个MR被创建。

Renovate Creates Onboarding MRs for Update Dependency and Pin Dependencies

入职后的第一个MRs。

让我们看看第一个MR,构成一个具体的依赖性更新MR。

Details To First Dependency Update

第一个依赖性更新的MR。

该MR详细描述了将会发生什么。在这个示例项目中,@testing-library/user-event 依赖关系被更新为v13.1.9。

我喜欢的是,你可以在配置部分验证你的Renovate配置。作为一个例子,由于默认配置,还没有定义自动合并,所以我们必须手动合并MR。我们稍后将看到如何改变这一点。

此外,如果你展开该部分,你可以访问发布说明。

Notes Listing The Changes Brought With The Current Update

发布说明会被内联到MR中。

第二个MR钉住了依赖关系,也就是说,删除了语义上的版本范围。这种行为--你已经猜到了--可以被改变

Renovate Page Of The Pinning MR Removing The Dependency Range Versions

依赖范围的版本会被钉住的MR删除。

关于引脚的细节在文档中详细讨论

扩展默认配置

在合并了初始入职的MR后,我们在根文件夹中找到了一个renovate.json 文件。

{
  "extends": [
    "config:base"
  ]
}

extends 数组中,最初有一个完整的配置预置([config:base](https://docs.renovatebot.com/presets-config/#configbase)) 被定义为代表所有语言的默认基本配置。这样一个完整的配置预设是一个默认预设的集合。下面是config:base 的摘录。

{
  "extends": [
    // ...
    ":ignoreUnstable",
    ":prImmediately",
    ":automergeDisabled",
    ":prHourlyLimit2",
    ":prConcurrentLimit20",
    "group:monorepos",
    "group:recommended",
    // ...
  ]
}

有了这个配置,Renovate就武装起来了。但我们有大量的配置选项可供支配,所以让我们修改我们的配置。

{
  "extends": [
    "config:base"
  ],
  "automerge": true,
  "automergeType": "pr",
  "timezone": "Europe/Berlin",
  "schedule": [
    "after 3pm every day",
    "before 5am every day"
  ]
}

我们覆盖由config:base 定义的默认合并行为(即:automergeDisabled ),并指示 Renovate 自动合并 MR。

此外,我们通过定义一个自定义时间表来覆盖默认的调度行为。schedule 的默认值是 "在任何时候",这在功能上等同于声明一个空的时间表;换句话说,Renovate将全天候在版本库上运行。我们定义了一个时间表,在每天下午3点到早上5点之间更新依赖关系。

值得阅读的是,有效的时区名称以及Renovate的时间表选项。我们也可以使用其中的一个时间表预设,比如说 [schedule:nonOfficeHours](https://docs.renovatebot.com/presets-schedule/#schedulenonofficehours).

自动对接

默认情况下,Renovate只有在你配置了一个至少有一个正在运行的测试的工作流时才会执行自动对接;否则,你需要在配置中添加"requiredStatusChecks": null 。如果MRs需要批准,这就给自动合并带来了另一个障碍。在这种情况下,你需要使用GitHub的辅助应用程序

消除噪音

如果你扫描了多个涉及不同技术的项目,那么MR的数量很快就会变得令人难以承受。定义自动ging规则是解决这个可能的一个很好的杠杆。

这需要合并信心)通过努力实现高测试覆盖率。如果这在目前是不可能的,或者只是一个长期的目标,你可能会认为只有补丁级的依赖关系才是自动的,因为破坏你的应用程序的风险是可控的。

为此,你可以利用 [packageRules](https://docs.renovatebot.com/configuration-options/#packagerules),这是一个强大的功能,可以让你使用regex模式匹配,将规则应用于单个软件包(例如,只有TypeScript >v4.2)或软件包组(例如,只有devDependencies 的补丁级依赖)。

例如,我们可以添加以下packageRule ,只对补丁级的依赖关系启用自动处理。

  "packageRules": [
    {
      "updateTypes": [
        "patch"
      ],
      "automerge": true
  }

另一个选择是根据定义的规则对依赖关系进行分组,以减少手动合并的工作量。下面的packageRule 将所有补丁级的devDependenciesdependencies

{
  "packageRules": [
    {
      "matchDepTypes": ["devDependencies", "dependencies],
      "matchUpdateTypes": ["patch"],
      "groupName": "(dev) dependencies (patch)"
    }
  ]
}

不过在出现bug的情况下,这可能会导致一个问题,即你必须追踪哪个依赖关系的更新导致了这个bug。

减少噪音的一个务实的选择是修改你的调度器,减少频率。在我的项目中,我们也使用技术来保持对漏洞的了解。如果发现了安全漏洞,你仍然有机会进行手动依赖性更新。

将Renovate与内部的GitLab一起使用

如果你在内部运行GitLab,本节将介绍如何让Renovate机器人启动和运行。在接下来的章节中,我展示了一个构成Renovate机器人的GitLab项目,只要发现依赖关系符合定义的规则,就会为其他GitLab项目创建MR。这是一个额外的步骤,与上一节相比,我们使用了一个GitHub应用程序。

重要的是要明白,你的仓库的配置(如自动合并设置)与GitHub的方法是相同的。工作流程也是一样的--入职、引脚MRs,等等。区别在于如何设置Renovate机器人。

创建Renovate机器人

与在GitHub上使用Renovate相比,我们需要做一些额外的工作,以使我们的Renovate机器人能够访问其他GitLab仓库并检索GitHub的发布说明。我们必须创建一个专门的GitLab项目来构成Renovate机器人。我们通过手动安装Renovate CLI工具作为npm依赖项来存档。

此外,我们通过创建一个.gitlab-ci.yml 文件来建立一个管道,以便在我们的CI/CD管道中运行Renovate CLI工具。我们的Renovate配置位于config.js 文件中。项目结构看起来像这样。

Dashboard Of The Project Structure For The Renovate Bot

GitLab Renovate机器人的项目结构。

在我们看文件内容之前,首先,让我们先处理一下访问其他GitLab项目的问题。要做到这一点,我们需要为一个GitLab账户创建一个个人访问令牌(PAT),该账户拥有我们希望Renovate分析的仓库的访问权。

点击用户的个人资料图片,进入首选项部分。接下来,进入访问令牌部分,创建一个范围为api,read_user, 和write_repository 的令牌。给它一个合理的名字,然后复制该令牌。

我倾向于不把令牌直接放到管道文件的源代码中(.gitlab-ci.yml ),而是创建一个环境变量。进入Renovate bot项目的设置,导航到CI/CD,展开变量部分。点击添加变量,勾选屏蔽变量,给它一个合理的名字,并将PAT粘贴到值域中。在我的例子中,我使用变量名称GITLAB_PAT

然后,我可以在.gitlab-ci.yml 文件中使用该变量。下面构成了我们需要的所有代码,以使Renovate机器人启动和运行。

image: node:latest
check_deps:
  script:
    - export RENOVATE_TOKEN=${GITLAB_PAT}
    - npm i
    - npm run check-dependencies

第一行是很重要的,在管道运行期间,有一个Node环境可用。我们定义一个管道步骤check_deps 。在script 部分,我们需要用前面提到的PAT设置一个名为RENOVATE_TOKEN 的环境变量,以授予Renovate对我们要处理的repos的访问权。

当然,我可以将CI/CD变量命名为RENOVATE_TOKEN ,并跳过额外的export 行,但我更喜欢这种方式来提高可追溯性。要获得更多关于GitLab CI/CD的信息,你可以在官方文档中找到。

有几种方法可以设置GitHub自带的Renovate机器人,但在这个例子中,我们选择用npm来做。我们用npm i 安装所有的依赖项,然后运行一个名为check-dependencies 的npm脚本。

package.json 文件只是将Renovate作为一个开发依赖,并提供一个npm脚本来调用Renovate CLI工具。

{
  "name": "renovate-bot",
  "devDependencies": {
    "renovate": "*"
  },
  "scripts": {
    "check-dependencies": "renovate",
  }
}

我们选择使用* ,在每次管道运行时安装最新的版本。Renovate的配置位于config.js

module.exports = {
  platform: 'gitlab',
  endpoint: 'https://gitlab.com/api/v4/',
  gitLabAutomerge: true,
  onboardingConfig: {
    extends: ['config:base'],
  },
  repositories: [
    'doppelmutzi/react-playground'
  ],
  packageRules: [
    {
      matchUpdateTypes: ["patch", "pin"],
      automerge: true
    }
  ],
} 

前三行是GitLab特有的,其余部分与上面描述的方法相同。

最后,你需要将用户添加到每个 repo 的成员部分(或 GitLab 组),并赋予其创建 MR 的权限,角色为开发者或维护者。

手动调用Renovate机器人

我们可以通过启动主管道来手动运行机器人。

Dashboard To Start Running The Main Pipeline Manually

手动运行主流水线。

点击CI/CD,然后点击运行管线按钮,运行主分支的管线。如果设置正确,管线步骤应该是绿色的。

Status Showing The Setup For Main Pipeline Is Successful

成功地设置主管道。

定期运行Renovate机器人

你可以以不同的方式配置Renovate的不同方面。作为一个例子,我将描述一种定义定期运行Renovate的时间表的替代方法。取代Renovate的时间表选项,我们定义一个管道时间表。转到CI/CD时间表部分(项目时间表)并创建一个新的时间表。

Page To Create A New Pipeline Schedule

通过定义一个自定义时间表,定期运行Renovate。

有了这个,我们项目中代表Renovate机器人的主要管道就在每天凌晨2点运行。

List Of Active Pipeline Schedule

激活时间表,定期运行Renovate。

只要你提交到主分支,这个流水线也会运行。

从GitHub上检索发布说明

为了如上图所示用GitHub Renovate应用将发布说明集成到MR中,你需要添加一个只读的PAT。事实上,创建一个专门的GitHub账户,只为Renovate创建一个PAT是一个有效的选择

要想拥有一个PAT,你需要登录GitHub,进入开发者设置中的PAT部分。点击生成新的令牌按钮,给它一个合理的注释,并在repo 部分勾选public_repo 选项。现在复制生成的令牌。

接下来,我们创建一个CI/CD环境变量,将其集成到GitLab的管道中,而不在代码库中直接揭示令牌。我们只需确保设置一个名为GITHUB_COM_TOKEN环境变量

在我们的GitLab项目中,我们导航到CI/CD部分**(设置**>CI/CD)并展开变量部分。我们需要添加一个变量,并将我们生成的GitHub令牌粘贴为值。我们可以使用GITHUB_COM_TOKEN 作为名称,这样就可以了。

我更喜欢给它一个不同的名字,并在.gitlab-ci.yml 里面创建这个环境变量,以提高我的同事的可追溯性。假设我创建了一个名为RELEASE_NOTES_GITHUB_PAT 的变量(我也会检查掩码变量)。我将按如下方式使用它。

check_deps:
  script:
    - export GITHUB_COM_TOKEN=${RELEASE_NOTES_GITHUB_PTA}
    - export RENOVATE_TOKEN=${GITLAB_PAT}
    - npm i
    - npm run check-dependencies

有了这个环境变量,发布说明就被整合到每一个MR中。CI/CD变量部分看起来像这样。

Page Showing The CI/CD Variable Section

发布说明的PATs和版本库的访问。

自动更新和MR审批

正如我在GitHub部分所描述的,当你为合并请求配置了强制审批时,Renovate不能自动合并MR。与GitHub的Renovate应用相比,在撰写本文时,除了将审批配置为可选外,GitLab中没有可能绕过这一障碍。

调试功能

如果你尝试新的配置,你可以将日志级别提高到debug ,以便从Renovate 日志模块中获得更多信息。通常情况下,对于日常使用来说,它过于冗长。

另一个有帮助的做法是进行一次模拟运行,而不是进行实际操作。下面摘自.gitlab-ci.yml ,让Renovate在干模式下运行所有分支,除了master ,结合增加的日志级别。

check_deps_dry_run:
  script:
    - export LOG_LEVEL=debug
    - export GITHUB_COM_TOKEN=${RELEASE_NOTES_GITHUB_PAT}
    - export RENOVATE_TOKEN=${GITLAB_PAT}
    - npm i
    - npm run validate-config
    - npm run check-dependencies -- --dry-run=true
  except:
    - master

这对验证所提供的配置文件也很有用。上面的命令npm run validate-configpackage.json 中调用一个名为validate-config 的npm脚本。

{
  "scripts": {
    "check-dependencies": "renovate",
    "validate-config": "renovate-config-validator config.js"
  }
}

它利用了内置的renovate-config-validator 工具来检查我们的配置文件是否有错误的配置。你可以在管道作业输出中发现任何问题。

合并冲突会被自动修复

如果一个MR被合并了,迟早会出现这样的情况:由于与之前的合并有冲突,另一个MR不能再被合并了。

Dashboard Showing Merge Conflicts Awaiting Request To Merge

很多时候,冲突位于package.json 文件中(同一库的多个条目有不同版本)。下次Renovate运行时,它将通过使用受影响的依赖关系的最新版本来识别和解决这些冲突。

简而言之,大多数时候,你不需要手动解决这些冲突。

最后的设置

为了对本文进行总结,本节展示了GitLab Renovate机器人的最终设置。

下面是.gitlab-ci.yml 的内容。

image: node:latest
check_deps:
  script:
    - export GITHUB_COM_TOKEN=${RELEASE_NOTES_GITHUB_PAT}
    - export RENOVATE_TOKEN=${GITLAB_PAT}
    - npm i
    - npm run validate-config
    - npm run check-dependencies
  only:
    - master
check_deps_dry_run:
  script:
    - export LOG_LEVEL=debug
    - export GITHUB_COM_TOKEN=${RELEASE_NOTES_GITHUB_PAT}
    - export RENOVATE_TOKEN=${GITLAB_PAT}
    - npm i
    - npm run validate-config
    - npm run check-dependencies -- --dry-run=true
  except:
    - master

我们的package.json 看起来像这样。

{
  "name": "renovate-bot",
  "devDependencies": {
    "renovate": "*"
  },
  "scripts": {
    "check-dependencies": "renovate",
    "validate-config": "renovate-config-validator config.js"
  }
}

而Renovate的配置(config.js)有如下形式。

module.exports = {
  platform: 'gitlab',
  endpoint: 'https://gitlab.com/api/v4/',
  gitLabAutomerge: true,
  onboardingConfig: {
    extends: ['config:base'],
  },
  repositories: [
    'doppelmutzi/react-playground'
  ],
  packageRules: [
    {
      matchUpdateTypes: ["patch", "pin"],
      automerge: true
    }
  ],
} 

The postRenovate:类固醇的依赖性更新首次出现在LogRocket博客上。