CircleCI-博客中文翻译-二-

CircleCI 博客中文翻译(二)

原文:CirecleCI Blog

协议:CC BY-NC-SA 4.0

安全 CI/CD -作为代码的安全性| CircleCI 和对比安全性

原文:https://circleci.com/blog/ci-cd-meet-cas-continuous-application-security/

用 CAS 的 7 个关键概念编写安全代码

您是否知道 72%的开发人员表示安全性会降低开发速度?此外,四分之三的安全主管找不到足够的具有关键技能的专业安全人员来支持敏捷和 DevOps 等快速开发环境。那么,如何在不影响代码和业务安全性的情况下交付 10 倍以上的代码呢?

连续应用安全(CAS)是一种方法,通过将基于纸张的安全策略和指南转变为“安全即代码”,使开发人员能够可靠地构建和操作安全的应用和 API。通过基于工具的安全实施,CAS 使开发、安全和运营团队能够以现代软件开发的速度协同工作。

应用程序安全性已经存在了 20 多年,许多传统概念的核心都有一些价值。但是,应用程序仍然是导致成功数据泄露的头号载体。最重要的是,今天 web 应用程序中严重漏洞的平均数量是 33 个,几乎与 2003 年第一个 OWASP 十大漏洞发布时的数量完全相同。甚至 OWASP 十大风险本身也没有改变。CAS 的 7 个关键概念旨在实现自动化、实时和可扩展,以适应数字化转型时代。

  1. 持续:在应用程序每天都受到攻击的世界里,新的威胁频繁出现,而且新的漏洞经常被发现,因此企业需要对其整个应用程序组合的安全性进行持续可见性和控制。在 CAS 中,漏洞和攻击会被立即检测和报告,而不是等待未被检测到的年度扫描。最重要的是,组织必须能够在受到攻击时立即部署新的防御措施,而不必重写和重新部署代码。

  2. 插装:这是一种为应用程序添加缺失功能的安全且经过验证的方法,无需重新编码、重新测试和重新部署。十多年来,许多流行的日志记录和应用程序性能管理产品都依赖于工具。安全规范增加了实时功能,以识别漏洞、阻止攻击、分析库、提供详细的应用程序清单,甚至实现集中的策略命令和控制。

  3. 交互式应用程序安全测试(IAST) :这是一种评估技术,它通过在应用程序运行时观察应用程序来检测漏洞。IAST 易于部署,比 SAST 或 DAST 工具有更多的应用背景,产生更好的覆盖率和准确性。

  4. 实时安全反馈:漏洞存在的时间越长,威胁得不到解决的时间越长,或者攻击未被发现的时间越长,安全成本就会急剧增加。实时提供安全反馈意味着漏洞可以作为正常软件开发的一部分被消除,并且攻击可以在开始之前被消除。这消除了分类、记录、跟踪、评分和重新测试风险的成本。

  5. 运行时应用程序自我保护(RASP) :这是一种防御技术,使用检测工具在运行时检测和阻止对应用程序的攻击。RASP 易于部署,与 WAF 或其他外部保护相比,它具有更多的应用背景,因此更加准确。Contrast Enterprise 在一个代理中同时提供了 IAST 和 RASP,可以在整个软件生命周期中工作。

  6. 安全:这不是只有巫师才能瞥见的神秘属性。CAS 使其具体化——可以构建、测试和测量。在 CAS 中,安全就是知道对业务最重要的威胁分配了强有力的防御措施;这些防御措施是正确的,配置得当,部署在所有正确的地方;并且它们可以检测和阻止已知的和新的漏洞和攻击。

  7. 传感器:在 CAS 中,“传感器”是一组安全工具,用于分析特定漏洞的代码,检测和阻止特定攻击,或创建对应用程序某些方面的可见性,如组件、框架、架构或后端连接。这些传感器是 IAST 和 RASP 技术的基础,并在对比企业的单一代理中交付。

有关持续应用安全性的更多详情,请点击此处

使用 CAS,不需要单独的安全步骤。它无法支撑建立在连续和自动化流程上的环境。漏洞分析在后台无缝完成,方法是使用智能传感器检测运行中的应用程序,从应用程序内部持续、实时地分析代码(以代码形式提供安全性)。

为了获得实际操作体验,我们鼓励您下载对比安全社区版对比安全 orb ,以将应用安全自动化到您的 CI/CD 管道中,并使您能够在软件开发生命周期的早期解决安全问题。

来源:


这篇文章是我们制作的关于 DevSecOps 的系列文章的一部分。要阅读本系列的更多文章,请单击下面的链接之一。

环回 API 的持续集成| CircleCI

原文:https://circleci.com/blog/ci-for-loopback-apis/

本教程涵盖:

  1. 环回入门
  2. 创建问题模型和数据源
  3. 环回 API 端点的自动化测试

远程工作人才的激增(以及远程首次就业的广泛接受)使得全球协作达到了前所未有的规模。

这并非没有风险——尤其是在软件行业。在同一个代码库上工作的多个开发人员面临着无意中引入破坏性变更和中断应用程序的风险。在相同代码基础上工作的团队必须确保遵循最佳实践,以便每个人总是在同一页面上。否则会导致意外停机和损失。持续集成(CI) 是高绩效开发团队采用的最佳实践之一。CI 是集成来自多个贡献者的变更以创建单个软件项目的可重复过程。

在本文中,我将使用 CircleCI 来展示如何将持续集成应用到一个回送项目中。LoopBack 是一个高度可扩展的 Node.js 和 TypeScript 框架,用于构建 API 和微服务。

本教程是为环回应用建立 CI/CD 实践系列的第一篇。下一个教程将向您展示如何自动化环回部署

对于这个项目,我们将建立一个测验应用程序的 API。API 将有端点来处理这些操作:

  1. 获取所有问题
  2. 获取数据库中问题的总数
  3. 获取具有指定 ID 的问题

为了增加效果,API 将强烈地拒绝删除问题的请求。

先决条件

  1. 在安装 LoopBack 之前,请确保下载并安装node . js8.9 . x 或更高版本。一个 JavaScript 运行时。
  2. 如果尚未安装 LoopBack 4 CLI,请安装它。

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

入门指南

LoopBack 4 CLI 是一个命令行界面,可以搭建项目或扩展。CLI 为遵循最佳实践的 LoopBack 4 项目提供了最快的入门方式。

npm install -g @loopback/cli 

使用以下命令创建一个新项目:

lb4 app 
? Project name: quiz_api
? Project description: A simple API for a quiz application
? Project root directory: quiz_api
? Application class name: QuizApplication
? Select features to enable in the project Enable eslint, Enable prettier, Enable mocha, Enable loopbackBuild, Enable vscode, Enable docker,
 Enable repositories, Enable services
? Yarn is available. Do you prefer to use it by default? Yes 

注意 : 如果 LoopBack 崩溃,无法创建应用程序,请确保您使用的是 LTS 版本的 Node。Node 的一些较新版本存在兼容性问题。

该项目附带了一个“ping”路由来测试项目。通过运行项目来尝试一下。导航到新创建的quiz_api目录,通过运行以下命令启动它:

cd quiz_api

yarn start 

在浏览器中,访问http://127.0.0.1:3000/ping Default Loopback ping page

配置 CircleCI

接下来,为 CircleCI 添加管道配置。在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在新创建的文件中,添加以下配置:

version: "2.1"

orbs:
  node: circleci/node@4.7.0

jobs:
  build-and-test:
    docker:
      - image: "cimg/base:stable"
    steps:
      - checkout
      - node/install:
          node-version: 16.0.0
          install-yarn: true
      - node/install-packages:
          pkg-manager: yarn
          cache-path: ~/project/node_modules
          override-ci-command: yarn install
      - run: yarn run test

workflows:
  main:
    jobs:
      - build-and-test 

这个配置拉入 Node.js orb: circleci/node。除此之外,这允许您在默认情况下启用缓存的情况下安装包。

注意 : 你应该安装了 16 的节点版本。在发布时,node 16 是与 LoopBack 兼容的最新版本。

还指定了一个名为build-and-test的作业,它执行以下操作:

  1. 签出最新代码
  2. 安装节点
  3. 安装在package.json中声明的软件包
  4. 运行项目中的测试

最后,配置指定了运行build-and-test作业的工作流。

接下来,在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。看到这个帖子帮助把你的项目推到 GitHub

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都会显示在你项目的仪表盘上。

在您的quiz_api项目旁边,点击设置项目

CircleCI 将检测项目中的config.yml文件。点击使用现有配置,然后开始建造。您的第一个工作流将成功运行!

CircleCi build page

现在您已经有了一个管道,您可以添加 API 的问题特性了。

构建问题模型

对于本教程,问题将包含这些属性的字段:

  • 困难
  • 问题
  • 回答正确

创建问题时,默认情况下会分配一个唯一的主键。

您可以使用lb4 model命令并回答提示来生成模型。按键,输入属性名为空的,生成模型。请遵循以下步骤:

lb4 model question 
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Model Question will be created in src/models/question.model.ts

Let's add a property to Question
Enter an empty property name when done

? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is id generated automatically? Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: difficulty
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: question
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: answer
? Property type: string
? Is it required?: Yes

Let's add another property to Question
Enter an empty property name when done

? Enter the property name: 

将在src/models/question.model.ts创建一个新模型。

构建数据源

接下来,创建一个数据源来保存 API 的问题。对于本教程,使用内存数据库。使用以下命令创建数据源:

lb4 datasource 

回应如下所示的提示:

? Datasource name: db
? Select the connector for db:  In-memory db (supported by StrongLoop)
? window.localStorage key to use for persistence (browser only):
? Full path to file for persistence (server only): ./data/db.json 

接下来,在项目的根目录下创建一个名为data的文件夹。在data目录中,创建一个名为db.json的文件,并将其添加到:

{
  "ids": {
    "Question": 9
  },
  "models": {
    "Question": {
      "1": "{\"difficulty\":\"medium\",\"question\":\"The HTML5 standard was published in 2014.\",\"answer\":\"True\",\"id\":1}",
      "2": "{\"difficulty\":\"medium\",\"question\":\"Which computer hardware device provides an interface for all other connected devices to communicate?\",\"answer\":\"Motherboard\",\"id\":2}",
      "3": "{\"difficulty\":\"medium\",\"question\":\"On which day did the World Wide Web go online?\",\"answer\":\"December 20, 1990\",\"id\":3}",
      "4": "{\"difficulty\":\"medium\",\"question\":\"Android versions are named in alphabetical order.\",\"answer\":\"True\",\"id\":4}",
      "5": "{\"difficulty\":\"medium\",\"question\":\"What was the first Android version specifically optimized for tablets?\",\"answer\":\"Honeycomb\",\"id\":5}",
      "6": "{\"difficulty\":\"medium\",\"question\":\"Which programming language shares its name with an island in Indonesia?\",\"answer\":\"Java\",\"id\":6}",
      "7": "{\"difficulty\":\"medium\",\"question\":\"What does RAID stand for?\",\"answer\":\"Redundant Array of Independent Disks\",\"id\":7}",
      "8": "{\"difficulty\":\"medium\",\"question\":\"Which of the following computer components can be built using only NAND gates?\",\"answer\":\"ALU\",\"id\":8}"
    }
  }
} 

JSON 文件的ids键让数据库知道分配新问题的下一个 ID。在models部分,我们提供了每个型号的数据。还指定了Question模型和数据库中的基本问题。

创建存储库

对于本教程,您将使用存储库在数据库和问题模型之间提供一个抽象层。使用以下命令创建新的存储库:

lb4 repository 

回应如下所示的提示:

? Please select the datasource DbDatasource
? Select the model(s) you want to generate a repository for Question
? Please select the repository base class DefaultCrudRepository (Juggler bridge) 

新创建的类(位于src/repositories/question.repository.ts)拥有为您的模型执行 CRUD 操作所需的连接。

创建控制器

使用以下命令创建新的控制器:

lb4 controller 

响应 CLI 提示,如下所示:

? Controller class name: question
Controller Question will be created in src/controllers/question.controller.ts

? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Question
? What is the name of your CRUD repository? QuestionRepository
? What is the name of ID property? id
? What is the type of your ID? number
? Is the id omitted when creating a new instance? Yes
? What is the base HTTP path name of the CRUD operations? /questions 

CLI 创建一个能够处理所有 CRUD 操作的控制器。但是,此时您只需要这些操作的一个子集。打开位于src/controllers/question.controller.ts的问题控制器,并对其进行编辑以匹配以下代码:

import {
  Count,
  CountSchema,
  Filter,
  FilterExcludingWhere,
  repository,
  Where,
} from '@loopback/repository';
import {
  del,
  get,
  getModelSchemaRef,
  HttpErrors,
  param,
  response,
} from '@loopback/rest';
import {Question} from '../models';
import {QuestionRepository} from '../repositories';

export class QuestionController {
  constructor(
    @repository(QuestionRepository)
    public questionRepository: QuestionRepository,
  ) {}

  @get('/questions/count')
  @response(200, {
    description: 'Question model count',
    content: {'application/json': {schema: CountSchema}},
  })
  async count(@param.where(Question) where?: Where<Question>): Promise<Count> {
    return this.questionRepository.count(where);
  }

  @get('/questions')
  @response(200, {
    description: 'Array of Question model instances',
    content: {
      'application/json': {
        schema: {
          type: 'array',
          items: getModelSchemaRef(Question, {includeRelations: true}),
        },
      },
    },
  })
  async find(
    @param.filter(Question) filter?: Filter<Question>,
  ): Promise<Question[]> {
    return this.questionRepository.find(filter);
  }

  @get('/questions/{id}')
  @response(200, {
    description: 'Question model instance',
    content: {
      'application/json': {
        schema: getModelSchemaRef(Question, {includeRelations: true}),
      },
    },
  })
  async findById(
    @param.path.number('id') id: number,
    @param.filter(Question, {exclude: 'where'})
    filter?: FilterExcludingWhere<Question>,
  ): Promise<Question> {
    return this.questionRepository.findById(id, filter);
  }

  @del('/questions/{id}')
  @response(403, {
    description: 'Question DELETE not permitted',
  })
  async deleteById(@param.path.number('id') id: number): Promise<void> {
    throw new HttpErrors.Forbidden('Question DELETE not permitted');
  }
} 

为端点添加测试

最后一步是添加验收测试套件,以确保您的问题控制器按预期工作。在src/__**tests__**/acceptance文件夹中,创建一个名为question.controller.acceptance.ts的新文件,并将以下代码添加到其中:

import { Client, expect } from "@loopback/testlab";
import { QuizApplication } from "../..";
import { setupApplication } from "./test-helper";

describe("QuestionController", () => {
  let app: QuizApplication;
  let client: Client;

  before("setupApplication", async () => {
    ({ app, client } = await setupApplication());
  });

  after(async () => {
    await app.stop();
  });

  it("successfully makes GET request to /questions", async () => {
    const res = await client.get("/questions").expect(200);
    expect(res.body).to.be.an.Array();
    expect(res.body).to.have.length(8);
  });

  it("successfully makes GET request to /questions/count", async () => {
    const res = await client.get("/questions/count").expect(200);
    expect(res.body).to.be.an.Object();
    expect(res.body.count).to.equal(8);
  });

  it("successfully makes GET request to /questions/{id}", async () => {
    const res = await client.get("/questions/2").expect(200);
    expect(res.body).containEql({ id: 2, difficulty: 'medium', answer: 'Motherboard', });
  });

  it("makes DELETE request to /questions which fails", async () => {
    await client.delete("/questions/5").expect(403);
  });
}); 

在运行测试之前,花一点时间做一些整理工作。使用以下命令 Lint 您的代码并修复任何问题:

yarn run lint:fix 

提交您的代码并在本地运行测试,以确保一切正常。使用此命令:

yarn test 

将最新的更改推送到您的 GitHub 库。这会触发 CircleCI build-and-test作业,该作业会像之前一样成功运行。干得好!

结论

在本文中,我们研究了如何使用 LoopBack 构建 API。我们还建立了一个 CircleCI 管道来管理项目的持续集成。

这种方法的好处是人为错误不会危及我们应用程序的安全性。通过自动化测试过程,您消除了人为错误对生产环境造成意外破坏的风险。它还为维护的软件增加了额外的质量控制和保证。尝试持续集成,让代码瓶颈成为团队的过去。

本教程的全部代码可以在 GitHub 上找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

面向移动应用开发的| CircleCI

原文:https://circleci.com/blog/ci-for-mobile-app-development/

测试是软件开发的关键部分。测试移动应用程序可能比测试服务器或基于网络的应用程序更困难,因为要检查应用程序的功能,应用程序必须在实际的 Android 或 iOS 设备上运行,或者在模拟器中运行。

幸运的是,通过使用持续集成(CI) 工具自动化您的移动应用程序测试,这个过程可以变得更加容易、更加高效、更加一致。

让我们回顾一下如何设置移动应用程序进行测试,并探索如何使用 CI 管道自动化构建和测试过程。

如何创建带有测试的移动应用程序

Android Studio 和 Xcode (iOS)的所有新项目都支持创建测试就绪的移动应用程序。每个 IDE 都提供了一种内置的方式来添加和运行测试,更具体地说,就是添加单元测试。

对于 Android 应用程序,Android Studio 创建了一个包含新项目的目录(/src/test/java/),我们可以在其中编写测试作为 JUnit 4 测试类的实现。我们还需要在build.gradle文件中配置几个依赖项。你可以参考这份谷歌指南,了解更多关于如何创建一个带测试的安卓应用的详细说明。

对于 iOS 应用程序,Xcode 会在项目方案中包含一个测试目标,并在您创建新项目时为单元测试提供存根方法。我们可以在这个目标中编写测试,然后通过选择 Product > Test 来运行它们,或者通过选择 Product > Perform Action > Run Test Methods 来选择并运行它们的子集。看看苹果关于使用单元测试的指南和关于使用 Xcode 测试的指南以获得更多信息。

一个移动应用 CI 解决方案将能够构建这些项目,并通过简单的配置运行其测试。

测试和自动化移动应用

看看测试类型,测试一个移动应用程序发生在几个隔离测试环境:

  • 单元测试——对我们部分代码的小规模测试,不需要在设备上运行,可以在构建机器上运行。
  • 集成测试 -可以与本地设备功能(如地理定位)交互的高级测试,需要在设备或模拟器上运行。
  • 功能和 UI 测试 -测试以确保应用程序运行和功能正常,并且必须在设备或模拟器上运行,如场景导航。
  • 性能测试 -测量应用性能指标的压力测试。

作为配置 CI 管道的一部分,我们可以自动化构建代码的过程,并对提交到存储库的每个新代码运行这些测试。CI 通常作为云上的服务器或本地机器上的服务器运行,它构建并运行配置的命令或任务。

使用单元测试,我们可以将 Android Studio 与 JUnit 一起使用,或者将 Xcode 与 XCTest 一起使用,或者,如果我们愿意,也可以使用其他标准的 Java 和 ObjC/Swift 测试框架,因为它们可以直接在构建机器上运行。

对于其他需要在设备或模拟器上运行的测试,像 Appium 这样的工具可以很容易地创建 UI 自动化之类的应用测试。

例如,通过将这些测试与 CircleCI 集成,我们可以将项目存储库添加到 CircleCI 应用程序,并设置一个config.yml文件来定义从存储库中提取最新代码的步骤,构建它,然后部署并运行移动应用程序和/或其测试,从而集成并自动化我们的构建和测试流程。

配置文件示例如下所示:

# .circleci/config.yml
version: 2.1
jobs:
  build-and-test:
    macos:
      xcode: 11.3.0
    environment:
      FL_OUTPUT_DIR: output
      FASTLANE_LANE: test
    steps:
      - checkout
      - run: bundle install
      - run:
          name: Fastlane
          command: bundle exec fastlane $FASTLANE_LANE
      - store_artifacts:
          path: output
      - store_test_results:
          path: output/scan 

我们鼓励在 CircleCI 上使用浪子,因为它简化了构建、测试和部署过程的设置和自动化,并且适用于 iOS 应用程序和 Android 应用程序。阅读用浪子构建和部署 Flutter 应用程序,了解更多关于使用 Fastalne 和 CIrcleCI 的细节。

并且通过使用 CircleCI orbs ,特别容易上手移动测试自动化。我们可以添加 orb,它们是配置元素的可共享包,直接在 CI 管道中使用,而不是花费大量时间试图找出如何集成不同的服务并指定作业和命令。宝珠目录给了我们很多选择。

移动应用安全问题

除了简化作为移动开发过程一部分的测试之外,CI 还有助于缓解关键的安全问题。

看看这些 CI 可以解决潜在安全问题的例子:

  • 凭证和认证 -在团队或组织中,将帐户密码和权限安全地限制给管理员是很重要的。运行敏感命令或任务(如使用私有证书进行代码签名或使用 API 密钥上传构建)将仅限于 CI 服务器。
  • 数据加密 -个人用户信息和密码需要小心处理,因此确保这些数据在设备和网络上始终加密的测试将有助于通过代码更改来保护我们的用户。
  • 保持以前的错误和问题不被重新引入我们的代码的最好方法之一是创建一个自动测试来检查每个构建的场景。
  • 错误处理 -移动应用崩溃和内存缓冲区限制可能是安全漏洞的来源,或者至少是糟糕的用户体验。测试优雅的错误处理可以减轻这些问题。
  • 设备功能和权限 -测试可以帮助确保我们的应用程序正确使用本机设备功能,如麦克风或摄像头,并且仅在用户希望被记录的正确应用程序场景中使用,绝不会超出该上下文。这可能与应用程序获得用户保存的照片或文件的访问权限类似。

通过 CI 部署移动应用

最后,一旦我们的移动应用程序经过测试并准备好发布,CI 也可以通过将其直接发布到应用程序商店来提供帮助。

从高层次的角度来看,CI 任务可以获取最新版本的代码,增加版本号,构建项目源代码以供发布,然后将打包的应用程序直接上传到谷歌 Play 商店或苹果应用商店。

我们建议在这个场景中使用 Git 分支,并指定一个发布分支,这样我们就可以将它与开发分支分开,并且只有当代码从开发分支合并到发布分支时,才允许 CI 将构建上传到商店。

使用 CircleCI 和浪子可以轻松配置移动应用程序部署。对于 iOS 应用程序,我们可以提供对 App Store Connect 和浪子配置的访问,其中包含构建和上传应用程序发布版本的步骤。浪子还可以使用它的屏幕截图和 frameit 操作在元数据处理过程中生成屏幕截图。

按照 CircleCI 文档中的步骤,在 iOS 上配置移动应用部署,在 Android 上配置移动应用部署

移动开发入门

我们刚刚浏览了使用 CI 自动化我们的移动应用测试所涉及的步骤的高级视图,正如我们所看到的,这并不困难。以正确的方式开始移动应用项目是有意义的:从一开始就内置测试和 CI。

有了 CircleCI,自动化应用测试和部署变得很容易。更好的是,开始不需要任何成本,如果你的项目是开源的,你可以申请一个特别计划,每月有免费的构建积分。

在我们结束之前,这里有一些有用的资源,可以帮助您开始 CircleCI 的自动化移动应用程序测试之旅:

祝您测试和自动化您的移动应用程序好运!

面向手机游戏开发者的 CI-circle CI

原文:https://circleci.com/blog/ci-for-mobile-game-developers/

来自出版商的说明:您已经设法找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。在这里阅读更多关于移动应用开发持续集成的信息,或者尝试在我们的文档博客中搜索当前信息。


CircleCI for Mobile Testing

本周早些时候,我们宣布了 OS X 的 CircleCI。这一新产品允许 iOS 开发人员使用 CircleCI 为 iPhone、iPad、Mac 和 Apple Watch 应用程序构建和测试代码,极大地改善了 iOS 开发周期。

移动应用程序开发的需求可能比 web 应用程序更复杂,而游戏生成的图形内容要比传统的移动应用程序多得多。传统应用程序的测试可以依赖于屏幕上显示的已知内容,或者更具体地说,在一组应用程序交互之后,某些具有预配置 id 的元素是可见的。然而,对于手机游戏,编写这样的测试变得越来越困难。

测试图形表示是困难的

complexity of in-game graphics

大多数设计用于与图形表示交互的测试机制依赖于搜索视图树或分析屏幕截图。如果有一种方法来表示正在测试的应用程序的状态——并在单次测试后回滚到该状态,这将非常有用

对于移动游戏,有效的状态实际上可能是一组状态,因为游戏的机制可能包括同一场景的多个视图,获得相同结果的多种方法,以及与外界的不同交互(AI)。即使仅仅通过与控制元件交互就有可能达到期望的状态,在移动模拟器或仿真器上达到该状态也可能需要大量的时间。

Do you have a test for that tube on the ground on the top of the screen? Screenshot from Space Marshals 屏幕上方地面那根管子有测试吗?截图来自太空警察

额外的抽象层将拯救我们

由于游戏中的表示层比传统应用程序的图形更通用、更灵活,因此可测试性更低,可能很难用传统的 XCTest 方法测试所有图形本身。

然而,如果发明了一个中间层——游戏状态的概念,事情就会简化很多。专注于独立于渲染机制生成、修改和测试游戏状态提供了必要的灵活性。专注于中间状态允许你用标准的测试工具测试游戏的实际逻辑,然后有一个单独的测试套件,只用于通过渲染类将游戏状态转换成屏幕上的实际图像。

如果呈现逻辑仅在真实设备上可用

如果游戏运行在模拟器/仿真器上,测试实际的渲染功能可能并不总是可能的。例如,为苹果 MetalKit 编写的代码只能在真实设备上运行。这意味着在没有真实设备的情况下在 CI 上运行测试时,您将不得不跳过呈现步骤。

好消息是:如果你决定将游戏状态从渲染逻辑中抽象出来,这将很容易实现。一旦逻辑被分离出来,如果构建的目标是模拟器/仿真器,并且实际上在 CI 上运行完整的单元测试套件,您就可以将这些类剔除。这篇博客文章为 MetalKit 提供了一个简洁的方法。

端到端测试

如果呈现逻辑仍然在真实设备上工作,您可能能够为您的呈现逻辑运行完整的端到端测试。在确保游戏状态交互层的正确性之后,你就可以实际渲染一些不同的游戏状态,并捕捉结果的截图。

如果渲染层正在积极地工作,则截图和标准具的实际比较将必须由人来完成,但是如果大部分工作被投入到游戏逻辑、性能优化或新的故事情节中,则确保渲染功能的结果没有改变是足够的措施。

游戏的非游戏部分

大多数游戏都有一套非游戏逻辑,如设置屏幕、菜单或场景选择。这些可以使用标准的类似 XCTest 的逻辑进行测试,因为只有一种正确的方法来表示一组稳定的信息。

如果游戏涉及任何类型的后端处理,可以使用传统的测试技术对所需的堆栈进行单独测试。

完美的游戏持续集成几乎是不可能的

由于渲染层没有完整的自动化测试,您必须自己测试,并在整个开发阶段保持测试。

我的一个同事曾经参观了一个游戏开发工作室,那里的办公室里安装了一个 Xbox,而那个 Xbox 大部分时间都没有被使用(这是有道理的)。当没有人使用 Xbox 时,当时正在开发的游戏会自动加载到设备上,主角的无敌模式会打开,然后它会对游戏进行完整的演练。在 Xbox 附近闲逛的开发人员会在经过屏幕时注意到渲染层问题——像游戏世界的部分渲染不工作,颜色关闭,或者物理的某些方面看起来不现实等方面很容易被注意到。

很难取代人工驱动的 QA 过程,但是可以将它尽可能地移到更高的位置,以节省开发人员的时间并最小化人为错误的风险。

渐进式网络应用的持续集成| CircleCI

原文:https://circleci.com/blog/ci-for-pwas/

本教程涵盖:

  1. 设置示例渐进式 web 应用程序
  2. 为示例应用程序创建测试
  3. 构建持续集成管道

Web 和浏览器技术不断进步,缩小了 web 和本地应用程序之间的性能差距。曾经只属于本机应用程序的功能可以在 web 应用程序中实现。这部分是由于渐进式网络应用(pwa)的出现。现在可以安装 Web 应用程序,接收推送通知,甚至脱机工作。在本文中,我们将构建一个简单的 PWA,为它编写测试,并通过构建一个持续集成 (CI)管道来自动化测试过程。

先决条件

要跟进这篇文章,需要做一些事情:

  1. JavaScript 的基础知识
  2. 系统上安装的 Node.js
  3. 全球安装在您系统上的 HTTP 服务器模块(npm install -g http-server)
  4. 一个的账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

设置演示应用程序

首先,通过运行以下命令创建一个应用程序文件夹:

mkdir my-pwa 

接下来,在应用程序的根目录中,创建一个名为index.html的文件。该文件将成为应用程序的主页。将此代码粘贴到文件中:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="manifest" href="manifest.json" />
    <link rel="stylesheet" type="text/css" href="styles.css" media="all" />

    <title>My PWA Application</title>
  </head>
  <body>

    <h2>
      Welcome to my Progressive Web Application.
    </h2>

    <script src="app.js"></script>
  </body>
</html> 

该代码块显示了一个典型的 HTML 页面,标题为“我的 PWA 应用程序”和一条欢迎消息。这个文件还引用了一个manifest.json文件(为安装配置我们的 PWA),一个用于一些基本样式的styles.css文件,以及一个将在我们的服务人员中加载的app.js文件。所有这些文件都将在本教程中创建。

要获得预览,请在应用程序的根目录下运行以下命令:

http-server 

这将调用http-server模块来启动一个专门的服务器为应用程序提供服务。运行该命令后,该地址将显示在控制台上。然后,您可以转到该应用程序的 URL。

Application Home Page

注意 : 我在谷歌 Chrome 浏览器上以匿名模式运行,开发者工具打开,移动视图激活。我更喜欢在开发过程中以匿名模式运行 PWAs,因为这样可以确保我的服务人员得到更新。

接下来,通过在应用程序的根文件夹中创建一个styles.css文件来添加样式。将此粘贴到文件中:

/* ./styles.css */

h2 {
  color: blue;
  padding: 5px;
} 

这个文件只是给了h2头一些填充,并把它涂成蓝色。

添加服务人员

服务人员创建了一个为 PWA 功能提供动力的发动机室。我们将通过在应用程序的根文件夹中创建一个名为serviceworker.js的新文件来为这个项目添加一个服务人员。将以下代码粘贴到其中:

// ./serviceworker.js

var cacheName = "sw-v1";
var filesToCache = ["./", "./index.html", "./styles.css"];

self.addEventListener("install", function (e) {
  console.log("[ServiceWorker] Install v1");
  e.waitUntil(
    caches.open(cacheName).then(function (cache) {
      console.log("[ServiceWorker] Caching app shell");
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener("fetch", function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
}); 

如果您以前从事过 PWA 项目,那么这段代码会很熟悉。它首先为缓存设置一个名称,当文件更新时,它会设置您的服务工作器的版本。当浏览器安装更新的服务工作者文件时,您可以很容易地识别当前运行的版本。我用了名字sw-v1来标识它是我的第一个版本。这是您缓存应用程序根文件(index.htmlstyles.css)的地方。

接下来,创建一个要缓存的文件数组。这些文件将缓存在浏览器的内存中,供用户在脱机时访问。

接下来,install事件使用缓存名称创建一个存储文件的缓存。

下一个事件activate是在安装了新版本的服务工作程序后检测到它时触发的。此事件指定旧的服务工作线程停止为缓存文件提供服务。

最后一个事件fetch,拦截应用程序的请求,并检查所请求资源的新缓存版本。如果有新的资源可用,则提供缓存的资源。如果没有找到新的版本,则对资源进行新的请求。

然后,您需要将刚刚创建的服务工作者加载到您的应用程序中。在应用程序的根文件夹中创建一个app.js文件,并粘贴以下代码:

// ./app.js

if ("serviceWorker" in navigator) {
  window.addEventListener("load", function () {
    navigator.serviceWorker.register("./serviceworker.js").then(
      function (registration) {
        console.log("Hurray! Service workers with scope: ", registration.scope);
      },
      function (err) {
        console.log("Oops! ServiceWorker registration failed: ", err);
      }
    );
  });
} 

是时候带你的服务人员去试运行了。确保你的应用程序仍在运行,然后在它所在的浏览器标签上进行硬重新加载:Ctrl + Shift + R。检查浏览器控制台中您记录的日志消息,以确认服务人员的安装。

Service worker installation

太好了!

您的服务人员已安装,文件已缓存以供脱机访问。要确认缓存,请转到 Chrome 开发者工具中的应用程序选项卡,并展开缓存存储部分。您将看到我们的服务人员刚刚创建的命名缓存。

Service worker cache

为了确认您的服务人员已经提供了离线功能,请使用Ctrl + C关闭http-server服务。然后在浏览器中刷新应用程序。以前,脱机页面会在此时显示,因为应用程序不再运行。然而,有了服务人员的魔力,您的应用程序主页仍然是可用的,没有中断。

添加清单文件

要完成 PWA 的创建,您需要创建一个清单文件。该文件激活应用程序的添加到主屏幕功能。这定义了您的应用程序如何安装在使用它的设备上。

在项目的根目录下创建一个manifest.json文件。把这个粘贴进去:

{
  "name": "My PWA",
  "short_name": "My PWA",
  "background_color": "#ffffff",
  "display": "standalone",
  "orientation": "portrait",
  "scope": "/index.html",
  "start_url": "/index.html",
  "icons": [
    {
      "src": "icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
} 

这个代码块定义了应用程序的name,首选的orientation,以及闪屏的background_color。添加short_name是一个最佳实践。此可选字段指定将在应用程序启动器或新选项卡页面中显示的名称。否则,将使用name,如果超过 12 个字符,它将被截断。

注意 : 你可以在这里生成一个清单文件。您还可以使用该站点为您的应用程序生成图标。

这是一个非常简单、精简的应用程序,所以我忽略了像theme_colorsplash_pages、一些标准图标大小和 iOS 图标支持等功能。

应用主页设置在/index.html,不同设备的安装图标大小在icons属性中定义。我还把包含我所有图标的icons目录移到了项目的根目录下。这是您的应用程序发挥 PWA 功能所需的一切。

为了证实这一点,运行一个灯塔测试。进入 Chrome 开发者工具中的 Lighthouse 选项卡运行测试。

Lighthouse

点击生成报告然后分析页面负载以获得您的 PWA 的审计报告。

Lighthouse Report

单击最右侧的 PWA 图标,转到 PWA 兼容性测试的结果。

注意 : 失败检查Does not redirect HTTP traffic to HTTPS将在您部署站点并启用 HTTPS 时通过。

如果您想了解更多关于获得干净版本的信息,请查看以下链接:

添加测试

要开始向您的应用程序添加测试,为您将要安装的 npm 包创建一个package.json文件。要在项目根目录下创建一个基本文件并跳过 QA 过程,请运行以下命令:

npm init -y 

要在应用程序中设置测试,请安装以下软件包:

使用以下命令一次性安装这些软件包:

npm install --save-dev @testing-library/dom @testing-library/jest-dom jsdom jest 

安装好这些文件后,在根文件夹中创建一个名为index.test.js的测试文件。将以下代码粘贴到其中:

// ./index.test.js

const { getByText } = require("@testing-library/dom");
require("@testing-library/jest-dom/extend-expect");
const { JSDOM } = require("jsdom");
const fs = require("fs");
const path = require("path");

const html = fs.readFileSync(path.resolve(__dirname, "./index.html"), "utf8");

let dom;
let container;

describe("Test for elements on Home page", () => {
  beforeEach(() => {
    dom = new JSDOM(html, { runScripts: "dangerously" });
    container = dom.window.document.body;
  });

  it("Page renders a heading element", () => {
    expect(container.querySelector("h2")).not.toBeNull();
    expect(getByText(container, "Welcome to my Progressive Web Application.")
    ).toBeInTheDocument();
  });
}); 

这个代码块从获取所有必需的依赖项并加载 HTML 文件开始。describe块中的beforeEach方法用JSDOM创建 DOM。

it块中运行一个测试,检查标题元素文本的出现:Welcome to my progressive web application.

现在,在package.json文件中设置您的test脚本。编辑现有的test键值对,如下所示:

...
"scripts": {
    "test": "jest"
},
... 

现在使用以下命令运行测试文件:

npm run test 

你的测试都会通过的。现在,您已经准备好开始构建您的 CI 渠道。

Local Tests Run

为 PWA 持续集成构建管道

为应用程序创建 CI 管道的步骤:

  • 向应用程序添加管道配置脚本。
  • 将项目推送到远程存储库:您可以使用 GitHub
  • 将存储库作为项目添加到 CircleCI 上。
  • 使用项目中的配置文件运行管道。

在项目的根目录下,创建一个名为.circleci的文件夹。添加一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:17.4.0
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test 

这段代码块从存储库中签出应用程序,并为 Node.js 环境更新npm版本。然后它安装依赖项并缓存node_modules文件夹。最后一步是运行测试。

将您的项目变更推送到 GitHub 库。确保在node_modules文件夹中添加一个.gitignore文件。

现在,将应用程序的存储库设置为 CircleCI 项目。在 CircleCI 仪表盘上,找到您的项目并点击设置项目

Select Project

系统将提示您编写新的配置文件或使用现有的配置文件。选择现有的分支,并输入 GitHub 上存储您的代码的分支的名称。点击设置项目

Select Configuration file

这将触发 CI 管道运行构建过程。您可以在 CircleCI 帐户的 Pipelines 页面查看该流程。您现在应该有一个成功的构建。

Add Project

恭喜你!所有步骤都完美运行,您的测试成功通过。现在您已经为您的持续集成实践做好了一切准备。您所需要做的就是将您的代码更改推送到您的存储库,CI 管道将自动构建并测试您的应用程序。

结论

在本文中,您成功地构建了一个 PWA,并为测试自动化建立了一个 CI 管道。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

React 本机应用的持续集成| CircleCI

原文:https://circleci.com/blog/ci-for-react-apps/

本教程涵盖:

  1. 创建和设置 React 本机应用程序
  2. 为您的应用程序编写测试
  3. 构建持续集成管道来运行测试

自从 2009 年发布以来, Apache Cordova 已经为移动应用程序开发创造了一个范式转变。在 Cordova 之前,只有那些了解特定类型移动操作系统的平台专用语言的开发者才能开发本地移动应用。有用于开发 iOS 应用程序的 Objective-C,或用于 Android 应用程序和平台(如黑莓)的 Java。Windows Phone 也有专门用于构建移动应用的语言。Apache Cordova(简称 Cordova)打破了平台语言的垄断。Cordova 使任何具有 HTML、CSS 和 Javascript 知识的开发人员能够用单一代码库构建移动应用程序,并为任何可用的移动平台编译它们。

由于 Cordova 应用程序中的 Webview 渲染引擎,一些开发人员认为这些应用程序不是真正的“原生”。反应本土解决了争论。React 允许开发人员通过提供直接映射到平台原生 UI 构建块的 JavaScript 组件来创建真正的原生应用。

先决条件

要遵循本教程,需要做一些事情:

  1. Javascript 的基础知识
  2. 您系统上安装的 Node.js (版本> = 10.13)
  3. 一个的账户
  4. GitHub 的一个账户
  5. 为 React(iOS 的本地开发)设置的环境

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

注意:React 环境是有条件可选的。没有它,您将无法在模拟器中运行示例应用程序。您仍然能够运行教程中描述的测试。

安装并设置好所有这些之后,是时候开始本教程了。

创建一个示例 React 本地应用程序

首先,创建一个新的 React 本机应用程序。选择应用程序的位置,然后运行:

npx react-native init MyTestProject 

如果出现提示,按键输入继续。这个命令使用npx来调用react-native init命令,在MyTestProject文件夹中搭建新的应用程序项目。

要在模拟器上启动项目,请在新创建的项目中从终端运行以下命令:

npx react-native run-ios --simulator="iPhone 13" 

前面的命令将构建项目并在一个iPhone 13模拟器上运行,如下所示:

virtual device running - Xcode

您让应用程序在它自己的终端窗口中运行。一旦您使用 Metro 对代码进行更改,它将自动刷新。Metro 是一个 Javascript bundler,可以在文件发生变化时随时在模拟器中重新加载应用程序。

在本教程中,您将创建一个简单的 React 本地应用程序,该应用程序为用户提供一个单击按钮并显示一条消息。然后,您将编写一个测试套件来测试这种行为。

转到App.js文件(在根文件夹中),将其中的代码替换为:

import React from "react";
import {
  SafeAreaView,
  StatusBar,
  StyleSheet,
  Text,
  useColorScheme,
  Button,
  TextInput,
  View,
} from "react-native";

import { Colors } from "react-native/Libraries/NewAppScreen";

const App = () => {
  const [message, setMessage] = React.useState();

  const isDarkMode = useColorScheme() === "dark";
  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? "light-content" : "dark-content"}
        backgroundColor={backgroundStyle.backgroundColor}
      />

      <Button
        title="Say Hello"
        onPress={() => {
          setTimeout(() => {
            setMessage("Hello Tester");
          }, Math.floor(Math.random() * 200));
        }}
      />
      {message && (
        <Text style={styles.messageText} testID="printed-message">
          {message}
        </Text>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  messageText: {
    fontFamily: "Arial",
    fontSize: 38,
    textAlign: "center",
    marginTop: 10,
  },
});

export default App; 

这段代码创建了我之前描述过的(非常简单的)UI:一个标记为Say Hello的按钮在被点击时显示消息Hello Tester。为了模拟异步操作,我们使用setTimeout在点击按钮后延迟几分之一秒的显示。React Native Text组件在按钮的底部显示消息。该代码通过使用Stylesheet组件为消息添加样式。

App running emulator

在模拟器出现的屏幕上,点击Say Hello按钮。

Button clicked - emulator

设置和添加测试

用 CLI 工具搭建一个新的 React 本机应用程序的一个很大的优势是,在 seed 项目中已经配置了一个基本的测试设置。测试设置使用 Jest ,并包含一个__tests__文件夹来存储测试套件。不过,对于本教程,我们需要使用 React 本地测试库

将 React 本机测试库作为开发依赖项安装:

npm i -D @testing-library/react-native@11.0.0 

安装完库之后,用下面的代码片段替换测试套件__tests__/App-test.js文件中的代码:

import "react-native";
import React from "react";
import App from "../App";

import { fireEvent, render, waitFor } from "@testing-library/react-native";

it("Renders Message", async () => {
  const { getByTestId, getByText, queryByTestId, toJSON } = render(<App />);

  const button = getByText("Say Hello");
  fireEvent.press(button);

  await waitFor(() => expect(queryByTestId("printed-message")).toBeTruthy());

  expect(getByTestId("printed-message").props.children).toBe("Hello Tester");
  expect(toJSON()).toMatchSnapshot();
}); 

这段代码包含一个对应用程序的消息显示行为的测试。React 本地测试库用于呈现包含应用逻辑的应用根组件App。然后引用这个按钮,并使用测试库中的fireEvent对象的press方法触发点击事件。

因为我们将异步行为添加到了对按钮点击的响应中,所以在测试其内容之前,我们需要等待测试 id 为printed-messageText组件显示出来。

一旦加载完毕,Text组件将针对字符串Hello Tester进行测试。

要运行测试套件,请转到项目的根目录并运行:

npm run test 

此命令会在您的 CLI 中显示一个屏幕,指示测试已通过。

Tests passed - CLI

编写 CI 管道

持续集成管道确保每当更新被推送到 GitHub 存储库时测试都会运行。第一步是在项目的根目录下创建一个名为.circleci的文件夹。添加一个名为config.yml的配置文件。在该文件中,输入:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:18.10.0
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}
      - run:
          name: Install Dependencies
          command: npm install
      - run:
          name: Run tests
          command: npm run test 

在这个管道配置中,Docker 映像首先用所需的 Node.js 版本导入。然后,npm被更新,依赖项被安装,缓存它们以便后续的构建更快。

最后,npm run test命令运行项目中包含的所有测试。

保存这个文件,将你的项目推送到 GitHub 。确保您使用的 GitHub 帐户是与您的 CircleCI 帐户连接的帐户。

现在,进入 CircleCI 仪表板上的项目页面。从项目列表中,搜索本教程创建的项目,点击设置项目

Select project

从模态输入配置文件所在的名称分支,点击设置项目

这将触发管道并成功运行。Build success

太棒了。

结论

适当的测试对移动应用来说可能比网络应用更重要。每次你修复一个 bug,都要求用户安装更新,这很容易惹恼他们。通过应用您在这里学到的知识来彻底测试您的 React 本机应用程序,让用户满意。更重要的是,自动化您的测试过程,并确保那些错误不会在第一时间被推到您的产品代码中。

本教程的完整源代码可以在 GitHub 上找到

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

在自托管基础架构上运行持续集成作业的优势| CircleCI

原文:https://circleci.com/blog/ci-on-self-hosted-infrastructure/

第一批持续集成(CI)工具都是自托管的,这意味着它们运行在开发人员的本地计算机或服务器上。尽管这种设置在当时受到开发团队的欢迎,但它的灵活性有限,开发人员必须花费时间来维护基础设施。

因此,许多团队转向了云托管的 CI。云托管的 CI 是大多数开发团队的最佳选择,因为它提供了一个灵活且可扩展的集中式平台,可以在最常见的环境中构建和测试软件。然而,即使是已经迁移到基于云的解决方案的团队,也可能有安全性和合规性要求或专门的计算需求,从而阻止他们在公共云上运行某些作业。在这些情况下,开发人员转向自托管 CI 基础架构。

本文解释了什么是自托管基础设施,并讨论了在其上运行持续集成作业的用例。它还解释了开发团队在公共云之外运行 CI 作业时可能会发现的一些优势。

什么是自托管 CI 基础架构?

自托管 CI 基础设施是在您控制的硬件、虚拟机、容器或 Kubernetes 集群上运行的持续集成软件。一些组织为其自托管的持续集成软件运营物理数据中心,但许多组织选择维护只有特定团队成员才能访问的私有云网络。无论采用哪种方法,团队都要付出更高的设置和维护开销来换取对计算环境的更多控制和更强大的隐私和数据安全措施。

然而,需要注意的是,基于云的和自托管的持续集成解决方案并不相互排斥。您可以在公共云中运行大多数 CI 作业,并在出现特定需求时运行自托管 CI。使用自托管运行器这种方法是可行的。自托管运行器是安装在私有架构上的软件,用于轮询基于云的 CI 服务以获取作业。

例如,CircleCI cloud 客户可以在不到五分钟的时间内安装自托管运行程序,并开始在一系列受支持的平台上运行私有云及内部作业,包括 Linux、macOS、Windows、Docker 和 Kubernetes。当自托管作业在您指定的私有基础架构上执行时,这些作业的结果会报告给 CircleCI web 应用程序。这为您提供了一个集中的位置来查看和管理您的测试结果以及基于云的和自托管的作业的应用程序数据。

自托管基础架构上 CI 作业的使用案例

自托管运行程序旨在解决开发团队的两个常见用例:独特的资源需求和特权访问需求。本节探讨了这两种用例以及它们可能出现的一些特定场景。

自定义计算类型

如果您对基于云的 CI/CD 提供商无法提供的特殊类型的计算实例有特殊需求,您可能需要在自托管基础架构上运行 CI 作业。例如,构建移动应用程序的团队可能会针对特定的硬件配置,并选择使用自托管运行程序在本地进行测试,以确保他们的应用程序在具有指定配置的设备上完全按照预期运行。

同样,在为嵌入式系统开发时,针对实际硬件或传感器运行测试是构建软件并为生产做好准备的关键部分。为消费电子产品、医疗设备、家用电器、销售点机器和其他嵌入式系统构建软件的团队需要一个与目标环境的规格相匹配的专用环境。一种方法是建立一个内部建筑和测试实验室,并在那里运行他们的测试。虽然该选项需要额外的设置和维护开销,但它是创建与部署环境非常相似的测试环境的最佳方式,从而降低了生产中不可预见的错误的风险。

特许访问和控制

银行、医疗保健和公共部门等高度管控行业中的组织通常面临严格的 IT 安全性和合规性要求,包括工作负载的运行位置和敏感数据的访问权限。借助自托管运行程序,您的团队可以授予某些作业访问私有网络的特权,设置静态 IP 地址以供服务器的 allowlist 验证,以及(对于在 AWS 中运行的自托管作业)分配身份和访问管理(IAM)权限以获得额外的访问控制层。

代码签名是需要这种额外安全性的敏感工作负载的一个例子。在构建签名的二进制文件时,组织必须保护他们的签名证书免受未经授权的访问。实现这一点的一种方法是将证书存储在一个安全的本地服务器上,该服务器可以通过自托管的运行程序进行访问。这允许团队利用云的速度和可伸缩性来构建和测试他们的应用程序,然后使用自托管的运行程序在部署之前签署代码

自托管 CI 作业的优势

在基于云的 CI 平台之外运行 CI 作业可以带来一系列好处,包括灵活性、安全性、成本节约以及增强对您的应用程序和开发流程的信心。

灵活性和对计算选项的控制

在自托管基础架构上运行 CI 作业可让您高度控制计算选项,例如每个作业使用的实例数量、区域和实例类型。您还可以根据 CI 作业所需的工作负载选择最佳的计算机类型。

例如,假设您的团队运行需要几个小时才能完成的测试。在这种情况下,在具有高 CPU 能力的大内存实例上运行它们是有意义的,因为这些实例允许您在不影响性能的情况下并行运行测试。根据基于云的 CI 提供商提供的执行环境,可能只有在自托管基础架构上才能运行具有高资源需求或特定架构要求的作业。使用 CircleCI 自托管运行程序,您可以利用云平台上可用的相同的并行性和测试拆分功能,以提高测试运行时间。

自托管 CI 还为您的团队提供了使用计算资源的时间和方式的灵活性。如果您的团队在工作日期间对私有基础架构的使用有所不同,您可以实施一个自动扩展解决方案,该解决方案将根据需求自动增加新的实例,然后在需要较少资源时将其拆除。

隐私和安全

在您的基础设施上运行持续集成工作的主要好处之一是控制谁和什么可以访问您的代码。虽然始终存在违规风险,但在专用基础架构上运行工作负载有助于降低风险。

在自托管基础设施上隔离运行持续集成作业,可以更轻松地限制漏洞的攻击面。您可以防止外界对网络的访问,只向需要它们的系统部分公开必要的内容。如果您的存储库中有客户数据或私钥等敏感数据,防止网络访问尤其有用。

成本节约

由于自托管基础架构有时只需公共云成本的一小部分,您可能会发现在自托管基础架构上运行资源密集型作业具有经济意义。通过在您的基础设施上设置 CI 实例(无论是 AWS EC2 实例还是托管在您的站点上的实例),您可以通过只为硬件资源付费来节省资金。

自托管基础设施提供计算资源,除了用于托管代码的硬件之外,没有任何成本。使用 Docker 之类的虚拟化软件,您可以充分利用基于云的持续集成系统的所有优势,同时还拥有在您管理的硬件上运行虚拟化软件的所有控制权。

增强对您的应用程序和开发过程的信心

自托管 CI 为测试和构建环境提供了更高的灵活性和控制力。在本地运行测试时,通过 CI 平台访问不常见的或专门的测试环境通常更容易。这允许您在不同的平台(例如,虚拟机)上运行测试,而不必在每次想要切换环境时等待 CI 提供者的更新。在您自己的基础设施上运行测试还允许您提供与您的生产目标完全匹配的测试环境。这确保了您的应用程序在部署到生产环境中时能够准确地按预期运行,从而提高了对您的应用程序的信心,并降低了将错误发送给客户的风险。

在您自己的基础设施上运行 CI 作业也可以改进您团队的开发过程。出于扩展方面的考虑,托管解决方案通常对构建时间的控制有限。借助自托管基础架构,您可以快速获得完成工作流程所需的精确资源。对测试运行时间有更多的控制可以确保它们不会减慢您的开发人员工作流程。

结论

云托管 CI 是大多数开发团队的最佳选择,因为它提供了满足最常见开发场景所需的所有灵活性、可伸缩性和性能,而无需维护您自己的基础架构。然而,云并不是万能的解决方案。随着设备和计算环境的持续增长,以及对应用程序和数据安全性的日益关注,自托管 CI 不会很快消失。

使用 CircleCI 的自托管运行程序,您可以选择哪些 CI 作业在云中运行,哪些在您组织的基础架构上运行,从而为您提供最大的灵活性来满足您组织可能面临的任何计算和安全需求。您可以通过注册免费 CircleCI 计划并报名参加免费 CircleCI 学院跑步课程来开始在您的 CI/CD 渠道中使用自托管跑步者。

用于测试微服务的 CI 管道| DeployHub 和 CircleCI

原文:https://circleci.com/blog/ci-pipelines-for-testing-microservices/

测试微服务与测试单一应用程序应该没什么不同。然而,我们必须应对一些挑战,以使这种转变变得容易。要回答的问题是,“我们如何在一个分解的应用程序上执行功能测试?”

随着我们从单一实践转向 Kubernetes 微服务环境,我们失去了应用程序版本的概念。相反,我们面对的是大量松散耦合的独立函数和 RESTful APIs。虽然 API 契约测试的概念适用于单元测试级别,但是我们还有工作要做,以整合我们的功能测试工具。

在功能级别测试微服务

对单个 API 或微服务进行单元测试并不能验证一个完整应用版本的就绪性。由于这个原因,我们不能完全消除功能测试过程。如果我们这样做,我们可能会错过一个微服务使用另一个微服务的旧版本的情况,这将导致负面影响。只有当我们将应用程序作为一个完整的产品进行功能测试时,才能发现这种程度的异常。

在单一环境中,应用程序版本是我们的发布候选。一个新的发布候选启动了我们的功能测试工具所做的工作,这在很多情况下落后于在 API 契约上执行单元测试的开发冲刺。虽然开发团队可能会进行增量编码更改,但是功能测试是基于一个完整的候选发布,并且该候选发布会导致一组完全不同的链接测试用例的执行。在测试微服务时,这种级别的功能测试仍然是必需的和关键的。

理想情况下,即使我们的应用程序构建在一组松散耦合的服务之上,我们也希望保留我们的功能测试。为此,我们需要对逻辑应用程序版本的可见性。为了在功能测试层面保持高效,我们需要对两个应用程序版本之间的变化有所了解,从而使功能测试有针对性。我们希望避免的是,仅仅因为我们没有参考哪个微服务导致了应用版本的新发布,就不断地重新测试一切。

重新想象持续集成

在单一环境中,我们依靠我们的应用程序构建(编译/链接)来创建应用程序的新版本,创建新的发布候选。然后,这个新版本被推送到测试团队来执行功能测试。有了微服务,这种构建就不复存在了。相反,我们的构建采用单个微服务并构建一个新的容器映像。然后,该容器映像被推送到容器注册中心。现在我们有了一个新的微服务发布候选,这反过来又创建了一个新的应用版本发布候选。欢迎来到新的持续集成流程。

测试微服务

DeployHub 在微服务实现中恢复应用版本,以支持您的功能测试。它使用自动配置管理来实现这一点,每次注册新的微服务时,自动创建新的应用版本。由于微服务是共享的,一个新的微服务通常会创建多个应用发布候选。DeployHub 跟踪微服务对所有消费应用的影响。这种影响分析允许 DeployHub 随后通知 CircleCI 启动多个应用程序的测试工作流,提供关于哪些微服务导致了新应用程序发布的详细信息。

部署中心球

一旦创建并注册了新的容器, DeployHub orb 就会集成到 CircleCI 中,以执行自动化的配置管理。然后,DeployHub 会自动增加所有受影响应用程序的应用程序版本,并运行每个应用程序的工作流。DeployHub 在应用程序级别使用一个键值对来指定部署的默认测试环境和 CircleCI 测试管道的名称,以便在部署后运行。此外,DeployHub 提供了一个影响分析视图,该视图在每个应用程序级别显示了哪个微服务发起了更改。

看看它的实际效果

我们让你轻松了。查看我们的 DeployHub-CircleCI 集成演示。

结论

通过将 DeployHub 集成到您的 CircleCI 管道中,测试微服务及其消费应用变得更加容易。测试变得由发布候选事件触发的自动化过程驱动,就像我们在单一环境中所做的一样。您的功能测试套件保持不变,insights 向您展示单个微服务更新对所有应用的影响。

关于 DeployHub

DeployHub 使组织能够通过受管的微服务方法实现业务敏捷性。DeployHub 通过提供微服务使用的 SaaS 集中视图以及跨集群和团队的共享,消除了微服务常见的混乱和复杂性。

要了解更多信息并使用 DeployHub CircleCI orb,请在 CircleCI orb 注册表上找到我们。您还可以在 DeployHub.com 的了解更多关于 DeployHub 的信息。DeployHub 基于 T4 的开源项目 T5。

与 GitOps | CircleCI 的持续集成

原文:https://circleci.com/blog/ci-with-gitops/

软件开发日新月异。一方面,您必须快速适应不断发展的需求,而另一方面,您的应用程序需要连续运行而不停机。

DevOps 帮助你快速适应变化。在其他计划中,持续集成 (CI)和持续交付 (CD)对于任何 DevOps 实践都是不可或缺的。

使用 CI,开发人员可以通过自动化构建和测试将他们的代码与其他人的代码合并。开发人员将他们的代码提交给源代码控制系统,通常是 Git。代码提交触发了自动化构建操作,随后是单元测试。新代码然后合并到主分支中。在增量代码更改后,您可以在构建和测试时快速修复任何问题。这也有助于解决任何问题,包括合并冲突和棘手的 bug,因为您是在小步骤中集成代码库的。

如果测试套件已经通过,连续交付可以让您在构建之后立即部署应用程序。您可以适应快速变化的需求,并确保应用程序在最短的停机时间内运行。

虽然 DevOps 在开发和部署软件方面非常有用,但是结合使用 Git 和 CI/CD 在软件工程领域之外也非常有用。将这些原则应用于非软件工作的想法被称为 GitOps。

GitOps 最常见的用途之一是使用基础设施即代码(IaC)工具供应云基础设施。

将 GitOps 与 IaC 一起使用

如上所述,DevOps 工作流旨在构建和部署软件。但是在 DevOps 可以工作之前,我们需要一个部署软件的地方。应用需要包含计算、存储和网络服务的环境。在开发生命周期中,您会使用许多环境,例如开发、试运行和生产。传统上,每个环境都有其配置,通常会随着时间的推移而变化,从而导致所谓的环境漂移。此后,配置无法自动复制,导致难以跟踪的错误,并需要手动维护。

要解决这个问题,您可以使用基础结构作为代码(IaC)。通过 IaC,您可以使用 Terraform、CloudFormation 和 Azure Resource Manager 模板等工具创建您想要的基础设施的声明,然后将该声明保存在 Git 存储库中。通过这样做,您可以遵循开发人员对其代码使用的相同方法。更改基础结构声明后,您将更新的文件提交到存储库中。这将触发 CI/CD 管道来验证配置,然后运行自动化工具来配置或停用基础架构资源。

然后,您可以将 Git 工作流用于基础设施部署。例如,您可以创建一个新的 Git 分支,在那里您可以使用环境声明文件。完成后,您将创建一个拉取请求。然后,您的更改将被审核,审核通过后,分支将被合并到主分支中。如果 CI/CD 系统正在监视存储库,合并将触发自动化的基础架构部署。

您甚至可以扩展 GitOps IaC 方法来管理 Kubernetes 用于配置的 JSON 或 YAML 文件。通过修改和部署这些文件,您可以自动执行集群中容器的滚动更新,本质上是使用 GitOps 来部署应用程序。在这种情况下,您只需要更改配置文件并将其推送到 Git 存储库。然后,CI/CD 系统会自动将更改部署到集群中。更具体地说,CI/CD 系统使用适当的命令行界面(CLI ),如 kubectl,来应用您提交的更改。

在实践中,您设置了一个包含各种作业的构建和部署管道。每个作业由一个或多个步骤组成。第一个作业可以提供资源。例如,它可以自动为您创建或更新 Kubernetes 集群。同样,该作业使用 CLI 脚本来调配资源。假设环境已经准备好,第二个作业可以检查源代码,构建源代码,并将应用程序部署到环境中。

使用 GitOps 的持续集成——一个例子

下面是使用 CircleCI 设置管道的步骤。我们将创建包含两个作业的管道。第一项工作将在 Azure 中部署托管的 Kubernetes 集群。第二项工作是将容器化的 ASP.NET 5 应用程序部署到该集群。

添加存储库

我们首先创建 GitHub 存储库, CircleCI-AKS-GitOps 。为了简单起见,我们只有一个主分支,在 k8s 文件夹下有 all-in-one.yml 文件(见下图)。该文件包含部署和服务声明。部署将使用 Docker 映像和示例【ASP.NET 5】应用创建两个 pod。这两个 pod 都将使用负载平衡器服务向公众公开。我们将在部署后获得该服务的公共 IP。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: aspnet-sample-app
spec:
  selector:
      matchLabels:
        app: aspnet-sample-app
  replicas: 2
  template:
      metadata:
        labels:
            app: aspnet-sample-app
      spec:
        containers:
        - name: aspnet-sample-app-pod
          image: mcr.microsoft.com/dotnet/samples:aspnetapp 
          ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: aspnet-sample-app-svc  
spec:
  selector:
    app: aspnet-sample-app
  type: LoadBalancer
  pxorts:
  - port: 80 

创建管道

要将这个存储库与 CI/CD 系统连接起来,您需要管道配置。这是一个包含作业和步骤声明的 YAML 文件。使用 CircleCI 时,可以在 config.yml 文件的。circleci 文件夹。

这里,我们使用 orb 为 Azure Kubernetes 服务和 Kubernetes 创建 config.yml:

jobs:
  create-deployment:
    executor: azure-aks/default
    parameters:
      cluster-name:
        description: |
          Name of the AKS cluster
        type: string
      resource-group: 
        description: |
          Resource group that the cluster is in
        type: string
    steps:
      - checkout
      - azure-aks/update-kubeconfig-with-credentials:
          cluster-name: << parameters.cluster-name >>
          install-kubectl: true
          perform-login: true
          resource-group: << parameters.resource-group >>
      - kubernetes/create-or-update-resource:
          resource-file-path: k8s/all-in-one.yml
          resource-name: deployment/dotnet-deployment
orbs:
  azure-aks: circleci/azure-aks@0.2.1
  kubernetes: circleci/kubernetes@0.4.0
version: 2.1
workflows:
  deployment:
    jobs:
      - azure-aks/create-cluster:
          cluster-name: aks-dotnet
          create-resource-group: true
          generate-ssh-keys: true
          location: eastus
          resource-group: aks-dotnet-rg         
      - create-deployment:
          cluster-name: aks-dotnet
          requires:
            - azure-aks/create-cluster
          resource-group: aks-dotnet-rg 

有两种工作:

  • azure-aks/create-cluster–使用 AKS 创建 Kubernetes 集群
  • 创建-部署–将 all-in-one.yml Kubernetes 配置应用于 AKS 集群

作业具有附加参数,使您能够应用其他配置。在这里,我们使用它们来设置集群名称(aks-dotnet)、区域(eastus)和 Azure 资源组(aks-dotnet-rg)。

CircleCI 的建筑

一旦我们配置了管道,我们就可以在 CircleCI 中查看项目。如果使用 GitHub 或 Bitbucket,可以使用现有账户在 CircleCI 注册。该服务将自动发现您的存储库。

CircleCI project page

然后,使用“设置项目”按钮创建新的配置文件或选择现有的管道配置。对于这个例子,我们使用 config.yml。之后,CircleCI 将开始构建项目。

CircleCI pipeline

CircleCI pipeline details

要成功连接到 Azure,您必须使用项目环境变量提供用户凭据或服务主体。我们的用户凭据存储在我们在项目设置中键入的 AZURE_USERNAME 和 AZURE_PASSWORD 下。

CircleCI environment variables

设置完这些变量后,CircleCI 将为您创建 AKS 集群,然后部署应用程序。请注意,第一个管道由于缺少凭据而失败。

CircleCI failed pipeline

测试资源

我们现在可以测试部署了。为此,我们登录 Azure 门户,打开 Azure CloudShell,然后调用以下命令:

az aks list -o table 

这个命令将显示所有 AKS 集群的列表,包括我们通过 CircleCI 部署的那个(aks-dotnet)。现在,我们需要登录到该集群,并读取负载平衡器服务的公共 IP。为此,调用以下命令:

az aks get-credentials -n aks-dotnet -g aks-dotnet-rg
kubectl get svc 

我们的应用程序的 IP 出现在外部 IP 列中。使用此地址查看正在运行的应用。

Kubernetes services

清理资源

要清理资源,请键入:

az group delete -n aks-dotnet-rg --no-wait --yes 

这将删除 AKS 集群而无需确认。或者,您可以用另一个自动删除集群的作业来补充管道。

总结和后续步骤

我们刚刚学习了如何使用 CircleCI 和 GitHub 存储库将容器化的应用程序部署到托管的 Kubernetes 集群。在实践中,您将使用另一个 Git 分支来补充上述工作流,并创建 pull 请求,以便其他人可以查看您所做的更改。

如果您已经在使用 CI/CD 系统,有很好的[迁移文档]https://circleci . com/docs/migration-intro/# section = getting-started 帮助您将项目迁移到 circle CI。

在这些知识的推动下,您现在可以开始使用 CirlceCI 构建您的 GitOps 工作流,轻松管理应用程序并腾出时间专注于新功能。您可以马上开始,今天就注册您的 CircleCI 免费试用

什么是 CI/CD 工程师| CircleCI

原文:https://circleci.com/blog/cicd-engineer/

最近,我花了很多时间思考技术领域采用 DevOps 的巨大增长。DevOps 的采用正在改变团队和组织构建和发布软件的方式。现在,大多数团队的软件开发和发布周期已经从几周、几个月甚至几年减少到几小时和几分钟。这直接归功于持续集成和持续交付(CI/CD)实践和原则的采用。

CI/CD 是现代软件开发的核心组件。团队正在开发适合他们特定情况的定制软件开发过程,以及反馈机制来提供对优化这些过程的选项的洞察。团队也在开发更紧密的沟通策略,以产生更深入的合作和更快、更有建设性的反馈循环。甚至业界也认识到 CI/CD 对软件开发的重要性,以至于它现在有了自己的基础,即持续交付基础(CDF)。以下是 CDF 的任务:

持续交付基金会(CDF)是许多发展最快的持续集成/持续交付(CI/CD)项目的供应商中立的家。它促进了行业顶级开发人员、最终用户和供应商之间的供应商中立的协作,以推进 CI/CD 最佳实践和行业规范。它的使命是发展和维持项目,这些项目是广泛的和不断增长的持续交付生态系统的一部分。

随着 CI/CD 垂直市场的增长,对我来说显而易见的是,个人必须发展 CI/CD 特定技能。如果你不确定我说的 CI/CD 特定技能是什么意思,没关系。我将解释我对一个新角色的想法,我给这个角色配音 CI/CD 工程师

CI/CD 工程师

我第一个承认,这个社区最不需要的就是一个新的基于流行语的职位名称和描述,但是我坚信 CI/CD 工程师在不久的将来会成为他们团队的宝贵资产。也许你在想:

在写这篇文章之前,我已经和社区的不同成员讨论过这个概念,包括开发人员、sre、QA 工程师和 scrum masters。对话总是从我问这个问题开始:你如何描述 CI/CD 工程师及其相关职责?

一些比较常见的回答是:

  • DevOps 工程师不需要助手。

  • 听起来像是发行经理。

  • 我看不出 DevOps 工程师和 CI/CD 工程师有什么不同。

  • CI/CD 工程师将负责领导 CI/CD 并保持团队积极性。

  • 直接负责组织内 CI/CD 工具和平台的运行状况和操作的个人。

  • 在优化他们团队的开发和发布实践方面知识丰富的资源。

我将收集到的反馈与我的想法进行了比较,这一角色的关键要素变得更加明显。

该角色的特征与候选人应该具备的技能有关。角色的职责定义了他们要负责的事情。以这种方式分解这些元素有助于我专注于我的描述。首先,我将讨论这个角色的候选人的特征。然后,我将继续讨论我对 CI/CD 工程师职责的看法。

CI/CD 工程师的特点

以下是我认为理想的 CI/CD 工程候选人应该具备的五项技能:

  • 很强的沟通技巧
  • 敏锐的分析技巧
  • 能够将复杂的流程分解成可理解的组件
  • 精通自动化和优化流程
  • 能够胜任团队建设和团队沟通策略

这个列表并不详尽,但感觉是一个很好的起点。现在,我将为这些特征中的每一个提供更详细的解释。

很强的沟通技巧

这个角色肯定会集中在多个垂直领域,这将需要个人与各种团队进行适当的沟通和互动。清晰沟通的能力对这一角色至关重要。

敏锐的分析技巧

该职位的大部分工作需要个人深入理解各种概念,以及它们如何与相关领域相关并对其产生影响。能够准确评估各自的环境是改善低效流程的一个非常重要的步骤。

能够将复杂的流程分解成可理解的组件

将复杂的主题分解成容易理解的部分是许多角色都具备的一个很好的特质,但在这种情况下尤其重要。结合敏锐的分析技能和解释复杂概念的能力,团队可以理解和捕捉他们当前的操作状态以及任何现有的缺陷。对运营环境有一个准确的认识,就能实现创新的解决方案,以达到和维持期望的状态。

精通自动化和优化流程

识别有缺陷的步骤使团队能够暴露削弱过程有效性的痛点和瓶颈,并使用可行的解决方案来解决它们。例如,想象一个顺序执行的流程,这意味着一个步骤需要在另一个步骤开始之前完成。这种行为通常被称为阻塞。根据我的经验,大多数顺序流程都有可以并行执行的步骤,从而消除了这种阻塞。能够设计和协调这些优化在这个角色中特别有用。

具备团队建设和沟通策略的能力

这种特征是基于我已经提到的一系列特征,加上在团队和组织中发展和维护信誉。信誉是被信任和信赖的品质。当人们对支持努力的个人有信心时,他们会更乐意接受和支持决策。这里的目标是围绕决策达成共识,并执行有利于所有利益相关者的计划。

CI/CD 工程师的职责

在这里,我将分享我对 CI/CD 工程师应履行的五大职责的看法:

  • 制定 CI/CD 原则
  • 反复审查和修改 CI/CD 原则
  • 维护 CI/CD 工具/平台(如果适用)
  • 开发和维护管道配置
  • 自动化流程

制定 CI/CD 原则

采用 CI/CD 原则可以极大地提高效率。这些实践由涉众定义的过程组成,并促进优化的软件开发。一个 CI/CD 工程师将是所有“CI/CD”方面的权威。作为权威,他们将积极参与这些相关流程的开发和实施。

反复审查和修改 CI/CD 原则

一旦建立了实践,团队不断地重新审视它们以验证它们是否最佳地支持了当前的操作是至关重要的。CI/CD 工程师将处于一个独特的位置,以深入了解各个团队如何运作以及如何与其他单位互动。他们将能够洞察哪些是有效的,哪些是无效的,并能够确定和提供纠正措施。迭代评审将提供对驱动软件开发的过程和表面改进选项的一致可见性。

管理 CI/CD 工具/平台(如果适用)

建立和采用 CI/CD 实践和原则后的下一步是利用 CI/CD 工具和平台,这将促进相关流程的执行。CI/CD 平台赋予我们的流程生命力,并提供执行特定管道的自动化。实施和管理 CI/CD 工具需要多种技能的结合,DevOps 是肯定的,但是我建议在这方面多强调一点 Ops 是合适的。我这样说是因为确保 CI/CD 工具始终如一地运行肯定属于“操作”范畴。这一特殊任务还取决于架构和操作环境。

根据我的经验,组织以两种不同的模式运行其 CI/CD 体系结构:

托管 CI/CD:
CI/CD 架构由 CircleCI 等第三方供应商提供。这些服务调配、管理和扩展底层 CI/CD 基础架构,技术团队只需付出很少的努力。这些服务旨在直接为开发人员服务,并去除 CI/CD 的所有困难部分。这些工具使工程团队能够开发软件,而不必为 CI/CD 基础设施的资源密集型和耗时的管理而烦恼。

自托管/本地 CI/CD:
在我看来,托管 CI/CD 应该在每个组织的雷达上,但由于数据法律和政府法规等情况,某些组织仍然需要自托管平台。自托管 CI/CD 要求团队调配和管理其 CI/CD 基础架构的所有方面。团队从整体上管理这些基础设施,这意味着他们必须设计、实现和管理连接、服务器/节点、操作系统以及必须在这个复杂的基础设施上安全运行的所选 CI/CD 工具。这需要 DevOps 团队投入大量的时间和精力,并且是一个更复杂的操作。

无论 CI/CD 体系结构如何,实现 CI/CD 的效率都至关重要。一旦工具可操作,它必须保持这种状态,否则会有性能下降的风险。当然,有许多 CI/CD 工具可用,选择最合适的工具并不是一件容易的事。我建议审查多种解决方案,并选择最能满足您团队需求的工具。

CI/CD 工程师可以负责这些 CI/CD 基础架构的某些或所有方面。理想情况下,这是 CI/CD 工程师和 DevOps 工程师的共同职责。CI/CD 工程师管理 CI/CD 工具服务,DevOps 管理底层架构。至少,CI/CD 工程师必须深入了解他们所支持的 CI/CD 平台,包括底层主机基础架构。

开发和维护管道配置

所有 CI/CD 工具都需要某种形式的管道配置,这种机制指定了在 CI/CD 流程中要执行的各个步骤和部分。根据所使用的 CI/CD 工具,可以通过不同的方式完成管道配置。一些工具利用图形用户界面(GUI)来配置管道,而其他工具则需要在代码中指定管道。在所有情况下,必须指定和维护 CI/CD 管道,这一职责将直接落在 CI/CD 工程师身上。

开发、管理和执行 CI/CD 管道是这个角色的最大职责。管道配置协调指定步骤的执行。换句话说,管道配置就是我们如何对 CI/CD 工具进行编程,以实现我们的目标。

CI/CD 工程师将确保管道得到正确定义并以最佳方式运行。他们将深刻理解管道的目标和其中发生的交易。他们将与 DevOps 团队合作,协调和优化在管道内执行的步骤。CI/CD 工程师将记录管道,以便所有相关方了解他们的软件是如何构建、测试、保护和部署的。

自动化流程

自动化是 CI/CD 的核心。它有助于处理管道命令、平台间连接以及与第三方服务的重要集成。如果您剥离这些层,在核心处,CI/CD 工程师将设计和管理支持持续集成和交付操作的自动化。经验水平可能有所不同,但要成为一名有效的 CI/CD 工程师,需要具备自动化基础知识。

CI/CD 工程师的技能

最后,以下是 CI/CD 工程师将具备的技能列表:

  • 脚本编写(语言无关)
  • 能够解释和编写源代码(语言无关)
  • 基础设施资产管理(网络、服务器、操作系统、数据库)
  • 熟悉软件打包工具。exe,。黛比。rpm,Docker)
  • 熟悉版本控制工具(Git、Subversion、Mercurial)
  • 管理云提供商(AWS、GCP、Azure)
  • 熟悉安全/漏洞工具
  • 熟悉代码覆盖率分析工具
  • 熟悉监控工具

只要计划是在担任该角色的同时实现和发展缺失的技能,我上面提到的技能并不是立即必需的。

结论

最后,我为一个我命名为 CI/CD 工程师的新角色准备了一个案例。我描述了一些我认为与角色直接相关的特征和职责。如果这个角色存在的话,这些特征和职责是必不可少的。

当我把我所建议的角色特征和职责结合起来时,我意识到我所建议的是一个由多个现有角色的零零碎碎组成的新角色,而且对我来说感觉正确。CI/CD 工程师应该是一个小小的开发者、操作员/SRE、发布经理、scrum master、云基础架构架构师、业务分析师、系统集成商、安全官和网络工程师。这是一个很大的要求,但是在提到的一些或所有角色中具有丰富经验和/或知识的个人将成为优秀的 CI/CD 工程师。

同样,这些是我对 CI/CD 工程师构成的想法。我很想知道你对这个角色的想法和意见,所以请发推文给我 @punkdata 加入讨论。希望我们能一起把这个想法变成现实。

感谢阅读!

持续集成。网络应用| CircleCI

原文:https://circleci.com/blog/cicd-for-dotnet/

。NET 是一个流行的开源、跨平台开发框架,用于为 web、桌面、移动和云构建快速、可伸缩的全栈应用程序。这种灵活性使得。NET 是开发企业 web 应用程序的领先平台。网络开发是市场上最受欢迎的技能之一。

高性能。NET 团队可以通过采用 DevOps 原则和一个成熟的、专用的持续集成平台来加速他们的开发周期,并且更加一致和可靠地发布代码。借助 CircleCI 的原生 Windows 支持,您可以自动化您的构建、测试和部署阶段。NET 开发工作流从概念到产品的快速和安全。

Windows 支持包含在 CircleCI 的免费计划中,因此团队和个人开发者可以利用持续集成带来的效率提升。

建筑。CircleCI 上的 NET 项目

CircleCI 的支持。NET 开发从 Windows 执行环境开始。

Windows executor 使用专用虚拟机为每个作业提供强大的、可定制的构建流程编排,并包括构建和测试您的所需的所有工具。NET 应用程序,包括:

  • Windows Server 2019
  • Visual Studio 2019
  • 。网络 5
  • 。NET Core SDK

您还可以访问在您的 Windows 环境中运行作业的三个 shell——PowerShell、Bash 和 CMD——您可以使用它们来 SSH 到您的 Windows VM 并对失败的管道进行故障排除。

想要构建私有基础设施的团队可以使用带有机器执行器和 Windows 映像的 runners 。为了让执行环境更加灵活,您可以在 Windows executor 上运行 Docker 容器。您还可以通过运行您的。NET 核心应用程序、SQL Server 和 Docker executor 上 Linux 容器中的其他依赖项。

的简单演示,演示如何使用 Windows executor 来构建您的?NET 项目,查看 Windows 上的 Hello World。

你可以在 CircleCI 学院的构建环境课程中或者通过观看约翰·哈蒙德的在 CircleCI 上使用 Windows 入门视频教程来了解更多关于使用 Windows executor 进行构建的信息。

为配置持续集成管道。带球体的网

orbCircleCI YAML 的预打包位,您可以在您的管道配置文件中使用它来降低复杂性,并跨项目共享命令、执行器和作业规范。

使用 Windows orb ,您可以调用 Windows executor,设置想要使用的 Windows 资源的大小,并开始在您的。NET 开发工作流和作业,只需几行代码。

将 Windows orb 添加到您的管道中就像在您的config.yml文件中包含以下行一样简单:

orbs:
  win: circleci/windows@2.4 

您可以通过在配置文件中包含额外的 orb 来扩展管道的功能。例如,你可以使用 MSIX 球体建造你的。NET 桌面应用程序作为签名的 MSIX 包

游戏开发者可以使用第三方 Unity orb 来构建 Unity 项目,并在构建完成后存储构建日志和工件。关于使用 CircleCI 构建 Unity 项目的更多信息,请参见使用 CircleCI 持续集成 Unity

构建、测试和部署。CI/CD 管道中的. NET 应用程序

随着持续集成管道的建立,您可以自动构建和测试您的。NET 应用程序,每次你的代码发生变化。

例如,您可以使用 Windows orb 来构建和测试 ASP.NET 核心 web 应用程序。在 Windows 执行环境中,您可以使用dotnet.exe test命令对您的代码自动运行单元测试,允许您在问题到达您的用户之前快速识别和修复问题。

您还可以将您的管道配置为自动部署。NET web 应用程序应用于各种宿主环境。一旦您的应用程序通过了您设置的所有测试,您就可以将您的应用程序部署到任意数量的托管平台上。

您可以使用 Heroku orb将 ASP.NET 核心应用部署到 HerokuAzure CLI orb将 ASP.NET 核心应用部署到 Azure Web 应用服务。自动化部署将消除手动批准步骤,并为您的开发团队节省大量时间和精力。

开始建设。CircleCI 上的免费网络

自动化。具有全功能 CI/CD 平台的. NET 开发工作流使团队更快更有弹性。合适的工具让开发者有更多的机会创新,并快速持续地向用户交付价值。CircleCI 一流的支持。NET 开发,包括新的。NET 6 和 Visual Studio 2022 版本,您可以放心地选择 CircleCI 作为您的团队的长期家园,进行持续集成和交付。

了解 CircleCI 如何帮助您构建、测试和部署您的。NET 应用的速度和信心,今天就注册一个免费的 CircleCI 账户

使用 GameCI 的 Unity orb | CircleCI 开发 Unity 游戏的 CI/CD

原文:https://circleci.com/blog/cicd-for-unity-projects/

我们最近与 GameCI 合作,在 CircleCI 和游戏开发领域之间架起了一座桥梁。这种合作带来了 Unity orb ,一个可重用的配置组件,你可以插入到你的 CircleCI 配置文件中来构建和测试你的 Unity 项目。

一段时间以来,持续集成和交付已经成为一些软件公司和 IT 部门的软件开发食谱的一部分。然而在游戏开发中往往不是这样。虽然后者和软件开发在本质上是相同的,但由于游戏的大小和硬件要求,游戏在集成到管道中时会更复杂。

Unity orb 是我们为游戏开发者简化 CI/CD 的第一步。有了它,你可以用最少的配置为几个平台构建你的游戏,包括 macOS、Windows、Linux、Android 和 iOS。如果你有用 Unity 测试框架编写的测试,orb 可以运行,解析结果到 JUnit,并为你存储它们以在 CircleCI web 应用中可视化

在接下来的部分,你将了解我们与 GameCI 的合作关系,并了解 Unity orb 能做些什么。如果你迫不及待地想在你的项目中设置 orb,请前往入门页面。

gamecih

GameCI 是一个开发者社区,由 WebberGabLeRoux 于 2019 年创立,专注于创建开源工具,以自动化 unity 项目的测试、构建和部署。在大卫费舍尔和几个贡献者的帮助下,它成长到支持 1000 多个寻求改进他们开发过程的团队。

最初,GameCI 专注于提供包含构建 Unity 项目所需一切的 Docker 图像。他们发展来创建和维护全面的 GitHub 操作,帮助开发人员测试、构建和部署。现在,他们的路线图包括与 GitHub 的大规模解耦,成为一个 CI 无关的 CLI 工具。

像 GameCI 一样,我们相信开源,并希望帮助游戏开发者将 CI/CD 纳入他们的开发流程,而不会将其变成业务的焦点。因此,我们联系了他们,建议他们合作实现一个 orb,遵循他们现有工具中包含的理念和功能。

我们为我们共同取得的成就感到非常自豪,并强烈鼓励您在他们的网站GitHubDiscord 上了解更多关于他们的信息。

团结球

Unity orb 包含了许多分布在构建测试任务之间的很酷的特性,我们将在这里介绍它们。如果你想知道真相,请访问 orb 的注册表源代码

顾名思义,build获取 Unity 项目,构建它,并有选择地保存结果工件。我们通过基于对packages-lock.json的更改缓存项目的依赖关系来优化流程,这意味着您可以获得开箱即用的缓存!另一个很酷的特性是能够为多个平台构建你的游戏。由于 orb 运行在多个执行器上,你可以在 Mono 和 IL2CPP 之间选择。在我们的测试中,我们成功构建了:

  • WebGL
  • tvOS
  • 马科斯
  • Linux 操作系统
  • Windows 操作系统
  • ios
  • 机器人

你可以在的test-build工作流程中下载并运行它们。你甚至可以直接从 CircleCI web 应用程序运行解压缩的 WebGL 构建,在你的浏览器中进行游戏!

</blog/media/2022-10-03-unity-build.mp4>

这份test工作帮助你运行 Unity 测试,并从中获得洞察力。我们通过将 Unity 的 XML 解析为 JUnit 并存储结果来实现这一点。如果测试没有通过,你也可以使用 requires 键来阻止工作。

Unity CI/CD test job

如果您需要对缓存进行更多的控制或者想要为您的作业定义自定义行为,您可以利用 orb 的命令。一名 GameCI 社区成员探索了这一概念,并在 this gist 中分享了他们的发现。他们使用persist_to_workspace功能在后期构建工作中将构建工件部署到 itch.io。

执行环境及其对您的影响

Unity 很酷的一点是,你可以编写一次游戏,然后在任何地方运行。然而,像脚本后端这样的细微差别需要你能够访问你游戏的目标平台来构建它。考虑到这一点,我们将 Unity orb 设计为灵活的执行环境。

可以在 macOSWindowsLinux executors 上运行 orb,执行环境不限于 CircleCI cloud。您可以在自托管运行器上构建项目,让您完全控制基础设施。在接下来的部分中,我们将讨论每个选项,并了解哪个选项最适合您。

在 CircleCI Cloud 上构建 Unity 项目

在我们的云上运行您的管道的最大优势是零基础设施开销和快速设置。你只需要担心配置你的上下文工作流。如果你有一个小项目,想要测试 orb,或者一般不关心更长的构建时间,我们建议你遵循这条路线。做好一切准备后,您的配置将如下所示:

version: 2.1

orbs:
  unity: game-ci/unity@1.2

workflows:
  build-unity-project:
    jobs:
      - unity/build:
          name: 'build-Windows64-il2cpp'
          step-name: 'Build project for Windows using Cloud'
          unity-license-var-name: 'UNITY_ENCODED_LICENSE'
          unity-username-var-name: 'UNITY_USERNAME'
          unity-password-var-name: 'UNITY_PASSWORD'
          executor:
            name: 'unity/windows-2019'
            size: 'large'
            editor_version: '2021.3.2f1'
            target_platform: 'windows-il2cpp'
          project-path: 'Unity2D-Demo-Game-CI-CD/src'
          build-target: StandaloneWindows64
          context: unity 

这个代码片段使用 Windows executor 为 Windows 构建一个 IL2CPP 项目。你可以在我们的演示项目配置文件和文档中找到更多针对不同平台的例子。

使用 CircleCI 自托管跑步者构建 Unity 项目

大型项目对带宽和硬件的要求可能会超出我们云产品的能力。在这些情况下,您需要将 Unity orb 用于自托管跑步者。由于它们的非短暂性,检出步骤不会在每次运行时都克隆您的整个存储库,您可以选择满足您需求的硬件。有了安装体验的最新更新,您可以在 5 分钟或更短时间内让 runner 运行起来

一旦你设置好了你的运行器,你所要做的就是改变你的配置文件来使用 macOSWindows 运行器执行器,同时传递你的资源类标签作为参数:

version: 2.1

orbs:
  unity: game-ci/unity@1.2

workflows:
  build-unity-project:
    jobs:
      - unity/build:
          name: 'build-Windows64-il2cpp'
          step-name: 'Build project for Windows using Runner'
          unity-license-var-name: 'UNITY_ENCODED_LICENSE'
          unity-username-var-name: 'UNITY_USERNAME'
          unity-password-var-name: 'UNITY_PASSWORD'
          executor:
            name: 'unity/windows-runner'
            editor_version: '2021.3.2f1'
            resource_class: 'ericribeiro/unity-runner'
          project-path: 'Unity2D-Demo-Game-CI-CD/src'
          build-target: StandaloneWindows64
          context: unity 

请注意,只有执行器的参数发生了变化。您的其余配置保持不变,允许您尝试 CircleCI cloud,如果前者不适合您的需求,可以快速切换到 runner。下图展示了windows-runner相对于windows-2019云执行器的表现。

Unity build time cloud vs runner

他们正在为相同的目标平台构建相同的项目,但是 runner 提前 20 分钟完成。构建时间的差距在于运行程序以前运行过一次任务。因此,所有环境依赖项都被缓存或已经安装。我们对 runner 使用了一个m5.2xlarge EC2 实例,对 cloud 使用了一个大的资源类。

最后的想法

我们很高兴能与游戏开发者和 GameCI 一起开始这一旅程。凭借从这项事业中获得的知识,我们希望开发更多适合游戏开发社区的工具,并改进 Unity orb。如果您有任何建议或反馈,请在 Twitter 上与我们联系,在我们的论坛上问好,或者在 Discord 上与我们聊天。

CircleCI 2.0 现在可以在您的防火墙后使用

原文:https://circleci.com/blog/circleci-2-0-is-now-available-behind-your-firewall/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


从今天开始,在防火墙后使用 CircleCI 的客户现在可以访问 CircleCI 2.0 提供的所有功能和性能,包括工作流、完整的 Docker 支持和基于虚拟机的作业。CircleCI 2.0 已于 7 月初面向我们的云客户正式发布,而对于那些已经切换到 2.0 的客户来说,结果令人印象深刻。

2.0 中的新功能

工作流
使用灵活的规则来协调任意数量的作业的执行,包括基于 git 分支或标签的作业过滤器,并行、串行或以更复杂的扇入和扇出配置系列运行作业的流量控制,以及需要人工批准的作业的手动门。

Docker 完全支持
当我们决定重新搭建平台我们的构建系统时,我们的口号之一是:如果某个东西在 Docker 上有效,它也应该在 CircleCI 上有效。团队现在可以使用他们自己的定制 Docker 映像作为他们的执行环境,并且可以从他们的工作中完全访问docker命令。我们还增加了 Docker 层缓存和其他优化,为您的团队提供他们想要的按需、Docker 友好的 CI/CD 系统。如果您的团队还没有使用 Docker,我们提供了一整套现成的图像,使用所有主要语言,他们可以开箱即用。

强大的调优选项
借助我们全新的定制缓存、针对 CPU 和 RAM 的按作业选项、高级并行性和跨作业工作空间,您的团队可以比以前更加精确地优化总构建时间。

情境
利用情境,在组织内跨项目共享环境变量很容易。开发人员可以在其工作流配置中通过一行请求在组织范围的上下文中运行作业。

将连续性与版本化版本相协调

作为一家以持续为核心的公司,我们相信快速迭代、频繁投入生产以及保持我们的整体高吞吐量。虽然每次我们合并到 master 时,我们的云托管客户都会看到新的更新,但我们知道,大多数运行自己的 CircleCI 安装的客户无法以这种方式运行。

为了确保我们提供一个强大的平台,并让您了解我们最新的功能和修复,我们将定期发布一个稳定的分支。我们不会为 1.x.y 版本发布单独的修补程序版本,因此使用 1.48.x 之前任何版本的客户下次升级时都会迁移到 2.x.y 版本。

此外,我们现在将为那些希望更频繁升级的人提供更多版本节奏选项。如果您的团队对更频繁的发布感兴趣,请联系您的客户团队。

CircleCI 入门

如果你是 CircleCI 的新用户,我们鼓励你注册并尝试一下。如果你准备好开始运行测试,联系我们在你的防火墙后安装 CircleCI 的试用版

那些已经安装了 CircleCI 的用户应该确保阅读发行说明,因为需要运行一些重要的迁移,这将需要一个很短的维护窗口。您还需要与您的客户团队讨论新的发布渠道,并需要在您的管理控制台中设置一些新的设置。

“CircleCI 企业”怎么了?

CircleCI 的长期客户会注意到,我们不再使用“CircleCI 企业”这个术语来描述我们的软件。我们发现,企业这个词更适合用来描述那些规模足够大的公司的一般需求,这些公司拥有跨不同业务部门的内部团队生态系统。虽然我们从名称中去掉了“企业”一词,但我们仍然致力于为大型企业提供他们成功所需的全套产品,无论他们如何部署 CircleCI。这包括我们的核心产品、我们的专业客户管理团队以及我们的高级支持层

我们希望 CircleCI 对任何规模的团队来说都是同样伟大的产品。无论您是在云中使用 CircleCI,还是在您控制的服务器上安装 circle ci,我们都致力于帮助各种规模的软件组织以可预测性更快地交付价值。

从 CircleCI 1.0 迁移到 2.0:成功秘诀

原文:https://circleci.com/blog/circleci-2-0-migration-best-practices/

作为一名高级成功工程师,在过去的几个月里,我一直在帮助我们的许多客户将他们的项目从 CircleCI 1.0 迁移到 2.0。在迁移了数百个项目之后,我知道团队最容易遇到问题的地方。在这篇博文中,我收集了我在将客户的项目迁移到 CircleCI 2.0 时最常与客户分享的技巧,以及一些在迁移过程中需要注意的问题。迁徙快乐!

增量测试 CircleCI 2.0

你知道你可以在 CircleCI 1.0 和 2.0 上构建同一个项目吗?当开始迁移到 CircleCI 2.0 时,您不必马上全部投入。要让您的项目构建在 1.0 之上并测试 2.0,请尝试以下方法:

  • 为测试 CircleCI 2.0 创建一个新分支。
  • 从该分支中删除 circle.yml 并添加一个. circleci/config.yml 文件。
  • 在那个分支上写一些最小的 2.0 配置,然后推它直到你得到一个绿色的构建。
  • 我们建议一次做一点点配置,这样你就可以感受一下它是如何工作的。最初,只需检查代码,然后尝试安装依赖项,然后尝试运行测试。稍后,您可以开始研究如何缓存依赖项并使用更高级的功能,如工作流。一点一点地建立你的配置。
  • 当一切工作正常时,您可以将带有新配置的分支合并到您的主项目中。

CircleCI 2.0 快速提示

  • steps中列出的命令只能在docker部分列出的第一个容器中运行。
  • 经常运行构建来测试到目前为止的配置。如果出现问题,您会知道自上次构建以来发生了什么变化。
  • 最初不要添加工作流;等到你有了一个功能性的构建。
  • 从头手动构建配置,但是使用配置转换端点作为参考。
  • 您不能在配置的environment部分用环境变量定义环境变量。
    • 解决方法是将变量回显到 BASH _ ENV 中
      • 这只适用于 bash,不适用于 sh (Alpine 图像只有 sh)
  • 用 bash if 语句有条件地运行命令。
    • if[$ CIRCLE _ BRANCH = " master "];然后。/ci . sh;船方不负担装货费用
  • 使用circleci step halt有条件地在该步骤停止构建
    • 允许您通过暂停有条件地使用setup_remote_docker
  • 时区可以通过定义一个 env 变量来改变。
    • TZ:"/usr/share/zoneinfo/America/New _ York "
  • 运行/dev/shm(即,/dev/shm/project)可以加速某些项目。
    • 像 Ruby gems 这样的东西是不能从共享内存中加载出来的。它们可以安装在系统的其他地方(~/vendor)并在。
  • 不要在很多命令前面加上 sudo,而是考虑将 shell 设置为 sudo。
    • shell: sudo bash -eo pipefail
  • Docker 构建和 docker-compose 一般应该在machine中运行,除非特定于语言的工具(Ruby、Node、PHP 等。)是预先需要的,那么远程环境就足够了。
  • 有些任务可以设置为在后台运行,以节省总体构建时间,但要小心耗尽资源。
  • 不同的 resource_class 大小可能是有益的,值得尝试一下,看看它们的影响。可能一点影响都没有。
  • 可以将\(PATH 设置为字符串。如果您不知道 Docker 图像的\)PATH,只需运行它并回显$PATH,或者看看env的输出。
  • 图像的 sha 可以在Spin up Environment步骤下引用。可以将图像硬编码为 sha 值,使其不可变。
  • 服务容器可以使用自定义标志运行。
    • 命令:[mysqld,–character-SET-server = utf8mb 4,–collation-server = utf8mb 4 _ unicode _ ci,–init-connect = ' SET NAMES utf8mb 4;']
  • 当在 CI 中运行时,配置您的测试运行程序,只产生两个线程/工作线程(或者更多,如果您使用 resource_class 的话)。有时候他们会基于不正确的价值观来优化自己。查看此视频了解更多信息。

迁移提示:1.0–2.0

  • 注意\(CIRCLE_ARTIFACTS 和\)CIRCLE_TEST_REPORTS 在 2.0 中没有定义。
    • 你可以自己定义它们,但是如果你这样做的话,一定要确定。
  • 在一个存储库中迁移 Linux 和 macOS(像 React Native)应该包括在将两个配置合并到一个工作流之前为每个 Linux 和 macOS 打开一个分支。
  • 你不能sudo echo -像这样管道:echo "192.168.44.44 git.example.com" | sudo tee -a /etc/hosts
  • Ubuntu 和 Debian 系统的字体是不同的。
  • Apache 2.2 和 2.4 的配置有很大的不同——请确保升级您的 2.2 配置。
  • 不要忘记所有 1.0 自动推断的命令和 UI 中手动存储的命令。

特定于语言的提示

Python

  • 通常期望类名而不是文件名来运行测试

红宝石

  • 在 AUFS 上,Ruby 文件的加载顺序可能与预期的不同
  • \(RAILS_ENV 和\)RACK_ENV 定义为test(这在 1.0 中是自动的)

基于 Java 的

  • Java(应用、工具和服务)会 OOM(内存不足),因为它不能识别有多少内存可用。应该定义一个环境变量。如果它还在飞,那就需要一个更大的容器。
  • Scala 项目的文件名可能太长,包括-Xmax-classfile-name标志。
    • https://discuse . circle ci . com/t/Scala-SBT-assembly-does-not-work/10499/10 Scala options++ = Seq("-encoding "、" utf-8 "、"-target:jvm-1.8 "、"-deprecation "、"-unchecked "、"-Xlint "、"-feature "、"-Xmax-classfile-name "、" 242" <=在此添加),

浏览器测试技巧

  • 测试有时会不可靠,看似毫无理由地失败。有些人选择自动重新运行失败的浏览器测试。缺点是破坏了定时数据。
  • 对失败的测试进行截图,使调试更容易。
  • VNC 可以安装和使用。安装metacity后,该浏览器可以在 VNC 随意拖动。从我们的一个浏览器中运行这个映像:ssh-p PORT Ubuntu @ IP _ ADDRESS-L 5902:localhost:5901 #通过 SSH 连接 sudo 安装 vnc4server 元城市 VNC 4 server-geometry 1280 x 1024-depth 24 export DISPLAY =:1.0 元城市& firefox &

码头专用提示

  • 在 cron 作业上构建 Docker 映像。
    • 每周、每天或任何需要的时候构建。
      • 通过 API 很容易触发一个新的 Docker 映像构建
    • 在映像中包含像 node_modules 这样的依赖项。
      • 它们有助于缓解 DNS 中断带来的问题。
      • 他们保持依赖版本受控。
      • 即使一个模块从节点的 repos 中消失,运行应用程序所必需的依赖关系也是安全的。
    • 私有映像可以包括私有 gem 和私有源缓存。
  • 数据库没有套接字文件,因此需要定义主机变量(\(PGHOST,\)MYSQL_HOST)来指定 127.0.0.1,强制使用 TCP。
  • 使用 CircleCI 便利版或官方 Docker Hub 镜像增加了在主机上缓存镜像的机会。
    • 构建这些图像将减少需要下载的图像图层的数量。
  • 使用容器的-ram 变体将在/dev/shm 中运行给定的守护进程。
  • 使用预安装的所有组件构建自定义映像可以加快构建速度并增加可靠性。
    • Heroku(作为一个例子)可以把一个坏的更新推到他们的安装程序,破坏你的构建。
  • dockerize 实用程序可用于在运行测试之前等待服务容器可用。
  • ElasticSearch 有自己的 Docker 注册表。
  • 容器可以有名称,因此多个给定的服务可以在同一个端口上以不同的主机名运行。
  • 特权容器可以在远程环境和machine中运行。
  • 卷不能从基本 Docker executor 装载到远程环境中。
    • docker cp可以传输文件。
    • 引用的卷将从远程环境中装载到容器中。

有关迁移到 CircleCI 2.0 的更多信息,请参见我们的文档。如果您需要更多帮助,请访问我们的支持页面

CircleCI 和 Snapcraft:你需要知道的一切

原文:https://circleci.com/blog/circleci-and-snapcraft/

软件包管理系统 Snapcraft 在 Linux 平台上为自己的位置而战,它重新想象了你如何交付你的软件。一组新的跨发行版工具可以帮助您构建和发布“快照”。我们将介绍如何使用 CircleCI 2.0 来支持这个过程,以及在这个过程中可能遇到的一些问题。

什么是快照包?还有 Snapcraft?

快照是 Linux 发行版的软件包。它们的设计吸取了在 Android 以及物联网设备等移动平台上交付软件的经验教训。 Snapcraft 是一个包含快照和构建快照的命令行工具的名称,是一个网站,几乎是围绕实现这一点的技术的整个生态系统。

Snap 包旨在隔离和封装整个应用程序。这一概念实现了 Snapcraft 提高软件安全性、稳定性和可移植性的目标,允许单个“snap”不仅可以安装在多个版本的 Ubuntu 上,还可以安装在 Debian、Fedora、Arch 等版本上。根据 Snapcraft 的网站:

为每个 Linux 桌面、服务器、云或设备打包任何应用程序,并直接提供更新。

资源

CircleCI 上的建筑快照

Snap 包可以在 CircleCI 上快速无缝地构建。我们有一个关于如何在 CircleCI 这里构建 snap 包的文档。Snapcraft 自己的文档网站也是一个有价值的资源。

Docker 图像

Snapcraft CLI snapcraft在 Ubuntu 中运行得非常好。然而,如果你试图在其他发行版中构建快照,可能会涉及更多的工作。本着与 snap 本身类似的精神,Snapcraft 团队创建了 Docker 映像,允许构建 snap 包,而不管 Linux 发行版(也包括 macOS 和 Windows)。这意味着您可以在 Debian 或 Fedora 上本地构建快照,或者在 Ubuntu 上构建完全干净的环境,与文件系统的其余部分分离。

我已经基于官方的 Snapcraft 映像创建了一组第二方的 Docker 映像,这些映像旨在在 CircleCI 等持续集成(CI)环境中很好地工作。两个 Docker 图片都可以在下面找到。

Snapcraft Docker 图像:

CircleCI 本地 CLI

CircleCI Local CLI 是一个省时的工具,允许您在本地运行 CI 构建,就在您自己的机器上。这是最有用的替代方法,可以替代进入构建进行故障排除。另一个成功的特性是在提交和推送到 GitHub 之前验证 CircleCI 配置文件的能力。再也不会因为您不小心在.circleci/config.yml中留下了一个“标签”而收到一封关于构建失败的电子邮件。

使用 CircleCI 文档中描述的构建过程,本地 CLI 现在被打包成一个 snap 包,可以通过 Snap Store 立即获得。安装说明可以在这里找到,但是它的要点是,对于 Ubuntu 16.04+用户:

sudo snap install docker circleci
sudo snap connect circleci:docker docker 

包管理和开源

你的团队计划使用其他工具打包软件吗?有没有您希望看到的开源流程和工作流?如果你想看到更多关于这些话题的博客帖子和文档,请在 CircleCI 讨论中告诉我们,或者甚至在 Twitter 上给我喊一声: @FelicianoTech

CircleCI 十周年| CircleCI

原文:https://circleci.com/blog/circleci-at-10-and-a-look-ahead/

今天是 CircleCI 十周年纪念日。我给全体员工发了一封电子邮件,捕捉到了这一重要时刻的情绪,我想在这里与你们分享。它让我们得以一窥过去十年我们作为一个组织所取得的成就,以及未来几十年我们还将取得多少成就。


至:所有员工

来自:吉姆

主题:10 岁的 CircleCI 和展望

日期:2021 年 9 月 28 日

团队,

再次感谢您在本月早些时候加入九月份的 Call Hands。我们将在明天分享一些有新闻价值的里程碑事件来结束这个月。但是花一点时间来认识到我们所有已经建立并继续建立 CircleCI 的人 10 年来取得的成功对我们的未来非常重要。

我们看起来与 2011 年大不相同。我们曾经庆祝 1000 名客户使用我们的平台。如今,我们是 200,000 多个团队的关键基础设施。2014 年,我们庆祝了 600 万美元的首轮融资对我们未来的意义。今年,我们宣布获得3.155 亿美元的总投资和 17 亿美元的估值。

我们是这个已经成立 10 年的相对较小的公司俱乐部的一部分,为此,我们有很多值得庆祝的事情。但是成功没有(现在也没有)保证。

在第 11 年、第 12 年等等,怎样才能成功?

就像前 10 年一样,我们将继续专注于为客户解决问题的方法。我们努力扩展 CircleCI,以满足世界上最高效的开发团队的需求。也就是说,我们希望每个客户——从运行他们第一个构建的开发人员到最有经验的 DevOps 团队——都有一个令人惊叹的体验。

但实际上,我们明年以及未来 10 年的成功取决于你们。我们作为个人、团队和组织整体的运作方式将决定我们是否有能力赢得面前的这个巨大机会。你在这里是有原因的,你的贡献很重要。

带着主人翁意识对待你的工作是我们快速成功前进所需要的心态。随着我们不断成长,思考如何进行实验,然后如何应用这些知识是非常重要的。虽然我们的北极星目的地保持不变,但是我们到达那里的路径可能会改变。变化是我们过程中不可避免的一部分。我们从事变革的事业,我们也将作为一个组织来生活。我们将一直重复、尝试、失败、成功,然后再尝试。最重要的是我们快速学习和拥抱快速实验文化的能力。

激动人心的时刻就在前面。

吉姆(人名)

圆形+ AWS ECR/ECS -圆形

原文:https://circleci.com/blog/circleci-aws-ecrecs/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


AmazonWebservices_Logo_svg

假期已经到了,AWS 上的 Docker 开发人员今年得到了一份不错的礼物。今天,亚马逊发布了一项新服务:EC2 容器注册中心(ECR)。ECR 为开发人员提供了一个安全、可伸缩、可靠的容器注册中心,而无需手动设置基础设施。作为认证发布合作伙伴,现在只需使用 CircleCI 和 AWS,就可以在一次 git 推送中构建、测试、上传和部署新的 Docker 容器。

当亚马逊向我们提供这项新服务的测试版时,我们迫不及待地想看看 CircleCI 的所有可能性。现在,我们已经能够使用它了,我们想分享我们在一个使用 CircleCI 的小型演示项目中看到的内容,以测试并部署一个新的多容器设置到 AWS。

码头集装箱,运行和存储

去年,亚马逊发布了他们的 EC2 容器服务,消除了处理运行 Docker 图像所需的基础设施的麻烦。通过这项服务,不仅可以测试代码,还可以通过 CircleCI 测试基础设施,这使得真正将基础设施视为代码变得更加容易。结合 CircleCI,该系统允许快速测试和部署 Docker 化代码,但并没有真正提供一种存储 Docker 映像和轻松管理权限的方法。

AWS 并不是第一个拥有 Docker 注册表的公司;Docker 和 Google 都有自己的注册中心,当然,您也可以在现有的 AWS EC2 实例中运行自己的注册中心。即使有这些选项,AWS 的新容器注册中心仍然更有用,因为它与现有的 AWS 服务紧密集成。我们发现新 ECR 有两大优势。首先,ECR 是一个一流的 AWS 服务,因此它与 Amazon 的身份访问管理完全集成。其次,ECR 在所有地区提供冗余的 Docker 注册服务器。这使得在 AWS 中使用 Docker 映像更加方便,并提供了一些急需的冗余和安全性,尤其是对于那些到目前为止一直在运行自己的注册表的开发人员。

入门:通过 CircleCI 使用 Amazon 的容器服务

在我们演示将 Docker 注册表切换到 Amazon ECR 之前,我们想向您展示如何将 ECS 与 CircleCI 一起使用。这里我们假设您已经在 Amazon 上设置了 EC2 容器服务(但是还没有部署任何映像),并且您了解 Docker 和 Docker compose我们自己的凯文·贝尔已经构建了一个简单的多容器服务,你可以在这个项目的 GitHub 页面上查看。但是,您应该知道,要通过 CircleCI 使用 ECS 和 ECR,您至少需要有环境变量

AWS _ ACCESS _ KEY _ ID AWS _ DEFAULT _ REGION AWS _ SECRET _ ACCESS _ KEY

通过项目的 CircleCI 环境变量设置页面进行设置。设置完这些变量后,启动并运行它们并不需要太多代码,而且 circle.yml 文件实际上并没有真正增加复杂性。下面是新 circle.yml 文件中仅有的非 Docker 或测试命令:

 machine:
    pre:
      - sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.0-circleci'
      - sudo chmod 0755 /usr/bin/docker
    services:
          - docker
  deployment:
    prod:
      branch: master
      commands:
        - ./deploy.sh 

正如您在这里看到的,该项目的 ECS 部分的真正内容在 deploy.sh 脚本中。如果你打开它,你可以看到它由四个独立但基本的功能组成。首先,我们用deploy_image将新创建的图像推送到我们的注册表中。在这种情况下,它被推送到 Docker.io,但我们很快就会改变这种情况。之后,我们用deploy_cluster部署我们的映像,创建一个任务定义,用AWS ECS register-task-definition注册任务定义,然后等待旧的任务修订被删除。之后,亚马逊应该会运行你的新 Docker 容器!

那么我们如何使用 ECR 呢?

亚马逊 ECR 甚至更容易设置。一旦在 AWS 上创建了 Docker 存储库,只需使用

` aws ecr 获取-登录'

然后使用 docker 注册中心的地址和您的 AWS 帐户 ID 标记您的图像,例如

` docker tag Ubuntu:trusty AWS _ account _ id . dkr . ECR . us-east-1 . Amazon aws.com/ubuntu:trusty '

最后以类似的方式推送到 AWS,例如

` docker push AWS _ account _ id . dkr . ECR . us-east-1 . Amazon AWS . com/Ubuntu:trusty '

这就是全部了!您现在正在通过 CircleCI 和 AWS 构建、测试、推送和部署您的代码和基础设施!

我希望这有助于您了解如何在项目中使用 Docker 和 CircleCI 以及 AWS!这自然不是通过 CircleCI 使用亚马逊的容器服务和容器注册的唯一方式。我们期待着建立我们的合作伙伴关系,并带来更多的方式来整合亚马逊 ECR 和 CircleCI。

一如既往,如果您发现自己迷路了或在使用这些服务时遇到了麻烦,我们很乐意回答您的任何问题。您可以通过我们的讨论社区网站联系我们的专家,或者通过【sayhi@circleci.com】T2 或应用内联系我们。

庆祝我们的 9 周年纪念和 16,795 年的建造| CircleCI

原文:https://circleci.com/blog/circleci-celebrates-nine-years-millionth-user/

今天,我们庆祝两个重要的里程碑——我们的九周年纪念日和我们的第 100 万名开发者加入该平台。

自从公司成立以来,CircleCI 发生了很多事情。在我们为客户构建、测试和部署的九年时间里,我们已经累计运行了超过 16,795 年的构建。

九年来,SaaS 和 CI/CD 市场也发生了很大的变化。

“当 CircleCI 成立时,CI/CD 是一种小众、前沿的业务。CircleCI 首席执行官 Jim Rose 表示:“现在,随着公司优先考虑自动化和连续交付系统,我们看到市场以前所未有的速度增长。

我们的一些长期客户,如 Spotify、Instacart 和 Stitch Fix,在过去的九年里也与我们一起取得了巨大的增长,我们也欢迎了新的顶级企业客户,包括 NBC Universal、花旗集团和联合利华。我们非常感谢我们的早期用户和最新客户,他们帮助我们实现了这些里程碑。

随着越来越多的公司成为软件驱动,自动化成为增长的关键,开发人员已经成为组织如何做出决策的核心部分。“不要把你的开发团队视为你试图控制的成本中心,视为你试图修补的安全漏洞,(今天)你真正考虑的是如何最大限度地发挥(你)船上难以雇用的所有这些非常昂贵的人的能力和生产力,”Rose 说。

作为一家以开发人员为中心的公司,在过去的九年里,我们一直将开发人员的需求作为我们的北极星。“开发者本质上是工匠,”Rose 说。“他们喜欢使用伟大的工具,因为伟大的工具让他们能够创造伟大的东西。”

在 CircleCI,我们知道持续集成和部署有多重要。我们最近的报告显示,我们的普通客户是优秀的 DevOps 执行者,这表明 CI/CD 是成为高绩效工程团队的一条已被证明的途径。因为 CI/CD 是我们关注的全部,所以我们有一个独特的机会来加速我们客户的生产之路,帮助他们提高产品稳定性和发布频率。

“只要我们专注于用户的需求,最终我们将处于良好状态,”罗斯说。“用户最终会为他们试图解决的特定问题选择最好的工具,我们认为我们在这方面做得很好。”

仅在过去一年,CircleCI 就取得了巨大的增长,包括筹集了 1 亿美元的 E 轮融资,使我们的总融资额几乎翻了一番,达到 2.155 亿美元。

查看我们在过去 12 个月中的其他重要里程碑:

企业势头:

  • 1 亿美元的 E 轮增长融资,总融资额几乎翻了一番,达到 2.155 亿美元
  • 被评为 Forrester Wave 的领导者:云原生持续集成工具。
  • 为我们遍布全球的员工队伍增加了 90 名新员工,在伦敦开设了第一家 EMEA 办事处
  • 欢迎 IVP 合伙人 Cack Wilhelm 成为 CircleCI 董事会的第一位女性成员。
  • 支持新的医疗保健政策,为从过渡相关医疗程序中恢复的员工提供全薪带薪休假。
  • 被列为 Ruby on Rails 社区的头号 CI/CD 提供商。
  • 出现在领先出版物的多个“值得关注的技术公司”列表中,如 Computer WorldBusiness Insider ,以及领先渠道出版物 CRN 的“最值得关注的 DevOps 初创公司”列表,该列表表彰了在 DevOps 自动化领域具有产品和服务创新能力的技术供应商。

产品创新:

  • 自从我们在 2018 年推出这些集成以来,orb 的采用率提高了 200%。今天,我们的注册中心列出了超过 1,900 个 orb,超过 58,000 个组织目前正在其 CI/CD 管道中使用 CircleCI orbs。
  • 添加了 AWS GovCloud、SOC 2 Type II 和 FedRamp 重新认证的安全认证。
  • 发布 CircleCI 的 insights API 端点的全面可用性,允许工程团队利用关键信息来显示工作流随时间推移的执行情况。
  • 发布了第一份数据驱动的 CI 案例报告,该报告分析了 3000 万个工作流,表明采用 CI/CD 是工程团队成功的可靠途径。

生态系统扩张:

  • 推出我们的开发者中心,这是一个专门的资源库,帮助各种规模公司的工程团队维护不断发展的软件堆栈并优化 CI/CD 管道。
  • 获得多个称号,以确保与亚马逊网络服务(AWS)的最佳兼容性,包括:APN 高级合作伙伴、AWS DevOps 能力认证、ATO on AWS 合作伙伴和 AWS Marketplace 精选合作伙伴。
  • 与 Hashicorp、Plandek、Salesforce 等技术解决方案公司建立了新的战略合作伙伴关系。
  • 为风险投资公司和加速器推出了投资组合合作伙伴计划,为他们的创业生态系统提供 CircleCI。

以下是过去九年的一些亮点:

  • 我们已经运行了超过 16,795 年的构建,最近的一份报告显示 80%的工作流在 10 分钟内完成。
  • 根据 CircleCI 上运行的总构建分钟数来衡量,平台使用率增加了 8,000%以上。
  • 已经发展到为 128 个国家的客户提供服务。

我们对过去九年来与我们一起成长的整个开发人员社区充满了感激之情——我们期待着继续支持您将您最好的想法变为现实。在我们的里程碑页面上查看 CircleCI 过去九年的所有增长和发展势头。

CircleCI 配置拆卸

原文:https://circleci.com/blog/circleci-config-teardown-how-we-write-our-circleci-config-at-circleci/

虽然每个人都喜欢抱怨 YAML(相信我,我们也抱怨它!),事实是这种简单的语言可以创建强大的管道来完成几乎任何你可以想象的事情。在这篇文章中,我将带您浏览我们自己的构建代理项目的配置文件,并使用它来突出我们的配置格式是如何形成的一些有趣的历史。

构建代理是我们注入到作业中运行它们的可执行文件。它是最终获取配置并执行作业中每一步的程序。CI 流程是 CircleCI 最复杂的流程之一,大多数服务都有一个更基本的 3 步构建、测试和部署流程。

我没有警告拥有这个代码库的团队我要这么做。他们有点害怕展示他们的配置缺点,但已经决定合作。

版本

配置文件中的第一个声明是版本字段。

version: 2.1 

我们当前的配置版本是 2.1,我们在 2018 年 11 月推出了一系列丰富的新配置功能,主要是orb、命令和执行器。

工作流程

接下来我们有了workflows定义,我们有了一个单一的工作流。该工作流程的目标是将经过全面测试的 Docker 映像推送到 Docker Hub

workflows:
  ci:
    jobs:
      - test
      - coverage
      - lint
      - verify_generated
      - test_windows
      - test_mac
      # snip... 

列出的第一组任务没有依赖关系——我们在 CircleCI 支持的三个平台上运行test (Linux)、test_windowstest_mac进行测试。我们希望在git push之后尽快得到关于任何失败测试的反馈。

workflows:
  ci:
    jobs:
      # snip...
      - docker_image:
          context: org-global
      - test_e2e:
          requires: [docker_image]
      - prod_smoke_tests:
          requires: [docker_image]
      - publish_image:
          filters:
            branches:
              only: master
          context: org-global
          requires:
            - test
            - coverage
            - lint
            - verify_generated
            - test_windows
            - test_mac
            - test_e2e
            - prod_smoke_tests 

Screenshot 2019-10-25 at 20.52.36.png

然后我们有一组具有依赖关系的作业——第一个作业是docker_image,构建将被测试的 Docker 映像,然后我们有两个下游作业:test_e2eprod_smoke_tests,它们依赖于 Docker 映像,最后,如果所有其他作业都成功,则publish_image作业将发布映像。

其中两个任务使用一个contextT2,它可以访问访问 CircleCI Docker Hub 账户所需的秘密。上下文允许您在 CircleCI 上的项目之间共享凭证,我们的受限上下文允许您将访问权限限制在团队的特定成员。

orbs:
  go: gotest/tools@0.0.10
  codecov: codecov/codecov@1.0.4
  win: circleci/windows@1.0.0 

接下来,我们引入对三个球体的引用。orb 是可共享的配置元素包,包括作业、命令和执行器。

我们从三个不同的名称空间引入三种类型的球体:

  • 我们导入的第一个 orb 是一个社区 orb - gotest/tools ,它有一些方便的命令用于测试用golang编写的项目。
  • 我们导入 CodeCov.io ,这是一个合作伙伴 orb,我们用它来报告我们项目的代码覆盖度量。
  • 最后是windows orb,这是配置 Windows 作业所需的第一方 orb。

这里的导入语法是经过特别选择的,以确保依赖关系的变化不会破坏我们的构建。orb 的每个发布版本都是不可变的,所以如果您导入一个具有特定版本的 orb,它将永远不会改变(除非您正在使用一个 dev orb )。

你可以选择放松这些保证——导入codecov/codecov@1.0将会导入最高版本的1.0.x,同样的,codecov/codecov@1将会导入最高版本的1.x.y

我们还支持一个名为volatile的特殊版本,它将始终导入已经发布的最高版本号的 orb。我们特意选择了单词volatile而不是latest,以便在配置文件中清楚地表明这是危险的,并且导入的 orb 可能会随着构建的不同而变化。

我们还写了一篇关于我们为 orbs 所做的设计选择的博客,你可能想看看。

实施者

下一部分是我们声明executors的地方,这是在配置文件中的一个地方声明执行环境的方法,以便在作业之间共享它。在配置版本 2.1 中添加了执行器,以解决我们在检查配置文件中的常见模式时看到的一些问题——YAML 锚在作业之间共享配置。我们从用户和自己的项目中看到的 YAML 锚最常见的用途之一是确保所有工作使用完全相同的 Docker 图像。

executors:
  default:
    docker:
      - image: circleci/golang:1.11 

通过使用executors块而不是 YAML 锚,我们的用户获得了更好的体验。执行程序声明的语法是在文件中声明执行程序的地方检查的,而不是在使用它的地方,因此修复错误更加简单。执行者也可以打包成 orb,在项目间共享。

命令

像 executors 一样,命令也被设计用来取代 CircleCI 中 YAML 锚的一种常见用法——在作业之间共享步骤。在 2.0 配置中,使用锚在作业之间共享单个步骤已经足够好了。问题在于共享多个步骤——YAML 锚没有拼接操作,所以不可能将一系列步骤插入另一个步骤;通过命令,您可以。

我们使用命令的一个简单方法是建立远程 Docker 连接,并确保在所有使用该命令的作业中使用相同版本的 Docker (18.09.3 ):

commands:
  remote_docker:
    steps:
      - setup_remote_docker:
          version: 18.09.3
      - run: docker version 

更高级的命令prep_for_docker_image_tests用于减少prod_smoke_teststest_e2e测试所需的一组步骤的重复;

 prep_for_docker_image_tests:
    steps:
      - run: mkdir /tmp/dockertag
      - attach_workspace:
          at: /tmp/dockertag
      - run:
          name: verify tag is present
          command: |
            if ! [ -f /tmp/dockertag/docker_image.tag ]; then
              echo "No docker tag found"
              echo "This is likely because the upstream job ran before the PR was created"
              echo ""
              echo "Re-run the workflow now that a PR exists to include the publish image step"
              exit 1
            fi
      - checkout
      - run: mv /tmp/dockertag/docker_image.tag . 

这个命令还有其他一些有趣的配置用法:

  • 我们使用工作区来访问上游作业的数据。在本例中,我们附加了一个工作空间来加载在docker_image任务中创建的 Docker 图像的标签。
  • verify tag is present步骤中,我们检测到一个问题(作业运行时没有 Docker 标签),我们产生一个详细的错误消息告诉用户发生了什么,如何修复它,最后我们调用exit 1使作业失败。如果发生这种情况,错误消息将在 CircleCI UI 中可见,并以红色突出显示,以便用户容易发现。
  • 我们既使用了run:的短格式,也使用了长格式。当命令比较简单时,我们使用简短的形式(例如mkdir /tmp/dockertag),当命令比较长时,我们给它一个描述性的名称来记录更高级别的操作(“验证标签是否存在”)。

乔布斯

我不会一一列举我们所有的工作,而是将注意力放在它们更有趣的方面。

验证 _ 生成

我们有一项工作是验证生成的代码是最新的。我的同行对我们如何安装这些依赖项并不感到特别自豪,但我保证会有缺点。

 verify_generated:
    executor: default
    steps:
      - checkout
      - go/mod-download
      - go/mod-tidy-check
      - run:
          name: install protobuf binaries
          command: |
            mkdir -p /tmp/protoc
            cd /tmp/protoc

            wget https://github.com/protocolbuffers/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip
            unzip protoc*.zip
            sudo mv bin/protoc /usr/local/bin/

            wget http://central.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.3.0/protoc-gen-grpc-java-1.3.0-linux-x86_64.exe
            sudo mv protoc-gen-grpc-java* /usr/local/bin/protoc-gen-grpc-java
            sudo chmod +x /usr/local/bin/protoc-gen-grpc-java

      - run: ./do generate-fakes
      - run: ./do generate-protos
      - run:
          name: Ensure that generated files are in sync
          command: git diff --exit-code 

项目中有一些生成的代码,这是为了生成 gRPC 互操作代码,并为测试生成一些模拟接口。我们生成代码,并将生成的代码提交给git。这里的作业在 CI 期间重新生成代码,如果在 CI 期间生成的代码与提交给 CI 的代码不同,那么构建将会失败。这允许我们在开发人员忘记提交生成的代码时中断构建。

测试窗口

 test_windows:
    executor:
      name: win/vs2019
      shell: bash --login -eo pipefail
    steps:
      - run: git config --global core.autocrlf false
      - checkout 

我们在 Windows 上构建和测试构建代理。为了使用我们现有代码库的 bash 架构,我们做了一系列小的配置调整,以确保我们的 Windows 构建能够顺利运行。Orbs 简化了新资源类的引入,因为它们允许我们在不需要大量开发工作的情况下提供配置调整。

首先,我们能够在八月份添加 Windows 支持,而无需在我们的 YAML 配置中添加任何新语法。我们能够使用 orbcircleci/windows实现 Windows,orb 暴露了将在 Windows Server 2019 上运行作业的executor。在引擎盖下,executor被扩展为一个常规的machine执行器,具有特定的imageresource_class字段,它将 shell 设置为powershell.exe

2017 年 11 月,我参与了 CircleCI 2.0 上 macOS 的发布。当时我们没有 orb,所以我们必须在工作声明中添加一个新的macos字段,以允许人们选择 macOS。这种改变需要上下改变堆栈,以使一堆服务意识到 config 中的这个新键。能够在不对配置文件格式进行任何更改的情况下启动 Windows 对我们内部来说是一个重要的里程碑,因为它验证了我们在 2018 年所做的大量内部更改和重新分解,使我们能够在服务中更好地分离关注点。

行尾

工作的第一步是当我们第一次开始在 Windows 上构建时,我为了解决一个问题而添加的,从那以后我们再也没有删除过。

git config --global core.autocrlf false 

我们有一些测试运行一组作业steps,然后将这些步骤的输出与包含预期输出的文件进行比较。我们用来比较字符串和预期输出文件的库在 Windows 上运行时有一个错误。通过将core.autocrlf设置为 false,我们避免了git在结账时将\n转换为\r\n的默认行为,这解决了这个问题。这解决了眼前的问题,我再也没有回去找到错误是什么。

贝壳

我们的 Windows 映像的默认 shell 是powershell.exe。我们还提供了cmd.exebash.exe,事实上,还有任何其他可以安装在映像上的 shell。为了测试build-agent,我们将 shell 设置为bash --login -eo pipefail,这与我们在 Linux 和 macOS 作业上运行的 shell 相同。我们安装的bash是 Windows 自带 gitbash版本。这允许我们像其他测试工作一样重用相同的脚本和命令。

测试 _mac

我们的 macOS 测试在一个简洁的配置中使用了大量的 CircleCI 特性。

 test_mac:
    macos:
      xcode: '10.3.0'
    steps:
      - checkout
      - run:
          name: Setup host
          command: ./scripts/ci/mac-setup
      - go/mod-download:
          prefix: v1-mac
          path: /Users/distiller/go/pkg/mod
      - run:
          name: Install GoLang devtools
          command: ./do install-devtools
      - go/mod-tidy-check
      - run:
          name: Test
          environment:
            GOTESTFLAGS: -coverprofile=coverage.txt
            GOTESTSUM_JUNITFILE: /tmp/test-reports/junit.xml
          command: |
            mkdir -p /tmp/test-reports
            ./do test-all
      - codecov/upload:
          flags: macos
      - store_test_reports 

macos stanza 本身非常简单:我们花了两周多的时间来设计它,并精心设计细节。将 CircleCI 1.0 与 CircleCI 2.0 进行对比,circle ci 1.0 为所需 xcode 的单一版本提供了空间,circle ci 2.0 增加了额外的嵌套级别:

 macos:
   xcode: '10.3.0' 

我们这样做是为了确保在macos键下有空间放置xcode旁边的其他键。这允许我们在未来增加配置语法,而不必进行重大更改。

接下来,我们很好地混合了不同类型的命令:

- checkout 

checkout命令是一个内置的步骤,用于从git中签出项目。

- run:
    name: Setup host
    command: ./scripts/ci/mac-setup 

下一步是运行./scripts/ci/mac-setuprun步骤。我们更喜欢将这样的脚本作为文件签入git,而不是在config.yml中保存多行 shell 命令。将脚本取出并放入它们自己的文件中,可以更容易地处理这些文件:

  • 语法突出显示在编辑器中工作正常
  • 文件内容从第 1 列开始,而不是出现在config.yml的第 13 列
  • 运行脚本很简单。我们大多数包含 shell 脚本的 repos 将使用shellcheckorb来自动检查 CI 中的错误。我强烈推荐你的项目。
- go/mod-download:
     prefix: v1-mac
     path: /Users/distiller/go/pkg/mod 

下一步,go/mod-download,是执行来自作为go: gotest/tools@0.0.10导入的gotest/tools orb 的命令。我们可以引用 orb 中带有前缀go/的任何命令。我们向命令传递两个参数,prefixpath。您可以使用我们的 CLI 工具来扩展命令,就像这样,使用circleci config process命令来查看它们编译成什么。在这种情况下,它扩展为以下内容:

 - run:
        name: Install git
        command: |
          command -v git && exit
          command -v apk && apk add --no-cache --no-progress git
    - restore_cache:
        name: Restore go module cache
        keys:
        - v1-mac-{{ arch }}-go-modules-
        - v1-mac-{{ arch }}-go-modules-{{ checksum "go.sum" }}
    - run:
        environment:
          GO111MODULE: 'on'
        command: go mod download
    - save_cache:
        name: Save go module cache
        key: v1-mac-{{ arch }}-go-modules-{{ checksum "go.sum" }}
        paths:
        - /Users/distiller/go/pkg/mod 

这里有趣的一点是,在我们的 macOS 构建上的用户是distiller,而在我们所有的 VMs 构建(Linux 和 Windows)、和我们的便利 Docker 映像中,用户总是circleci。原因是 CircleCI 上最初的 macOS(当时的 OS X)是由 2014 年加入 CircleCI 的 Distiller 团队实现的。为了使 Distiller 用户平稳过渡到 CircleCI 1.0 平台,我们将用户名保留为distiller,以匹配 Distiller 产品。三年后,当我们发布 CircleCI 2.0 的 macOS 版本时,我们将用户名保留为distiller,以便为我们的客户实现从 1.0 到 2.0 的平稳过渡。5 年后,我们在这里,用户名仍然是distiller

*下一步在run步骤中很好地使用了环境变量。在 config 中设置job级别的环境变量是很常见的,但是我发现也可以在特定的run步骤中设置它们,这种情况并不常见。在可能的情况下,我喜欢将选项提取到环境变量中,而不是将长参数列表提取到命令中。在我看来,它使配置更具声明性,更少程序性。

- run:
    name: Test
    environment:
      GOTESTFLAGS: -coverprofile=coverage.txt
      GOTESTSUM_JUNITFILE: /tmp/test-reports/junit.xml
    command: |
      mkdir -p /tmp/test-reports
      ./do test-all 

最后一步是呼叫我们的一个合作伙伴 orb:

- codecov/upload:
    flags: macos 

这个命令将把测试的代码覆盖结果上传到 CodeCov.io 。这里的flags参数允许 Codecode 合并多个测试报告。在构建期间,我们在三个平台上运行代码覆盖:

然后,我们使用标志macoswindowslinux上传所有三个测试运行的覆盖率数据。Codecov 能够将这三个报告合并成一个报告,为我们提供构建的整体代码覆盖度量。

这张图片显示了我们每月改变.circleci/config.yml的频率:image

产品 _ 烟雾 _ 测试

这项工作是我最喜欢的工作之一,我们在 CircleCI 的所有构建中都要做这项工作。

 prod_smoke_tests:
    docker:
      - image: circleci/python:3.6
    steps:
      - prep_for_docker_image_tests
      - run:
          name: install dependencies
          working_directory: e2e/canary
          command: |
            pipenv install --skip-lock
      - run:
          name: Trigger e2e smoke tests
          working_directory: e2e/canary
          command: |
            export CIRCLECI_BUILD_AGENT_IMAGE="$(< ../../docker_image.tag)"
            export CIRCLECI_API_TOKEN="${PICARD_DUMMY_API_TOKEN}"
            mkdir -p /tmp/test-reports
            time pipenv run pytest -n10 --junit-xml=/tmp/test-reports/results.xml ./tests.py
      - store_test_reports 

这里的 e2e(端到端)测试是使用pytest从 Python 中触发的,这允许我们收集测试元数据(以“JUnit”XML 格式)并直接在作业页面上报告测试失败,而不必通读作业输出。

测试本身在circleci.com上运行了七个生产版本,但是使用了这个版本中新标记的 Docker 映像,而不是生产中通常使用的版本。我们在 Windows、macOS 上运行构建,使用 Docker,使用 Linux VM,以及一些特定的功能和故障模式测试。所有这些测试都必须通过,工作流才能成功。

我们的test_e2e工作是类似的——它在build-agent和一些与之通信的上游服务之间运行一系列集成测试。

这意味着,我们只有在证明所生产的工件(新的build-agent)能够在所有平台上运行生产中的构建之后,才会将拉请求报告为绿色。当我部署时,这样的测试给了我很多信心。

下一步是什么

我们一直在努力改进我们的配置格式。我们目前在预览版中有我们的管道参数 API,它允许你用暴露给我们的配置处理系统的特定参数来触发项目执行。

我们希望在我们的想法页面上听到您关于配置 CircleCI 的想法和反馈。

感谢您的关注。要我以后再解释另一个配置文件吗?@ Twitter 上的我提名一个。*

根据 Forrester | CircleCI 的数据,CircleCI 实现了 664%的投资回报率和 1398 万美元的净现值

原文:https://circleci.com/blog/circleci-delivers-664-roi-and-13-98-million-npv-according-to-total-economic-impact-study/

今天,我们分享来自 CircleCI 的总体经济影响的发现,这是 Forrester Consulting 代表 CircleCI 进行的一项委托研究。

该研究显示,CircleCI 在三年内实现了 664%的投资回报率(ROI ),并强调我们的平台将开发人员的工作效率提高了 10%。这带来了超过 430 万美元的更高的效率值,使组织能够提高推动业务成功的工程速度。

要了解 CircleCI 为您的团队带来的投资回报,请访问 CircleCI 估算师。回答四个简单的问题会让你估计 CircleCI 对你公司的影响。

据 Forrester 称,企业组织正在努力更快地在客户面前获得创新的数字体验。他们需要高性能的软件开发团队和工具来帮助他们构建、测试和迭代,而不受笨重技术的干扰。

为了更好地了解投资 CircleCI 的收益、成本和风险,Forrester 采访了四位 CircleCI 客户,并将他们的经验和结果整合到一个单一的复合组织中。研究发现,总体而言,CircleCI 用户的软件开发速度有了显著提高。

研究发现的具体益处包括:

  • 端到端软件开发构建时间缩短了 50%。借助 CircleCI 的并行化和动态配置特性,组织可以将单个构建时间缩短 50%。在三年多的时间里,每年累积 80,000 到 120,000 个构建,对于被采访的组织来说,更短的软件开发周期价值 780 万美元。
  • 减少系统维护将开发人员的工作效率提高了 10%。使用 CircleCI 的团队不再需要维护他们的 CI/CD 系统,也不再需要冒着因 CI/CD 引擎更新而影响工作效率的风险。这意味着开发人员有更多的时间来承担交付商业价值的任务。在三年的时间里,这种开发人员生产力的提高为受访组织带来了超过 430 万美元的价值。
  • 基础设施成本降低 50%。将 CircleCI 的所有功能添加到其先前的环境中会使组织的年度基础架构支出增加 50%。使用 CircleCI 还可以让组织重新利用其 DevOps 专业人员来专注于更有成效的工作。总的来说,这在三年内为受访组织带来了近 220 万美元的收益。
  • 代码质量的提高将开发者的生产力提高了 10%到 20%。使用 CircleCI,开发人员可以节省他们修复 bug 和缺陷的时间。随着平台在过程的早期捕获坏代码,代码的质量得到了提高。在三年多的时间里,这种代码质量的改进对受访的组织来说价值接近 110 万美元。

在这项研究中,一位使用 CircleCI 的工程总监说:“CircleCI 帮助我们极大地适应了业务需求,它让我们的内部客户感到满意,这也间接让我们的外部客户感到满意(因为我们能够更快地交付高质量的产品)。”

下载研究报告了解更多关于使用 CircleCI 提高投资回报率、生产力和安全性的信息。

用有四个问题的 TEI 计算器估算你团队的 CircleCI ROI

CircleCI 文档由您创建:庆祝我们的开源贡献者

原文:https://circleci.com/blog/circleci-docs-are-built-by-you-celebrating-our-open-source-contributors/

在 CircleCI,我们已经注意到将文档开发作为一个开源项目的积极影响,因为每一个改进,无论大小,都会在同一天帮助成千上万的读者。CircleCI 文档是开源的,因为在开放环境中工作会产生更好的文档,并使更多的人能够有效地影响和更改文档。

感谢我们的贡献者

作为 CircleCI 的工程文档经理,我要感谢所有为 GitHub 上的 circleci-docs 库做出贡献的人。在上个月,33 位作者合并了对我们的公共文档报告的改进!对社区成员 stmcallister、smasuda、mikegee、skion、gfoidl 的拉取请求进行了特别的大声呼喊。

doc-insights.png

doc-traffic-blog.pngcircle ci 2.0 docs 每月唯一访问量

无论您是新开发人员还是经验丰富的开发人员,我们都非常感谢您对 docs 的贡献。投稿是给你的简历添加一些开源经验的好方法(对开发人员和技术作者很有用),也是通过分享你的专长和知识参与 CircleCI 的好方法。我们的 2.0 文档集每月有近 60,000 名独立访问者,因此,即使是一个小小的贡献或错别字修复也能帮助大量读者使用 CircleCI 开发更好的代码。你也会有好的伙伴!我们的内部贡献来自 CircleCI 的各个角落,包括销售、支持、工程、营销和产品管理。

显著的贡献

将文档翻译成日语

2018 年 12 月,我们收到了来自社区成员 Naturalclar 的第一份完整翻译,他将使用 CircleCI 状态徽章的的说明本地化为日语。我们热烈欢迎文档本地化人员加入我们的社区,并感谢为我们的日语开发人员提供优秀翻译的努力。我们的日本文献收藏馆每月已经有超过 1700 名访客。

jp-doc-traffic-blog.png 日文文档:每月唯一访问量

Orbs 发布流程更新

三月, danielcompton 贡献了文档更新和反馈来澄清 orb 发布的 CircleCI 文档。分享他的观点并建议一种新的方法来描述这个过程使这个文档更加清晰,他的贡献将帮助每个读者在未来更容易地遵循这些说明。

目录修复和错别字

二月份 jodastephen提出了一个 pull 请求,要求更新 Maven/Gradle 测试元数据的设置,并在文档中调出一些帮助我们更新文档的其他框架的信息。当月晚些时候, friederbluemlePR 中更新了 49 个文档文件,以提高商标术语和变量使用的一致性

语言指南改进

4 月份, stmcallister 更新了 Go 语言指南中的链接,使示例项目 repo 更容易在文档中找到,并且 smasuda 增加了一个选项,以防止在运行 Haskell 语言指南中的命令时耗尽内存。

归档详细问题

在上一个季度,我们还提交了几个详细的问题,帮助我们了解我们的文档中需要改进的地方。如果您不想提出拉动请求,这是一个很好的参与方式!只需进入新问题页面,选择您想要归档的文件类型,然后点击“开始”按钮,将信息提交给 CircleCI docs 团队。

doc-issue.png

例如, y-nk 使用 CircleCI CLI 文档提交了关于缺少卸载中 CLI 的信息的问题, Aghassi 提出了一个问题,帮助我们澄清了关于如何管理回购组织变更和 CircleCI 上下文的信息。

我想分享我们对所有这些人和更多帮助我们不断使 CircleCI docs 变得伟大的人的感激之情!

那么,你愿意为 CircleCI Docs 做贡献吗?以下是一些帮助您入门的信息:

为 CircleCI 文档供稿

欢迎所有人加入我们的社区!CircleCI 每月将 20 到 30 名贡献者的 PRs 合并到 docs repo 中,15%是 CircleCI docs 团队的技术作者,60%是 CircleCI 的工程师或产品经理,25%是开源贡献者。我们将始终优先合并您添加到我们文档中的内容,以帮助您之后使用这些文档的每一位读者!如果我们不能立即合并它,我们将得到一个技术审查员来检查和修改您建议的变化,所以我们可以合并您的公关。

如何参与

如果您想通过编写新的文档来学习一些东西,或者如果您想通过改进现有的说明来分享您的知识,欢迎您参与 CircleCI docs。我们努力创建世界级的 CI/CD 文档,这是通过整个 CircleCI 开发人员社区的贡献来实现的。我们还希望成为一个我们的开发人员有影响力的地方,可以帮助创建产品的这一部分,支持所有 CircleCI 用户,并帮助他们发现如何充分利用 CircleCI。

考虑到这一点,如果您需要灵感,下面的部分概述了我们希望与您在文档方面合作的一些领域!

为质量做出贡献

每个人都可以通过以下方式提高 CircleCI 文档的整体质量:

  • 向 YAML 示例添加注释,解释关键点并提供相关文档的 URL。
  • 将每个文档的链接添加到词汇表中,在词汇表中我们会提到 CircleCI 特有的术语。
  • 向术语表中添加新的术语和定义,使其更加完整。
  • 在内联文本中添加相关文档之间的链接。例如,只要我们提到“缓存”或“缓存”这个词,我们就应该将这个词链接到缓存文档。

为示例做贡献

在公共项目中使用 CircleCI 的任何人都可以向 CircleCI 文档提供示例:

  • 如果您有一个使用 CircleCI 的开源项目,可以考虑将它添加到我们的项目列表中,这样其他开发人员可以从您的配置中学习。
  • 考虑将您的配置片段的注释示例添加到我们关于特定主题的现有文档中。
  • 我们很喜欢你的配置中的示例工作流和 orb 的贡献,因为这些是相对较新的功能,但有这么多的排列,更多的例子将帮助用户创建他们自己的定制。

为 API 文档做贡献

如果您的应用程序有一个调用 CircleCI API 的用例,那么您可以用示例调用的细节来记录您的应用程序的用例。

为运营最佳实践做出贡献

如果您正在管理自己的 CircleCI 安装,您可以通过提出您的建议、请求和反馈来对 v2.16 的新安装操作文档提供反馈。

关于为文档做贡献的更多基础知识:

准备好扬名立万了吗? 探索文档

CircleCI 文档现在是开源的!-切尔莱西

原文:https://circleci.com/blog/circleci-docs-open-sourced/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


文档很重要。无论你是在记录代码、网站还是 API,清晰简洁的文档对开发者来说都是从你所提供的东西中获得全部价值的关键。文档通过提供逐步演练、示例代码和 API 响应来帮助初学者入门。它通过提供可靠的参考、节省时间的快捷方式和优化技巧来帮助退伍军人。我们想要双倍的价值。

专用 GitHub 存储库

许多公司在 GitHub 上有他们的文档。这使得文档可以在公开的环境下工作,并让社区帮助做出贡献。我们一直是这一运动的支持者,并自豪地宣布 CircleCI Docs 现在是开源的。你可以在 GitHub 上找到它们:https://github.com/circleci/circleci-docs

今天就开始

CircleCI 文档是用 Markdown ( GitHub 味 Markdown 特指)编写的,由 Jekyll 提供动力。这是一个非常常见和简单的设置。你可以在 README.md 中找到如何开始的说明,以及如何在 CONTRIBUTING.md 中添加和编辑文档的信息。

展望未来

我们希望 CircleCI 社区像我们一样成为 docs 的一部分。我们正在为编程语言(如 Go (Golang))添加更多信息和示例,为测试套件添加更多指南,为各种提供商添加更多部署示例,并改进我们当前的文档以增加更大的价值。我们正在寻求反馈。如果有你想看的指南或说明,请打开 GitHub 问题。我将参加Write Docs North America 2016,在那里我们可以讨论 CircleCI docs 以及一般的 Docs,以及如何为每个人改进它们。

CircleCI Enterprise“任意云”部署和 CircleCI for OS X - CircleCI

原文:https://circleci.com/blog/circleci-enterprise-any-cloud-deployment-and-circleci-for-os-x/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


今天,我们很高兴地宣布推出两种新产品,而不是一种!

2015 年的大部分时间,我们都在打造一个更好的移动平台。我们知道软件团队不只是在真空中构建 iOS,它通常是更广泛堆栈的一部分。软件团队希望在一个平台上构建他们所有的软件。在收购 Distiller 以帮助实现我们的移动愿景,并经过一年多的公开测试和有限发布后,CircleCI for OS X 现已正式发布。

当我们在 12 月宣布 CircleCI Enterprise 时,我们为大大小小的软件团队提供了一种安全、可扩展的方式来在防火墙后使用 CircleCI。今天,我们很自豪地宣布“任意云”部署的扩展产品,让团队能够灵活选择内部或基于云的配置。值得一提的是,CircleCI 企业用户也可以使用安全的 CircleCI OS X 舰队来运行他们的 I OS 版本。

CircleCI 企业

CircleCI Enterprise

CircleCI Enterprise 的核心是同一个持续集成和交付平台,受到全球成千上万开发人员的信赖。除了在防火墙后运行的固有安全性之外,CircleCI Enterprise 还为当今最具创新性和快节奏的软件团队树立了操作简便性和可扩展性的新标杆。

Manheim 生产工程总监 Jason Riggins 表示:“我们的团队使用不同的技术,需要一个能够根据我们的规模和复杂性进行扩展的 CI 平台,同时还能让我们的工程师直接配置他们的产品。“有了 CircleCI Enterprise,我们的 devops 团队可以花更少的时间处理那些会阻碍我们的工程师的繁忙工作,而将更多的时间用于提高工作效率。”

CircleCI Manheim Quote

CircleCI Enterprise 易于维护、易于更新,并且完全可脚本化,是为高吞吐量软件团队设计的。CircleCI Enterprise 将构建配置、提供构建容量和并行设置的能力直接推给开发人员,将开发运维团队从手动管理插件和服务器容量等复杂配置中解放出来。CircleCI Enterprise 自动与 GitHub 或 GitHub Enterprise 同步,即使是最复杂的团队也可以进行简单的设置。CircleCI Enterprise 集成了当今软件交付渠道中流行的开发工具和服务,包括 Docker,无需管理相互冲突的集中式插件。

立即开始:CircleCI Enterprise 提供 30 天免费试用。如果您想要一个更加定制化的实现,或者需要在您的防火墙后运行 CircleCI,请通过 CircleCI Enterprise 联系我们,或者发送电子邮件到【enterprise@circleci.com】T2,我们将迅速做出回应。

OS X 的 CircleCI

[ CircleCI for OS X ]

在当今的竞争格局中,应用程序开发速度至关重要,随着应用程序和系统变得越来越大、越来越复杂,保持这种速度非常困难。CircleCI for OS X 允许 iOS 开发人员为 iPhone、iPad、Mac、Apple TV 和 Apple Watch 应用程序构建和测试代码,大大缩短了开发周期。

CircleCI Shyp Mobile Quote

CircleCI 提供了比当今市场上任何其他产品更多的并发性,因此任何规模的团队都可以更好地构建和快速扩展。移动团队对效率有不同的需求,一些团队排在 web 测试之后,另一些团队一次部署,有限的并发性会降低他们的速度。灵活的定价计划提供不同的分钟容量,以便个人或组织只为他们需要的分钟付费。

开始,作为当前客户进入账户设置- >计划定价- >更新计划- >基于 OS X 构建

借助 CircleCI for OS X,软件团队可以:

  • 快速移动:无与伦比的并发选项、更智能的 Cocoapods 缓存和预装的关键依赖项带来了快速、实用和灵活的体验。

  • 放心部署:无缝代码设计以及 Crashlytics beta、HockeyApp 和 TestFairy 的开箱即用部署功能,意味着配置移动 CI/CD 基础设施的时间更少,而创建软件的时间更多。

  • 清晰、实时地了解构建数据: Insights 是一款交互式可视化仪表板,CircleCI for OS X 用户可以一目了然地实时了解所有构建。

  • 寻求一流支持: CircleCI 致力于为所有时区的用户提供最佳支持。更新的文档和一个用户论坛,Discuss CircleCI 使个人和团队能够专注于正确的解决方案,并重新开始构建优秀的应用程序。

我们才刚刚开始

有这么大的新闻,我们已经全力以赴了。我们要感谢过去一年在测试版和限量版中与我们合作的所有团队和个人。没有您的反馈和支持,我们不可能完成最终产品。我们将继续欢迎并鼓励您的反馈,您可以相信我们会根据您的反馈进行改进。无论是功能请求、错误报告,还是您只是想给我们一个指示,表明您喜欢我们的发展方向,我们真诚地期待您的反馈。和往常一样,你可以通过应用程序或发邮件给 sayhi@circleci.com 联系我们。

CircleCI orbs 提供了一种与 AWS 服务集成的简单方法

原文:https://circleci.com/blog/circleci-for-aws-deep-integration-security-and-flexibility/

希望将 AWS 服务与其 CI/CD 管道相集成的团队不应该为了建立一致的工作流而费尽周折。这正是我们创建各种 AWS orbs 的原因,这些 orb 为构建和测试代码、创建和推送工件以及将您的应用程序部署和更新到您的 AWS 帐户提供简单的开箱即用的解决方案。

为了向 AWS 用户提供一套全面的 orb,我们还专门为 AWS 的新无服务器应用程序模型(SAM)添加了一个新的 orb,这是 CI 管道和 AWS Lambda 之间的首次简化集成。现在,您可以使用我们的 orb 在 CI/CD 管道上自动测试更新的 Lambda 函数,然后再向用户发布,而不是手动操作您的系统来测试您的 Lambda 代码。

我们的 AWS orbs 套件提供了一个高效的解决方案,可以节省您自己设置集成的时间。例如,您需要编写大量的脚本来在大多数 CI 平台上构建和推送 Docker 映像。使用我们的 ECR orb(目前已有超过 1,100 家组织使用),您只需六行预打包的配置就可以实现同样的功能。看到如此多的团队从我们当前的 AWS 集成中受益,令人振奋。此外,我们还在不断扩展和增强我们的 AWS orbs,以支持最受欢迎的使用案例,并根据客户的要求集成其他 AWS 服务。

查看我们最新的 AWS orbs,帮助将 AWS 服务与您的 CI/CD 渠道集成:

使用这些 orb 部署到 AWS:

AWS-SAM-无服务器 -利用 AWS 无服务器应用程序模型,在 CircleCI 上构建、测试和部署您的 AWS SAM 无服务器应用程序。

ECR——构建图像,并将它们推送到亚马逊弹性容器注册中心(Amazon ECR)。

ECS -部署并更新亚马逊弹性容器服务(Amazon ECS)

EKS -为 Kubernetes(亚马逊 EKS)部署和更新亚马逊弹性容器服务。

CodeDeploy -将应用程序部署到 AWS CodeDeploy。

通过这些 orb 与 AWS 服务集成:

CLI -安装并配置 AWS 命令行界面(awscli)。

S3 -使用这套工具与亚马逊 S3 合作。要求:bash。

AWS 系统管理器参数存储库 -加载 AWS 系统管理器参数存储库密钥作为环境变量。

你能做什么

你还想用 AWS 做一些 orb 中没有的事情吗?orb 是开源的,所以在一个现有的 orb 上增加功能只是获得你的 PR 批准和合并的问题。查看 orbs 注册表中所有可用的 orbs。你是否有一个用例,你觉得它与当前的 AWS orbs 集不同?你可以自己创作一个并贡献给社区。我们甚至发布了为 orb 创建自动化构建、测试和部署管道的最佳实践(第 1 部分第 2 部分)来帮助您。

要将 CI/CD 集成到您的 AWS 基础设施中,请让您的团队利用第三方服务并消除内部开发的需要。有了 orb,您的团队只需要知道如何使用这些服务,而不需要知道如何集成或管理它们。开始用 AWS 球体在 CircleCI 上建造。

circle ci for Google Cloud Platform | circle ci 和 GCP 集成

原文:https://circleci.com/blog/circleci-for-google-cloud-platform-scalable-flexible-and-efficient/

为了最好地支持我们客户的个性化需求,CircleCI 为各种云提供商提供简单的集成解决方案。对于使用谷歌云平台(GCP)的客户,我们刚刚发布了一套集成,以帮助用户实现完全支持的端到端测试。我们在设计这些集成时考虑了速度、灵活性、安全性和可扩展性,以帮助支持具有各种用例的团队。

对于希望开始使用谷歌云的团队,我们有一套现成的集成,其启动时间比我们的竞争对手更快。有了这些 orb,您可以轻松地安装和配置 Google Cloud 命令行界面(CLI),部署到 Google Kubernetes 引擎,或者在 Google Container Registry 中存储图像——所有这些都只需要在配置中添加几行代码。

对于更复杂的用例,开发人员会发现自己受到集成新工具所需的时间投资的限制。对于新的架构来说尤其如此——比如 Google Cloud Run,它需要团队构建高级集成来设置自动化部署。orb 不是处理复杂的基础设施,而是通过为 Google Cloud Run 等工具提供完全支持的无服务器模型,并通过将 CI/CD 管道与 Google Binary Authorization 等现代技术轻松连接,帮助团队专注于他们最重要的工作。

查看我们最新的 GCP orbs,帮助将 GCP 服务与您的 CI/CD 渠道相集成:

谷歌云平台 orbs

谷歌云 CLI
安装并配置谷歌云 CLI (gcloud)

谷歌云 GKE
创建集群,构建并发布 Docker 映像,并将映像部署到谷歌 Kubernetes 引擎(GKE)集群。

谷歌云 GCR
在谷歌容器注册表(GCR)上构建、推送和标记图像。

Google Cloud Run
构建无状态映像并部署到 Google Cloud Run 作为无服务器应用程序。

GCP 二进制授权
配置 Google 的二进制授权服务来签名和认证容器映像以便部署。

您可以做什么:

你还想对 GCP 做些什么在球体上无法得到的事情吗?orb 是开源的,所以在一个现有的 orb 上增加功能只是获得你的 PR 批准和合并的问题。查看 orbs 注册表中所有可用的 orbs。你有没有一个用例让你觉得与当前的 GCP 球体集不同?你可以自己创作一个球体并将其贡献给社区。我们甚至发布了为 orb 创建自动化构建、测试和部署管道的最佳实践(第 1 部分第 2 部分)来帮助您。

开始用 GCP 圆球在 CircleCI 建造。

使用 Git 钩子自动决定跳过构建

原文:https://circleci.com/blog/circleci-hacks-automate-the-decision-to-skip-builds-using-a-git-hook/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


开发人员使用 CircleCI 构建各种项目。这些项目中的许多都遵循典型的一个项目一个回购的代码组织方法。有些包括文档、日志、供应商包、部署脚本等等。还有谷歌等公司使用的单边回购协议。这些回购带来的额外复杂性导致我们许多客户的工作流程不同。

![快进 _ 图标 - 宏 _ 摄影 _of_a_remote_control.jpg](/blog/media/快进 _ 图标- 宏 _ 摄影 _ of _ a _ remote _ control . jpg)

有时候,客户不希望 CircleCI 构建一个微不足道的提交。也许他们只是在自述文件中添加了注释,或者更新了日志文件。不构建可以为实际需要的提交节省容器时间。CircleCI 通过支持提交消息中的[ci skip][skip ci]标志来解决这个问题。如果经常需要这样做,每次在提交消息前加上[skip ci]会很快变得令人讨厌。这可以用 Git 钩子自动完成。

在这篇博文中,我们将使用commit-msg Git 钩子创建我们的自动构建跳过特性。

我们将使用什么

Git 挂钩

对于那些不熟悉的人,Git 有一个钩子系统。这些是时间点,在 Git 中可以运行脚本的特定操作之前或之后。例如,假设每次提交时,我们都希望确保使用最新的上游代码。在 Git 保存提交之前,可以利用 Git 挂钩来更新主服务器,并在主服务器上重置当前分支。

[跳过配置项]

如前所述,CircleCI 支持 CI 标准提交标志[skip ci]。当该标志出现在提交消息中时,CircleCI 不会运行构建。这可以节省运行的容器数量和使用的总构建时间。

使用. ciignore 文件

为了模仿 Git 对.gitignore文件的使用,我们将为这篇博文创建一个.ciignore文件的概念。我们正在创建的 Git 钩子将使用这个文件来知道在运行构建时应该忽略哪些文件、目录和模式

正在实施。回购中的 ci 忽略

本文中我们使用的场景是根目录中的一个.ciignore文件,它将告诉 Git 我们不想在 CircleCI 上构建,因为提交中唯一的更改是在logs/目录中。我们回购的根源可能是这样的:

$ ls -la
total 9
drwxrwxr-x 4 felicianotech felicianotech 4096 Jun 15 00:44 ./
drwxrwxr-x 4 felicianotech felicianotech 4096 Jun 15 00:44 ../
-rw-rw-r-- 1 felicianotech felicianotech    7 Jun 15 00:32 .ciignore
-rw-rw-r-- 1 felicianotech felicianotech 1077 Jun  1 20:29 circle.yml
-rw-rw-r-- 1 felicianotech felicianotech  135 Jun  1 20:23 Dockerfile
drwxrwxr-x 8 felicianotech felicianotech 4096 Jun 15 00:52 .git/
drwxrwxr-x 2 felicianotech felicianotech 4096 Jun 15 00:45 logs/
-rw-rw-r-- 1 felicianotech felicianotech   43 Apr 29 22:23 main.go
-rw-rw-r-- 1 felicianotech felicianotech   20 Apr  1 16:40 README.md
-rw-rw-r-- 1 felicianotech felicianotech   31 Jun 15 00:44 test.txt 

.ciignore文件放在回购的根目录中,而我们的 Git 钩子的文件路径将是.git/hooks/commit-msg。这些文件的内容可以通过下面的 GitHub Gists 看到。

。ci ignore——这个文件包含一个单一的模式,logs/*。我们希望跳过日志目录中的变更构建。

。这是我们的钩子,一个简单的 Bash 脚本。这里有一个快速分类:

  1. 脚本首先检查.ciignore是否存在。如果它不存在,那很酷。一切照旧。
  2. 然后,我们调用git diff来获取一个机器可读的文件列表,这些文件是暂存的(将在这次提交中被更改)。
  3. 我们为要忽略的模式列表加载.ciignore
  4. 我们循环遍历这些更改,每当它匹配一个忽略模式时就删除它。
  5. 如果我们仍然有更改,这意味着我们需要实际构建提交,这样我们就退出了。
  6. 如果我们到达这一步,我们将在提交消息(存储在文件$1 中)前面加上[skip ci],这样可以节省一些构建时间。

第 4 步和第 5 步很重要,因为即使在我们想要忽略的目录中发生了变化,如果主要的源代码也发生了变化,我们仍然想要构建。否则,我们可能会在一个场景中结束,在这个场景中,我们在同一次提交中添加了一个特性并更新了一个日志文件,而它永远不会被构建,这意味着该特性的推出现在被延迟了。

笔记

需要记住的一点是,钩子不会随着回购而移动。这意味着客户端钩子,我们在这里使用的,不在 Git 的版本控制之下,当一个回购被克隆或推送时,它们也不会被复制。为了绕过这一点,有些人在回购本身的目录中保留挂钩,然后在克隆回购后将它们符号链接到位。

使用 Git 钩子创建一个.ciignore文件只是如何利用 Git 钩子改进 CircleCI 日常工作流程的一个例子。其他的想法可以是使用 Git 钩子在提交时修改circle.yml,只运行特定的测试。

你用 Git 钩子和 CircleCI 吗?我们很想在讨论上听到它。

想加入才华横溢的 CircleCI 团队吗?我们正在招聘!

YAML - config.yml |循环

原文:https://circleci.com/blog/circleci-hacks-reuse-yaml-in-your-circleci-config-with-yaml/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


【2018 年 9 月更新: 写这篇博文是为了演示如何使用一个名为锚和别名的原生 YAML 特性让 CircleCI 配置文件变得更加干燥。现在 CircleCI 配置 v2.1 可以完成这个目标。来看看

工作流让许多人在他们的配置文件中获得了“工作快乐”。一项工作是“构建”,另一项是“测试”,还有一项是“部署”?也许,那还不算太坏。如果一个扇出、扇入的工作流总共有 5 个作业,每个作业都使用相同的 Docker 图像,会怎么样?在那个 config.yml 文件中会有冗余。我们可以减少它,让 YAML 为我们工作。

今天,我们将介绍 YAML 1.2 版规范中一个更酷、更鲜为人知的特性,主播&别名。我们将使用它们来减少对以下示例 CircleCI config.yml 文件的重复编辑:

# .circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths:src/public/
  html-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With HTML Proofer"
          command: echo "This is where we'd do testing with HTML Proofer."
  ui-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With Selenium or Behat"
          command: echo "This is where we'd run a headless browser and test our site's UI."
  process-testing:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test the commit for org or team processes"
          command: |
            echo "This is where we'd test this commit for things our project wants to enforce"
            echo " such as code formatting, the signing of a CLA, security requirements, etc."
  deploy:
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Deploy Our Docs Site"
          command: echo "This is where we'd deploy using rsync, awscli, etc."
# Below would be the Workflows specifc config, which we're leaving off for brevity (this example as already lengthy. 

YAML 锚&别名

YAML 允许将一个节点声明为锚点。这意味着该节点将在 YAML 中的某个地方被引用。在上面的配置示例中,您将看到每个作业声明的开头都是为每个作业重复的。具体来说,我们在配置文件中重复以下行 5 次:

...
    docker:
      - image: felicianotech/docker-hugo:0.27.1
    working_directory: ~/project
... 

相反,我们可以在.circleci/config.yml的开头使用一个定位符,将这些行设置为作业的默认行:

defaults: &defaults
  docker:
    - image: felicianotech/docker-hugo:0.27.1
  working_directory: ~/project
... 

然后我们用一个别名来写更少的行。这里有一个使用前面的build作业的例子:

...
  build:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths:src/public/ 

结果呢

如果我们在整个配置文件中重复这个过程,我们会从最初例子中的 56 行配置变成 47 行。

也许,在这个例子中,减少 9 行并不值得称道,但是我们不仅仅减少了行数:我们已经使维护 YAML 文件变得更加容易。如果我们使用的 Docker 图像用一个新的标签更新,比如说felicianotech/docker-hugo:0.28,我们可以在配置的顶部更新图像标签一次,然后就完成了。此工作流中的 5 个作业现在都将使用更新的映像,只有一处更改。

你可以在 CircleCI 讨论中继续关于 YAML 主播和别名的讨论。更多高级 YAML 用法的例子可以在维基百科上找到。您可以在下面看到修改后的完整配置示例。


我们使用 YAML 锚点和别名的示例配置文件:

# .circleci/config.yml
defaults: &defaults
  docker:
    - image: felicianotech/docker-hugo:0.27.1
  working_directory: ~/project

version: 2
jobs:
  build:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: "Build Our Statically Generated Docs Website with Hugo"
          command: HUGO_ENV=production hugo -v -s src/
      - persist_to_workspace:
          root: /root/project
          paths: src/public/
  html-testing:
    <<: *defaults
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With HTML Proofer"
          command: echo "This is where we'd do testing with HTML Proofer."
  ui-testing:
    <<: *defaults
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test With Selenium or Behat"
          command: echo "This is where we'd run a headless browser and test our site's UI."
  process-testing:
    <<: *defaults
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Test the commit for org or team processes"
          command: |
            echo "This is where we'd test this commit for things our project wants to enforce"
            echo " such as code formatting, the signing of a CLA, security requirements, etc."
  deploy:
    <<: *defaults
    steps:
      - attach_workspace:
          at: /root/project
      - run:
          name: "Deploy Our Docs Site"
          command: echo "This is where we'd deploy using rsync, awscli, etc."
# Below would be the Workflows specific config, which we're leaving off for brevity (this example as already lengthy. 

CircleCI Hacks:在每次提交时用 Git 钩子验证 CircleCI 配置

原文:https://circleci.com/blog/circleci-hacks-validate-circleci-config-on-every-commit-with-a-git-hook/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


CircleCI 2.0 开创了持续集成和交付的新时代。在 2.0 的新特性中(包括新的配置文件格式,对 Docker 映像和工作流的强调)有了通过 CircleCI CLI 本地构建的能力。我们可以使用本地构建和 Git 在每次提交时轻松地验证我们的配置文件。

CircleCI CLI 非常适合通过本地构建来解决问题构建,并作为运行支持 SSH 的构建的替代方法。

这篇文章的重点是 CLI 的验证功能。

问题是

你有没有提交一些修改,把它们上传到 GitHub,然后在失败前运行 CircleCI 构建 3 秒钟?这可能是由于一些简单的原因,比如在第 7 行隐藏了一个分散的制表符,而不是正常的 2 个空格的缩进。

当你的配置文件中有几个这样的小错误时,可能会有一个持续几分钟的“修复、提交、推送、失败、重新开始”的循环。当处理代码中的重要特性时,这可能是可以接受的,但对于配置语法来说就不是了。

本地构建可以用一行代码来验证您的配置:circleci config validate -c .circleci/config.yml。有了快速 Git 挂钩,我们甚至可以实现自动化。

解决方案

我们将在 git pre-commit钩子中运行validate命令,以确保我们的配置文件按预期工作(通常是.circleci/config.yml)。

  1. 首先,确保在本地机器上安装了构建工具。在 CircleCI 文档中有相关说明。
  2. 接下来,您需要在 repo 的.git目录中创建以下文件来创建 Git 挂钩。这可以用:vim .git/hooks/pre-commit来完成。如果你没有使用vim,用nano或者任何你喜欢使用的编辑器替换。
  3. 对自动化系统的自动化部分自我感觉良好。

Git 挂钩:

#!/usr/bin/env bash

# The following line is needed by the CircleCI Local Build Tool (due to Docker interactivity)
exec < /dev/tty

# If validation fails, tell Git to stop and provide error message. Otherwise, continue.
if ! eMSG=$(circleci config validate -c .circleci/config.yml); then
	echo "CircleCI Configuration Failed Validation."
	echo $eMSG
	exit 1
fi 

仅此而已。一旦 git hook Bash 脚本被正确地放置在存储库中,CLI 中的构建工具将在每次提交之前验证您的配置。如果有效,您的提交将照常进行。否则,您将看到错误消息,提交将被取消。

更多信息

CircleCI 推出对 AWS GovCloud 的支持

原文:https://circleci.com/blog/circleci-launches-support-for-aws-govcloud/

我们最近发布了 CircleCI server v2.19,其中包括 AWS GovCloud 支持以及其他新的升级和性能增强。在 AWS 上运行 v2.19 的服务器安装现在配置为在 GovCloud 上工作,帮助客户满足各种监管要求,如 FedRAMP、ITAR、国防部 SRG、CJIS 和 HIPAA。现在,政府的开发者社区将能够在世界上最安全的环境中轻松快速地移动。

GovCloud 是专为美国政府机构和其他监管行业(如医疗保健、金融、司法、公共安全和能源)打造的 AWS 服务。借助 AWS GovCloud,用户可以获得所需的控制力和信心,利用最灵活、最安全的云计算环境安全地运营业务。对 GovCloud 的支持紧随 CircleCI 的 SOC 2 Type II 合规性之后,在获得 FedRAMP 认证18 个月后,成为第一个也是唯一一个获得认证的持续集成 (CI)解决方案。

CircleCI 的目标是通过为客户提供最强大的 CI 工具来帮助他们推动创新。不幸的是,为政府机构构建产品和服务的开发人员被迫依赖 CI 选项,这限制了他们的生产力,而且维护成本通常很高。

CircleCI 服务器提供了在私有基础设施的防火墙后运行 CircleCI 的访问。server v2.19 中的其他升级和性能增强包括:

  • 升级的 Nomad 实例:我们已经将我们的 Nomad 集群从 0.5x 升级到 0.9,减少了死作业的累积,提高了服务器客户的性能。在升级到服务器 2.19 版之前,您的 Nomad 启动配置必须按照本指南进行更新。如果您已经将 Nomad 外部化,请在升级前联系您的支持工程师。
  • 使用默认资源类增强控制和灵活性:用户现在可以设置和编辑默认资源类,为开发人员提供用于其配置作业的 CPU/RAM 选项。有关更多信息,请参见我们的在服务器 v2.19 中定制资源类的指南。

推出对吉拉软件的支持

原文:https://circleci.com/blog/circleci-launches-support-for-jira-software/

您如何确保团队中的每个人都知道正在构建什么、状态是什么以及下一步是什么?让产品、工程、设计、营销等部门的每个人都保持协调和最新可能是一项复杂但至关重要的任务。

Atlassian 的吉拉软件是敏捷团队用来解决这个问题的主要工具。吉拉软件通过允许用户将复杂的软件项目组织成可消化的、可操作的任务,使得管理这些项目变得更加透明和可理解。现在,吉拉软件通过直接在吉拉软件 UI 中报告 CircleCI 的工作和部署状态,为软件交付团队提供了更多的价值。

Jira_UI.png

为吉拉软件启用 CircleCI

CircleCI for 吉拉使用 CircleCI orbs 可以根据作业状态轻松分配新任务和修复。orb 是可重用的开放式配置包,允许您将外部工具(如吉拉)集成到您的工作流程中。首先,你需要将吉拉 orb 添加到你的 config.yml 中,并在你的吉拉实例上安装 CircleCI for 吉拉应用

以下是入门的详细说明:

  1. 在此安装 CircleCI for 吉拉应用

  2. 在你的.circleci/config.yml文件的顶部使用 CircleCI 版。

  3. 在您的版本下面添加 orbs 节,调用 orb:

     orbs:
         jira: circleci/jira@1.0.2 
    
  4. 在现有作业中使用jira/notify命令向吉拉开发面板发送状态。

注意: 如果你还没有打开启用管道,你需要进入项目设置- >高级设置并打开它。

Jira_orb.png

从 CircleCI 创建吉拉问题

除了将您的作业状态报告给吉拉软件之外,您还可以直接从 CircleCI UI 中的作业页面创建新的吉拉问题。只需点击工作页面右上角的吉拉问题图标,编辑详细信息,然后点击“创建 Jira 问题”

Create_IssueJira.jpg

工具之间更强的联系,形成更强的团队

您的 CI/CD 管道是关于代码如何从想法到交付的最新和最全面的真实来源。将 CircleCI 连接到吉拉软件会将这些见解带给您团队中的每个人。现在,产品经理将能够更好地估计新功能的构建速度,或者看到团队可能在哪里陷入困境。工程经理可以了解工作是否在不同的团队之间传递。设计可以更好地估计何时需要新的模拟或营销创意。将这些数据公之于众意味着每个人都可以做出更好的决策,更流畅地工作以实现您的目标。

在 CircleCI,我们相信一个互联的、可扩展的开发者生态系统是最能为我们的用户服务的。与 Atlassian 合作使这种吉拉软件集成成为可能,这只是您将看到的有助于整合您的工具的许多步骤之一,以帮助您的团队更快、更自信地前进。

矩阵作业-复杂软件的简单配置| CircleCI

原文:https://circleci.com/blog/circleci-matrix-jobs/

CircleCI 具有多种特性,这些特性使其可配置、灵活且高效。其中一些是球体可重用配置管道变量,以及一个 UI ,使查看状态和修复构建比以往任何时候都更有效。矩阵作业是效率工作台的另一个工具。

什么是矩阵作业?

矩阵作业是开发人员为复杂软件编写简单配置的秘密武器。只需几行配置,您就可以从单个测试进入更全面的测试套件,或者从一个任务进入并行化的相关工作。CircleCI 内置了对矩阵的支持,作为我们工作流语法的一部分。我们的矩阵实现与我们的参数化作业协同工作,以服务于许多用例。

假设我正在开发一个广泛使用的开源 JavaScript 库。在构建和发布新版本的库之前,我想在几个不同版本的 Node.js (10 到 13)上测试我的代码,并确认它在 Linux 和 macOS 上都可以工作。这意味着我有一个二维案例“矩阵”:

那么在 CircleCI 我们如何表达这个?首先,我们将定义两个执行器,代表我们想要测试的操作系统。然后,我们必须创建一个名为 test 的参数化作业,它的参数与矩阵的维数相匹配。

version: 2.1

executors:
  linux:
    docker:
      - image: cimg/base:2020.01
  macos:
    macos:
      xcode: 11.4

orbs:
  node: circleci/node@2.0.0

jobs:
  test:
    parameters:
      os:
        type: executor
      node-version:
        type: string
    executor: << parameters.os >>
    steps:
      - checkout
      - node/install:
          node-version: << parameters.node-version >>
          install-yarn: true
      - run: yarn test 

然后,我们将创建一个使用矩阵调用此作业的工作流:

workflows:
  all-tests:
    jobs:
      - test:
          matrix:
            parameters:
              os: [linux, macos]
              node-version: ["10.9.0", "11.9.0", "12.9.1", "13.9.0"] 

推送此配置并检查我的 pipeline 结果显示,我已经创建了一个包含 8 个作业的工作流,涵盖了所有基础:

如需了解设置和运行 matrix 作业的更多信息,请观看此视频。

https://www.youtube.com/embed/K7VqfPTLwzQ

视频

所有 CircleCI 用户均可使用矩阵作业。这个特性非常强大,因为它可以与任何作业参数组合一起工作,为每个单独的作业生成人类可读的名称。进行跨平台测试的方法并不局限于有限的几种,比如只使用 Node.js 版本和操作系统。它可以是任何可表示为作业参数的东西。这些文档包括更高级的功能,如排除特定的组合和使用模板依赖来创建复杂的工作流。

要了解这篇文章中提到的其他特性,请访问以下链接:

CircleCI 导师与 Maven Youth - CircleCI

原文:https://circleci.com/blog/circleci-mentors-with-maven-youth/

在过去的一周里,CircleCI 团队的一些人很荣幸地与湾区非营利组织 Maven Youth 合作,作为他们 Maven Youth Camp 的一部分,他们参加了一个充满乐趣和技能分享的上午。我们选择与 Maven Youth 合作,是因为该组织正在为技术领域的酷儿青年迈出大步,以及我们在 CircleCI 建立自己的内部 LGBTQ+ 员工资源小组 (ERG)时,希望在更大的社区中做有影响力的工作。

Maven 青年营的特色是每天实地考察当地的技术组织和合作伙伴,以及来自众多顶级技术公司(Autodesk、网飞等)的嘉宾和志愿者。我们的招募协调员戴维·洛佩斯一直在与该组织的创始人兼首席执行官莫尼卡·阿伦比德联系,询问我们如何才能在这个项目期间最好地支持这些年轻人。通过交谈,他们得出结论,年轻人真正受益的一个项目是关于简历制作和面试的研讨会。我们每个人都记得我们职业生涯的开端,以及写第一份简历是多么令人生畏,所以我们很高兴能作为更老练的技术专业人士来分享我们的观点。

在我们与年轻人相处的时间里,我们进行了一些很棒的对话,内容包括如何撰写一份简历,让你展现出自己最好的一面,但仍然感觉像你自己,与招聘人员交谈的技巧和应该问什么问题,以及如何知道一家公司是否支持你的价值观。我们团队的一些建议包括:

  • 是否将简历写作视为一种创造性的努力:包括突出你经历中最重要、最有意义和最令人印象深刻的方面,无论是学校经历、兼职工作还是个人项目。

  • 你的研究!确保你了解该产品的用途,谁使用该产品,以及该产品与竞争对手的不同之处。Youtube 是一个很好的资源!

  • 准时参加你的面试(Maven Youth 的创始人 Monica 分享了一个提前两小时参加第一次面试的故事!).眼神交流,握手,感谢面试官和你交谈。

  • 是否准备好电梯间推介。知道并能够清楚地表达你在那里的目的,为什么这对你很重要,以及你如何为公司的使命服务。

*** 准备好回答典型的面试问题,比如“你最大的缺点是什么?”(根据我们团队的经验,这不是一个恶作剧问题,而是一个展示你对自己弱点的自我认识以及如何利用自己的优势来克服它们的机会。)

*   不要羞于问公司的价值观是什么。面试候选人的人应该能告诉你。如果他们不知道自己的价值观,那可能不是你想去的地方。

*   **不要忘记**面试你的人也是人:你可以真实,可以的话尽量放松,做你自己!** 

我们非常感谢 Monica、Maven 青年营营员和青年领袖邀请我们,并期待很快与他们再次合作!你可以在他们的网站了解更多关于 Maven Youth 的信息,在推特上关注他们的冒险,探索导师机会

新的和扩展的特性

原文:https://circleci.com/blog/circleci-new-and-expanded-features/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


CircleCI 为 2016 年开了一个好头,我们与 CircleCI for OSX 和 CircleCI Enterprise 推出并扩展了我们的产品。我们的团队在过去的这个季度自豪地发布了新产品,但我们也一直在努力发布新功能、升级和错误修复,这些都是我们的用户引起我们注意的

New and Expanded Features

这个季度博客将花一些时间来强调我们引以为豪的功能。如果你等不及再等三个月的功能回顾,请在我们的变更日志上实时更新。如果你有功能请求或者想要报告一个错误,你可以在讨论上这样做。我们很高兴收到您的来信!

这是最新消息

一级造影机支架

自从我们的标记构建 API 发布以来,我们现在能够支持 Phabricator 一级。要了解更多关于如何用 Circle 设置 Phabricator 的信息,你可以点击这里。这个特性请求部分是由 Phabricator 集成在我们的讨论论坛上的受欢迎程度驱动的。

选择“执行环境”的能力

现在你可以选择使用哪个 Ubuntu 映像来构建你的项目。我们在“项目设置”下添加了一个名为“执行环境”的新部分。您可以在 Ubuntu 12.04 或 Ubuntu 14.04 图像之间进行选择。如果您正在构建 OS X 项目,您可以在“执行环境”设置页面上的“构建 OS X 项目”部分下启用它。

每个项目的见解

您现在可以查看每个回购构建状态和构建性能。点击了解更多信息

API 检索特定分支的最新构建工件

我们添加了功能来检索给定项目分支上最近完成的、成功的或失败的构建工件。点击了解更多信息。这也部分是因为它在我们的讨论论坛上的受欢迎程度。

标签构建 API

我们现在支持带注释的和轻量级的标签。现在可以在 POST 请求的 JSON 主体中提供“tag”作为键。点击阅读更多

标记构建现在支持构建参数

现在,您甚至可以对标记的构建使用 build_parameters。这使得它与其他构建触发 API 保持一致。点击阅读更多

支持[跳过配置项]或[跳过配置项]

您现在可以使用“[skip ci]”和“[ci skip]”,因为我们已经开始支持这两种功能。点击阅读更多

Python 符号链接行为改变

我们已经更新了我们对 Python 虚拟环境的处理。之前,我们在您的项目根目录中创建了一个指向系统“venv”目录的符号链接。对于 2016 年 3 月 11 日之后创建的项目,我们将不再这样做。如果您需要覆盖每个项目或每个组织的新默认值,请发电子邮件至 support@circleci.com联系我们。

仅管理员权限

我们添加了逻辑来限制任何非管理 Github 用户修改项目设置。您可以为您的组织或组织内的特定项目启用此功能。GitHub 成员仍然可以使用诸如重建、通过 SSH 重建和无缓存重建等功能。如果你想启用这个功能,请联系 support@circleci.com。

新的环境变量

我们添加了两个新的环境变量,CIRCLE_REPOSITORY_URLCIRCLE_BUILD_URL。这些变量在您的构建中是可用的,并且可以轻松访问您在 GitHub 上的 repo 的 URL 和在 CircleCI 上的构建的 URL。这对于向自动化部署添加元数据非常方便。你可以在我们的文档的环境变量一节中读到更多关于我们的环境变量的内容。

测试摘要现在显示 Junit 格式 XML 解析错误

我们现在在“Test Summary”选项卡下显示 JUnit 格式的 XML 输出解析错误。如果构建生成了一个无效的 Junit 格式的 XML,我们将显示一个带有文件名和错误发生的行号的警告。

浏览器无响应问题

我们已经修复了许多导致构建页面时浏览器无响应的问题。我们目前正在显示控制台输出的前 400,000 行,并为您提供下载完整日志的能力。

Maven 依赖缓存

Maven 构建现在在依赖阶段使用dependency:go-offline而不是dependency:resolve,所以它们应该在运行测试之前将所有依赖保存到缓存中

我们希望你喜欢 CircleCI 的所有新功能!如果有你想看的功能,在上添加一个请求,讨论,如果你想加入我们的测试程序,内圈,你将在我们的最新功能公开之前尝试它们。如果您有任何问题要问我们,我们希望在 support@circleci.com听到您的回答。

切尔莱西欢迎我们最新的 ERG,切尔莱西

原文:https://circleci.com/blog/circleci-newest-erg-circleshei/

CircleCI 全体员工祝国际妇女节和妇女历史月快乐!我们最近推出了员工资源组(ERGs)框架,以服务于我们组织内丰富多样的利益。今天,我们想分享我们最新的 ERG,CircleSHEi,告诉你这个小组是如何走到一起的,并分享一些本周我们一起参加的动员会的笔记。

CircleCI 多元化计划的历史

在过去的两年里,CircleCI 运营着一个归属与包容小组,该小组每周召开一次会议,讨论意识主题,并提供一个空间来推动与技术领域代表性不足的团体的合作伙伴关系,举办活动并将其成员与 CircleCI 联系起来。在这项初步工作的基础上,通过 CircleCI 招聘和人力资源团队的支持者,我们的第一个员工资源小组(ERG)queer sphere 于去年年底成立。其章程是培养、倡导和发展酷儿 CircleCI 员工。作为第一个 ERG,QueerSphere 组织了一个正式的 ERG 框架,并为更多 ERG 铺平了道路——计划举办活动,提供培训,组织工作以促进我们工作场所的包容性,并与专注于提高技术领域边缘化群体代表性的组织建立合作伙伴关系。

本周,我们为最新的 ERG 举行了第一次启动活动:CircleSHEi。这个由旧金山 CircleCI 女性和盟友组成的团体,有来自美国和加拿大各地的远程参与者,共有 26 名参与者,他们花了一个小时一起吃早餐,每个成员都讲述了自己的职业生涯故事。这是一个很好的方式来了解整个团队,了解我们是谁,并在整个公司建立新的联系。我们引发了很多持续一整天的对话;这是一个激动人心的开始!

Circle[SHE]i Women & Allies breakfast

职业道路故事和主题

CircleSHEi 动员会的参与者来自公司的各个领域:工程、产品、财务、收入、营销、解决方案工程,以及来自 CircleCI 管理团队的四名女性。每个参与者讲述的职业道路故事帮助我们互相了解,看到互相帮助的机会,并通过共同的线索将我们联系在一起。我们故事的一些主题包括在一个角色中筋疲力尽,以及利用关系做出积极的改变。另一个主题是在工作中展现或分享你的情绪。出现的第三个主题是关于与女性和盟友建立个人联系,这改变了你的职业道路,带你进入一个新的行业或以新的活力回到你以前工作过的行业。CircleSHEi 集团致力于在充满挑战的时期相互支持,帮助彼此找到下一个职业发展方向或支点,以避免或减轻倦怠,并在我们可能并不总是感觉强大的领域找到优势。

CircleSHEi 使命陈述和宗旨

通过支持妇女和盟友的声音、提供培训、促进包容、创建接入点以及提供知识共享和指导平台,为她们建立积极的文化。

我们特别希望通过以下方式实现我们的使命:

  • 支持积极的女性工作文化,帮助女性员工取得成功,赋予女性发展职业成长道路的权力,并使女性能够获得或给予指导。

  • 通过包容性的工作场所支持女性的声音,提供倾听、偏见、采访和沟通礼仪方面的培训,让每个人都能安心工作。

  • 通过事件和意识促进包容,为女性开发新技能创造访问点,为女性提供分享故事和行业知识的平台,并成为通过行动解决女性技术问题的场所。

集团的宗旨与我们的共同价值观紧密相连:

  • 倾听:我们重视在公司听到不同的声音
  • 社区:我们鼓励多元化的社区
  • 乐于助人:尽管我们有分歧,但我们一起工作
  • 理解:我们尊重彼此以及我们独特的背景和经历,我们以积极的态度对待彼此

欢迎所有人

CircleSHEi 对所有性别开放,包括各种背景的人。有些人刚从大学毕业,有些人有几十年的行业经验。我们有婴儿和幼儿的父母。有些人在男性主导的行业和团队中有多年的经验,以及从时装设计到银行的多样化专业背景。非常感谢 CircleCI 的所有女性和女性联盟,感谢我们的执行赞助商帮助推出 CircleSHEi,并投入精力发展我们包容和欢迎的文化。

下一步是什么

我们今年第一季度的目标是与 QueerSphere 组织一次联合志愿者或慈善活动,通过回馈我们的社区来继续女性历史月庆祝活动。

介绍私人球体| CircleCI

原文:https://circleci.com/blog/circleci-private-orbs/

在你的组织中私有共享配置:私有 orb 现在在 CircleCI 上

两年前,CircleCI 推出了开源的 orbs。有了这些 orb,全球各地的开发人员已经能够简化他们的整个开发过程,并缩短他们的生产时间。在此期间,已经创建了 2,000 多个 orb,每月有 10,000 多个独特的组织在使用它们,它们已经集成到近 1,800 万个 CI/CD 管道中。

不熟悉宝珠?orb 是 YAML 配置的可重用包,帮助开发人员自动化重复的过程,加快项目设置,并使其易于与第三方工具集成。

开发人员正在利用我们的公共注册表上可用的各种 orb 来帮助自动化否则将是手动的、耗时的、重复的过程。例如,下面是一些我们最常用的 orb:

  • Slack orb 为开发人员的 CI/CD 管道实现基于事件的通知
  • CircleCI 合作伙伴 Codecov 的 orb 提供跨应用的测试覆盖
  • 社区开发的 React Native orb 构建并测试 React Native 项目

CircleCI 的 orbs 的下一步是什么?

我们的许多客户已经要求 CircleCI 的团队为他们提供一种在多个项目之间私有共享配置的方法,这是他们组织独有的。我们听了,现在,我们很兴奋地宣布我们发布了私人球体

这些组织通常有许多他们必须管理的回购,因此标准化配置并能够在项目间私下共享它是一个重要的优先事项。所有 CircleCI 计划为您的组织提供无限数量的私人球体。

CircleCI 私人宝珠在哪里有用?

私有 orb 为开发人员提供了更多的隐私、效率和跨团队的协作。这对于在医疗保健、金融和其他具有高治理和法规遵从性标准的行业中工作的团队尤其有用。

CircleCI 私人球体如何工作

私有 orb 不会出现在公共注册表中,只有您组织内的人员可以访问它们。具有读或写权限的用户只能查看和管理私有 orb,前提是他们在您的组织中经过身份验证。

通过查看 CircleCI 的“组织设置”选项卡中的专用“orb”页面,经过身份验证的用户可以快速轻松地查找和管理您的组织创建的所有现有 orb。管理员和 orb 作者可以直接从这个页面快速重复或更新有用的配置。

如何创作一个私人球体

要创建新的私有 orb,请使用 CircleCI 本地 CLI 工具。管理员可以完全控制他们的团队可以使用哪些 orb,因为只有管理员有权使用 orb 开发工具包手动 orb 创作流程创建、发布和更新私有 orb。

看看下面的视频,了解如何创作一个私人球体:

获得最佳 CI/CD 体验

除了私有 orb 和 Runner, Scale plan 还包括同时运行多个构建的无限并发性,以及用于执行计算密集型应用的强大资源。这些功能使您的团队能够为您的最终用户构建令人难以置信的应用程序体验。

我们付费计划的管理员现在可以创建、发布和维护他们的私人球体。关于如何在组织内的多个项目间开始私有共享配置的更多细节,请参考我们的 orbs 文档

要了解更多关于私有 orb 如何提高隐私、效率和组织内部共享的信息,请联系我们的客户成功团队或阅读我们的讨论帖子

CircleCI 在 Segment - CircleCI 上的开源 Clojure 库

原文:https://circleci.com/blog/circleci-s-open-source-clojure-library-on-segment/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


只是一个简短的帖子来说明我们在分部的好朋友已经更新了他们的文档,所以 CircleCI 的 analytics-clj 库现在是他们的官方 Clojure 库。

来自细分市场:

clojure 库允许您记录来自 clojure 代码的分析数据。请求到达我们的服务器,然后我们将您的数据发送到您在集成页面上启用的任何分析服务。

该库是开源的,由非常棒的 CircleCI 提供,谢谢!你可以在 Github 上查看。clojure 库是我们 Java 库的包装器。

CircleCI 的 analytics-clj 库包装了 Java 2.0 库。这使得用户可以利用 Java 2.0 相对于 Java 1.0 的优势(例如发送特定于集成的数据),Java 1.0 由官方的 Clojure 库包装。

提醒一下,CircleCI 每月提供 4 个免费的 Linux 容器给开源项目。我们将 OS 项目定义为任何公共 GitHub repo。

资源

分析-clj 图书馆回购:https://github.com/circleci/analytics-clj

分部的完整文件:https://segment.com/docs/sources/server/clojure/

切尔莱西仍然安全;警惕并意识到针对您的凭据的网络钓鱼企图

原文:https://circleci.com/blog/circleci-security-update/

我们最近看到越来越多的网络钓鱼企图,其中未经授权的行为者冒充 CircleCI 来访问我们用户在 GitHub 上的代码库。CircleCI 的任何系统都没有受到损害,我们客户的数据和信息仍然安全。客户数据的隐私和安全对 CircleCI 来说至关重要,我们想提醒您保持警惕,警惕试图访问代码库的行为。

社交工程威胁,如网络钓鱼诈骗,依靠复杂的社交工程攻击或操纵(而不是固有的漏洞)来获得对系统和网络的未经授权的访问。从我们的讨论帖子中重申,所有来自 CircleCI 的授权电子邮件将只包含 circleci.com 或其子域名的链接,我们绝不会要求用户登录来查看我们服务条款的更新。

我们将继续采取措施提醒我们的社区,因为我们看到未来假冒 CircleCI 的网络钓鱼企图。随着这些尝试的出现,我们通过应用内消息、横幅提醒、电子邮件、我们的社区论坛和我们的社交媒体账户提醒我们的客户。如果您对来自 CircleCI 的消息的有效性有任何疑问,请不要犹豫将其转发给我们,以便在security@circleci.com进行核实。

谢谢你的支持和持续的警惕。

将可发货配置转换为 CircleCI | CircleCI

原文:https://circleci.com/blog/circleci-vs-shippable-ci-configuration-comparing-build-elements/

CircleCI 构建管道在一个.circleci/config.yml文件中定义。这个配置文件不同于其他 CI/CD 服务,比如 Shippable。有兴趣将他们的代码库和/或开源项目构建配置迁移到 CircleCI 的开发人员有时会因为不了解他们当前的 CI/CD 服务配置和 CircleCI 的构建配置之间的差异而却步。

在这篇文章中,我将比较可发布的构建配置元素和它们的 CircleCI 对应物,并尽可能地帮助翻译。由于 Shippable 和 CircleCI 是非常不同的系统,并不是所有的构建元素都可以在 CircleCI 中使用,反之亦然,但是我希望本指南可以帮助那些希望了解它们如何相似和不同的人。如果你现在准备从可发货转变,开始在 CircleCI 这里上构建。

下表列出了可发货的构建元素及其 CircleCI 等效元素(如果适用)。

摘要

我希望这篇文章可以作为开发者的指南,帮助他们理解可发布配置和兼容的 CircleCI 构建配置之间的区别。上面列出的参考表显示了常见的可发货到 CircleCI 配置元素,对于有兴趣迁移 CI/CD 管道以在 CircleCI 平台上构建、测试和部署的可发货用户来说,这是一个很好的起点。

准备好从可发货切换了吗?在 CircleCI 这里开始建造。此外,如果你遇到困难,你可以通过社区论坛联系 CircleCI 社区。

将 Travis CI 配置转换为 CircleCI | CircleCI

原文:https://circleci.com/blog/circleci-vs-travis-ci-configuration-comparing-build-elements/

CircleCI 构建管道在一个.circleci/config.yml文件中定义。此配置文件不同于其他 CI/CD 服务,如 Travis CI。有兴趣将他们的代码库和/或开源项目构建配置迁移到 CircleCI 的开发人员有时会因为不了解他们当前的 CI/CD 服务配置和 CircleCI 的构建配置之间的差异而却步。在这篇文章中,我将比较 Travis CI 构建配置元素和它们的 CircleCI 对应元素,并尽可能帮助翻译。由于 Travis CI 和 CircleCI 是非常不同的系统,所以并不是所有的构建元素都可以在 CircleCI 中使用,反之亦然,但是我希望本指南可以帮助那些希望了解它们如何相似和不同的人。下表列出了 Travis CI 构建元素及其 CircleCI 对等元素(如果适用)。

摘要

我希望这篇文章可以作为开发人员的指南,帮助他们理解 Travis CI 配置和兼容的 CircleCI 构建配置之间的区别。上面列出的参考表显示了常见的 Travis to CircleCI 配置元素,对于有兴趣迁移 CI/CD 管道以在 CircleCI 平台上进行构建、测试和部署的 Travis CI 用户来说,这是一个很好的起点。关于从 Travis CI 迁移到 CircleCI 的实际例子,请查看我们的文档,其中包括一个示例翻译

如果你被卡住了,你可以通过社区论坛联系 CircleCI 社区。

从类组件到反应钩子

原文:https://circleci.com/blog/class-components-to-react-hooks/

本教程涵盖:

  1. 如何处理钩子和类组件中的状态
  2. 了解什么是定制挂钩
  3. React 挂钩的编写和自动化测试

从 16.8 版本开始, React 提供了一种使用组件和全局状态的方法,而不需要类组件。然而,这并不意味着钩子是类组件的替代品。使用类组件有一些好处,我将在本教程的后面描述。不过,首先,我将引导您了解如何处理钩子和类组件中的状态,理解什么是定制钩子,最后为钩子编写测试,并将您的应用程序集成到 CircleCI 中。

先决条件

要学习本教程,您需要:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

钩子与基于类的组件

类组件基本上是 JavaScript 面向对象的类,带有可以用来呈现 React 组件的函数。在 React 中使用类的优点是它们包含生命周期方法,这些方法可以识别状态何时改变,并使用关键字this.state更新全局状态或组件状态。相比之下,钩子用于 react 功能组件,使您能够在功能组件中拥有组件状态和其他 React 特性,而不需要类。React 提供了一种无需类生命周期方法就能挂钩到全局状态的方法,用于更新应用程序的全局和局部状态。

在本节中,您将创建一个计数器组件,它利用 React 挂钩和一个类来递增和递减计数。然后,我将向您展示如何在这两者中初始化和更新状态。

您的第一步是克隆您将使用的存储库。

克隆存储库

在首选工作目录中的终端上,运行以下命令:

git clone https://github.com/mwaz/class-components-to-react-hooks.git # Clone repository

cd class-components-to-react-hooks # Change directory to the cloned repository 

克隆存储库之后,安装依赖项并启动应用程序。运行以下命令:

npm install # Install dependencies

npm start # Start the application 

一旦运行了start命令,您应该能够看到应用程序在浏览器上执行,如下所示。

Counter application

这是一个简单的 React 类组件,由下面的代码片段创建:

// src/class.js
import React from "react";

class CounterClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({
      count: this.state.count + 1,
    });
  }
  decrement = () => {
    this.setState({
      count: Math.max(this.state.count - 1),
    });
  }
  render() {
    return (
      <div className="counter">
        <h1>COUNTER</h1>
        <div className="buttons">
          <button onClick={this.increment}>Increment</button>
          <button onClick={this.decrement}>Decrement</button>
        </div>
        <p>{this.state.count}</p>
      </div>
    );
  }
}

export default CounterClass; 

这个代码片段的构造函数方法将状态设置为this.state,并用0初始化计数。然后,它定义了单击按钮时调用的函数。这些函数使用setState()方法更新状态。这是用类更新计数器应用程序的类组件实现。接下来,我们将回顾如何使用功能组件和钩子实现相同的功能。克隆的存储库中的src/hooks.js包含钩子实现:

// src/hooks.js
import { useState } from 'react'

export default function CounterHooks() {
const [ value, setValue]= useState(0);

const handleIncrement = () => {
    setValue(value + 1)
  }
  const handleDecrement = () => {
    setValue(value - 1)
  }
  return (
    <div className="counter">
      <h1>COUNTER</h1>
      <div className="buttons">
        <button data-testid="increment" onClick={handleIncrement}>Increment</button>
        <button data-testid="decrement" onClick={handleDecrement}>Decrement</button>
      </div>
      <p data-testid="count">{value}</p>
    </div>
  );
} 

您可以使用useState()钩子来初始化状态,而不是像前面的代码片段那样使用this.stateuseState钩子还能够与应用程序中的其他组件共享状态,就像使用this.state的类组件一样。

这些代码片段显示了代码可读性的提高。不仅消除了复杂性,而且使功能组件只做一件事——呈现计数器应用程序。既然你知道了什么是 React 钩子,为什么不探究一下 React 中最常见的钩子以及它们是如何使用的呢?

使用状态与使用效果挂钩

在 React 中,可以使用不同的钩子来执行操作。其中之一就是useEffect()钩。这个钩子帮助您处理 React 领域之外的事情,比如 API 调用、异步事件和其他副作用。一个简单的useEffect钩子的结构如下所示:

 useEffect(() => {
    //Your code here
  }, []) 

useEffect钩子所期望的第一个参数是一个回调函数,在这里编写要执行的代码。第二个是称为dependency array的数组[]。如果省略数组,回调函数将在每次代码更改时运行。如果数组为空,回调函数将运行一次。如果提供了值,回调函数将在每次值改变时运行。

注意: 依赖数组是一个接受依赖或变量的数组,如果值改变,回调函数会再次运行。

接下来,尝试在简单逻辑中使用一个useEffect()钩子,将 count 的值记录到 Chrome 浏览器控制台。在这种情况下,您希望返回计数的值,因此您可以将该值添加到依赖数组中,如以下代码片段所示:

 useEffect(() => {
    console.log(value);
  }, [value]) 

当组件加载并且useEffect钩子被调用时,控制台将计数的值记录到 Chrome 浏览器控制台。每次计数值发生变化时,控制台都会记录新的计数值。

相反,useState()钩子是用来初始化应用程序状态的钩子。它将一个值作为参数,并返回一个包含两个值的数组。第一个值是当前状态,第二个值是可以用来更新状态的函数。

使用像useState()useEffect()这样的 React 钩子,您可以消除像componentDidMount()componentDidUpdate()这样的生命周期方法的使用。相反,您可以使用钩子来处理状态逻辑。

注:React 内置了生命周期方法。它们用于在特定操作发生时执行操作,例如渲染、挂载、更新和卸载。它们仅用于基于类的组件中。

研究了一些钩子之后,您可以继续研究使用钩子的一些优点和缺点。

挂钩的优势

  • 钩子不需要用this来绑定click事件的函数,也可以访问组件或全局状态中的values
  • 钩子使得代码更加整洁,易于阅读和测试。
  • 钩子提供了更多的灵活性,它们可以被重用,尤其是在多个组件中的定制钩子。
  • 有了钩子,你不需要使用生命周期方法。副作用可以用一个函数来处理。

挂钩的缺点

  • 开始使用钩子可能是一个挑战,特别是对于一个新的开发者。
  • 每次状态改变时,组件都会重新呈现,除非您使用其他挂钩来防止这种情况。

创建自定义挂钩

在上一节中,我描述了使用钩子的优点和缺点。在这一节中,我将引导您创建一个可以在计数器应用程序中的任何地方使用的定制钩子。将这段代码添加到src/components/useCounter.js文件中:

// src/components/useCounter.js
import { useState, useEffect } from "react";

export function useCounter() {
  const [value, setValue] = useState(0);
  const [isEven, setIsEven] = useState(false);

  useEffect(() => {
    if (value % 2 === 0) {
      setIsEven(true);
    } else {
      setIsEven(false);
    }
  }, [value]);

  const handleIncrement = () => {
    setValue(value + 1);
  };
  const handleDecrement = () => {
    setValue(value - 1);
  };

  return [value, isEven, handleIncrement, handleDecrement];
} 

这段代码添加了一个新的状态值isEven,它检查该值是否为偶数。该代码片段继续检查计数值,并确定它是偶数还是奇数。它根据值将isEven设置为真或假。

useEffect 钩子内部的回调函数使用一个if - else语句来设置isEven的值。它还使用依赖数组中的值来确保每次计数改变时,无论是递减还是递增,函数都会运行。

useCounter钩子返回状态值以及incrementdecrement函数,这样您就可以在钩子组件中访问它们。

既然已经有了自定义钩子,就可以用它来设置和更新custom-hook.js文件中的状态:

// src/components/custom-hook.js
import { useCounter } from './useCounter'

export default function CounterHooks() {
const [ value, isEven, handleIncrement, handleDecrement ]= useCounter();

 return (
    <div className="counter">
      <h1>COUNTER</h1>
      <div className="buttons">
        <button data-testid="increment"  onClick={handleIncrement}>Increment</button>
        <button data-testid="decrement" onClick={handleDecrement}>Decrement</button>
      </div>
      <p data-testid="count">{value}</p>
      <div className={isEven ? "even" : "odd"}>{ isEven ? "Even" : "Odd"}</div>
    </div>
  );
} 

这段代码使用useCounter()钩子来设置状态值,并访问incrementdecrement函数。它使用这些函数来更新状态。isEven状态值根据应用程序上显示的计数器数字显示计数器是偶数还是奇数。

Odd and Even hooks display

既然您已经对钩子有了一些兴趣,那么是时候学习如何测试它们了。

测试挂钩

在这一节中,我将描述如何为 Hook 组件编写测试。您将使用 Jestreact-testing-library ,它们都是在您设置克隆应用程序时安装的。

从测试按钮是否工作开始。将这段代码添加到App.test.js文件中:

// src/App.test.js
import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";

describe("Counter component test suite", () => {
  test("displays the heading", () => {
    render(<App />);
    expect(screen.getByRole("heading").textContent).toBe("COUNTER");
  });

  test("increment button works", () => {
    render(<App />);
    const count = screen.getByTestId("count");
    const incrementBtn = screen.getByTestId("increment");
    expect(count.textContent).toBe("0");
    fireEvent.click(incrementBtn);
    expect(count.textContent).toBe("1");
  });

  test("decrement button works", () => {
    render(<App />);
    const count = screen.getByTestId("count");
    const decrementBtn = screen.getByTestId("decrement");
    expect(count.textContent).toBe("0");
    fireEvent.click(decrementBtn);
    expect(count.textContent).toBe("-1");
  });
}); 

这个代码片段“点击”了incrementdecrement按钮来检查计数值是递增还是递减。针对计数值断言的。通过在终端运行npm test来运行测试。

PASS  src/App.test.js (5.799 s)
  Counter component test suite
    √ displays the heading (432 ms)
    √ increment button works (77 ms)
    √ decrement button works (48 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        12.097 s
Ran all test suites related to changed files. 

在这种情况下,测试通过了。万岁!该代码片段显示,react-testing-library模拟了用户在应用程序上的点击事件,并验证测试的 DOM 状态是否如这些断言中所预期的那样发生了变化。现在,您可以进入下一部分,学习如何将您的测试与持续集成管道相集成。在这种情况下,我们将使用 CircleCI。

积分电路

CircleCI 是一个通过持续集成和持续部署(CI/CD)的原则,帮助软件团队自动构建、测试和部署的平台。

在项目的根文件夹中,创建一个.circleci目录,并向其中添加一个config.yml文件。将以下代码片段添加到配置文件中:

# .circleci/config.yml
version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:14.17.1
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Hooks component tests
          command: npm test
      - store_artifacts:
          path: ~/repo/class-components-to-react-hooks 

提交并推送对存储库的更改。然后进入 CircleCI 仪表盘

打开项目页面,该页面列出了与您的 GitHub 用户名或组织相关联的所有 GitHub 存储库。对于本教程,请点击class-components-to-react-hooks。选择设置项目

Setting up project CircleCI

选择使用分支main中现有配置的选项。

Setting up CircleCI

瞧啊。在 CircleCI 仪表板上,展开构建工作流详细信息,以验证所有步骤是否成功。

Successful pipeline execution

展开Hooks component tests构建步骤,验证钩子测试是否成功运行。

Successful tests execution

现在,当您对应用程序进行更改时,CircleCI 将自动运行这些测试。

结论

在本教程中,您已经了解了 React 挂钩及其在基于类的组件中的作用。我描述了使用钩子的利与弊,以及如何使用不同类型的钩子来获得与基于类的组件相同的结果。最后,您能够使用关于钩子的知识来编写一个定制的钩子组件。您在 counter 应用程序中使用了自定义钩子,并为它编写了测试。

我很高兴为你创建这个教程,我希望你觉得它很有价值。直到下一个,继续学习,继续建设!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

面向 JavaScript 开发者的 Clojure 微服务

原文:https://circleci.com/blog/clojure-microservices-for-js-devs-pt-1/

这个系列由泰勒·苏伯格和穆萨·巴里格扎伊共同撰写。

CircleCI 在成长,这太棒了。然而,我们面临的一个增长挑战是,我们的后端主要是用 Clojure 编写的,很少有开发人员知道 Clojure。包括我自己在内的许多 CircleCI 工程师都在工作中学习了 Clojure。

在加入 CircleCI 之前,我是一名 JavaScript 开发人员。作为软件工程师的通用语言,JavaScript 是一种相对简单易学的语言。当然,它有(许多)怪癖,但这些都被出色的文档、无数的教程帖子和高度审查的堆栈溢出答案所掩盖。

相比之下,Clojure 的环境就不那么友好了。一旦学会了如何在括号的密集森林中导航,就很容易学会编写 Clojure 函数的基础知识。然而,构建可用的微服务有一个陡峭的学习曲线。在 Clojure 之旅的这个阶段,开发人员几乎没有什么资源。

这是展示如何设置 Clojure 微服务的系列文章的第一部分:

在这些帖子中,我们将使用 JavaScript 作为一个比较点来理解 Clojure 的新特性和有趣之处。我们不会讨论 ClojureScript ,一个面向 JavaScript 的 Clojure 编译器(应该有自己的博文)。我的假设是您已经知道编写 Clojure 函数的基础,所以我们可以直接开始设置一个简单的 Clojure 微服务。

到本系列结束时,我们希望您会发现编写 Clojure 微服务可以变得简单而愉快。

Clojure vs JavaScript

在我们开始创建您的第一个 Clojure 微服务之前,我们应该探索一下 Clojure 和 JavaScript 之间的主要区别和相似之处。这将帮助我们在心理上准备好用 Clojure 而不是 JavaScript 思考意味着什么。

函数式编程

Clojure 设计的基本前提是,开发人员在面向对象程序中创造复杂性的能力远远超过他们理解这种复杂性的能力。Clojure 的要点是尽可能简单地获取、转换和移动数据。

这在 Clojure 中的主要表现方式是通过函数式编程风格。幸运的是,如果您来自 JavaScript,这种函数式风格会有些熟悉。毕竟函数在 JavaScript 中是一流的,现代 web 开发(如 React )往往是用函数风格编写的。事实上,Clojure 据称是 React 的设计的灵感来源。

种在 JavaScript 中使用的种常见模式,但您不会在 Clojure 中看到:

  • 创建大量对象来管理复杂的过程,如维护数据库连接
  • 在对象中定义函数作为命名函数的一种方式
  • 维护对象中的状态

这实际上意味着,至少根据 Peter Norvig 的估计,经典的“四人帮”的 23 种设计模式中的 16 种在功能范例中已经过时了。在本系列的下一篇文章中,我们将探索我们的微服务如何在没有传统对象的情况下管理复杂的进程,如数据库或 RabbitMQ 连接。

不变

在面向对象的语言中,对象是通过引用传递的。对象的可变性会使您的 JavaScript 应用程序更难预测,更难调试。

const cheese = { foodGroup: "dairy" };
const limburger = cheese;
limburger.smellsBad = true;
console.log(cheese);
// {foodGroup: "dairy", smellsBad: true} 

在 JavaScript 中,可以用不变性库来解决这个问题;比如 Immutable.jsLodash ,或者 Ramda 。在 Clojure 中,默认情况下数据是不可变的。

(def cheese {:food-group "dairy"})
(def limburger (assoc cheese :smells-bad true))
(println cheese)
; {food-group "dairy"} 

不可变的数据结构使我们更容易编写纯粹的、易于测试的函数。这一事实将决定我们如何在微服务中处理组合功能。与 JavaScript 相比,Clojure 倾向于用更小的纯函数编写,这些函数被重用并重新组合成更大的函数。

动态类型

当然了。JavaScript 也是动态类型化的,所以我们可能期望 Clojure 有一个等价的 TypeScript。事实并非如此。

有一个名为 Clojure.spec 的 Clojure 核心库,乍一看像是 Clojure 的 TypeScript。但是,Clojure.spec 不是类型系统,与 TypeScript 有重要的区别:

  • Clojure.spec 是为灵活性和动态组合数据而设计的。它不像 TypeScript 那样严格,但是让您能够以几乎任何您可以想象的方式定义数据应该是什么样子。例如,假设一个函数的返回类型应该是一个质数。
  • Clojure.spec 在运行时而不是编译时运行。这意味着您不会在 IDE 中得到参数与所需类型不匹配的提示,但这确实意味着您可以使用 Clojure.spec 来检查生产中的类型(例如,实时验证来自 API 的数据)
  • Clojure.spec 允许您自动生成测试,而不是编写单独的单元测试。

简而言之,Clojure 中的动态类型是该语言的一个显式设计决定(尽管是一个有争议的决定), clo jure . spec 围绕动态编程进行了优化。Clojure 没有类似的 TypeScript,这是故意的。

一切都是数据

来自 JavaScript 世界,Clojure(和 Lisps)的另一个奇怪的属性是代码是数据(自命不凡的人用同形异义)。

您可以用告诉 Clojure 将函数调用视为列表数据结构,而不是对其求值。在这个例子中,我们创建了一个定义,它等于一个未赋值的函数调用。这个未赋值的函数调用只是一个列表。然后我们取列表中的第一个成员,加法运算符。

clj꞉dev꞉>  (def code-is-data '(+ 1 3))
clj꞉dev꞉> (first code-is-data)
; + 

理解 Clojure 的这一方面对于理解很重要。宏看起来与函数非常相似;他们接受论点并有一个身体。然而,它们在一个关键方面不同于函数。函数评估它们的参数并返回数据,而宏返回一个数据结构,然后被评估。为了演示,我们可以创建一个使用中缀而不是前缀符号的宏。

clj꞉dev꞉>  (defmacro infix
            [[num-1 operator num-2]]
            (list operator num-1 num-2))
clj꞉dev꞉>  (infix (2 * 3))
; 6 

当您构建 Clojure 微服务时,您会经常遇到外部库中的宏。这些宏最初可能会令人困惑,因为它们看起来好像违反了 Clojure 语法规则。实际上,它们是 _ 扩展 _Clojure 的语法。

虚拟机(Java Virtual Machine 的缩写)

前面我说过,在 Clojure 中,您编写功能性代码,而不是创建有状态对象。这并不完全正确。

Clojure 运行在 JVM 平台上(虽然 Clojure 也可以编译成 JavaScript )。这意味着在 Clojure 中,您可以访问 Java 世界提供的一切。你可以导入 Java 库并创建类

这是一种“鱼与熊掌不可兼得”的情况,将功能性动态语言的表达能力与 JVM 的能力和信任结合在一起。

例如,我们可以使用 Clojure 的“ new ”表单来创建 Java 的 Date 类的实例。

(defn now [] (new java.util.Date))
(now)
; #inst "2021-04-22T15:06:19.329-00:00" 

取代

Clojure 和 JavaScript 都允许您在 REPL(读取-评估-打印循环)中编写代码。然而,在 JavaScript 中,我们倾向于依赖测试驱动的开发,并在本地运行我们的 web 应用来驱动我们的开发工作流。

在 Clojure(和许多其他 Lisps)中,REPL 是开发工作流的核心。事实上,Clojure 是专门为 REPL 驱动的工作流设计的。Clojure 的 REPL 允许您进行交互式的灵活开发体验,最终收紧您的迭代/反馈循环。

如果您喜欢冒险,您甚至可以使用 Clojure 的 REPL 来修改生产中的代码,而无需提交。众所周知,深空 1 号探测器在 1 亿英里之外使用普通的 LISP REPL 修复了一个错误。

包扎

作为一名 JavaScript 开发人员,使用 Clojure 进行思考可能会很自然。您可能已经使用函数式风格编写代码,使用动态类型,并使用 REPL 编写代码。

尽管如此,Clojure 在几个关键方面与 JavaScript 有本质的不同:不可变数据、多态而不是在对象内定义方法,以及同象性(代码就是数据)。这些方面中的任何一个都可能是 JavaScript 开发人员的挑战。

理解 Clojure 和 JavaScript 之间的异同将在我们的下一篇文章中对你大有裨益,在下一篇文章中,我们将构建你的第一个 Clojure 微服务

面向 JavaScript 开发人员的 Clojure 微服务第 3 部分

原文:https://circleci.com/blog/clojure-microservices-for-js-devs-pt-3/

这个系列由穆萨·巴里格扎伊和泰勒·苏尔贝格共同撰写。

这是 JavaScript 开发人员关于如何设置 Clojure 微服务的系列文章中的第三篇,也是最后一篇。以前的职位是:

那些以前的帖子是有用的背景,但是你可以克隆回购并在不阅读它们的情况下进入这篇帖子。

使用 Clojure 测试 API

与 JavaScript 相比,Clojure 的一个方便的特性是它自带了一个内置的单元测试库, clojure.test 。按照惯例,在 Leinegen 项目中,你有一个 src 目录,我们知道它保存了所有的代码,并且用于测试一个测试目录,将你的代码从你的测试中分离出来。对于您想要测试的在src下的任何文件,您可以在后缀为\_test/test下创建一个匹配的文件。

假设我们有一个文件adder.clj,在 Leiningen 项目的下面的文件结构中。

src/
├─ adder/
│  ├─ adder.clj 

为了编写adder.clj的单元测试,我们将创建以下测试目录:

src/
├─ adder/
│  ├─ adder.clj
test/
├─ adder/
│  ├─ adder_test.clj 

注意test目录是如何镜像我们的src目录的,以及我们如何在我们想要测试的文件上添加一个\_test后缀。

我们想在adder.clj中测试下面的函数。

(defn add-numbers [x y]
  (+ x y)) 

要在adder.clj中为add-numbers函数编写测试,首先要引入 Clojure 核心测试框架。

(ns adder
  (:require [clojure.test :refer [deftest])) 

利用deftest宏,编写测试就像:

(deftest test-add-numbers
  (is (= 4 (add-numbers 2 2)))) 

在我们的示例项目中,我们对所有的 HTTP 端点处理程序都进行了单元测试。下面是一个例子(可以在 test/clo jure _ for _ js _ devs/handlers _ test . clj 中找到)。这是为我们的/counter路径测试处理程序。/counter路径将请求者的 IP 地址作为一个关键字添加到 Redis 中,并将值加 1,记录一个 keeping 的次数/counter

(testing "counter-handler"
    (let [response "Counter: 44"
          req (->  (ring-mock/request :get "/counter"))]
      (bond/with-stub! [[redis/getKey (constantly 44)] [redis/incr (constantly nil)]]
        (is (= (handlers/counter-handler req {}) response))))) 

这里发生了很多事情,所以让我们来为您分析一下。

首先,我们为想要测试的端点(/counter)初始化一个请求对象req。我们正在利用 ring-clojure(我们的 HTTP 服务器框架)附带的请求模拟库。这为我们节省了时间;我们不需要写出完整的请求映射。

接下来,我们使用库bond/with-stub!。在 JavaScript 中,最流行的测试框架是 Jest 。如果你以前和 Jest 合作过,bond 会给你类似的特性。jest.mock()允许您模仿模块,并声明您希望模仿函数的返回值是什么。这就是bond/with-stub在 Clojure 中为我们做的事情。因为这些是单元测试,我们想要模拟对 Redis 的调用,特别是键getKeyincr。对于getKey,我们希望在测试中调用它的任何时候都返回 44,同样,对于incr,我们希望返回nil

最后,我们断言我们对handlers/counter-handler的调用将匹配我们的响应Counter: 44。注意,handlers/counter-handler中的最后一个参数是我们的 Redis 组件,但是在这个测试中,我们传入了一个空的 map {}。因为我们正在存根化我们的 Redis 调用,所以我们可以为这个参数传递一个空映射,因为在我们的测试中不需要 Redis。

写集成测试怎么样?在 JavaScript 中,Jest 的一个有用特性是测试的设置和拆卸。当编写集成测试时,这个特性很方便。例如,如果我们有一个查询城市数据库的应用程序,您可能会开玩笑地这样做:

beforeAll(() => {
  initializeCityDatabase();
});

afterAll(() => {
  clearCityDatabase();
});

test("city database has Vienna", () => {
  expect(isCity("Vienna")).toBeTruthy();
}); 

beforeAll/afterAll 允许您在测试之前和之后运行代码。

Clojure 内置了对测试设置和拆卸的支持。它还利用了夹具。这里有一个完整的例子:

(ns adder_test
  (:require [clojure.test :refer [testing use-fixtures]))

(defn my-fixture [f]
  ;; The function you want to run before all tests
  (initializeCityDatabase)

  (f)  ;;Then call the function we passed.
  ;; The function you want to run after all tests
  (clearCityDatabase)
 )
lang:clojure
(use-fixtures :once my-fixture)

(deftest city-db
  (is (= "'Vienna'" (IsCity)))) 

这里我们用:once调用 use-fixtures,这意味着在名称空间中的所有测试中只运行一次我的 fixture。您还可以通过:each为每个测试重复运行您的夹具。use-fixtures这里的工作和以前一模一样/一语双关。我们用我们希望在之前和之后被调用的方法来包装我们的测试(在我的 fixture 中显示为(f))。

你可以在示例项目test/clojure_for_js_devs/test_core.clj中看到完整的例子。在的帖子 2 中,我们讨论了system-map的目的。系统图允许我们的应用程序管理它所依赖的每个软件组件的生命周期。在我们的例子中,我们的组件是 Redis 连接和一个 HTTP 服务器:

(defn- test-system
  []
  (component/system-map
   :redis (redis/new-redis "redis://localhost:6379")
   :http-server (component/using
                 (http/new-server "localhost" 0)
                 {:redis :redis})))

(defn- setup-system
  []
  (alter-var-root #'system (fn [_] (component/start (test-system)))))

(defn- tear-down-system
  []
  (alter-var-root #'system (fn [s] (when s (component/stop s)))))

(defn init-system
  [test-fn]
  (setup-system)
  (test-fn)
  (tear-down-system)) 

我们有一个方法setup-system,它使用组件库(来自文章 1)来启动所需的组件(HTTP 服务器和 Redis)。除了setup-system,我们还有tear-down-system,它运行 component/stop 来在测试完成后关闭所有组件。

现在,如何在 Clojure 中运行测试呢?如果没有测试运行器,您可以在 REPL 中调用(run-all-tests)来运行所有命名空间中的所有测试。或者,如果您正在使用 Leiningen 跟踪,您可以调用lein test

您可能已经使用了 Jest 这样的测试运行器,它具有在代码中检测到变化时自动运行测试的特性。Clojure 社区也有各种具有类似特性的测试运行程序。最受欢迎的是 Kaocha 。我们在示例项目中使用 Kaocha。

要设置 Kaocha,首先将它作为一个依赖项添加到 dev dependencies 下的project.clj文件中:

 :profiles {:uberjar {:aot :all}
                 :dev {:dependencies [[lambdaisland/kaocha "1.0.829"]
                                  [circleci/bond "0.5.0"]
                                  [ring/ring-mock]]}} 

在 JavaScript 中,您可以为想要运行的自定义命令创建自定义脚本。比如npm run prod:ci。Leinegen 也有这个特性,称为别名。我们可以创建一个别名test来加载lambdaisland/kaocha依赖项。

:aliases {"kaocha" ["run" "-m" "kaocha.runner"]} 

最后,添加 Koacha 配置文件。使用以下配置在您的根项目目录中创建一个tests.edn文件:

#kaocha/v1
{:kaocha/color? true} 

现在,如果您调用lein test,kaocha 将执行测试并报告结果。

在 CircleCI 中运行 Clojure 测试

如果你正在寻找持续集成(CI)重要性的介绍,我强烈推荐你去看看https://circleci.com/continuous-integration/。在这一节中,我们将回顾 CI 工作流以及如何在 CircleCI 中运行 Clojure 测试,

在我们在.circleci/config.yml下的示例项目中,我们有一个工作流,用于在每次提交回购时测试我们的项目。

version: 2.1
jobs:
  build:
    docker:
      - image: circleci/clojure:lein-2.9.5
      - image: redis:4.0.2-alpine
        command: redis-server --port 6379
    working_directory: ~/repo
    environment:
      LEIN_ROOT: "true"
      JVM_OPTS: -Xmx3200m
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "project.clj" }}
            - v1-dependencies-
      - run: lein deps
      - save_cache:
          paths:
            - ~/.m2
          key: v1-dependencies-{{ checksum "project.clj" }}
      - run:
          name: install dockerize
          command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
          environment:
            DOCKERIZE_VERSION: v0.3.0
      - run:
          name: Wait for redis
          command: dockerize -wait tcp://localhost:6379 -timeout 1m
      - run: lein test

workflows:
  build:
    jobs:
      - build 

jobs键是我们定义可以在 CI 渠道中使用的工作的地方。我们的管道只有一个任务,“构建”,它将构建我们的应用程序,然后运行我们的测试。“构建”将使用 Docker 映像来设置我们的 CI 环境。我们的 Clojure 应用程序将使用预先构建的 CircleCI Docker 映像:circleci/clojure:lein-2.9.5运行。这些映像通常是官方 Docker 映像的扩展,包括对 CI/CD 特别有用的工具。在我们的例子中,circleci/clojure:lein-2.9.5安装了 Clojure 和 lein。我们还使用 Redis Docker 映像,因为我们的集成测试需要访问 Redis 服务器。

下面是我们“构建”工作的所有步骤的回顾:

  1. 我们首先从我们的回购中检查我们的代码。
  2. 然后,我们包装lein deps调用,用缓存获取我们所有的依赖项。我们不需要在每次 CI 运行时都重新安装所有的依赖项,因此缓存将加快我们的 CI 构建。
  3. 然后我们引入 Dockerize,这是一个工具,它使我们的 CI 能够在运行测试之前等待其他服务可用。在我们的例子中,我们使用 Dockerize 在运行集成测试之前等待 Redis。
  4. 最后,我们运行lein test来运行我们所有的测试。

我们现在让 CI 管道在每次提交时运行,自动确保没有人对我们的 Clojure 项目进行重大更改。

就是这样!现在,您已经有了一个 Clojure 微服务,它使用持续集成工作流进行基本测试。我们希望您发现这个系列很有价值,并且有信心在您的下一个项目中使用 Clojure。

使用 runner | CircleCI 管理 CircleCI 上的代码签名

原文:https://circleci.com/blog/code-signing-with-runner/

代码签名是测试和分发桌面和移动应用程序的重要部分。它确保最终用户的系统可以验证您的应用程序的合法性。由于对签名证书安全性的需要,它们存储在本地,而不上传到云中。这个约束可能会阻止您的团队完全自动化您的 CI/CD 管道。幸运的是,使用 runner,您的团队可以在您控制的机器上保持证书安全,同时仍然允许 CircleCI 作业对您的包进行签名。

什么是代码签名?

代码签名在移动应用领域很常见,谷歌 Play 商店和 App Store 要求每个发布的应用都使用它。代码签名向用户保证他们下载的应用程序将被他们想要安装的设备信任。

桌面应用程序的代码签名

代码签名可用于桌面二进制文件,但不太常见。在 Windows 上运行未签名的二进制文件时,您可能会得到一个提示,询问您是否信任发行者。操作系统会提醒您这是一个未签名的应用程序。该应用程序可能值得信任,也可能不值得信任,因此您可以将该提示用作警告标志。

有两种方法可以管理应用程序的代码签名:

  • 将签名嵌入到二进制文件中,以便操作系统可以对其进行验证;这是 macOS、Windows 和移动操作系统使用的。
  • 提供二进制文件的散列的签名;用于 Linux。

这两种方法都可以用来验证下载的文件是否正确。

保护私有证书的安全

最佳实践是尽可能保证这个私有证书的安全,不要把它放在网上或云中。因此,在 CircleCI 中存储私有证书不是一个好主意,无论是在安全上下文中还是在环境变量中。不建议将证书或私有 GPG 密钥上载到 CircleCI。

然而,当涉及到测试时,有一个例外。在 CI/CD 过程中,本地创建的证书可以用于上传和测试,只要该证书没有被其他机器分发和信任。当您使用 runner 来保证您的团队控制的机器上的证书安全时,这个异常使得对您的 CI/CD 管道执行端到端测试成为可能。

在您的机器上设置转轮

在你自己的机器上安装 runner 很简单,目前在 Ubuntu、macOS 和 Windows 上都支持。您可以在安装 CircleCI 转轮中找到多个平台的说明。

您还需要将私有证书安装到您用来签名的平台所要求的位置。以下是说明:

完成这些步骤后,您就可以开始使用 CircleCI runner 为您的应用程序签名了。

使用签名哈希在 Linux 上签名

Linux 不使用签名二进制文件的概念。相反,用户依赖于文件散列和签名。

第一步是计算要分发的文件的文件哈希。您可以使用各种强度的 SHA 和,包括 SHA1(不推荐)、SHA256 或 SHA512 哈希。对于本教程,我们将使用 SHA512。

$ sha512sum xyz.exe
cf83e...927da3e  xyz.exe 

将结果保存为SHA512SUMS。如果您想在一个签名中包含多个文件,只需将它们的文件名传递给sha512sum命令。每个文件的散列将被打印到单独的一行。不要编辑输出,否则以后将无法验证散列。

现在你可以签名了。如果您已经生成了一个私有 GPG 密钥,那么签名非常简单:

$ gpg --sign SHA512SUMS --output SHA512SUMS.sig 

这段代码输出文件SHA512SUMS.sig,该文件已经使用您的密钥进行了签名。用您的二进制文件分发这个.sig文件,并指示用户在安装您的公钥后验证输出。您可以在这里查看的说明

$ gpg --decrypt SHA512SUMS.sig > SHA512SUMS
gpg: Signature made Tue 22 Jun 13:53:56 2021 BST
gpg:                using RSA key 1234XYZ
gpg: Good signature from "CircleCI <contact@circleci.com>" [ultimate]
$ sha512sum --check SHA512SUMS
xyz.exe: OK 

macOS 上的代码签名

和 Linux 一样,macOS 也可以使用文件哈希和签名。与签署团队的 macOS 或 iOS 应用程序文件相比,这有点乏味。反过来,macOS 会自动验证它们。

确保构建应用程序所需的所有证书和配置文件都安装在本地 runner 机器上。这些证书和配置文件必须安装在为运行程序守护程序配置的同一用户下。最简单的方法是在机器上用 Xcode 构建一次项目。这允许 Xcode 创建和安装它需要的证书和描述文件。

runner launch 守护进程需要添加一个额外的选项,以便可以通过作业访问 keychains:

<key>SessionCreate</key>
<true/> 

另一个关键步骤是确保新的苹果全球开发者关系证书在 runner 机器上可用。作为跑步者用户,你可以在这里下载它

以在 CircleCI 上构建应用程序但不创建签名的.ipa文件的方式构建您的工作流程。相反,它应该创建一个供以后使用的.xcarchive文件。尝试构建.ipa文件将会失败,因为在云作业中没有代码签名资产。

该归档保存到运行程序作业中拉入的工作区。runner 作业获取这个归档文件,跳过再次构建项目,并通过 gym 运行它,gym 使用安装在 runner 机器上的代码签名资产将它导出为一个签名的.ipa文件。这将代码签名资产安全地保存在您的团队控制的机器上。

Windows 上的代码签名

如果你安装了 Visual Studio,你可以使用微软的一个特殊的签名工具SignTool.exe。签署文件只需要一个有效的证书(可以是自签名的)并执行SignTool.exe:

SignTool.exe sign /a /fd SHA256 xyz.exe 

不像在 Linux 进程中,不会创建其他文件。相反,签名嵌入在文件本身中。

如果要对文件进行签名以进行更广泛的分发,请确保用于签名文件的证书受到广泛信任。要么购买由可信机构签名的证书,要么自己生成并分发证书。但是,除了内部使用之外,不建议使用自签名。正如我在前面的教程中所描述的,Windows 用一个提示来标记未签名的二进制文件的执行,以继续处理一个不可信的文件。如果找到有效的签名,操作系统会显示详细信息,以便用户查看。

使用 runner 将签名添加到 CircleCI 配置中

您知道如何对二进制文件进行签名,或者提供一种可信的方法来验证二进制文件是否正确。现在,您可以将它整合到您的 CircleCI 配置中。第一步是用证书或私有 GPG 密钥验证运行者是否正确安装在机器上。

在本例中,我们将使用 Windows Server 2019 虚拟机和 runner 通过 CircleCI 创建可执行文件。如果你愿意,你可以为其他用例修改这个过程。

以下是配置示例:

version: 2.1
orbs:
  msix: circleci/microsoft-msix@1.1
jobs:
  build:
  	docker:
  		- image: cimg/go:stable
  	steps:
  		- checkout
  		- run:
  			command: GOOS=windows GOARCH=amd64 go build -o your-package.exe
  			name: Build application for Windows
  		- persist_to_workspace:
  			root: .
  			paths: your-package.exe
  sign:
	shell: powershell
    machine: true
    resource_class: your-namespace/windows-vm
    steps:
      - attach_workspace:
      		at: .
      - msix/sign:
          package-name: your-package
workflows:
  demo:
    jobs:
      - build
      - sign:
      		requires: ["build"] 

这个工作流程的第一步是在 CircleCI 上执行密集的构建过程。保存到工作区,将二进制文件传输到 runner 机器。这种方法使您自己的基础结构很小,但是足够强大,可以控制您的签名密钥或证书。

结论

代码签名对于分发应用程序变得越来越重要。一些系统拒绝执行未经可信方签名的二进制文件。由于有了 runner,很容易将 CircleCI Cloud 的速度与在自己控制的机器上运行敏感工作负载的安全性结合起来。我希望这篇教程对你有所帮助,我也希望你能受到启发,带领你的团队完成更多与跑步者的代码签名的应用。

组件测试与单元测试

原文:https://circleci.com/blog/component-vs-unit-testing/

测试是软件开发生命周期的重要部分。它在持续集成/持续部署(CI/CD)管道中扮演着重要的角色,使开发人员能够一致地发布可靠、有弹性和安全的软件。

测试和测试方法论有很多种:端到端测试、动态测试、集成测试、其他。本文主要关注组件测试和单元测试。您将了解它们是什么,它们的区别,每种方法更适合的场景,以及如何自动化您的单元测试和组件测试,以便更快、更可靠地交付。

让我们从解释什么是单元测试和组件测试开始。

什么是单元测试?

单元测试是一种开箱测试的形式,在这种测试中,测试评估的是代码的内部工作——它的结构和逻辑——而不是它对最终用户的功能。单元测试包括测试独立于软件其余部分的单个代码段(单元)。单元测试是在与系统其余部分完全隔离的情况下创建和执行的。这种分离是单元测试和集成测试的主要区别,后者关注的是代码单元和组件如何相互协作。

为了执行独立的单元测试,开发人员创建他们正在测试的功能需要的所有资源的模型。这确保了函数得到的输入是一致的,输出是可预测的。因为输出是可预测的,所以您可以对这些输出设置检查和验证,这样就可以快速测试对这段代码的更改。只要它通过了检查和验证,您就可以确信一切仍然正常工作。单元测试类似于并且经常与功能测试结合在一起,在功能测试中,输出与预期的结果进行比较。

单元测试是测试驱动开发 (TDD)的重要组成部分,这种方法鼓励你在编写代码之前创建单元测试。这种方法让您对预期的输入和输出有了清晰的认识,并确保只有通过测试所需的代码才能进入您的应用程序,帮助您保持专注,避免范围蔓延

什么是组件测试?

组件测试是封闭盒测试的一种形式,意味着测试评估程序的行为,而不考虑底层代码的细节。组件测试是在开发完成后,对整个代码部分进行的。

组件测试比单元测试需要更长的时间,因为组件是由多个代码单元组成的。虽然这很费时间,但仍然是非常必要的。有时单个的单元可以独立工作,但是当你一起使用它们的时候就会出现问题。

组件测试检查用例,所以它可以被认为是一种端到端(E2E)测试的形式。端到端测试和组件测试复制真实场景,并从用户的角度针对这些场景测试系统。

实现组件测试是有益的,因为它可以揭示用户可能遇到的 bug 和错误。在用户知道这些错误之前捕捉并修复它们总是更好的。

有两种类型的组件测试:小型组件测试和大型组件测试。

小型组件测试

在小规模的组件测试中,被测试的组件仍然与系统中的其他组件隔离开来。您仍然应该利用模型和测试端点来模拟与被测试的组件相连接的组件。这种形式的测试确保组件准备好与系统的其余部分集成。

大规模组件测试

组件测试是在没有隔离的情况下完成的,这意味着被测试的组件可以访问外部组件。在大规模组件测试中,仍然只有主组件被测试,而不是连接的组件或者组件之间如何交互。这就是集成测试。

组件测试与单元测试:你应该使用哪一个?

单元测试通常是根据设计规范建立的,而组件测试是由用例组成的。

首先,开发人员根据每个代码单元的设计规范创建单元测试。您希望确保每个组件都正常工作。组件测试在测试过程中使用真实场景,这为您提供了一个很好的指示,说明软件一旦发布将会如何执行。

单元测试和组件测试是独一无二的测试方法,实际上在一起工作效果最好。开发人员在开发的同时运行单元测试。整个组件完成后,测试工程师或 QA 团队进行组件测试。

单元测试和组件测试用例

有些情况更适合单元测试或组件测试——它们不是同义词。

单元测试最适合系统的内部工作,比如在系统和数据库之间操作数据,或者映射 API 调用中使用的数据。组件测试在用例可以被充实和测试的地方工作得最好,就像网站或应用程序上的页面。

组件测试的另一个关键之处是当一个组件依赖于几个较小的组件或特性时。每个小功能都可以进行单元测试,但是整个组件必须在所有部分都运行时进行测试。

为了获得最佳覆盖率,您应该结合使用这两种测试方法。例如,您可以使用这两者来测试网站注册表单。表单上的字段包括名字、姓氏、电子邮件地址、密码和一个要求您重复密码的字段。然后,您通常会发现一个 submit 按钮,它会接收表单中的字段,并将其发送给一个 API 类型的服务。

字段和提交按钮的验证最好用单元测试来测试,以检查函数是否正确地处理数据并按照预期做出响应。开发人员将根据设计规范创建模拟值来执行测试。

例如,如果 API 的名字有 25 个字符的限制,开发人员将选择一个短名字来遵守字符限制,这不考虑无效输入。这就是组件测试有用的地方。

由于组件测试是封闭盒测试的一种形式,测试人员不知道设计限制。因此,他们会基于用户可能做的事情和假设场景创建一个用例列表,比如输入一个短名字、一个长名字,或者如果没有输入名字。组件测试填补了单元测试没有覆盖的空白,确保了一个全面且经过测试的系统。测试代码越多,测试代码的场景越多,应用程序就越好。

如何使用 CI/CD 进行自动化测试

一旦创建了所有的单元测试,就可以将用例写成代码了。之后,您可以实现一个 CI/CD 管道来自动化测试过程。这意味着每次提交到存储库的一个分支时,管道将自动构建这个分支并运行您创建的测试。

自动化测试在软件交付过程中释放了大量时间——尤其是当代码库和系统变得更加庞大的时候。这也让提交代码变更时感到安心。开发人员将立即知道他们实现的更改是否对他们的代码产生了负面影响。如果提交的变更意外地影响了系统的不同部分,那么对测试的检查和验证将会失败。

结论

在本文中,您了解了单元测试和组件测试之间的区别,以及自动化测试套件的好处。每一次提交,你都会确信你的系统的每一个方面都处于工作状态。如果有些东西不符合您的测试要求,您将会立即知道,而不是等到用户报告问题时。测试软件的好处远远超过创建和维护测试所需的时间——尤其是当它们是自动化的时候。

实现一个有效的测试计划可能需要一些时间,并且自动化测试过程可能是复杂的。CircleCI 可以帮助自动化您的 CI/CD 管道,以加速您团队的交付并增强您对代码的信心。首先,立即注册一个免费帐户

CI/CD 管道中的条件工作流执行| CircleCI

原文:https://circleci.com/blog/conditional-pipeline-execution/

本教程涵盖:

  1. 什么是条件以及如何在 CI/CD 管道中使用它们
  2. 为什么您可能希望在管道中使用条件
  3. 如何使用条件执行设置管道配置

持续集成和持续部署(CI/CD)的 DevOps 实践改进了软件交付。CI/CD 平台监控并自动化应用程序开发流程,确保更快、更好地开发应用程序。CI/CD 管道构建代码、运行测试,并部署已通过所有自动化检查的应用程序的生产就绪版本。

由于不断需要确保流程的完整性,开发团队可能希望基于前一个作业的结果来执行管道作业。本教程将向您介绍条件管道,以及如何根据您指定的条件在 CI/CD 工作流中选择性地执行作业。我将解释如何设置管道,如何为它们的执行设置条件,以及job A的失败如何影响job B的执行并对管道的结果有所贡献。

先决条件

完成本教程需要以下内容:

  • 系统上安装的 Node.js
  • 一个的账户
  • GitHub 的一个账户
  • 了解 CircleCI 管道的工作原理

具备这些先决条件后,您就可以开始学习本教程了。

了解条件管道执行

大多数管道由多个步骤组成,在软件的最终版本交付之前必须完成这些步骤。可以根据指定的一个或一组条件跳过一个或多个管道阶段。

为了有效,管道应该有明确定义的步骤,在进行下一步之前必须对这些步骤进行验证。下面是一个包含四个作业的管道示例:

  • lint check
  • code build
  • testing
  • deployment

每个作业都依赖于正在验证或正在成功运行的前一个作业。如果您想要部署一个已经通过所有测试的应用程序,那么您已经为您的 CI/CD 管道创建了一个条件。也就是说,在执行部署步骤之前,必须通过代码和构建步骤。

为了让您更好地理解条件管道执行,我创建了一个三步管道的图示。

Conditional pipeline execution

有三条流水线,每条流水线有三个作业。在第一个管道中,验证了所有作业,管道成功。但是,在第二个管道中,第一个作业通过了,触发了构建和测试,但是第二个步骤中的失败会阻止部署作业。管道出现故障。

管道 3 与其他管道的不同之处在于,如果第一个作业失败,进程会立即停止。

设置 API 在测试执行前运行 lint

本节教程将演示 lint 故障如何导致管道故障。

确保您的代码具有适当的格式和林挺,为运行测试和构建提供了良好的基础。一个任务的失败(例如,如果您的代码没有正确地链接)应该阻止构建和测试阶段的触发。管道不会满足条件,会失败,这是意料之中的。

我已经为您构建了一个 API,供您在本教程中使用。您需要做的就是克隆存储库:

git clone https://github.com/CIRCLECI-GWP/conditional-pipeline-execution-demo.git 

这个命令将项目克隆到本地机器上一个名为conditional-pipeline-execution-demo的目录中。这应该在您克隆存储库的当前工作目录中。完成后,运行下面的命令。

注意: 要在 CircleCI 上运行管道,我建议您创建一个 Mongo Atlas 帐户,并根据克隆存储库的README.md中提供的说明进行设置。

更改目录:

cd  conditional-pipeline-execution-demo 

安装必要的依赖项:

npm install 

安装完成后,使用以下命令启动服务器:

npm  start 

本教程的 API 现在正在您的本地机器上运行。可以通过访问http://localhost:3000/进行测试。

好消息:这个 API 已经正确配置了漂亮的ESLint 。lint 和更漂亮的配置在package.json文件中处理。还编写了一些测试。

接下来,确保存储库格式遵循配置的更漂亮的指导方针。运行以下命令启动 lint 和格式化检查:

npm run format:check # checks if formatting guidelines are followed 

用更漂亮的格式格式化所有文件:

npm run format:write # Writes onto formatted files applying the eslint and prettier rules 

Lint 代码:

npm run lint:check # checks if code is linted 

Formatting and linting commands

现在,您已经验证了格式和林挺按预期工作,您可以配置 CircleCI 管道,包括在满足条件时选择性地执行管道。

CircleCI 配置文件设置

在 API 的根目录下创建一个.circleci目录。在其中,创建一个名为config.yml的空文件,并添加以下内容:

# CircleCI configuration file

version: 2.1
workflows:
   lint-build-test:
       jobs:
           - lint-code
           - build-and-test:
                 requires:
                     - lint-code
jobs:
   lint-code:
       docker:
           - image: cimg/node:16.13.2
       steps:
           - checkout
           - run:
                name: update npm
                command: 'sudo npm install -g npm'

           - restore_cache:
                key: dependency-cache-{{ checksum "package-lock.json" }}

           - run:
                name: install dependencies
                command: sudo npm install

           - save_cache:
                key: dependency-cache-{{ checksum "package-lock.json" }}
                paths:
                    - ./node_modules

           - run:
                 name: lint check
                 command: npm run format:check

    build-and-test:
        docker:
            - image: cimg/node:16.13.2
        steps:
            - checkout
            - run:
                    name: update npm
                    command: 'sudo npm install -g npm'

            - restore_cache:
                    key: dependency-cache-{{ checksum "package-lock.json" }}

            - run:
                    name: install dependencies
                    command: sudo npm install

            - save_cache:
                key: dependency-cache-{{ checksum "package-lock.json" }}
                paths:
                        - ./node_modules

            - run:
                    name: run tests
                    command: npm test 

这个config.yml文件有一个包含两个任务的工作流:lint-codebuild-and-test

注意在工作流定义中使用了requires键,它指定了lint-code是成功执行build-and-test任务所必需的。这意味着只有当第一个作业成功时,才会执行第二个作业。当第一个作业失败时,进程会自动停止,因此在这里设置一个条件。Lint 检查必须通过,我们的管道才能成功地构建和测试代码。

按照工作流程,我们将定义实际的作业。这两项工作的第一步都是指示 CircleCI 使用node docker 图像。然后,他们将更新节点包管理器,并使用npm来安装所需的依赖项。这两项工作的最后一步都是执行这三个动作:lintchecktest。该配置还使用restore_cachesave_cache步骤来缓存依赖项,然后恢复它们。这些步骤大大减少了执行管道所需的时间。

将代码推送到 GitHub

注意: 如果您克隆了存储库,那么变更已经存在于存储库中,因此这是一个可选的步骤。如果您正在使用不同的存储库和相同的配置,您将需要将更改推送到该存储库。

保存您的工作,提交并推送更改到 GitHub 存储库。从 CircleCI 仪表板项目部分,点击库名称旁边的设置项目

Select project on CircleCI

当你被提示时,使用main,这是我们默认的分支。然后点击设置项目开始在 CircleCI 上运行项目。

Setting up a project on CircleCI

CircleCI 仪表板将显示该构建失败,这是意料之中的。这完美地向我们介绍了本教程的下一节,关于管道的条件执行。

管道的条件执行

由于我们有意引入的 lint 问题,我们的构建失败了。为了使管道有效,您需要能够在一组条件不满足时停止它。如果测试失败,或者当项目的林挺失败时,您应该能够终止部署。

  • 管道中的lint check步骤显示了一个步骤中的故障如何级联到管道的其余部分。CircleCI 跳过了运行我们的测试,因为它在执行来自我们的package.json文件中的命令npm run format:check时遇到了一个故障。成功完成那项工作是the build-test工作的要求。
  • format:check命令运行prettier --list-different .命令,验证所有文件是否使用了更漂亮的配置。如果发现与配置不匹配的文件,就会抛出异常。当遇到未打印的文件时,会在 CI 上抛出异常,并且管道会失败。

Failing lint check

由于 lint 失败而导致的管道故意失败,意味着您现在可以验证,如果在运行不是并行运行时某个步骤失败,后续步骤将不会执行,并且管道将会失败。在这种情况下,我们已经在代码片段的配置中明确声明了这一点:

# congig.yml line 6, 7, and 8
- build-and-test:
    requires:
      - lint-code 

Failing lint check

现在,您已经配置了一个管道,只在满足特定条件时运行特定的作业。您还可以编写并行管道,每个管道都有一组不同的条件。CircleCI 可以很容易地解释这一点,并正确地执行您的管道。

高级条件执行

在这一节中,我将提供更多关于根据条件设置运行或不运行管道的细节。本节涵盖:

  • 为手动批准保留工作流
  • 使用“when”和“or”逻辑语句

为手动批准保留工作流

我们可以配置工作流,要求在进入下一个作业之前进行手动作业批准。任何拥有回购推送权限的人都可以通过点击“批准”按钮来批准暂停的工作流程,从而恢复工作流程。

我们已经建立了一个工作流程。您可以通过将带有关键字type: approval的作业添加到作业列表来修改它。

workflows:
  lint-build-test:
    jobs:
      - lint-code
      - hold:
          type: approval
          requires:
            - lint-code

      - build-and-test:
          requires:
            - hold 

这段代码向名为hold的工作流添加了一个新任务。type: approval是将暂停工作流的键值对。在现实世界中,您可以出于获得 QA 或部署经理的批准或手动防止意外部署等目的而保留管道。使用requires键在hold任务中设置一个条件,当lint-code成功时运行它。在hold作业被批准后,任何需要它的后续作业将运行。然后,用户可以手动继续构建和测试作业。

将这些更改提交给 GitHub。在 CircleCI 仪表板上,第一个作业执行,然后挂起并等待批准。

Hold executing pipeline

点击仪表板上的暂停选项卡,进入下一步,其中包括工作流程的可视化表示。点击保持框,然后在弹出窗口中批准作业。

Approved job

作业获得批准后,工作流恢复,后续作业可以运行。

使用逻辑语句控制管道执行

除了使用requires关键字定义工作流条件之外,还可以将when子句与逻辑语句结合使用。当逻辑语句用于when子句时,它们解析为truefalse值。在此示例中,您将修改之前的工作流,并仅在修改临时分支或主分支时执行该工作流。进行如下代码块所示的更改:

workflows:
  lint-build-test:
    when:
      or:
        - equal: [main, << pipeline.git.branch >>]
        - equal: [staging, << pipeline.git.branch >>]
    jobs:
      - lint-code
      - hold:
          type: approval
          requires:
            - lint-code
      - build-and-test:
          requires:
            - hold 

whenor子句设置了一个条件,阻止工作流lint_build_test运行,除非工作流在分支stagingmain上。

现在,如果您在任何其他分支上运行它,工作流将无法执行。

Approved job

使用这些标志,您可以提供诸如“发布只能在发布分支上进行”或“部署只能在主分支上进行”之类的规则,以避免意外的生产前部署。

验证成功构建

现在,您已经知道如何编写条件管道以及条件如何影响管道结果,是时候解决林挺问题并将代码推送到 GitHub 了。进行更改后,您的管道会成功执行,并且lint-codebuild-and-test作业也会成功执行。

Successful execution

瞧啊。您的管道现在是绿色的。它运行 lint-check 管道,然后继续构建和测试代码。在这种庆祝模式下,是时候结束本教程了。

结论

在本教程中,您了解了条件管道以及如何构建它们。您在 CircleCI 管道中执行了一个作业,条件是第一个作业必须在第二个作业执行之前通过。您甚至尝试了一些更高级的概念:持有工作流并使用whenor语句。您有第一手的经验,即一个作业的失败会导致连锁故障,从而关闭整个管道。您还学习了如何通过内置的 CircleCI 特性实施控制。并且您能够观察到您的条件管道成功执行。我希望你喜欢这个教程,并发现它很有用。直到下次,继续学习!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

配置最佳实践:并发性和并行性| CircleCI

原文:https://circleci.com/blog/config-best-practices-concurrency-parallelism/

您上次更新 CI/CD 工作流是什么时候?一年前?从来没有?我的朋友们,你们并不孤单。重新配置工作流可能是 DevOps 从业者最艰巨的任务之一。但是有了从 CircleCI 计划中获益的新机会,有一个简单而有效的起点:理解并发和并行。

使用并发性和并行性可以显著缩短构建时间。但是您需要知道它们是什么,以及如何在您的配置文件中找到它们。

什么是并发?

并发意味着多个计算任务同时发生。它在计算中无处不在。我们有如此多的事情同时发生:应用程序运行,计算机在网络中通信,甚至用户访问网站。并发是如此普遍,以至于很容易假设我们知道它是如何工作的。虽然定义总是保持不变,但并发的实际应用可能会有细微差别。那么在 CircleCI 中并发意味着什么呢?

简单地说,CircleCI 中的并发性是在任何时间点正在执行的任务的数量。例如,CircleCI 的免费计划提供了 30 的并发限制,这意味着您可以同时运行多达 30 个任务。

计算有多少并发任务正在发生比找出有多少作业正在运行或者有多少容器正在被使用要复杂一些。流水线可以有各种各样的曲折来改变并发任务的数量,像条件逻辑测试分割。在这篇文章中,我将重点讨论如何管理并发性以及何时管理并发性很重要。

什么是并行?

在 CircleCI 中,如果不谈论并行性,很难写出并发性。这两个概念经常被混淆,但有不同的应用。我们已经知道并发是在工作流中任何给定时间执行任务的数量。我们可以通过改变并行性来影响并发性,这也是一些混乱开始的地方。

并行性在特定作业的相同副本之间分割工作。

并行最常用于分割测试套件。作业的所有副本都有相同的指令,但运行时使用不同的变量。并行度在 CircleCI 配置文件中设置,并行作业的数量计入您的并发总数。

应用并发和并行

这里有一个使用 CircleCI free 计划的例子,它的并发限制是 30。假设您有 10 个作业,每个作业需要 1 分钟来执行。如果没有并发,此工作流将需要 10 分钟才能完成。有了并发性,这些作业中的每一个都可以同时运行,工作流将在 1 分钟内完成,而不是 10 分钟。如果将并行度设置为 3,您仍然可以同时运行 10 个作业,并且不会超出并发限制。您可以在一分钟内运行 10 个作业的 3 个副本,总共完成 30 个任务。

对于总共 28 个并发任务,您可以轻松地在 7 个作业上运行 4 个副本(并行度为 4)。自由计划允许的最大并行度为 4,但如果你真的需要速度,其他计划有更多的选择。

如果您遇到同时执行的任务总数超过 30 个的情况,一些工作将不得不等待。例如,如果您将 8 个作业的并行度设置为 4,则其中一个作业(及其所有副本)将不得不等待,直到有可用资源。由并行性创建的作业副本总是同时运行,因此即使您仅超出并发限制 2(如本例所示),一个作业的所有四个副本都会等待另一个作业完成。

如何为您的管道增加并行性和并发性

大多数时候,您不需要担心并发性。并发限制由您的 CircleCI 计划设置,并在后台实施。您可能根本不需要管理并发性。大多数情况下,您将通过使用并行性来利用您的并发性。设置并行性很简单:在 config.yml 中设置 parallelism 键的值。任何大于 1 的值都意味着您正在运行并行任务。

如何设置并行度

 ~/.circleci/config.yml

version: 2
jobs:
  test:
    docker:
      - image: cimg/<language>:<version TAG>
        auth:
          username: mydockerhub-user
          password: $DOCKERHUB_PASSWORD  # context / project UI env-var reference
    parallelism: 3 

这个例子有一个作业(名为 test)正在 Docker 上运行。将 parallelism 设置为 3 意味着该作业的三个副本(称为任务)将同时在三个独立的 Docker 容器上运行。这些任务之间的唯一区别是环境变量,因此可以在这些任务之间划分工作。这最常用于测试分割,你可以在我们的文档中读到更多。

结论

作为 DevOps 实践者,我们关心并发性,因为它通过同时运行多个进程来帮助更快地完成工作。了解计划的并发限制以及所有与它相关的任务有助于优化您的构建。

并发可能是一个简单的概念。当至少有一些作业可以同时运行时,它们会更快地完成。并行性有助于您定制并发执行的任务,并且可以在配置文件中轻松设置。并发性和并行性都有助于更快地完成任务,这样您就可以着手处理失败、传递和交付软件的重要事务。

下一步是继续学习测试拆分,并继续自动化和加速您的工作。如果您对 CircleCI 配置的专家评估感兴趣,以帮助优化您的工作,您可以通过注册高级支持计划获得专门支持工程师的定制评估。

配置最佳实践:依赖项缓存| CircleCI

原文:https://circleci.com/blog/config-best-practices-dependency-caching/

让我们面对现实:创建最佳 CI/CD 工作流并不总是一项简单的任务。事实上,编写有效和高效的配置代码是许多开发人员在 DevOps 之旅中面临的最大障碍。但是,您不需要成为专家就可以建立快速、可靠的测试和部署基础设施。通过一些简单的技巧,您可以优化您的config.yml文件并释放您的 CI/CD 管道的全部潜力。

在本系列中,我们将深入探讨我们的解决方案工程师在与企业级客户进行一对一配置审查时提出的一些常见建议。今天,我们重点关注依赖缓存,这是一种强大的技术,用于保存作业之间的构建数据,并加速您的工作流。

什么是依赖缓存?

用一般的计算术语来说,缓存是一个过程,其中频繁使用的数据被存储在内存中,以便可以快速检索供将来使用。在 CI/CD 管道中,您通常需要在构建阶段安装相同的依赖项,即运行应用程序所需的库或包。为了避免每次运行工作流时重复下载相同的依赖项,您可以使用依赖项缓存来保存这些包,以便在将来的作业中使用。

缓存作业之间的依赖关系可以减少几秒甚至几分钟的构建时间。提高构建速度会导致更少的时间浪费和更快的测试反馈,允许您和您的团队快速有效地向您的用户发布变更。

什么时候应该使用依赖项缓存?

缓存依赖项是我们向希望加快工作流速度的用户提出的最常见的建议之一。这种技术对于依赖于包管理器的项目特别有用,例如 Node.js 的 npm 和 Yarn、Python 的 pip 和 Ruby 的 Bundler,以便在构建过程中安装多个二进制文件。

例如,使用 React 和 Node.js 编写的全栈 web 应用程序可以快速积累数十、数百甚至数千个依赖项和子依赖项。如果没有依赖项缓存,下面的config.yml文件将在每次build_and_test作业运行时下载应用程序所需的每个包:

jobs:
  build_and_test:
    docker:
      - image: cimg/node:16.11.1
    steps:
      - checkout
      # install dependencies
      - run:
          name: install dependencies
          command: npm install
      # run test suite	
      - run:
          name: test
          command: npm run test 

此配置缺少依赖项缓存。它将 CircleCI 的 Node.js Docker image 设置为执行环境,并在安装必要的依赖项和运行为项目定义的测试之前,从相关的 Git 存储库中检出应用程序代码。因为没有设置依赖项缓存,build_and_test作业将在每次工作流运行时从头开始,不必要地一遍又一遍安装相同的依赖项。

如何向管道添加依赖项缓存

向 CircleCI 工作流添加依赖关系缓存非常简单,只需为您创建的每个版本的缓存设置restore_cachesave_cache步骤以及唯一标识符或键。这是您在上面看到的相同配置,这次使用依赖项缓存进行了优化:

...
jobs:
  build_and_test:
    docker:
      - image: cimg/node:16.11.1
    steps:
      - checkout
      # look for existing cache and restore if found
      - restore_cache:
          key: v1-deps-{{ checksum "package-lock.json" }}
      # install dependencies    
      - run:
          name: install dependencies
          command: npm install
      # save any changes to the cache
      - save_cache:
          key: v1-deps-{{ checksum "package-lock.json" }}
          paths: 
            - node_modules   
      # run test suite
      - run:
          name: test
          command: npm run test 

这个配置现在包括了依赖缓存。restore_cache步骤检查现有的缓存,如果找到,就将其恢复到作业中。在这种情况下,npm install将只安装那些不在缓存中的依赖项。然后,在加载完项目的所有依赖项之后,save_cache步骤会将更新后的依赖树保存到node_modules目录中的一个新缓存中。

注意,restore_cachesave_cache都包含密钥标识符。键的{{ checksum "package-lock.json" }}部分是一个动态值,称为模板。这个特殊的模板计算项目的package-lock.json文件内容的 SHA256 散列,并将v1-deps-添加到结果中。如果package-lock.json(或者您在 cache-key 中指定的任何依赖关系管理文件)发生变化,那么restore_cache将会丢失,并且save_cache将会创建一个新的缓存。

关于这个例子和一般的依赖关系缓存,还有几件重要的事情需要注意:

  • 缓存是不可变的。一旦save_cache写入给定的键,它就不能被覆盖。

  • 在运行测试之前包含save_cache步骤是很重要的,这样即使测试失败,你的依赖项也会被保存。

  • 除了依赖关系管理文件的校验和值之外,还可以在模板中使用其他动态信息,包括正在构建的 VCS 分支、CircleCI 作业号和构建的纪元时间。欲了解更多信息,请使用键和模板查阅关于的文档。

最后,如果只有少数依赖关系发生了变化,但缓存的其余部分是有效的,则可以通过设置一个回退键来恢复缓存的一部分:

- restore_cache:
        keys:
          - v1-deps-{{ checksum "package-lock.json" }}
          - v1-deps- 

在这个例子中,CircleCI 将首先尝试加载与当前版本的package-lock.json相关联的缓存。如果锁文件由于添加了依赖项而发生了更改,那么将找不到缓存。接下来,CircleCI 将使用静态回退键v1-deps-加载带有v1-deps-前缀的最新有效缓存。一旦加载了之前的缓存,npm install将下载任何缺失的依赖项。

有关部分缓存恢复的更多信息,请查看文档

结论

依赖缓存是优化 CircleCI 配置的最直接有效的方法之一。通过对您的工作流程进行一些小的调整,您可以节省大量的构建时间,让您专注于做您最擅长的事情:快速地向您的用户交付价值。

依赖项缓存只是您可以对配置进行的许多不同优化中的一种。其他基于缓存的优化包括将数据保存在工作区中,以及设置 Docker 层缓存来加速 Docker 构建。在本系列的后续文章中,请关注对这些特性和其他特性的深入研究。

如果您对 CircleCI 配置的专家级审查感兴趣,您可以通过注册高级支持计划从专门的支持工程师那里获得个性化的一对一评估。

配置最佳实践:Docker 层缓存| CircleCI

原文:https://circleci.com/blog/config-best-practices-docker-layer-caching/

让我们面对现实:创建最佳 CI/CD 工作流并不总是一项简单的任务。事实上,编写有效和高效的配置代码是许多开发人员在 DevOps 之旅中面临的最大障碍。但是,您不需要成为专家就可以建立快速、可靠的测试和部署基础设施。通过一些简单的技巧,您可以优化您的config.yml文件并释放您的 CI/CD 管道的全部潜力。

在本系列中,我们将深入探讨我们的解决方案工程师在与企业级客户进行一对一配置审查时提出的一些常见建议。今天,我们重点关注 Docker 层缓存(DLC),这是一种用于在作业之间保存 Docker 层并加快工作流程的强大技术。

DLC 直到最近才在性能和规模计划中提供,现在已包括在新的 CircleCI 免费计划以及 CircleCI 服务器的安装中。在本帖中,我们将讨论什么是 Docker 层缓存,为什么它很重要,以及如何将它添加到您的管道中。

什么是 Docker 层缓存?

Docker 是 CircleCI 平台上使用的容器化技术。在 Docker 执行器机器执行环境中,您可以运行 Docker 构建命令来封装您的应用程序以进行测试和部署。使用 Docker 层缓存,您可以保存您构建的 Docker 映像的各个层,以便在后续管道运行中重用它们。

Docker 层缓存可以绕过部分或全部映像构建步骤,从而在构建过程中为您的团队节省大量时间。Docker 映像是从 docker 文件构建的,docker 文件中的每个命令都会在映像中创建一个新层。当您使用docker builddocker compose构建 Docker 映像时,DLC 会将各个层保存到与运行作业的机器或远程 Docker 实例相连的卷中。下次运行影像构建作业时,CircleCI 将从缓存中检索任何未更改的图层。

如果您的 Dockerfile 在管道运行之间保持不变,将从缓存中检索整个图像。如果您在两次运行之间对 docker 文件进行了更改,CircleCI 将检索到该更改之前的所有层,然后基于新的 docker 文件构建图像的其余部分。您的 docker 文件在运行之间更改得越少,您的映像构建得就越快。

什么时候应该使用 Docker 层缓存?

Docker 容器从根本上改变了软件开发的前景,通过将二进制文件和依赖项打包到一个可移植的软件单元中,消除了潜在的环境冲突或“它在我的机器上工作”的难题,允许团队协作、安全和一致地构建、测试和部署应用程序。许多团队已经采用容器化作为实现云原生开发实践的方式,并将他们的软件架构转向更分布式的方法。

如果您的应用程序依赖于容器,您可能会发现自己在每次运行 CI/CD 管道时都构建相同的映像,这会严重消耗您的构建时间。通过启用 Docker 图层缓存,您可以重用缓存的图像图层,而不是在每次运行构建时从头开始构建,从而显著减少构建时间。

请注意,DLC 只会减少在远程 docker 环境中使用docker builddocker compose或类似的 Docker 命令构建自己的 Docker 映像所需的时间。它不影响旋转主对接容器所花费的时间。如果您在 Docker 容器中运行管道,但不在工作流中构建新的映像,那么您不会看到通过实现 DLC 来减少构建时间。

如何将 Docker 层缓存添加到管道中

您可以使用 Docker 层缓存来加速 Docker 和机器执行环境中的 Docker 映像构建。在 Docker 执行环境中,您创建的任何 Docker 映像都构建在一个名为 remote Docker environment 的二级容器中,这为构建过程增加了额外的安全性,并允许您访问各种 Docker 命令以及将 SSH 到您的构建中进行调试。您可以使用setup-remote-docker键启动远程 Docker 环境,然后通过将docker_layer_caching设置为true来向构建作业添加 Docker 层缓存:

version: 2
jobs:
  build:
    docker:
      # DLC does nothing here, its caching depends on commonality of the image layers.
      - image: cimg/node:17.3.0
        auth:
        username: mydockerhub-user
          password: $DOCKERHUB_PASSWORD
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      # DLC will explicitly cache layers here and try to avoid rebuilding.
      - run: docker build . 

在这个示例config.yml文件中,您将docker设置为build作业的执行环境,并设置一个远程 Docker 环境,将docker_layer_caching设置为true。CircleCI 将缓存您使用docker build命令构建的 Docker 图像的图层,以便您下次运行该作业时,可以避免重新构建任何未更改的图层。

机器执行环境不需要远程 Docker 环境来运行dockerdocker-compose命令,因此您可以将docker_layer_caching: true键直接包含在机器执行器键的下面:

version: 2
jobs:
  build_elixir:
    machine:
      image: ubuntu-2004:202104-01
      docker_layer_caching: true
    steps:
      - checkout
      - run:
          name: build Elixir image
          command: docker build -t circleci/elixir:example . 

在本例中,您将ubuntu-2004:202104-01 Linux 映像设置为机器执行器类型,然后设置docker_layer_caching: true为该环境启用 Docker 层缓存。当您使用docker build命令构建 Elixir 映像时,CircleCI 将缓存各个层,以便在以后的运行中重用。

为项目启用 DLC 后,保存的所有图层将在作业运行之间的三天内可用。任何三天后不再使用的缓存图层都将被删除。每个项目最多可以创建 50 个 DLC 卷。有关如何在您的项目中实现 Docker 层缓存的更多信息,以及一些潜在的用例和限制,请访问 DLC 文档

结论

Docker 层缓存是在 CircleCI 上加速 Docker 构建的重要部分,现在包含在 CircleCI 免费计划中。如果您的团队构建容器化的应用程序,您可以通过在项目中重用未更改的 Docker 层来节省宝贵的开发时间。这不仅使您的团队更加高效,并为您的组织节省资金,而且还帮助您更快地向您的用户交付价值,这是当今软件开发环境中的一个关键优势。

DLC 只是您可以对配置进行的许多不同优化中的一种。其他基于缓存的优化包括将数据保存在工作区中,以及缓存依赖项以加快构建。关于依赖缓存的更多信息,请参见配置最佳实践:依赖缓存。请继续关注本系列后续文章中对可用配置优化的其他深入探讨。

要开始使用最强大的 CI/CD 平台更快、更自信地构建、测试和部署您的应用,立即注册免费计划。如果您对 CircleCI 配置的专家级审查感兴趣,可以通过注册高级支持计划,获得专门支持工程师的个性化一对一评估。


CircleCI 配置 SDK | CircleCI 简介

原文:https://circleci.com/blog/config-sdk/

我们很高兴地宣布,新的 CircleCI Config SDK 现已作为开源类型脚本库提供。开发人员现在可以使用 TypeScript 和 JavaScript 编写和管理他们的 CircleCI config.yml文件。

对于习惯了成熟编程语言的生态系统和灵活性的开发人员来说,YAML 有时会感到受限或令人生畏。使用 Config SDK,您可以从类型安全和带注释的 JavaScript 定义和生成您的 YAML 配置。您甚至可以利用包管理来模块化配置代码的任何部分,以便重用。

当与 CircleCI 的动态配置配合使用时,Config SDK 可以在运行时动态构建您的 CI 配置,允许您根据任意数量的因素选择要执行的内容,例如 Git repo 的状态、外部 API 或只是一周中的某一天。

入门指南

对于我们的例子,假设我们管理几个 Node.js 项目,它们都是使用相同的框架构建的,并且通常需要相同的 CI 配置。因此,我们决定构建一个所有项目都将使用的配置“模板”,并且可以集中管理和更新。我们将创建并发布一个 NPM 包,它将为我们所有的节点项目生成完美的配置文件。

</blog/media/2022-09-19-config-sdk.mp4>

让我们构建配置模板包,然后构建将使用它的管道。

设置

我们将从创建一个标准的 NPM 包开始。您可以使用 TypeScript 或 JavaScript,但是为了加快速度,我们将在本例中使用 JavaScript。此处显示的示例基于回购 wiki 中的这一页。

首先在一个新目录中初始化一个 JavaScript 项目。

mkdir <your-package-name>

cd <your-package-name>

npm init -y

npm i --save @circleci/circleci-config-sdk 

@circleci/circleci-config-sdk包将允许我们用 JavaScript 定义一个 CircleCI 配置文件。虽然我们可以简单地定义一个配置并导出它,但是我们也可以利用动态配置并导出一个函数。在我们的示例中,我们将保持简单,创建一个配置生成函数,它将为我们的部署采用一个tag参数,并采用一个path参数来选择配置文件将被导出到的位置。

创建应用程序

创建一个index.js文件并导入 CircleCI 配置 SDK 包和节点的fs包,这样我们就可以将配置写到一个文件中。

const CircleCI = require("@circleci/circleci-config-sdk");
const fs = require('fs'); 

接下来,我们将开始使用 Config SDK 构建配置文件的组件。您会注意到,因为我们正在使用基于类型脚本的库,所以我们能够接收代码提示、类型定义、文档和自动完成。

创建执行者

假设我们正在为 Node.js 项目构建一个配置,我们将从定义我们的作业将使用的 Docker 执行器开始。您可以传入 Docker 映像、资源类和您想要配置的任何其他参数。

// Node executor
const dockerNode = new CircleCI.executors.DockerExecutor(
  "cimg/node:lts"
); 

创造就业机会

我们正在构建一个工作流,它将在每次提交时测试我们的应用程序,并在我们提供某个标签时部署它。像我们的执行器一样,我们将定义这两个作业,都使用我们刚刚定义的执行器,并且每个都有一组用于各自目的的独特步骤。

// Test Job
const testJob = new CircleCI.Job("test", dockerNode);
testJob.addStep(new CircleCI.commands.Checkout());
testJob.addStep(new CircleCI.commands.Run({ command: "npm install && npm run test" }));

//Deploy Job
const deployJob = new CircleCI.Job("deploy", dockerNode);
deployJob.addStep(new CircleCI.commands.Checkout());
deployJob.addStep(new CircleCI.commands.Run({ command: "npm run deploy" })); 

可以使用步骤实例化作业,或者将作业动态添加到现有作业中,如上所示。在这个过于简化的例子中,我们缺少了一个缓存步骤,但是您可以看到我们是如何构建配置元素的。

创建工作流

定义了我们的工作之后,是时候在工作流中实现它们,并定义它们应该如何运行了。我们之前提到过,我们希望test作业在所有提交时运行,而deploy作业只在给定的标签上运行。

现在我们正在使用配置文件的顶级组件,让我们最后定义一个新的 CircleCI 配置对象,并命名我们将向其中添加作业的工作流。

//Instantiate Config and Workflow
const nodeConfig = new CircleCI.Config();
const nodeWorkflow = new CircleCI.Workflow("node-test-deploy");
nodeConfig.addWorkflow(nodeWorkflow); 

我们没有向测试作业添加任何参数,因为我们希望在所有提交时运行它,所以我们可以直接将它添加到我们的 config 对象中。

nodeWorkflow.addJob(testJob); 

对于部署作业,我们需要首先定义工作流作业,以便我们可以向它添加过滤器。我们现在要添加一个过滤器,告诉 CircleCI 忽略所有分支的这个作业,这样它就不会在每次提交时都执行。我们一会儿将处理为标签启用它。

const wfDeployJob = new CircleCI.workflow.WorkflowJob(deployJob, {requires: ["test"], filters: {branches: {ignore: ".*"}}});
nodeWorkflow.jobs.push(wfDeployJob); 

导出配置生成器功能

现在我们已经定义好了所有的东西,我们准备好创建和导出最终的部分。我们将创建一个函数,它接受我们前面提到的标签和路径参数,并将我们定义的内容写入一个新文件。

/**
* Exports a CircleCI config for a node project
*/
export default function writeNodeConfig(deployTag, configPath) {
 // next step
} 

在新创建的writeNodeConfig函数中,我们将把这里传递的标记过滤器添加到我们工作流中的部署作业中,最后使用 config 对象上的generate函数将配置写到由path参数提供的文件中。

/**
* Exports a CircleCI config for a node project
*/
export default function writeNodeConfig(deployTag, configPath) {
  wfDeployJob.parameters.filters.tags = {only: deployTag}
  fs.writeFile(configPath, nodeConfig.generate(), (err) => {
    if (err) {
      console.error(err);
      return
    }
  })
} 

这里是完整的源代码,你也可以在 wiki 中找到:

const CircleCI = require("@circleci/circleci-config-sdk");
const fs = require('fs');

// Node executor
const dockerNode = new CircleCI.executors.DockerExecutor(
  "cimg/node:lts"
);

// Test Job
const testJob = new CircleCI.Job("test", dockerNode);
testJob.addStep(new CircleCI.commands.Checkout());
testJob.addStep(new CircleCI.commands.Run({ command: "npm install && npm run test" }));

//Deploy Job
const deployJob = new CircleCI.Job("deploy", dockerNode);
deployJob.addStep(new CircleCI.commands.Checkout());
deployJob.addStep(new CircleCI.commands.Run({ command: "npm run deploy" }));

//Instantiate Config and Workflow
const nodeConfig = new CircleCI.Config();
const nodeWorkflow = new CircleCI.Workflow("node-test-deploy");
nodeConfig.addWorkflow(nodeWorkflow);

//Add Jobs. Add filters to deploy job
nodeWorkflow.addJob(testJob);
const wfDeployJob = new CircleCI.workflow.WorkflowJob(deployJob, {requires: ["test"], filters: {branches: {ignore: ".*"}}});
nodeWorkflow.jobs.push(wfDeployJob);

/**
* Exports a CircleCI config for a node project
*/
export default function writeNodeConfig(deployTag, configPath) {
  wfDeployJob.parameters.filters.tags = {only: deployTag};
  fs.writeFile(configPath, nodeConfig.generate(), (err) => {
    if (err) {
      console.error(err);
      return
    }
  });
} 

发布包

随着您的index.js文件的完成和writeNodeConfig函数的导出,是时候将包发布到您选择的包存储库,比如 NPM 或 GitHub。

完成后,您应该能够将您的包导入到其他项目中,就像我们之前导入@circleci/circleci-config-sdk一样。

创建 CI 渠道

您现在有了一个可以生成 CircleCI 配置文件的已发布的 NPM 包。我们可以使用 CircleCI 的动态配置在运行时拉入这个包,并动态运行我们生成的配置文件。我们可以在许多类似的 NodeJS 项目中复制这个基本模板,当我们想要更新或更改我们的配置时,我们将能够简单地更新我们创建的包。

创建 config.yml

像往常一样,我们的 CircleCI 项目需要一个.circleci目录和一个config.yml文件。在这种情况下,配置文件将是我们所有项目中使用的基本模板,它只是告诉 CircleCI 为当前管道启用动态配置特性,生成新的配置文件,并运行它。我们还将创建一个dynamic目录,供以后使用。

└── .circleci/
    ├── dynamic/
    └── config.yml` \ 

使用下面的示例配置文件:

version: 2.1
orbs:
  continuation: circleci/continuation@0.3.1
  node: circleci/node@5.0.2
setup: true
jobs:
  generate-config:
    executor: node/default
    steps:
      - checkout
      - node/install-packages:
          app-dir: .circleci/dynamic
      - run:
          name: Generate config
          command: node .circleci/dynamic/index.js
      - continuation/continue:
          configuration_path: ./dynamicConfig.yml
workflows:
  dynamic-workflow:
    jobs:
      - generate-config 

您可以在 GitHub 上的 Wiki 中找到这个配置文件和其他示例。

这个配置是一个样板文件,负责执行我们的包,其中包含我们想要执行的“真正的”逻辑。

请注意,setup键被设置为true,启用了流水线的动态配置。使用 Node orb,我们安装一个位于.circleci/dynamic的节点应用程序(我们将回到这一点),并运行 Node 中的.circleci/dynamic/index.js。这将使用我们之前写的包并在./dynamicConfig.yml创建一个新的配置文件,最终由continuation orb 执行。

我们将在所有的节点项目中使用这样的配置,它不太可能需要经常更新或更改。我们没有修改这个配置,而是更新了我们之前创建的包。

创建配置应用程序

最后要做的是构建我们的“配置应用程序”。这是负责实现我们的包的应用程序,并作为我们计划执行的“真实”配置的来源。因为在这个例子中,我们将配置的大部分逻辑外包给了一个外部包,所以在这个例子中,我们的配置应用程序大部分也是样板文件。

将目录更改为.circleci/dynamic,我们将在这里设置我们的应用程序。初始化一个新的存储库并安装您之前发布的包。

npm init -y

npm i <your-package-name> 

运行这两个命令后,您应该有一个显示您的包的依赖关系的package.json文件。

"dependencies": {
  "my-circleci-node-config": "^1.0.0",
} 

您可以修改语义版本字符串来指定在运行时您的包的版本。这意味着,您可以更新您的my-circleci-node-config包,并且,如果您愿意,让所有使用这个包的 CircleCI 项目在下次您的 CI 管道被触发时立即获得这些更改。

不推荐:假设您一直想获取您创建的自定义依赖项的最新版本,您可以使用:

"dependencies": {
  "my-circleci-node-config": "x",
}, 

为了安全起见,只引入次要的补丁更新,而不是主要版本。

"dependencies": {
  "my-circleci-node-config": "1.x",
}, 

最后,在.circleci/dynamic.index.js.中使用您的定制包

我们的包导出了一个名为writeNodeConfig的函数,它接受我们想要触发部署的tag的值,以及我们想要将配置导出到的path。我们知道之前在 config.yml 中的路径,我们设置为./dynamicConfig.yml,因为我们在dynamic目录中,我们将前置..。对于标签,我们将使用通用的正则表达式字符串v.*

import writeNodeConfig from '<your/package>';

writeNodeConfig("v.*", "../dynamicConfig.yml") 

这是我们的整个应用程序。我们只需要取出我们想要的包的任何版本,并调用它从我们在包中创建的模板生成我们的配置文件。

运行管道

概括地说,我们有一个样板文件config.yml指示 CircleCI 启用动态配置,并使用 Node.js 在运行时构建一个新的配置文件。负责构建我们的新配置的节点应用程序使用了一个包依赖项,它包含了我们想要的配置的“模板”。我们现在可以在许多不同的项目中使用这个包,并集中更新它。我们的项目既可以通过在 package.json 中指定一个x来获取这个包的最新版本,也可以使用像 dependabot 这样的工具,在这个包更新时,自动向所有使用这个包的项目打开一个获取请求。

有了动态配置和 Config SDK,可能性是无限的。上面的教程是基于 GitHub 上 wiki 的这一页。查看我们的 wiki文档的剩余部分,获得更多关于 Config SDK 的有趣例子。

我们希望听到您的意见,并了解您如何在自己的管道中利用 Config SDK。在 Twitter 上与我们联系,在我们的论坛上问好,或者在 Discord 上与我们聊天。

通过 Code | CircleCI 配置为您的管道添加可追溯性

原文:https://circleci.com/blog/configuration-as-code/

通过修改纯文本文件来配置应用程序、服务和环境是现代软件开发的标准部分。代码配置(CaC)通过系统地生成、存储和管理配置文件,更进一步。CaC 允许开发团队自动化他们的应用程序和环境的配置管理,同时确保整个开发生命周期的一致性和可追溯性。

在 DevOps 术语中,CaC 意味着在源代码库中定义配置文件以及代码和测试。其思想是,不要在每个新部署的文本文件中手动进行更改,而是在版本控制中以编程方式进行相同的更改,以避免手动错误的风险。这种方法为您的开发过程增加了更多的安全性和自动化层。

在本文中,您将了解什么是 CaC,它是如何工作的,以及它对 DevOps 团队的价值。您还将探索 CaC 与 GitOps 的关系,它如何应用于 CI/CD 管道,以及它如何与作为代码的基础设施进行比较。

使用新的触发器和权限控制来管理您的生态系统中的变化

Learn More

什么是配置?

配置是系统的一组标准、属性和设置。开发人员使用配置文件来定义他们的应用程序、基础设施和持续集成管道的设置和参数。

应用程序配置

应用程序配置包括一组定义应用程序如何运行的设置。这些设置允许您更改应用程序的行为或功能。现代程序有许多分布式组件托管在云上,运行在托管在多个区域或虚拟机的容器上。如果您将配置设置分散到所有组件中,那么在排除错误或部署过程中,您可能会面临复杂化的风险。应用程序配置意味着从一个安全的中心位置存储和管理您的所有应用程序设置。

基础设施配置

基础架构配置是定义和配置支持应用程序所需的物理和逻辑资源的过程。它包括您需要采取的所有步骤,从选择硬件和软件到安装,最后配置它们供您的应用程序使用。

基础架构配置会影响从性能到可靠性和可伸缩性的一切,这些都是构建应用程序时需要考虑的重要因素。

管道配置

CI/CD 管道是容器化、源代码控制、监控、配置管理和构建工具的集合。这些工具自动化了应用程序和基础设施的构建、测试和部署过程。管道配置指的是一组描述自动化流程应该如何执行的指令,这些指令反过来会受到应用程序和基础架构配置的影响。这些指令包含在以 YAML 、JSON、XML 或其他格式编写的管道配置文件中。

什么是配置即代码?

通常,应用程序代码被提交给版本控制存储库。配置存储在这个存储库之外,开发人员手动创建和定制他们每个部署的配置。

CaC 是不同的,因为配置设置(包括资源供应和环境设置)是在可执行代码中定义的。这意味着您像对待代码一样对待配置设置(包括在版本控制中维护设置),并在管理代码的同时管理配置文件。

CaC 作为 CI/CD 管道的一部分运行良好,自动化了与基础设施维护、供应和软件配置相关的许多方面和任务。这种自动化通过减少开发人员必须执行的容易出错、耗时的手动任务来支持他们。

基础设施即代码与配置即代码

基础设施即代码(IaC)是将基础设施视为软件的实践。当您将基础设施视为软件堆栈中的另一个应用程序时,这意味着您可以编写代码来描述基础设施的外观。一旦经过测试,您就可以根据该描述自动创建或销毁基础结构。

IaC 和 CaC 都可以自动提供和配置您的软件,但是它们的方式不同。

在 IaC 中,您用代码对基础设施进行建模,以便机器可以管理它。在部署系统之前,您需要编写脚本来描述您希望系统是什么样子,以及您希望如何配置它。IaC 通常用于自动化物理和虚拟服务器的部署和配置。

在 CaC 中,您在部署应用程序之前对其配置进行建模。当您推出新的软件配置时,您的整个应用程序配置设置都会更新,无需手动干预。CaC 可以用于任何应用类型,包括容器和微服务。

IaC、CI/CD 和合并请求是 GitOps 的核心实践。GitOps 是一种管理声明性基础设施的方法,它使用 Git 作为唯一的事实来源。使用 GitOps,基础设施变更是软件集成和交付过程的核心组件,您可以将它们集成到同一个 CI/CD 管道中。这种集成使得更改配置更加容易。开发人员需要做的就是创建配置变更并将其推送到源代码控制库。在此 repo 中,使用 CI/CD 工具测试代码,并将更改应用于底层基础架构。

将配置作为开发运维代码的优势

作为代码的配置为开发团队提供了许多好处:

  • 降低了配置错误的风险
  • 开发过程的标准化
  • 提高对应用和基础架构变更的可见性和控制力

避免错误配置漏洞

通常,开源软件安全事故的原因可以追溯到配置错误。这个问题在大规模云环境中非常普遍,在这种环境中,数千甚至数百万个实例需要以完全正确的方式进行配置。CaC 通过实现自动化和促进一致性和简洁性,有助于减少配置错误。

自动化日常流程减少了人为错误,并为开发团队腾出时间来从事更有价值的任务。CaC 提供了跨独特的开发、测试和生产环境轻松更改配置的能力。使用 CaC 的团队可以确信他们在所有环境中以相同的方式部署他们的应用程序。

当所有配置都集中存储时,团队可以很容易地看到他们的更改如何影响他们的基础架构或应用程序环境的其他方面。这有助于确保更改不会破坏其他服务器或网络上的任何内容。当一切都被描述为代码和自动化时,一组明确的指令控制一切,大大降低了部署变更时的错误风险。

支持管道标准化

使用 CaC,您可以创建针对所有环境运行相同脚本的管道。这有助于确保所有环境都运行相同的应用程序版本。在实现开发最佳实践时,将配置编写为源代码有助于实现您的目标。您可以优化您的配置以适应最佳实践,例如安全扫描代码质量分析参数化

如果您维护微服务,您可以使用 CaC 来验证您是否有类似的构建计划。通过实现标准流程,您可以确保您的微服务协同工作。您还有机会检查和测试配置文件,并确保它们在提交到主分支之前遵循了设定的标准。

改善合规性和变更管理

CaC 提供了配置项和相关策略之间的可跟踪性。当配置项在部署到生产环境之前必须符合规定时,开发人员必须有一种方法来记录这个过程并证明它发生了。可追溯性通过提供一种创建确保系统安全性和稳定性的策略的方法,帮助 DevOps 从业者改进遵从性和变更管理。

管理 CaC 的监督和法规遵从性方面是用户的一个主要优势。当您有一个配置工件的单一存储库时,在变更点会有一个审计跟踪。这有助于确定谁做了什么改变,他们改变了什么,以及何时改变的。由于可以通过版本控制系统或其他跟踪机制来跟踪和审计变更,因此如果出现问题(意外的或恶意的),可以识别变更并追溯到其发起人。

您可以通过在配置文件中定义合规性标准来实施这些标准。除非被用户或管理员明确覆盖,否则这些文件中的任何更改都将在所有环境中传播。您可以通过以下方式轻松控制应用程序随时间的变化:

  • 在配置代码本身中定义基础设施的确切状态
  • 审核对该应用程序的配置文件所做的所有更改

这两个过程创建了对应用程序所做的所有更改的可审核记录。通过识别应用程序配置文件在一段时间内出现的任何错误或问题,可以维护对安全标准的遵从,并确保应用程序正常运行。

结论

将配置作为代码实现到您的开发过程中可以为您的开发团队提供巨大的好处。它自动化了跨环境应用配置的过程,使应用更新更容易,并确保一切都能协同工作。因为它使用单个存储库,所以更改易于管理和跟踪。配置为代码是一个强大的工具,用于管理和控制复杂的基础设施和管道,同时改进代码的开发和部署。结果是您需要的可见性和控制力,以加速您的开发,而不牺牲您对部署的信心。

CircleCI 使实施 CaC 方法来管理您的管道、应用程序和基础设施变得简单。你可以注册一个免费计划并开始使用 CaC 来使你的开发过程更加稳定、安全和合规。

容器与虚拟机:区别在哪里?圆环

原文:https://circleci.com/blog/containers-vs-virtual-machines/

在计算领域,虚拟化是创建计算机硬件平台、存储设备和网络资源的虚拟版本,而不是物理版本。虚拟化从物理资源中创建虚拟资源,如硬盘、中央处理器(CPU)和图形处理器(GPU)。通过虚拟化资源,您可以将资源网络组合成一个对象呈现给用户。例如,您可以将来自一个或多个来源的数据存储在一个硬盘驱动器网络上,该网络对用户来说是一个单独的驱动器。

虚拟化是一种技术,许多流行的开发工具,如容器和虚拟机(VM)都是基于这种技术构建的。世界各地的组织都依赖于通过这些工具实现的虚拟化的安全性、可访问性和灵活性。在本文中,您将了解容器和虚拟机之间的区别,它们在现代软件开发中各自扮演的重要角色,以及如何将容器和虚拟机整合到您的开发管道中。

什么是容器?

有两种类型的容器:应用程序容器和系统容器。系统容器现在已经不太常见了,但在 2015 年 Docker 让容器变得常见之前,它很受欢迎。一般人们讨论容器是指应用程序容器。

容器是一个进程或一组进程,与共享内核上的其他程序相隔离。内核是位于计算机操作系统(OS)核心的计算机程序,通常完全控制系统中的一切。容器是具有虚拟资源的客户操作系统。容器不知道主机操作系统上运行的其他进程。与可以直接访问硬件的主机操作系统不同,来宾操作系统对硬件的访问是有限的,就像主机操作系统上运行的任何其他应用程序一样。

容器是由名为 images 的只读模板构建的,这些模板是从中央存储库中取出来在主机上运行的。

通常,容器运行单个进程或应用程序,尽管它们可以处理多个进程或应用程序。容器通常运行在 Linux 机器上,它们运行在一个单独的名称空间中。这些容器既不依赖于运行它们的机器上的其他进程,也不与它们直接交互。其他操作系统上的实现遵循类似的架构原则。容器与其他过程和设备之间的任何通信都是通过附加的接口软件进行的。

尽管容器封装了它们需要运行的文件和二进制文件,但是它们需要一个容器引擎来运行。有几个基本工具允许用户快速管理和创建容器。

最流行的容器系统 Docker 使用一个守护进程来创建和管理容器。其他容器系统如 Buildah 和 Kaniko 提供了无守护进程的架构。无守护进程的容器构建可以提供更高的安全性,因为 Buildah 和 Kaniko 不需要 root 访问就可以获得完整的功能。然而,这些工具本身不能运行或管理容器。开发人员可能更喜欢像 Docker 和 Podman 这样的工具,因为它们允许用户构建映像、运行和配置容器。

容器很有用,因为它们非常便于携带。大多数常见的容器引擎运行在多种环境中,并且在资源使用方面是轻量级的。因为容器打包了它们需要的所有依赖项,所以不管在什么兼容系统上实现它们,它们都可以一致地运行。这意味着,一旦您从一个或多个容器中构建了一个应用程序,您就可以在许多不同的系统上实现它。

这种可移植性使得容器非常适合需要计算机网络运行相同软件的大型组织。容器也用于进行并行测试,作为持续集成和持续部署(CI/CD)管道的一部分。容器还可以同时运行重复的任务,并将一个大型应用程序的单个进程分成单个的微服务

什么是虚拟机?

像容器一样,虚拟机是一个用于虚拟化的软件。但与容器不同的是,您必须在称为管理程序的软件层之上构建虚拟机。虽然容器是为单个服务制作的隔离进程,但是虚拟机运行完整且独立的操作系统。这意味着单个虚拟机可以处理广泛的任务并同时执行它们,并且可以用于更广泛的用例集。

因为虚拟机创建了一个完整的计算环境,所以您可以在其上安装新软件,并将它们的代码更改为操作系统级别。您甚至可以为处于给定状态的虚拟机创建快照,以便在以后出现问题时将其回滚到该配置。像容器一样,虚拟机与同一硬件上的其他软件是分开的,这使得它们成为软件测试的完美环境。

因为虚拟机分配的资源低于来宾操作系统级别,所以危及一台虚拟机的恶意应用程序不太可能影响主机操作系统或访问机器的固件。这保证了在同一台机器上运行的其他虚拟机的安全。此外,由于您的虚拟机可以使用与其主机设备不同的操作系统,因此您可以使用虚拟机在不同的环境中测试软件。

有许多工具可用于构建和管理虚拟机。最重要的是虚拟机管理程序,它管理一个或多个虚拟机对底层资源的访问。其他工具帮助用户同时创建和管理许多虚拟机。一些开发人员使用预先配置的虚拟机来确保它们设置正确,并拥有启动所需的所有基本程序。

容器与虚拟机:您应该使用哪一种?

虚拟机和容器都是具有特定用例的强大技术。两者都提供了安全运行进程的隔离环境,但它们的具体用途不同。

与运行在 VM 中的应用程序相比,容器化的应用程序可以更直接地访问硬件,这使得容器非常适合轻量级用例。假设您希望在多个单独的实例中运行单个流程,或者彼此独立地运行许多不同的流程。在这种情况下,使用容器是有意义的。它们占用的资源很少,因此很容易快速启动并大规模运行。

关注安全性的组织可以在创建容器本身之前安全地检查容器映像,以了解它将做什么。这种透明性使集装箱易于扫描,但这是有代价的。您必须扫描共享容器中的漏洞以避免在使用这些漏洞的系统中复制这些漏洞。

更新容器化应用程序的每个实例也很容易。您制作了容器映像的更新版本,并根据该更新的映像创建了新的容器。然后,您可以删除过时和不太安全的容器,而不会影响其他进程。您甚至可以自动化更新过程,并依靠容器的快速启动时间来确保每次更新都能快速进行。

容器可以使一些复杂的任务变得简单。例如,您可以使用 Docker 映像和 CircleCI 配置文件轻松构建一个 CI/CD 管道。管道使得快速测试一个图像,然后将它推送到 Docker Hub 或另一个容器注册中心变得容易。这让您可以在 CI/CD 管道中从构建到部署快速移动。

容器也方便了微服务的使用。微服务将较大的应用程序分割成较小的进程,为用户提供更大的灵活性,并安全地分离这些进程。在虚拟机上运行微服务将涉及为每个微服务启动一个单独的虚拟机,这是一种低效的资源分配。或者它涉及在同一个虚拟机上运行多个服务,这就失去了隔离的好处。

尽管很受欢迎,容器并没有完全取代虚拟机。在许多情况下,容器补充了虚拟机的使用。如果您想测试一个可能会危及整个操作系统的应用程序,或者需要在不同操作系统上运行的服务之间共享硬件,那么您需要一个虚拟机。因为给定机器上的所有容器共享同一个内核,所以恶意代码更容易危害整个机器。

因为容器依赖于运行它们的内核,所以从容器内部实现操作系统级的改变是具有挑战性的。例如,假设您想从一个容器中用一个sysctl命令修改一个系统内核。在这种情况下,您必须给予容器一定级别的特权,这将抵消从隔离中获得的任何安全好处。

相反,您可以使用一个机器执行器从一个 VM 中完成它。这是可能的,因为虚拟机的操作系统安装在虚拟机内,而容器依赖的内核在容器外

*## CI/CD 中的容器和虚拟机

如前所述,用户可以使用容器在他们的代码中创建一个 CI/CD 管道

在 CI/CD 中使用容器和 VM 的好处之一是标准化。当多个开发人员为一个程序贡献代码时,如果他们在相同的环境中编写代码,他们就不太可能遇到问题。

团队还可以在虚拟机中部署容器,让它们在相同的环境中构建,同时保持对操作系统级别以下资源的控制。克隆的构建环境也使得包含更大的自动化成为可能,帮助您的 CI/CD 管道尽可能平稳地工作。

容器还可以通过使在机器之间移动和部署代码变得容易来促进 CI/CD 自动化。您可以构建一段代码作为容器映像,并将其作为可读文件部署到中心。像 Wit.ai 这样的公司将容器集成到 CI/CD 中,以自动化他们的测试和部署管道,从而实现最大效率。

当你想用 Docker 做漏洞管理或者自动化新容器的构建时,CI/CD 和容器也可以共生工作。例如,您可以使用容器作为频繁和增量补丁测试的自动化、持续过程的一部分,而不是发布对系统所有部分影响不太清楚的大量更新。这使得管理漏洞变得快速、顺利,并且可以在团队成员之间分配。

虚拟机和容器对 CI/CD 管道的贡献不同。它们协同工作的一种具体方式是使用 VM 作为机器执行器来运行要求更高的容器。已经使用 Docker 执行器的人可能需要从 Docker 迁移到机器执行器。然而,机器执行器的更隔离的环境和对系统资源的更大的访问可以使这一努力变得有价值。

结论

从构建到测试到部署,再到漏洞管理,容器和虚拟机与您的 CI/CD 管道协同工作,保持运营平稳运行。

容器和虚拟机是虚拟化程序的两个关键工具。您使用哪一种取决于您需要做什么,并且两者都对促进您的 CI/CD 渠道做出了重要贡献。

通过结合虚拟机的安全性和容器的效率,您可以利用虚拟化的所有优势。通过使用虚拟机来保护您的应用程序和容器,以便在不同的机器之间移动和部署代码,您可以发挥它们的优势。*

离子应用的| CircleCI

原文:https://circleci.com/blog/continous-integration-for-ionic-apps/

在混合移动开发的世界中,没有其他框架比 Ionic 框架对行业的贡献更大。Ionic 创建于 2013 年,最初建立在 Angular.jsApache Cordova 的基础上,现已发展成为一个功能齐全的应用框架,用于开发移动、桌面和渐进式网络应用。Ionic 也变得与框架无关,允许开发人员使用 Angular.jsReact.jsVue.js 进行开发。

在本教程中,我们将建立一个方便的任务管理器。我们将为应用程序中的特性编写测试,并建立一个持续集成 (CI)管道来自动化我们的开发和测试。我们将使用 Ionic React 开发我们的应用程序,Ionic 版本支持使用 React.js 开发。

先决条件

要跟进这篇文章,需要做一些事情:

  1. React.js 的基础知识(不是一个交易破坏者,您只需要复制粘贴代码片段)
  2. 系统上安装的 Node.js
  3. 安装在您系统上的 Ionic CLI
  4. 一个的账户

搭建离子应用平台

首先,让我们通过运行以下命令来构建一个新的 Ionic 应用程序:

ionic start task-manager tabs --type=react 

这将立即触发 Ionic CLI 使用名为task-manager的文件夹中的tabs模板为我们搭建一个新项目。

注意: 你可能会在某个时候被提示Create free Ionic account?。点击n拒绝。关于创建 Ionic 账户的更多信息,请参见此处

完成后,进入应用程序的根目录(cd task-manager),运行以下命令在您的 web 浏览器中为应用程序提供服务:

ionic serve 

此命令完成后,您将在浏览器中看到一个类似于下图的应用程序视图。

注: 我用的是 Chrome 开发工具中激活的移动预览。

如果你的字体是黑色背景和白色字体,不要惊讶。它使用您机器的默认模式。

构建任务管理器应用程序

接下来,让我们开始构建主应用程序。进入应用程序的src/pages文件夹,打开Tab1.tsx文件。这是新搭建的应用程序的默认主页。删除该文件中的所有内容,并替换为以下代码:

// src/pages/Tab1.tsx

import React, { useState } from "react";
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonList,
  IonItemSliding,
  IonItem,
  IonLabel,
  IonItemOptions,
  IonItemOption,
  IonFab,
  IonFabButton,
  IonIcon,
  IonModal,
  IonButton,
  IonCard,
  IonCardContent,
  IonInput,
} from "@ionic/react";

import { add } from "ionicons/icons";

import "./Tab1.css";

interface Task {
  id: number;
  name: string;
}

const Tab1: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [showModal, setShowModal] = useState(false);
  const [taskName = "", setTaskName] = useState<string>();

  function addNewTask() {
    const new_id = tasks.length + 1;

    const newTask = {
      id: new_id,
      name: taskName,
    };

    tasks.push(newTask);

    setTasks(tasks);

    setTaskName("");

    setShowModal(false);
  }

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Task Manager</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList>
          {tasks.length > 0 ? (
            tasks.map((item: Task) => {
              return (
                <IonItemSliding key={item.id}>
                  <IonItem className="todo-item">
                    <IonLabel>{item.name}</IonLabel>
                  </IonItem>
                  <IonItemOptions side="end">
                    <IonItemOption onClick={() => {}}>Done</IonItemOption>
                  </IonItemOptions>
                </IonItemSliding>
              );
            })
          ) : (
            <IonItem>
              <IonLabel>You have yet to add tasks for today</IonLabel>
            </IonItem>
          )}
        </IonList>

        {/* Modal*/}
        <IonModal isOpen={showModal}>
          <IonCard>
            <IonItem>
              <IonLabel>Add New Task</IonLabel>
            </IonItem>

            <IonCardContent>
              <IonItem>
                <IonInput
                  value={taskName}
                  placeholder="Enter Task Name..."
                  onIonChange={(e) => setTaskName(e.detail.value!)}
                ></IonInput>
              </IonItem>

              <IonButton
                expand="full"
                color="primary"
                onClick={() => addNewTask()}
              >
                Add Task
              </IonButton>
            </IonCardContent>
          </IonCard>
          <IonButton onClick={() => setShowModal(false)}>Close Modal</IonButton>
        </IonModal>

        {/* Add Task Button */}
        <IonFab vertical="bottom" horizontal="end" slot="fixed">
          <IonFabButton onClick={() => setShowModal(true)}>
            <IonIcon icon={add} />
          </IonFabButton>
        </IonFab>
      </IonContent>
    </IonPage>
  );
};

export default Tab1; 

现在我们来看一下上面的代码片段。这是我们的任务管理器应用程序的全部代码。

我们首先导入必要的依赖项,包括页面的 css 文件。然后我们定义一个接口来定义我们的任务对象。

interface Task {
  id: number;
  name: string;
} 

接下来,我们将我们的组件创建为 React function 组件类型React.FC,并通过使用钩子定义我们想要在状态中保存的数据来开始该功能:一个由Task组成的tasks数组,一个用于控制任务创建表单的打开和关闭的showModal布尔值,以及一个在创建过程中保存新任务值的taskName

const [tasks, setTasks] = useState<Task[]>([]);
const [showModal, setShowModal] = useState(false);
const [taskName = "", setTaskName] = useState<string>(); 

接下来是我们调用的添加新任务的函数。该函数通过根据数组的长度设置其id来创建一个新任务,并在将新任务添加到我们现有的任务列表后清除表单。

function addNewTask() {
  const new_id = tasks.length + 1;

  const newTask = {
    id: new_id,
    name: taskName,
  };

  tasks.push(newTask);

  setTasks(tasks);

  setTaskName("");

  setShowModal(false);
} 

接下来,我们呈现我们的模板以显示我们的任务列表,并且当任务列表为空时,还显示一条有帮助的消息,内容为You have yet to add tasks for today

列表后面是一个模态组件,它包含用于添加新任务的任务表单。组件下面是一个浮动的操作按钮,用户单击它可以打开模式。

在预览之前,打开与Tab1.tsx位于同一文件夹中的Tab1.css,并用以下代码替换其内容:

/* src/pages/Tab1.css */

.todo-item {
  --min-height: 70px;
  font-size: 1.2em;
} 

这只是增加了列表项的高度和字体大小。

现在,进入你的浏览器,加载你的应用程序的主页(Tab1)。您将看到下面显示的页面。

因为我们还没有添加任何任务,所以会显示消息您还没有添加今天的任务。右下角还有我们的添加任务按钮,带有plus符号。

添加任务

要添加新任务,请单击右下角的蓝色按钮,弹出任务创建表单,然后键入一个任务,如下所示。

点击添加任务添加任务,并继续添加 2 到 3 个任务。现在我们应该在页面上有足够的任务来让我们的应用程序看起来正常。

太好了!

现在我们有了一个功能应用程序。不是生产就绪,但足够好,可以开始测试。

向任务管理器应用程序添加测试

我们测试 Ionic 应用程序的一个优势是,在搭建应用程序的同时,Ionic CLI 已经设置了应用程序测试所需的所有包和配置。

使用 Jest 测试框架以及 React 测试库Ionic React 测试实用程序库来测试 Ionic React 应用程序,这是一个小的实用程序套件,用于模拟 Ionic 中的常见功能,如触发定制的 Ionic 事件。

该项目已经在src文件夹中为App.tsx文件提供了一个测试文件。正如您可能已经猜到的,测试文件被命名为App.test.tsx,它遵循 Jest 测试的惯例。

// src/App.test.tsx

import React from "react";
import { render } from "@testing-library/react";
import App from "./App";

test("renders without crashing", () => {
  const { baseElement } = render(<App />);
  expect(baseElement).toBeDefined();
}); 

该测试只是检查应用程序是否正确呈现并且没有崩溃。

若要运行此测试,请运行以下命令:

npm run test 

这将调用jest命令来运行文件中定义的测试。按照指示,按下a运行所有测试。上面的命令是我们将用来运行应用程序中所有测试的命令。

现在,让我们添加一些我们自己的测试。我们将创建一个测试文件来测试我们的应用程序所在的Tab1.tsx文件中的应用程序逻辑。

src/pages文件夹中创建一个名为Tab1.test.tsx的文件。也是Tab1.tsx文件所在的地方。在新创建的文件中,放置以下代码:

// src/pages/Tab1.test.tsx

import React from "react";
import { render } from "@testing-library/react";
import Tab1 from "./Tab1";

test("Page title is Task Manager", async () => {
  const { findByText } = render(<Tab1 />);
  await findByText("Task Manager");
});

test("When there are no Tasks, inform the user that no tasks have been created", async () => {
  const { findByText } = render(<Tab1 />);
  await findByText("You have yet to add tasks for today");
}); 

在这个文件中,我们添加了两个测试。第一个测试检查我们的页面是否显示了正确的标题,即任务管理器。第二个测试检查我们的应用程序最初加载时是否没有任务,如果有,它显示消息您还没有为今天添加任务

太好了!

现在,让我们通过在项目的根目录下运行以下命令来运行这些测试:

npm run test 

您将在命令行界面上看到一个类似于下图的屏幕。

太棒了。

现在,我们已经按照预期运行了我们的测试。

自动化我们的测试

我们的最终任务是自动化我们的持续集成过程。为此,我们将采取以下措施:

  1. 向我们的项目添加一个配置脚本,以便在 CircleCI 上设置 CI 管道
  2. 将我们的项目推到 GitHub 库
  3. 为我们的应用程序创建 CircleCI 项目
  4. 在 CircleCI 上运行 CI 管道

我们开始吧。

首先,让我们将 CI 管道的配置文件添加到项目中。

在项目的根目录下,在.circleci文件夹中创建一个config.yml文件。在config.yml文件中,输入以下代码:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:12.16
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: test
          command: npm run test 

这个配置获取一个 Node.js 映像,并安装在package.json中定义的所有依赖项。一旦安装完成,就执行测试脚本npm run test来运行我们应用程序中的所有测试。

我们的管道配置文件准备就绪。

接下来,提交您的所有更改,并将项目推送到 GitHub 帐户上的 GitHub 存储库,该帐户连接到您的 CircleCI 帐户。

下一步是将我们的项目的存储库设置为 CircleCI 项目。

在 CircleCI 控制台上,转到添加项目页面添加项目:

Add Project

点击设置项目。这将加载下一个屏幕。

Start Building - Config sample

在设置页面上,点击开始构建。在构建开始之前,您会得到一个提示,要么下载并使用所提供的 CircleCI 配置文件,并将它放在一个单独的分支上,要么手动设置一个。

Start Building - Add manually

选择手动添加继续。这将提示另一个对话框,检查确认您已经设置了配置文件,可以开始构建了。

Start Building - Confirm configuration

点击开始构建完成设置。这将使用我们项目中的配置文件立即触发管道。

构建运行完成后,您将获得一个成功的构建。

Build successful

点击构建查看幕后流程(我已经折叠了测试步骤)。

Build process

从上面的屏幕可以看出,我们的项目设置正确,所有的测试都运行良好。

现在,随着你的开发,你所要做的就是添加更多的功能,给它们添加测试,然后推送到 GitHub。一旦您推送,CI 管道将会运行,并且您的所有测试都将被执行。如果任何测试失败,您的构建将失败,这将表明您的应用程序中有一个需要修复的 bug。

这将确保您不会将任何错误推入您的应用程序。

结论

在本文中,我们为 Ionic 应用程序建立了一个自动化 CI 管道,通过自动化测试过程来改进我们的开发工作流程。拥有一个能够正确运行我们的测试的 CI 管道,可以通过确保我们不会以失败的应用程序而告终,或者在一起工作于一个项目时,我们不会将有问题的代码推送到我们团队的存储库中,来保护我们自己。

快乐编码:)


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

连续交货:圣诞故事- CircleCI

原文:https://circleci.com/blog/continuous-delivery-a-christmas-tale/

那是圣诞节的前一天晚上,在整个创业过程中,冷酷的工程师带着老巴特去扑灭用户发现的火灾;
他们乱砍,他们懈怠,却不发出声音。

没有礼物,没有长袜,没有节日的欢乐;只有拉克鲁瓦和一些剩下的啤酒。
管理层也在床上醒着,
破产的景象在他们脑海中跳动。

这个 bug 相当糟糕,是一个在九月份推出的安全漏洞,但是没人发现。
“那次公关是巨大的!我们如何审查它?!"
一个认可它的正直的灵魂喊道。

“我在评论里解释了你没看”,
打出了 perp,然后加了一个“皱眉”的表情符号。
接着,从那次谈话中,传来了这样的嘈杂声,
我从桌子上跳起来,想看看发生了什么事。

纳撒尼尔在大叫,他的脸像樱桃;伊薇特握紧拳头,轻声嘘道,“他怎么敢…
我们现在在这里,只是因为他不会写测试:
他们经常被嘲笑,充其量也就是相当古怪。”

办公室里一片混乱。小包装的免费零食被扔进了这场争论。
拿走我的笔记本电脑,我像闪电般飞向
,撕开终端,打出一些痛击。
我们的网络很快从稳定走向死亡;
争论停止了,这时我说:

“伙计们,这对这里的每个人来说都很累,
但如果我们继续战斗,我有点担心
这个错误不会被修复,你知道这意味着什么:
我们的用户会在推特上谈论所有这些事情。”

在我两句话的演讲后,一片死寂。但是突然从上面传来一声尖叫,
汽车朗姆酒的叮当声,硬靴的重击声,
我们听到邮件斜道里传来沉闷的声音。
当杨帆让我疑惑的眼神出现的时候,
却是一个穿着红色衣服的男人,咧着嘴笑。一捆东西扔在他的背上,他看起来大得足以吃掉一头小牦牛。

“晚上好!”他对我们吼叫。“窃丨听器怎么样了?我觉得你们每个人都需要一个快速的拥抱。”
然后他向我走去,但我举起手:
“圣诞老人先生,我们很忙;这个修复需要落地。”

“不用担心!”他吼道,“这就是我来这里的原因。我会教你我的方法,换一杯啤酒。”
他把手伸进包里,拿出一台旧戴尔,
一拳打醒它,砸开一个壳。

他修补了一会儿,然后给我们看了屏幕,上面布满了颜色——鲜红色和绿色。
勤奋的精灵为他们的玩具推承诺,
确保所有女孩和男孩的完美。

“我的礼物源源不断地送来,
你的也应该如此,这样你们都可以免于调试过去引入的故障。
检查一次,检查两次,你就会看到它的持久性。”

然后他砰的一声关上盖子,我给他拿了一杯吉尼斯黑啤酒,他一口吞下;这个人只知道做生意。
他把手指放在鼻子旁边,
点了点头,沿着邮件通道站了起来。

我们坐在那里不确定,困惑和不知所措。我听到了轻柔的叮当声——我有了一条新短信。
“记住!”上面写着。“这没有什么魔力。
它需要工作和良好的测试套件,就像我的清单一样。”

当我看到这一幕时,不由自主地笑了起来;谁知道神秘、快乐的老精灵
会如此精通部署的艺术?
他的访问是及时的,充满了喜悦。

我带我们上线,没多久
就搞定了狡猾的 bug,然后去打乒乓球。
我坐下来重写我们的季度目标,
包括 CI 和 CD 作为两极。
(注意,不是北极,因为那是给圣诞老人用的,
磁极充当工程师法则。)

我发出了一封电子邮件,一份简洁的宣言,详细描述了我们的计划,嘿,很快!
信件被发射到虚拟空间,
整个办公室里,人们欢呼着拥抱着。

他们拿起电话,做最后一分钟的计划,给所爱的人和 Ubers 打电话,这两种需求都很高。我穿着连帽衫,坐在办公桌前,
终于可以深呼吸了。
在远处响起,我听到一个声音在喊叫:

"祝大家圣诞快乐,现在试试 CircleCI !"


来自圣尼古拉斯的一次拜访。图片来源此处

宣布 Docker containers - CircleCI 连续交货

原文:https://circleci.com/blog/continuous-delivery-with-docker-containers/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


容器是云基础设施中的新标准,CircleCI 上的 Docker 允许您用它们构建整个 CI 和 CD 工作流。

有什么新消息?您现在可以在我们的执行环境中使用 Docker 的所有功能。所有常用的 Docker 命令都按预期工作,因此您可以随心所欲地构建和运行 Docker 容器。

circle+docker

为什么这很酷? Docker 容器允许您删除测试和生产环境中几乎所有不同的变量。您可以在 Docker 文件中指定从 Linux 发行版到启动时运行的可执行文件的所有内容,将所有这些信息构建到 Docker 映像中,对其进行测试,并将完全相同的映像逐字节部署到生产环境中。你现在可以在 CircleCI 上运行整个过程。

在幕后我们已经使用容器技术来创建我们所有客户的执行环境,并提供 docker 集成,我们必须支持 docker 在我们的多租户环境中安全运行,并在我们自己的容器中以嵌套模式运行(始终是 docker-in-docker!),这并非完全无关紧要。我们还进行了第三方安全审查,并花了一些时间让 Docker 在 CircleCI 上通过测试,包括我们自己的测试和有限的测试。

使用 CircleCI 上的 Docker为了帮助任何对 CircleCI 上的 Docker 感兴趣的人快速入门,我们有完整的文档,涵盖了将映像部署到 Docker Hub 以及将应用程序持续部署到 AWS Elastic Beanstalk 和 Google Compute Engine 上的 Kubernetes。

下一步是什么?Docker 是一个如此灵活的工具,我们知道有许多令人兴奋的应用程序是我们甚至还没有想到的。如果你认为你有一个特别棒的 Docker 的用法,你想向全世界炫耀,那么请发一封电子邮件给 kevin@circleci.com,让他在我们的博客上发表一篇关于它的文章。在那之前,祝大家快乐!

在 HN 上讨论这个!

将 Deno APIs 持续部署到 Heroku | CircleCI

原文:https://circleci.com/blog/continuous-deployment-deno/

我第一次承担维护生产服务器的任务时,我依赖于我的前任留给我的清单。该清单包含所有维护步骤及其相应的命令。在那些早期的日子里,我虔诚地复制每个命令,在按下回车键之前反复检查每个字符。慢慢地,但肯定地,这些命令被记住了,直到有一天我意识到我不需要清单。

但是后来我的任务增加了,我发现我不能足够快地键入命令。我试着用自定义脚本来缩短这个过程,但是没有多大帮助。然后有惊无险开始了,很快手动部署变得更加可怕而不是有趣。这就是手动部署的现实;你总是一个错误的命令就可能导致灾难。寻找更安全、更高效的替代方案让我不断地进行部署。我很快发现,自动化部署过程消除了错误键入命令的风险,以及与人类完成重复任务相关的其他错误。

在本教程和它的同伴中,我将带领你使用 CircleCI 和 Heroku 为一个 Deno 项目建立一个 CI/CD 管道。Deno 是一个简单、现代、安全的 JavaScript 和 TypeScript 运行时。Deno 为项目提供了以下优势:

  • Deno 在默认情况下是安全的。除非明确启用,否则没有文件、网络或环境访问权限。
  • 支持现成的 TypeScript。
  • 仅提供一个可执行文件。

先决条件

开始之前,请确保您的系统上安装了以下项目:

  • Deno 的最新安装。

对于存储库管理和持续集成/持续部署,您需要:

创建项目目录

创建一个新目录来保存所有项目文件:

mkdir deno_circleci_heroku

cd deno_circleci_heroku 

设置 Deno 服务器

在这一节中,我们将使用 Oak 中间件框架来设置我们的 Deno 服务器。它是 Deno 的 HTTP 服务器的一个可以媲美 KoaExpress 的框架。首先,创建一个名为server.ts的文件,并向其中添加以下代码:

import { Application, Router } from "https://deno.land/x/oak@v7.5.0/mod.ts";
import { parse } from "https://deno.land/std@0.99.0/flags/mod.ts";

const app = new Application();
const { args } = Deno;

const DEFAULT_PORT = 8000;
const port = parse(args).port ?? DEFAULT_PORT;

const router = new Router();

router.get("/", (context) => {
  context.response.type = "application/json";
  context.response.body = { data: "This API is under construction" };
});

app.addEventListener("error", (event) => {
  console.error(event.error);
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port });
console.log(`Server is running on port ${port}`);

export default app; 

这个例子展示了一个基本的 API,当一个请求被发送到索引路由时,它返回一个“正在构建”的消息。一旦我们的管道设置成功,我们将为这个端点添加实际的功能。

请注意,我们以一种不寻常的方式声明了我们正在监听的端口。这是因为在运行 API 时,端口是由 Heroku 提供的。你可以在这里阅读更多关于这个的内容。该端口将在deno run命令中提供,并将被解析和用于运行应用程序。如果没有指定端口(当应用程序在本地运行时),将使用端口 8000。

运行应用程序

我们可以使用以下命令运行应用程序,看看到目前为止我们做了些什么:

deno run --allow-net server.ts 

导航至http://localhost:8000/查看响应。

API response

为应用程序编写测试

是时候为我们的 API 端点编写一个测试用例了。创建一个名为server.test.ts的新文件,并添加以下内容:

import { superoak } from "https://deno.land/x/superoak@4.2.0/mod.ts";
import { delay } from "https://deno.land/x/delay@v0.2.0/mod.ts";

import app from "./server.ts";

Deno.test("it should return a JSON response with status code 200", async () => {
  const request = await superoak(app);
  await request
    .get("/")
    .expect(200)
    .expect("Content-Type", /json/)
    .expect({ data: "This API is under construction" });
});

// Forcefully exit the Deno process once all tests are done.
Deno.test({
  name: "exit the process forcefully after all the tests are done\n",
  async fn() {
    await delay(1);
    Deno.exit(0);
  },
  sanitizeExit: false,
}); 

在本地运行测试

导航到终端。从应用程序的根目录,使用以下命令运行测试:

deno test --allow-net server.test.ts 

您应该会看到下面的响应。

test it should return a JSON response with status code 200 ... ok (28ms)
test exit the process forcefully after all the tests are done
 ...% 

有了您的测试用例,您就可以设置您的管道。

配置 Heroku

在应用程序文件夹的根目录下,创建一个名为Procfile的新文件。请注意,这个文件没有扩展名。

Procfile中,添加以下命令。

web: deno run --allow-net server.ts --port=${PORT} 

这个命令声明了我们的应用程序的 web 进程和启动时要执行的命令。注意,我们正在传递参数port并将其绑定到 Heroku 提供的环境变量PORT

接下来要做的是在 Heroku 上创建一个新的应用程序。你可以从 Heroku 仪表盘上完成这项工作。点击新建,然后点击新建 App。填写表格。您可以使用您喜欢的名称和地区。

Create Heroku App

接下来,点击创建 app 完成创建过程。然后,您将被重定向到新创建的应用程序的部署视图。

您的下一个任务是添加一个构建包。为此,点击设置选项卡。在构建包部分,点击添加构建包。

Buildpack Button

这将打开一个表单,您可以在其中选择一个官方支持的构建包,或者为您的构建包提供一个 URL。目前,Heroku 还没有官方支持的 Deno 构建包,所以您需要提供 Deno 构建包的 URL。将https://github.com/chibat/heroku-buildpack-deno.git粘贴到输入栏,点击保存更改

Add BuildPack URL

我们最不需要的就是 API 密钥。除了应用程序名称之外,您将使用它将 CircleCi 管道连接到 Heroku。要获取 API 密钥,打开账户设置页面,向下滚动到 API 密钥部分。

Reveal API Key

点击显示按钮,复制 API 密钥。将它保存在您以后可以轻松访问的地方。

配置 CircleCI

接下来,我们需要为 CircleCI 添加管道配置。对于本项目,管道将包括两个步骤:

  1. 构建和测试——在这一步中,您将为应用程序构建项目并运行测试。如果任何测试失败,将显示一条错误消息,该过程将终止。
  2. 部署到 Heroku——如果构建和测试步骤成功完成,最新的更改将在这一步部署到 Heroku。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在新创建的文件中,添加以下配置:

# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1

orbs:
  heroku: circleci/heroku@1.2

jobs:
  build-and-test:
    docker:
      - image: denoland/deno:1.10.3
    steps:
      - checkout
      - run: |
          deno test --allow-net server.test.ts

workflows:
  sample:
    jobs:
      - build-and-test
      - heroku/deploy-via-git:
          force: true # this parameter instructs the push to use a force flag when pushing to the heroku remote, see: https://devcenter.heroku.com/articles/git
          requires:
            - build-and-test 

这个配置引入了 Heroku orb circleci/heroku,它自动让我们访问一组强大的 Heroku 任务和命令。其中一个任务是heroku/deploy-via-git,它直接从你的 GitHub repo 将你的应用程序部署到你的 Heroku 账户。

该配置指定了一个名为build-and-test的作业。这项工作检查最新的代码,使用 Deno 创建 Docker 映像,并在server.test.ts中运行测试

最后,该配置指定了一个工作流,该工作流运行build-and-test作业,然后运行heroku/deploy-via-git作业。注意,有一个requires选项告诉 CircleCI 只有在构建任务完成时才运行deploy-via-git任务。

接下来,我们需要在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。查看将您的项目推送到 GitHub 以获取指导。

接下来,登录你的 CircleCI 账户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

点击deno_circleci_heroku项目旁边的设置项目

CircleCI 检测项目的config.yml文件。点击使用现有配置,然后开始建造。您的第一个工作流将开始运行,但会失败!

CircleCI Build Failed

部署过程失败,因为我们没有提供 Heroku API 密钥。要解决这个问题,点击项目设置按钮,然后点击环境变量菜单选项。添加两个新变量。

  • 对于HEROKU_APP_NAME,添加您在 Heroku 中使用的应用名称。该名称将是deno-heroku-circleci或自定义名称(如果您创建了一个名称的话)。

  • 对于HEROKU_API_KEY,输入您之前从帐户设置页面检索的 Heroku API 密钥。

从头重新运行您的工作流,这一次您的工作流将成功运行。要确认工作流是否成功,您可以在浏览器中打开新部署的应用程序。您的应用程序的 URL 应该是这样的格式https://<HEROKU_APP_NAME>.herokuapp.com/

应显示正在构建的API 响应。

实施索引路线

现在您的工作流已经启动并运行,您可以实现索引路径了。对于这个 API,它将返回硬编码用户的列表。在应用程序目录的根目录下,创建一个名为users.ts的新文件,并添加以下代码:

export default [
  {
    _id: "60d7b2ccfb294b7be115b6c4",
    isActive: true,
    picture: "http://placehold.it/32x32",
    age: 22,
    eyeColor: "green",
    name: "Harding Taylor",
    gender: "male",
    email: "hardingtaylor@lexicondo.com",
    phone: "+1 (839) 440-2917",
    address: "686 Harman Street, Reno, New Jersey, 5152",
    about:
      "Nisi irure aliquip aliquip Lorem. Elit ullamco commodo laborum aliqua commodo Lorem occaecat pariatur aute est reprehenderit ad. Qui nostrud enim aliqua consequat sit duis pariatur ex consectetur aute elit ad commodo officia.\r\n",
    registered: "2017-02-10T11:52:40 -01:00",
    friends: [
      {
        id: 0,
        name: "Donovan Nielsen",
      },
      {
        id: 1,
        name: "Barrera Hartman",
      },
      {
        id: 2,
        name: "Carmen Bean",
      },
    ],
    greeting: "Hello, Harding Taylor! You have 5 unread messages.",
    favoriteFruit: "banana",
  },
  {
    _id: "60d7b2cc411283f9ea9fdaff",
    isActive: false,
    picture: "http://placehold.it/32x32",
    age: 25,
    eyeColor: "blue",
    name: "Charlene Gibson",
    gender: "female",
    email: "charlenegibson@lexicondo.com",
    phone: "+1 (864) 446-3848",
    address: "132 Columbus Place, Clarksburg, Delaware, 281",
    about:
      "Exercitation voluptate cillum cillum do et voluptate officia Lorem sint. Ullamco quis ullamco et mollit. Veniam et labore aliquip elit fugiat proident labore.\r\n",
    registered: "2015-01-02T04:41:06 -01:00",
    friends: [
      {
        id: 0,
        name: "Morris Barrera",
      },
      {
        id: 1,
        name: "Aguilar Pearson",
      },
      {
        id: 2,
        name: "Denise Jacobs",
      },
    ],
    greeting: "Hello, Charlene Gibson! You have 5 unread messages.",
    favoriteFruit: "strawberry",
  },
  {
    _id: "60d7b2cc836c6ddb012508c1",
    isActive: false,
    picture: "http://placehold.it/32x32",
    age: 27,
    eyeColor: "blue",
    name: "Lavonne Barton",
    gender: "female",
    email: "lavonnebarton@lexicondo.com",
    phone: "+1 (958) 505-3633",
    address: "403 Ruby Street, Wintersburg, Pennsylvania, 1063",
    about:
      "Nisi sit cillum aute qui sunt ipsum deserunt ut. Fugiat laboris cupidatat mollit exercitation proident ad ut laboris nostrud amet amet dolor. Excepteur qui ea amet ut reprehenderit magna et proident sunt eu duis velit. Aliquip culpa proident aliqua dolore aliqua sint cillum anim amet duis esse nisi eu. Amet anim fugiat irure sit velit ad excepteur exercitation Lorem cillum sit deserunt dolore irure. Officia pariatur aliquip aliquip officia sunt sit dolore duis excepteur proident. Labore elit sunt ea deserunt officia nostrud.\r\n",
    registered: "2018-06-09T04:57:28 -01:00",
    friends: [
      {
        id: 0,
        name: "Barber Jimenez",
      },
      {
        id: 1,
        name: "Eliza Robbins",
      },
      {
        id: 2,
        name: "Charmaine Alexander",
      },
    ],
    greeting: "Hello, Lavonne Barton! You have 8 unread messages.",
    favoriteFruit: "banana",
  },
  {
    _id: "60d7b2cc112a8197462d9a8e",
    isActive: true,
    picture: "http://placehold.it/32x32",
    age: 32,
    eyeColor: "green",
    name: "Lea Evans",
    gender: "female",
    email: "leaevans@lexicondo.com",
    phone: "+1 (891) 524-3545",
    address: "725 Dennett Place, Alamo, New York, 1382",
    about:
      "Laborum dolor labore reprehenderit voluptate laborum ullamco non dolore magna officia ex velit. Ex nostrud duis ullamco cillum commodo occaecat pariatur nostrud nostrud occaecat incididunt minim eu. Minim ut ea quis laboris sunt.\r\n",
    registered: "2017-11-10T06:28:55 -01:00",
    friends: [
      {
        id: 0,
        name: "Fulton Oneal",
      },
      {
        id: 1,
        name: "House Watts",
      },
      {
        id: 2,
        name: "Isabel Melton",
      },
    ],
    greeting: "Hello, Lea Evans! You have 4 unread messages.",
    favoriteFruit: "strawberry",
  },
  {
    _id: "60d7b2cc3689b747a755bd89",
    isActive: true,
    picture: "http://placehold.it/32x32",
    age: 30,
    eyeColor: "green",
    name: "Jocelyn Harper",
    gender: "female",
    email: "jocelynharper@lexicondo.com",
    phone: "+1 (964) 464-2509",
    address: "524 Banner Avenue, Brenton, Texas, 1373",
    about:
      "Aliqua consectetur anim incididunt eu aute minim proident esse. Commodo eu tempor cillum veniam duis incididunt cupidatat. Tempor qui eu incididunt nostrud amet velit quis amet consequat. Deserunt nostrud eu laboris commodo irure fugiat dolore nisi in consequat ea in ullamco duis.\r\n",
    registered: "2015-04-10T11:13:10 -01:00",
    friends: [
      {
        id: 0,
        name: "Beatriz Carver",
      },
      {
        id: 1,
        name: "Gillespie Ferrell",
      },
      {
        id: 2,
        name: "Chris Boyer",
      },
    ],
    greeting: "Hello, Jocelyn Harper! You have 8 unread messages.",
    favoriteFruit: "banana",
  },
  {
    _id: "60d7b2cc03d9f88b8a833480",
    isActive: true,
    picture: "http://placehold.it/32x32",
    age: 39,
    eyeColor: "green",
    name: "Sharpe Wallace",
    gender: "male",
    email: "sharpewallace@lexicondo.com",
    phone: "+1 (979) 492-3250",
    address: "522 Madison Place, Charco, Missouri, 2144",
    about:
      "Voluptate culpa labore excepteur ut commodo veniam elit ea consectetur laboris adipisicing. Adipisicing ea officia qui reprehenderit. Ut ipsum elit irure id nisi. Enim cupidatat ea ea veniam est et enim nisi tempor. Minim laboris ipsum et ipsum mollit exercitation est labore voluptate cillum in dolor. Nostrud dolore labore et reprehenderit.\r\n",
    registered: "2017-11-12T10:09:50 -01:00",
    friends: [
      {
        id: 0,
        name: "Valenzuela Shelton",
      },
      {
        id: 1,
        name: "Margret Stuart",
      },
      {
        id: 2,
        name: "Rosie Nixon",
      },
    ],
    greeting: "Hello, Sharpe Wallace! You have 3 unread messages.",
    favoriteFruit: "apple",
  },
]; 

接下来,更新server.test.ts以匹配该代码:

import { superoak } from "https://deno.land/x/superoak@4.2.0/mod.ts";
import { delay } from "https://deno.land/x/delay@v0.2.0/mod.ts";
import app from "./server.ts";

Deno.test(
  "it should return a JSON response containing users with status code 200",
  async () => {
    const request = await superoak(app);
    await request
      .get("/")
      .expect(200)
      .expect("Content-Type", /json/)
      .expect(/"users":/);
  }
);

// Forcefully exit the Deno process once all tests are done.
Deno.test({
  name: "exit the process forcefully after all the tests are done\n",
  async fn() {
    await delay(1);
    Deno.exit(0);
  },
  sanitizeExit: false,
}); 

server.ts中,用以下代码更新索引路线:

router.get("/", (context) => {
  context.response.type = "application/json";
  context.response.body = { users };
}); 

不要忘记从users.ts导入用户:

import users from "./users.ts"; 

提交您的代码并推送到您的 Github 库

git add .

git commit -m "Implement index route"

git push origin main 

将更改推送到 Github 存储库之后,回到 CircleCI 仪表板,新的工作流正在那里运行。一切完成后,刷新你的 Heroku app。您新实现的路由将返回用户列表。

API response User List

结论

在本教程中,我向您展示了如何使用 GitHub、CircleCI 和 Heroku 为 Deno API 设置 CI/CD 管道。

通过自动化发布新功能的过程,大大降低了人为错误对生产环境造成负面影响的风险。此外,产品还增加了一定程度的质量保证,因为新功能只有在通过指定的测试用例后才会部署。

这些实践有助于创建一个更有效的软件管理过程,该过程自动化重复的、平凡的部署方面,以便您的团队可以专注于解决问题。

本教程的完整代码库可从 GitHub 上的获得。

感谢您的阅读!


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

Azure web apps | CircleCI 的持续部署

原文:https://circleci.com/blog/continuous-deployment-for-azure-web-apps/

在过去的十年里, Azure 已经成为可用的最杰出的云计算平台之一,只有 AWS 可以与之匹敌。作为微软 Azure 服务套件的一部分,Azure web apps 为托管用多种语言构建的 web 应用程序提供了一个打包的环境。因为这个环境完全由 Azure 管理,所以开发人员的控制选项有限。其中一个限制涉及到将应用程序部署到托管环境的过程,它只允许连接远程存储库,而 Azure 接管整个部署过程。

在本教程中,您将学习如何在 Azure web 应用程序的部署工作流中构建一个自定义管道,重新获得对该过程的完全控制,以便您可以在部署之前运行测试。

先决条件

要跟进这篇文章,需要做一些事情:

  1. 您系统上安装的 Node.js (版本> = 10.3)
  2. 一个蓝色的账户
  3. 一个的账户
  4. GitHub 的一个账户

安装并设置好所有这些之后,是时候开始本教程了。

首先,我们将使用以下策略来创建自定义部署:

  • 将我们的项目推到远程存储库
  • 在 Azure 的存储库上创建一个特定的部署分支,用于触发部署
  • 构建到主分支的管道以运行测试
  • 如果测试通过,自动推送到 Azure 的部署分支,以获取更改并部署应用程序

简单吧?

创建 Azure web 应用程序

转到您的 Azure 门户仪表板,单击创建资源以创建新的服务实例。从热门列表中,点击网页应用。您也可以使用搜索框。

Create Web App - Azure

创建 Web 应用页面上,选择 Azure 订阅和资源组。资源组是一种标记相关 Azure 服务的方式。您可以创建一个新组或从页面上的列表中选择一个组。

接下来,转到实例细节部分,输入您的 web 应用程序的名称。默认的. azurewebsites.net 地址在 Azure 中必须是唯一的。此外,确保名称遵循标准的 URL 命名规则。对于本教程,使用名称node-api

填写下一组选项:

  • Publish:代码
  • Runtime stack:节点 12 LTS(因为我们将托管一个 Node.js 应用程序)
  • Operating System : Linux
  • Region:选择离您最近的一个或任何首选选项(教程为Central US)

Create Web App Options - Azure

保留其余选项的默认值。

点击审核+创建。在审核页面,确认您选择的选项,然后点击创建。Azure 将开始设置我们的 web 应用环境。该过程完成后,您将被引导至您的 web 应用仪表板。如果没有,点击转到资源

设置 Node.js 项目

接下来,我们需要克隆我们想要测试的项目,并将其部署到我们刚刚创建的 web 应用程序中。对于本教程,我们使用一个带有单个端点的基本 Node.js API,它返回一组todo对象。我们的项目还包含一个用于测试端点的测试套件。在系统上为项目选择一个位置,然后运行:

git clone --single-branch --branch base-project https://github.com/coderonfleek/node-azure-web-app.git 

将项目克隆到您的系统后,转到项目的根目录并安装依赖项:

cd node-azure-web-app
npm install 

您现在可以使用npm start命令运行应用程序。该命令在地址http://localhost:1337启动应用程序。一旦应用程序启动并运行,将您的浏览器指向http://localhost:1337/todos,您将找到todos的列表。

Todos Endpoint - Node App

使用Ctrl + C在命令行停止应用程序。

运行应用程序测试:

npm run test 

CLI 中的输出将显示测试已经通过。

Run tests - Node App

接下来,在项目的根目录下运行rm -rf .git命令来删除任何包含的.git历史。将项目推至 GitHub 。确保这是连接到您的 CircleCI 帐户的 GitHub 帐户。

转到您的远程存储库并创建一个deploy分支。

deploy branch - GitHub

这个分支将致力于连接到 Azure web apps 的部署。

将部署分支连接到 Azure

我们需要做一些设置,以便当新的更改被推送到这个分支时,Azure 将触发部署。返回您的 web 应用程序页面,点击部署中心

注意: 不要使用标有预览的菜单。

部署中心页面,选择 GitHub ,然后点击页面下方的授权

接下来,从构建提供者部分选择应用服务构建服务,并点击继续。系统会提示您选择 GitHub 用户、组织和分支机构。

Azure repo configuration - GitHub

点击继续进入总结页面。点击完成完成该过程。然后,Azure 会将应用程序初步部署到 web 应用程序中。完成后,访问 web 应用程序 URL 上的/todos端点。从 Azure 网页上,当你点击浏览时,可以访问 URL。

Todos App live - GitHub

在 CircleCI 建立项目

现在,转到 CircleCI 仪表板上的项目页面。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

在设置页面上,点击 Use Existing Config 表示您正在手动添加一个配置文件,而不是使用样本。将提示您下载管道的配置文件或开始构建。

Build Prompt - CircleCI

点击开始建造。此构建将失败,因为您尚未设置配置文件。

配置对 GitHub 的写访问

部署策略的最后一步是在测试通过后自动推进到deploy分支。要做到这一点,您需要对 GitHub 存储库进行经过验证的写访问。幸运的是,CircleCI 提供了添加一个User API Key来实现这一点的方法。在你的项目上,进入项目设置- > SSH 密钥。在用户密钥部分,点击授权 GitHub 进行连接。

User API Key - CircleCI

一旦 CircleCI 和 GitHub 连接,一个添加用户密钥按钮被添加到用户 API 密钥部分。单击此按钮生成一个“指纹”,以后可以在部署管道中使用。复制并保存在安全的地方。

因为指纹将与您的 GitHub 电子邮件和用户名一起在管道脚本中使用,所以将它们放在环境变量中更安全。在项目设置的侧菜单上,点击环境变量并添加这三个环境变量:

  • GITHUB_EMAIL:您连接的 GitHub 账户的电子邮件
  • GITHUB_USERNAME:你的 GitHub 用户名
  • GITHUB_FINGERPRINT:之前生成的认证指纹

编写测试和部署脚本

最后,是时候编写部署脚本了。这个脚本有四个部分:

  • 主分支机构的结帐代码
  • 安装应用程序依赖项
  • 运行测试
  • 将更新推送到deploy分支

为了处理最后一步,我们将使用一个npm userland 模块在package.json中编写一个deploy脚本。使用该模块允许我们避免在管道脚本中执行原始的git操作。将使用 gh-pages 包。通常这个包在通过将文件推送到一个专用的gh-pages分支来将静态站点部署到 GitHub 页面时使用。幸运的是,这个包很容易配置,可以将文件从一个分支推送到任何一个存储库中的另一个分支。

将此软件包安装在项目的根目录下:

npm install gh-pages --save-dev 

接下来,在package.json文件中添加deploy脚本:

"scripts" : {
  ...
  "deploy" : "npx gh-pages -b deploy --message '[skip ci] Updates' -d ./"
} 

这个脚本调用gh-pages,使用npx将文件从main分支推送到deploy分支。添加了[skip ci] Updates--message参数,以便在将更改推送到该分支时,CircleCI 不会重新运行管道。

现在,您可以开始编写管道脚本了。在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml文件中,输入:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test
      - run:
          name: Configure Github credentials
          command: |
            git config user.email $GITHUB_EMAIL
            git config user.name $GITHUB_USERNAME
      - add_ssh_keys:
          fingerprints:
            - $GITHUB_FINGERPRINT
      - run:
          name: Deploy to Azure Web App
          command: npm run deploy 

以下是该文件中发生的情况:

  • 首先提取一个合适的映像,并将代码签出到工作目录中
  • 安装依赖项并缓存node_modules文件夹。
  • npm run test被调用来运行项目测试。如果测试通过,使用适当的环境变量配置 GitHub 凭证,并通过 fingerprint 环境变量添加ssh
  • 最后,运行npm run deploy调用deploy脚本将更改推送到deploy分支,以便 Azure 获取应用程序并将其部署到 web 应用程序。

为了显示我们的应用程序的变化,向todos.js中的数组再添加一个todo对象:

module.exports = [
  ......,
  {
    id: 4,
    task: "Make Dinner"
  }
]; 

接下来,更新__tests__/apiTest.js中的测试套件,检查四个todo对象是否从集合中返回:

expect(res.body.length).toBe(4); 

保存对项目的更改,并提交到远程存储库中的主分支。你成功部署了!

Deployment Successful - CircleCI

单击工作流以查看详细信息。

Deployment Details - CircleCI

现在,前往 Azure web 应用程序的部署中心。正在触发一个部署流程。它开始时是待定的。

Deployment Pending - Azure

然后它运行。

Deployment Running - Azure

完成后,它会显示“Success (Active)”,以表明您已经对应用程序进行了最新的更改,并且正在运行。

Deployment Success - Azure

现在,如果您在已部署的 web 应用程序上再次访问/todos端点(如果需要,可以刷新),您应该会看到新添加的todo对象:

Todos Updated - Web App

结论

DevOps 是关于提供、设计和集成解决方案的。在本教程中,我们已经能够将 CircleCI 在构建高度可定制的 CI/CD 管道方面的能力与 Azure web apps 托管平台的部署相结合。想要在 Azure 上托管应用程序的架构师和工程师可以充分利用 CircleCI 强大的管道。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

Go 应用程序的持续部署| CircleCI

原文:https://circleci.com/blog/continuous-deployment-for-go-applications/

Go 是一种开源编程语言,由 Google 支持,可以轻松构建简单、可靠、高效的软件。Go 越来越受欢迎,因为它是为开发 C 和 Java 有局限性的分布式系统而构建的。Go 使用网络服务器的效率和友好的语法使它成为开发网络应用程序的最佳选择。

在本教程中,我将演示如何将一个演示 Go 应用程序部署到 Heroku。准备好了吗?让我们开始吃吧。

先决条件

要遵循本教程,需要做一些事情:

  1. 编程基础知识
  2. 安装到您的系统上(您可以在这里找到安装指南
  3. 关于 Heroku 的报道
  4. 一个的账户
  5. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

用 Go 构建一个简单的 API

让我们从创建一个非常简单的具有两个端点的模拟 API 开始。API 根的一个端点/和另一个端点/register,后者接收一些用户数据,并以json格式的成功响应将其回显。

我们将使用 Go 和 Gin 创建这个 API。Gin 是一个用于 Go 的标准 web 框架,类似于用于 Node.jsExpress.js 或者用于 Python 的 Django

在系统上的首选位置,创建一个名为go-registration-api的文件夹,并导航到该文件夹的根目录:

mkdir go-registration-api
cd go-registration-api 

接下来,通过运行以下命令初始化 Go 项目:

go mod init go-registration-api 

go mode init命令类似于 Node.js 中的npm init,它将文件夹初始化为一个名为go-registration-api的 Go 项目。该命令还会在项目的根目录下创建一个go.mod文件。这个文件类似于 Node.js 中的package.json文件,它管理依赖关系和更多的项目级配置。

让我们创建我们的项目入口文件。在项目的根目录下,创建名为main.go的条目文件,并粘贴以下代码:

package main

import "fmt"

func main (){
    fmt.Println("It's Aliveeee!!!")
} 

这是一个简单的“Hello World”Go 应用程序,我们在其中大喊“这是 Aliveeee!!!"到屏幕上,唤醒弗兰肯斯坦的怪物。

要运行我们的代码,请到您的终端。在项目的根目录下,运行以下命令:

go run main.go 

在上面的代码中,我们调用 Go 的run命令,并向它提供我们想要运行的文件。这将编译并运行代码。It's Aliveeee!!!打印在 CLI 上。

为了方便起见,我们不想每次选择运行我们的应用程序时都必须运行go run main.go,所以我们需要创建一个makefile文件。在项目的根目录下创建一个名为makefile的文件(没有文件扩展名)。这个文件将帮助我们用更简单的命令代理一些常用的Go CLI 命令。将以下内容输入makefile:

dev:
	go run main.go 

注意:makefile 只对标签有效。上面的第二行是制表符一次。

这里,我们为go run main.go创建了一个代理dev命令。现在,我们可以运行以下内容:

make dev 

这将给出与运行go run main.go相同的结果。它作为我们在开发模式下运行 Go 项目的命令。

构建 API

现在我们可以开始构建我们的模拟 API 了。首先,通过运行以下命令安装 Gin 框架:

go get -u github.com/gin-gonic/gin 

这将下载运行 Gin 所需的所有包。在项目的根目录下创建一个go.sum文件。这个文件的工作方式类似于 Node.js 中的package-lock.json,它跟踪项目中安装的所有包的确切版本。

一旦 Gin 安装完毕,替换main.go中的所有代码:

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Welcome to the Go Registration API",
		})
	})

	r.POST("/register", func(c *gin.Context) {
		fullname := c.PostForm("fullname")
		phone := c.PostForm("phone")
		email := c.PostForm("email")

		c.JSON(200, gin.H{
			"status":   "success",
			"message":  "User details successfully posted",
			"fullname": fullname,
			"phone":    phone,
			"email":    email,
		})
	})
	r.Run()
} 

在上面的代码中,我们从导入gin包开始。然后,在我们的main函数中,我们用下面的代码行创建了一个新的gin

r := gin.Default() 

该命令返回一个路由器,其中包含用于日志记录和恢复等任务的有用中间件。接下来,我们创建了两个端点:

  • /:一个GET端点,返回一个json对象,并带有一条欢迎用户使用 API 的消息
  • /register:接收用户表单数据的POST端点,并在json成功响应中回显

最后,我们调用r.Run()来确保应用程序不会退出,而是继续运行。

运行 API 端点

是时候让我们的模拟 API 兜一圈了。通过键入以下命令运行应用程序:

make dev 

这将启动一个本地服务器,在http://localhost:8080为应用程序提供服务。

注意 : 如果在您进行更改时应用程序正在运行,使用Ctrl + C关闭应用程序并重启。

使用 Postman 调用端点会产生下面的两个截图。

Home endpoint test

Register endpoint test

为部署设置 Heroku 应用程序

我们需要一个托管服务来部署我们的 Go 应用程序;本教程我选择了 Heroku。通过导航到 Heroku 仪表板并点击新建来设置 Heroku 应用程序。选择新建应用,填写应用名称。

New App - Heroku

成功创建应用程序后,您将被重定向到应用程序页面。记住您的应用程序名,在本例中是go-registration-api,因为您稍后会用到它。

在您的Account Settings页面上找到您的 Heroku API 密钥。当你点击网页右上角的头像时,你可以从下拉菜单中进入账户设置

将项目连接到 CircleCI

既然我们已经设置了 Heroku,我们需要自动部署我们的 Go 应用程序。从将你的项目推送到 GitHub 开始。

接下来,转到 CircleCI 仪表板上的添加项目页面。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

在设置页面上,单击使用现有配置以指示 CircleCI 我们正在手动添加配置文件,而不是使用显示的示例。接下来,您会得到提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有设置配置文件。

接下来,我们需要为刚刚添加的项目设置环境变量。这为我们的项目提供了对 Heroku 进行部署的认证访问。

管道页面点击项目设置。确保您的项目当前已被选中。

Project settings - CircleCI

项目设置页面,点击侧边菜单上的环境变量

Environment variables - CircleCI

环境变量页面中,点击添加环境变量。添加这些变量:

  • HEROKU_APP_NAME:在这种情况下,go-registration-api是您的 Heroku 应用程序的名称
  • HEROKU_API_KEY:在账号标签中找到你的 Heroku 账号 API 密匙

成功!您已经在 CircleCI 控制台上完成了部署到 Heroku 的设置。

使用 CircleCI orbs 配置自动部署

现在是时候部署我们的 Go 应用程序了,让它真正成为“Aliveeee!!!"。首先,我们需要在项目的根目录下创建一个Procfile文件。

创建Procfile文件(没有文件扩展名),并将以下命令添加到该文件中:

web: bin/go-registration-api 

Heroku 运行我们的 Go 应用程序的构建版本,位于bin目录中。

接下来,创建一个部署管道配置文件来包含我们的配置脚本。在项目的根目录下,创建一个名为.circleci的文件夹。在该文件夹中创建一个config.yml文件。在config.yml文件中,输入以下配置:

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

我们可以只用 7 行代码部署我们的应用程序,因为我们使用了 CircleCI orbs 的强大功能。这个配置吸引了切尔莱西的赫罗库之球。这个 orb 抽象了许多样板 Heroku CLI 命令,并为部署应用程序提供了一个易于使用的 API。

接下来,我们称之为heroku/deploy-via-git作业。这项工作从远程存储库中检出我们的代码,并将其部署到 Heroku。它使用我们之前定义的环境变量向 Heroku 认证。

将更改提交到您的项目,并推送到远程存储库来运行部署脚本。

Build Successful - CircleCI

点击成功标签查看部署详情。

Build Details - CircleCI

记录状态细节以显示 Heroku 如何将应用程序检测为 Go 应用程序,安装依赖项,构建项目,并运行我们的Procfile文件中的命令。

https://go-registration-api.herokuapp.com访问我们的应用程序。

App Live - Heroku

我们还可以使用 Postman 在实时应用程序上测试我们的/register端点。

Postman Live Test

结论

Go 是开发人员的一个选择,他们喜欢新语言和框架的花哨功能,但又想要 C、Java 和 C++的强大功能和企业级功能。如果你对 Node.js、Python 和 Ruby 感到厌烦或沮丧,为什么不试试呢?

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

Next.js 应用程序的持续部署| CircleCI

原文:https://circleci.com/blog/continuous-deployment-for-next-js-apps/

React.js 是一个非常强大的前端框架,多年来它的流行和采用已经证明了这一点。然而,构建一个完整的生产级应用程序涉及到 React 框架本身不包含的特性集。生产级应用程序开发需要路由、代码分割、捆绑、CSS 模块等特性。这些都不是 React.js 提供的现成功能。这就是 Next.js 的用武之地。

Next.js 就是 Next.js 官网上宣传的生产的 React 框架。它为 React.js 开发人员提供了生产级功能,可以轻松构建生产就绪的应用程序。

在本教程中,您将学习如何将 Next.js 应用程序自动部署到 Heroku。

先决条件

要遵循本教程,需要做一些事情:

  1. Javascript 的基础知识
  2. 您系统上安装的 Node.js (版本> = 10.3)
  3. 一个英雄的账户
  4. 一个的账户
  5. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

创建 Next.js 项目

首先,通过运行以下命令创建一个新的 Next.js 项目:

npx create-next-app next-cd 

这将在next-cd文件夹中自动创建一个 Next.js 应用程序(你可以给这个文件夹取任何你选择的名字)。一旦搭建过程完成,进入项目的根目录,使用以下命令运行应用程序:

cd next-cd
npm run dev 

这将启动一个开发服务器,该服务器在其默认的3000端口(http://localhost:3000)为应用程序提供服务。在你的浏览器中加载这个 URL,你会看到默认的主页。

New App (Local) - Next.js

创建 Heroku 应用程序

下一步是设置一个 Heroku 应用程序来托管我们的应用程序。导航到您的 Heroku 账户管理控制台,进入新建->-新建 app ,用您喜欢的名字新建一个 app。

New App - Heroku

记下您刚刚输入的应用程序名称。你以后会需要这个的。

接下来,在仪表盘的账户设置部分找到你的 Heroku API 密钥。在教程的后面部分,您也将需要它。点击网页右上角的头像,你可以从下拉菜单中进入账户设置

为部署设置 CircleCI 项目

要在 CircleCI 上设置您的项目,您需要将您的项目推送到 GitHub

接下来,转到 CircleCI 仪表板上的Add Projects页面添加项目。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

在设置页面上,单击 Use Existing Config 以指示 CircleCI 我们将手动添加一个配置文件,而不使用显示的示例配置。接下来,您会得到提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有添加配置文件。我们以后再做。

我们需要在 CircleCI 控制台上做的最后一件事是为我们刚刚添加的项目设置环境变量。这将使我们的项目能够对我们的 Heroku 应用程序进行身份验证访问以进行部署。

点击Pipelines页面上的项目设置进入您的项目设置(确保您的项目是当前选择的项目)。

Project settings - CircleCI

在这个页面上,点击侧面菜单上的环境变量。然后点击添加环境变量

Add Environment variable - CircleCI

添加以下环境变量:

  • HEROKU_APP_NAME:这是您的 Heroku 应用程序的名称(在我的例子中是next-cd)。
  • 您的 Heroku 帐户 API 密钥。这可以在 Heroku 上的账号标签下的账号设置下找到。

添加完成后,您现在已经在 CircleCI 控制台上为部署到 Heroku 做好了一切准备。

自动化 Next.js 应用程序的部署

现在我们可以开始为将 Next.js 应用程序部署到 Heroku 构建自动化脚本了。

您需要创建一个包含 Heroku 构建和启动应用程序的指令的Procfile文件。在项目的根目录下创建一个名为Procfile(没有文件扩展名)的文件,并输入以下内容:

npm run build
NODE_ENV=production npm run start 

该文件中的第一个命令创建了 Next.js 应用程序的生产优化版本。下一个命令在生产模式下启动应用程序。

现在我们需要确保 Heroku 在生产中使用动态端口。对package.json中的start脚本做如下小小的修改:

...
"start": "next start -p $PORT"
... 

这样,我们的 Next.js 应用程序现在将在生产中使用 Heroku 设置的动态$PORT变量。

最后,我们为 CircleCI 创建部署脚本,以构建我们的部署自动化管道。在这个脚本中,我们使用 CircleCI 的 Heroku orb 将我们的应用程序从 GitHub 仓库直接部署到 Heroku。orb是易于使用的包,它抽象了许多样板文件,有时是复杂的命令/工作流,提供了一个开发人员友好的 API 供我们在配置文件中使用。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml里面,输入以下代码:

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

在上面的配置中,我们引入了 Heroku orb ( circleci/heroku@0.0.10),它自动为我们提供了一组强大的 Heroku 任务和命令。其中一个任务是heroku/deploy-via-git任务,它将应用程序直接从 GitHub repo 部署到 Heroku 帐户。这项工作已经负责安装 Heroku CLI、安装项目依赖项、运行构建脚本和部署应用程序。它还获取我们的环境变量,以便顺利部署到我们的 Heroku 应用程序。

现在是检验我们迄今为止的工作的时候了。提交对项目的所有更改,并推送到您的远程 GitHub 存储库。这将自动触发部署管道。

Build Successful - CircleCI

通过点击 build,您可以看到部署细节。

Build Details - CircleCI

不要开始炫耀,有时成功的构建并不完全等同于成功的部署。Heroku 平台本身还有很多可以出错的地方。为了确认我们已经成功部署,请转到您的 Heroku 应用程序仪表板并单击Open app。这将在 Heroku 指定的 URL ( https://[APP_NAME].herokuapp.com)打开应用程序。下面的默认主页确认部署成功。耶!

Site Live - Heroku

现在,让我们确认,当我们将更改推送到我们的远程存储库时,它们是自动部署的。将./pages/index.js中的h1标签从:

<h1 className={styles.title}>
    Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1> 

收件人:

<h1 className={styles.title}>
    Welcome to <a href="https://nextjs.org">My Next.js! Site</a>
</h1> 

然后提交并推送更改以触发另一个部署。一旦构建成功,刷新您的实时站点,您将看到下面的屏幕。

Site Live - Heroku

结论

通过手动部署过程对应用程序进行从很小到很大的更改很快就会变成一个令人沮丧和费力的过程。有了 CircleCI,您可以实现流程自动化,消除任何由手动流程引入的常规或人为错误。在本教程中,我们已经能够成功地创建一个自动化部署管道,以实现对我们的应用程序的任何更改的无缝部署,使他们少了一件担心的事情。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

将 Nest.js 应用程序持续部署到 Heroku | CircleCI

原文:https://circleci.com/blog/continuous-deployment-nestjs/

如果你已经在软件开发领域,尤其是 web 开发领域工作了一段时间,那么你就会知道将你的源代码部署到 web 服务器上是多么的乏味和有压力。大多数时候,这是通过使用文件传输协议(FTP)上传来完成的。但是现在我们有许多方法来自动化部署过程。在本教程中,我们将学习如何使用 CircleCI 设置 Nest.js 应用程序到 Heroku 的持续部署。

先决条件

要想从本教程中获得最大收益,以下内容是必需的,也是至关重要的:

  • 安装在电脑上的 Node.js
  • 安装在您电脑上的 Nest CLI
  • GitHub 的一个账户
  • 一个的账户
  • 英雄的叙述
  • 虽然不是强制性的,但是您应该知道一些关于 TypeScript 的事情。

搭建新的 Nest.js 应用程序

要创建新的 Nest.js 应用程序,请从终端导航到开发文件夹的根目录,并运行以下命令:

nest new nest-heroku-demo 

系统会提示您选择首选的软件包管理器。选择npm并点击键盘上的ENTER继续。一个新的 Nest.js 应用程序将被创建在一个名为nest-heroku-demo的文件夹中,它的所有依赖项都将被安装。

安装过程完成后,进入新创建的项目文件夹,使用以下命令运行应用程序:

// move into the project
cd nest-heroku-demo

// start the server
npm run start:dev 

您可以在默认端口3000上查看欢迎页面。

Default Page

创建演示应用程序

我们将快速创建一个基本的 Nest.js 应用程序,它具有一个端点,用于呈现产品列表。为了简单起见,我们将创建一个模拟产品列表,并将其作为响应返回。首先,用CTRL + C停止应用程序在终端上运行,并在您选择的代码编辑器中打开项目。接下来,在src文件夹中创建一个名为mock的文件夹。在其中,创建一个名为products.mock.ts的文件。将以下内容粘贴到该文件中(src/mock/products.mock.ts):

export const PRODUCTS = [
  {
    id: 1,
    name: "First product",
    description: "This is the description for the first product",
    price: "200",
  },
  {
    id: 2,
    name: "Second product",
    description: "This is the description for the second product",
    price: "500",
  },
  {
    id: 3,
    name: "Third product",
    description: "This is the description for the third product",
    price: "800",
  },
  {
    id: 4,
    name: "Fourth product",
    description: "This is the description for the fourth product",
    price: "100",
  },
  {
    id: 5,
    name: "Fifth product",
    description: "This is the description for the fifth product",
    price: "250",
  },
]; 

一旦 HTTP GET 请求被发送到/products端点,这里导出的产品列表将作为响应返回。我们稍后将讨论关于这个端点的更多细节。

设置服务

我们将使用默认的AppService来返回产品列表。用以下代码替换src/app.service.ts的内容:

import { Injectable } from "@nestjs/common";
import { PRODUCTS } from "./mock/products.mock";

@Injectable()
export class AppService {
  products = PRODUCTS;

  async getProducts() {
    return await this.products;
  }
} 

在上面的文件中,我们从模拟文件导入了PRODUCTS,并在getProducts()方法中返回了列表。

创建产品端点

接下来,我们将在默认的AppController中创建/products端点。为此,导航到src/app.controller.ts文件并用以下内容替换其内容:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("/products")
  getProducts() {
    return this.appService.getProducts();
  }
} 

默认情况下,AppService已经被注入该控制器。这里,我们创建了一个名为getProducts的新方法,前缀为/products,并在 AppService 中调用了getProducts()方法。

运行应用程序

从项目的根目录中,使用npm run start:dev再次启动应用程序,并导航到http://localhost:3000/products。您将看到一个页面,显示我们在 JSON 中被模仿的产品列表。

Product List on Localhost

注意 : 我安装了一个扩展来美化 JSON。

推送至 GitHub

参见本指南,了解如何将项目推送到 GitHub

创建 Heroku 应用程序

部署需要设置 Heroku 应用程序。这有助于 Heroku 准备接收您的源代码。首先:

您将被重定向到一个页面,在该页面上您将输入应用程序的基本详细信息。输入首选名称。

Create Heroku app

我将这个应用程序命名为nest-heroku-demo。Heroku 应用程序的名称需要是唯一的,所以你可以随意使用任何你认为合适的名称,然后点击创建应用程序。为了让 CircleCI 惟一地标识我们的 Heroku 应用程序并自动对其进行部署,我们需要创建环境变量。这将是我们刚刚创建的应用程序的名称和我们的 Heroku 帐户的 API 密钥。要查看您的 Heroku API 密钥,请点击您的个人资料图片,并从下拉列表中选择帐户设置

Account Settings

这将带您到一个页面来管理您的帐户。确保选择了账户选项卡,并向下滚动到 API 键部分。

API Key section

点击显示查看 API 密钥,然后复制。保管好它,因为我们以后会用到它。

为连续部署添加 CircleCI 配置

在本节中,我们将创建一个 CircleCI 配置文件,我们将在其中为我们的应用程序编写部署脚本。为此,在应用程序的根目录下创建一个名为.circleci的文件夹。然后在其中创建一个文件,并将其命名为config.yml。将以下代码粘贴到这个新文件中:

version: 2.1
orbs:
  heroku: circleci/heroku@1.0.1
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

上面的配置文件指定了该项目的 CircleCI 配置版本。在orbs键中,我们调用了撰写本文时可用的最新版本的 Heroku orb 。这个 orb 抽象了设置 Heroku CLI 所涉及的复杂性,因为它将自动安装并用于将应用程序部署到 Heroku。

在 CircleCI 建立项目

现在我们已经创建了一个 Heroku 应用程序,并设置了配置以方便 CircleCI 将我们的 Nest.js 应用程序部署到 Heroku,我们需要在 CircleCI 上配置我们的项目。使用包含 Nest.js 应用程序存储库的链接 GitHub 帐户登录到您的 CircleCI 帐户。在项目页面上,找到您的项目名称,然后单击设置项目

Set up project

将提示您几个关于配置文件的选项。选择在我的回购中使用.circleci/config.yml选项。在 GitHub 上输入你的代码所在的分支名称,然后点击设置项目按钮。

Select Config File

您的第一个工作流将开始运行,但会失败。

Failed Build

现在,如果您点击上面页面中的 job heroku/deploy-via-git,您将看到部署失败的详细原因。

Heroku Key Error

别担心,我们只需要做以下事情:

  • 添加 Heroku 应用程序的细节作为环境变量
  • 更新我们项目中src/main.ts的端口
  • 创建一个Procfile

我们将在下一节中完成所有这些工作。

向 CircleCI 添加环境变量

为了让 CircleCI 在部署过程中对 Heroku 应用程序进行身份验证访问,我们需要从 Heroku 帐户向 CircleCI 管道添加两个环境变量

点击管道页面上的项目设置进入您的项目设置(确保您的项目是当前选择的项目)。

Project settings

在设置页面的侧边栏菜单上,点击环境变量

点击添加环境变量。这将显示一个提示,您可以在其中输入变量名和值。所需的变量有:

  • HEROKU_APP_NAME:之前创建的 Heroku 应用程序的名称
  • 从你在 Heroku 上的账户面板获得的 Heroku API 密钥

Environment variable page

我们几乎完成了设置自动化应用程序部署所需的所有配置。

更新应用程序中的端口

在代码库中,我们需要用一个选项来更新main.ts,以使用固定端口或动态分配的值。这是因为 Heroku 经常为每个新应用程序动态分配一个端口,所以设置一个固定值(Nest.js 应用程序的默认值)会导致错误。打开src/main.ts并更新其内容,如下所示:

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT || 3000); // update this line
}
bootstrap(); 

有了这个选项,应用程序既可以在3000的固定端口上运行,也可以在.env文件中指定的任何动态端口上运行。

创建 Procfile

最后,您需要在应用程序的根目录下创建一个名为Procfile的新文件,并将以下内容粘贴到其中:

web: npm run start:prod 

这个文件告诉 Heroku 在启动时将用于执行应用程序的命令。

保存这个文件,并将您的更改推送到 GitHub 存储库。现在,请查看您的 CircleCI 仪表板,以了解部署进度。

Deployment success status

我们项目的 CircleCI 管道的状态表明构建是成功的,我们的应用程序已经部署。您可以通过导航到 Heroku 为该应用程序生成的链接来确认这一点。URL 的格式总是相同的:https://YOUR_HEROKU_APP_NAME.herokuapp.com/。您还可以查看我的运行实例的端点。

Heroku App

恭喜你!您刚刚向 Heroku 部署了一个 Nest.js 应用程序。

结论

在本教程中,我们能够构建一个简单的 Nest.js 应用程序,设置一个 Heroku 应用程序,并自动将 Nest.js 应用程序部署到 Heroku。这个项目的完整源代码可以在 GitHub 上找到


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

将 Express GraphQL 服务器持续部署到 Heroku | CircleCI

原文:https://circleci.com/blog/continuous-deployment-of-an-express-graphql-server-to-heroku/

自从 2015 年由脸书在公开发布以来,GraphQL 的受欢迎程度与日俱增。该技术使前端客户能够从后端查询他们到底需要什么,并获得了 Pinterest、Coursera、Airbnb 和脸书等知名公司的广泛认可和采用。

在这篇文章中,我们将学习如何使用 CircleCI 通过持续集成 (CI)管道将 Node.js 编写的简单 GraphQL 服务器部署到 Heroku。

先决条件

要跟进这篇文章,您需要设置以下内容:

  • 安装在您的系统上的 Node.js (您可以通过在您的终端上运行命令node -v来打印您安装的 Node.js 版本来确认这一点)
  • 安装在您的系统上的 Git (您可以通过在您的终端上运行命令git来确认这一点;这应该会打印出可用的 git 命令)
  • 英雄的叙述
  • GitHub 的一个账户
  • 一个的账户

#搭建 GraphQL 服务器项目要开始设置 GraphQL 服务器,请通过运行以下命令为项目创建一个新文件夹:

mkdir graphql-test-server 

现在进入文件夹的根目录,运行以下命令来构建一个新的 Node.js 项目:

npm init -y 

-y可选标志允许您跳过确认创建package.json文件问题的默认答案,直接接受它们。

我们将创建一个 ExpressJS Node.js 应用程序,并构建 GraphQL 服务器。为此,我们需要安装以下软件包:

  • express:创建我们的 ExpressJS Node.js 应用程序
  • graphql:node . js 的 GraphQL npm 包
  • express-graphql:graph QL 的 ExpressJS 中间件

通过运行以下命令安装这些软件包:

npm install --save express graphql express-graphql 

太好了!

我们现在有了开始组装 GraphQL 服务器所需的包。

定义 GraphQL 模式

通过运行以下命令,在项目的根目录下创建一个名为src的文件夹:

mkdir src 

在这个文件夹中,创建一个名为schema.js的新文件,并粘贴下面的代码。

 const { buildSchema } = require("graphql");

const schema = buildSchema(`
    type Query {
        users: [User!]!,
        user(id: Int!): User!
    }

    type Mutation {
        editUser(id: Int!, name: String!, email: String!): User!
    }

    type User {
        id: ID!
        name: String!
        email: String
        posts: [Post!]
    }

    type Post {
        id: ID!
        title: String!
        published: Boolean!
        link: String
    }
`);

module.exports = schema; 

在上面的代码中,我们创建了四个类型,包括两个内置类型(QueryMutation)和两个自定义类型(UserPost)。

从自定义类型开始,我们定义了以下内容:

  • User:一种类型,表示应用程序中的用户及其相应的字段和一个相关的posts字段,该字段返回用户创建的帖子数组
  • Post:表示由用户在应用程序中创建的出版物的类型及其相应的字段
  • Query:在Query类型中,我们定义了两条可以从 GraphQL 服务器查询的信息,如下所示:
    • users:一个Users的数组
    • user:参数列表中带有指定id的单个User
  • Mutation:在Mutation类型中,我们定义了editUser变异,给定用户的id,可以调用它来编辑用户,然后更新信息(nameemail)

模式是使用graphql包的buildSchema创建的,并在文件末尾导出。

模拟数据

为了获得查询结果,我们需要数据。在这个练习中,我们将使用 MongoDB。

然而,由于这篇文章的目标是演示如何部署一个简单的 GraphQL 服务器,所以我们不会费心设置一个 MongoDB 服务器。相反,我们将使用一个使用mongodb-memory-server的模拟版本。这个包允许我们创建和使用内存中的 MongoDB 数据库服务器。

要进行设置,请在项目的根目录下运行以下命令来安装所需的软件包:

npm install --save mongodb mongodb-memory-server 

根据您的下载速度,此安装可能需要一段时间。在撰写本文时,MongoDB 的大小至少为 66 兆字节。

安装完成后,在src文件夹中创建一个名为data.js的新文件。该文件将导出一组硬编码的用户数据。将以下代码粘贴到文件中。

const Users = [
  {
    id: 1,
    name: "Fikayo Adepoju",
    email: "fik4christ@yahoo.com",
    posts: [
      {
        id: 1,
        title: "Creating an Emoji Game with Vue, Auth0, and Google Vision API",
        published: true,
        link:
          "https://auth0.com/blog/creating-an-emoji-game-with-vue-auth0-and-google-vision-api/",
        author: 1
      },
      {
        id: 2,
        title: "Electron Tutorial: Building Modern Desktop Apps with Vue.js",
        published: true,
        link:
          "https://auth0.com/blog/electron-tutorial-building-modern-desktop-apps-with-vue-js/",
        author: 1
      },
      {
        id: 3,
        title: "State Management with Vuex: a Practical Tutorial",
        published: true,
        link:
          "https://auth0.com/blog/state-management-with-vuex-a-practical-tutorial/",
        author: 1
      }
    ]
  },
  {
    id: 2,
    name: "John Doe",
    email: "john@company.com",
    posts: [
      {
        id: 4,
        title: "Build a CI powered RESTful API with Laravel",
        published: true,
        link:
          "https://circleci.com/blog/build-a-ci-powered-restful-api-with-laravel/",
        author: 2
      },
      {
        id: 5,
        title: "Automate your Nuxt.js app deployment",
        published: true,
        link: "https://circleci.com/blog/automate-your-nuxt-js-app-deployment/",
        author: 2
      }
    ]
  },
  {
    id: 3,
    name: "Jane Paul",
    email: "jane@company.com",
    posts: []
  }
];

module.exports = {
  Users
}; 

在上面的代码中,我们有一个用户对象及其相应的帖子的数组。一旦我们的内存数据库被实例化,这些信息将被用来播种它。

下一个任务是设置我们的模拟 MongoDB 数据库服务器。在src文件夹中,创建一个名为database.js的新文件,并将以下代码插入该文件。

const { MongoMemoryServer } = require("mongodb-memory-server");
const { MongoClient } = require("mongodb");
const data = require("./data");

let database = null;

async function startDatabase() {
  const mongo = new MongoMemoryServer();
  const mongoDBURL = await mongo.getConnectionString();
  const connection = await MongoClient.connect(mongoDBURL, {
    useNewUrlParser: true
  });

  //Seed Database
  if (!database) {
    database = connection.db();
    await database.collection("users").insertMany(data.Users);
  }

  return database;
}

module.exports = startDatabase; 

在上面的代码中,我们导出了一个startDatabase函数。这个函数只是启动一个新的(模拟的)MongoDB 服务器实例,并获取数据库实例的连接字符串。然后,它使用连接字符串创建一个新的 MongoDB 客户端连接。

连接到数据库后,我们用从我们的data.js文件导出的用户数据作为种子。在用数据播种数据库之前,检查数据库引用是否为null,以防止应用程序每次启动时都将数据输入数据库。

最后,该函数返回数据库引用。

定义我们的解决方案

接下来,我们需要为 GraphQL 查询定义解析器,以便为每个查询操作返回适当的数据。我们将为usersuser查询以及editUser变异定义解析器。

src文件夹中创建一个名为resolvers.js的新文件,并粘贴以下代码。

const resolvers = {
  users: async (_, context) => {
    const { db } = await context();
    return db
      .collection("users")
      .find()
      .toArray();
  },
  user: async ({ id }, context) => {
    const { db } = await context();
    return db.collection("users").findOne({ id });
  },
  //Mutation resolvers
  editUser: async ({ id, name, email }, context) => {
    const { db } = await context();

    return db
      .collection("users")
      .findOneAndUpdate(
        { id },
        { $set: { name, email } },
        { returnOriginal: false }
      )
      .then(resp => resp.value);
  } 
};

module.exports = resolvers; 

如上所述,现在我们的服务器上有了三个 GraphQL 请求的解析器。这些解析器使用我们之前通过从 GraphQL 服务器的context对象引用而创建的数据库引用来获取和更新数据。

在下一节中,当我们把所有东西放在一起时,这个引用将被添加到 GraphQL 服务器设置的context对象中。

设置 GraphQL 服务器

是时候将我们构建的每个组件放在一起,以设置我们的 GraphQL 服务器了。

在我们开始之前,我们需要再安装一个包。graphql包附带了GraphiQL应用程序,它允许您查询 GraphQL 端点,但是让我们添加一个更健壮的工具。我们将安装graphql-playground-middleware-express包,为我们查询 GraphQL 端点提供一个更好的界面。

运行以下代码来安装该软件包:

npm install --save graphql-playground-middleware-express 

我们将使用这个中间件来建立查询我们的服务器的 GraphQL 平台。

让我们通过在项目的根目录下创建一个名为index.js的文件并在其中放置以下代码来设置我们的服务器。

const express = require("express");
const graphqlHTTP = require("express-graphql");
const schema = require("./src/schema");
const resolvers = require("./src/resolvers");
const startDatabase = require("./src/database");
const expressPlayground = require("graphql-playground-middleware-express").default;

// Create a context for holding contextual data 
const context = async () => {
  const db = await startDatabase();

  return { db };
};

const app = express();

app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    rootValue: resolvers,
    context
  })
);

//Graphql Playground route
app.get("/playground", expressPlayground({ endpoint: "/graphql" }));

const port = process.env.PORT || "4000";

app.listen(port);

console.log(`🚀 Server ready at http://localhost:4000/graphql`); 

在上面的文件中,我们从导入所有必需的模块开始。然后,我们创建 GraphQL 上下文,并从中返回一个包含对 MongoDB 实例的引用的对象。接下来,我们创建 ExpressJS 应用程序,并用 GraphQL schemaresolverscontext设置express-graphql中间件。然后,我们在路线/playground处设置 GraphQL 操场来加载我们的 GraphQL 端点。最后,我们启动 ExpressJS 服务器监听端口 4000,并向控制台打印一条消息。

查询 GraphQL 服务器

现在我们已经设置好了服务器,让我们试一试。首先,让我们在package.json中创建一个start脚本来启动我们的服务器。

将以下脚本添加到package.json文件的scripts部分。

...

“scripts” : {
	...,
	“start” : “node index.js”
} 

运行npm start启动服务器。

一旦服务器启动并运行,打开你的浏览器并访问http://localhost:4000/playground打开游戏场。您将看到一个类似于下图的屏幕。

操场自动指向我们的 GraphQL 端点。从这里,我们可以针对我们的端点编写查询并获得结果。将下面的查询粘贴到操场的查询部分,然后点击播放来运行它:

{
  users {
    name
    email
    posts {
      title
      published
    }
  }
} 

运行上面的查询将产生下面屏幕上显示的结果。

如上所示,我们的 GraphQL 服务器工作正常,并返回预期的数据。

和 CircleCI 一起部署到 Heroku

我们最后的任务是在 Heroku 主机平台上通过 CI 管道和 CircleCI 部署我们的 GraphQL 服务器。

我们将采取以下步骤来部署我们的 GraphQL 服务器:

  • 将项目推送到 GitHub 存储库
  • 创建一个新的 Heroku 应用程序并获取 Heroku API 密钥
  • 向我们的 CircleCI 帐户添加一个新项目,并将其连接到 GitHub repo
  • 将我们的 Heroku 应用程序名称和 API 键作为环境变量添加到我们的新项目中
  • 编写部署到 Heroku 的 CircleCI 配置文件
  • 将配置推送到我们的 GitHub repo 以部署到 Heroku

我们开始吧。首先,把项目推给一个 GitHub 回购。

接下来,创建一个 Heroku 应用程序,如下所示。您输入的名称是您将在 CircleCI 上保存为 Heroku 应用程序名称的名称。

你可以通过进入账户设置并向下滚动到 API 密钥部分来获得你的 Heroku API 密钥。

将我们的应用程序部署到 Heroku 的下一步是将 GitHub 存储库中的应用程序连接到 CircleCI。

转到您的 CircleCI 仪表板,在添加项目部分添加项目。

接下来设置你的项目(在这里是simple-graphql-node-server,点击设置项目。这将把你带到一个类似下面的页面。

点击开始构建开始设置项目。这将立即给出一个错误,表明在项目中找不到 CircleCI 配置文件。这是可以理解的,因为我们还没有包括我们的管道配置文件。我们稍后将会这样做。

下一步是将我们的 Heroku 细节作为环境变量添加到新建立的项目中。为了将我们的项目从 CircleCI 推送到 Heroku,我们需要在 CircleCI 和 Heroku 之间配置一个经过认证的握手。这是通过在 CircleCI 项目的设置中创建两个环境变量来实现的。这两个环境变量是:

  • HEROKU_APP_NAME:这是您的 Heroku 应用程序的名称(在本例中为simple-graphql-node-server)
  • 您的 Heroku 帐户 API 密钥。这可以在账号设置下你的 Heroku 账号的账号标签中找到。

要添加这些细节,请前往您的 CircleCI 仪表板,并为您的项目单击设置。在设置页面的工具条菜单上,点击构建设置下的环境变量

在环境变量页面上,创建两个名为HEROKU_APP_NAMEHEROKU_API_KEY的变量,并添加各自的值。

有了这些,我们的 CircleCI 配置可以使用它们对 Heroku 平台进行认证部署。现在,让我们编写 CircleCI 配置,将 GraphQL 服务器部署到 Heroku。

在项目的根目录下创建一个名为.circleci的文件夹,并在其中创建一个config.yml文件。添加以下配置。

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

上面的代码使用 CircleCI 的 Heroku orb 来执行到 Heroku 的无缝部署。一个 orb 是一个可重用的 YAML 配置包,它将重复的配置压缩成一行代码。这使得部署到 Heroku 变得非常容易,而不需要自己编写复杂的配置。

保存这个文件,并将您的更改推送到 GitHub 存储库。然后观察您的 CircleCI 仪表板在成功推送后触发部署。您将在仪表板上看到一个成功的部署,类似于下面的屏幕。

厉害!

现在让我们在 live 应用程序上访问我们的操场,以确认我们的部署是好的。访问链接https://YOUR_HEROKU_APP_NAME.herokuapp.com/playground。在我们的练习中,这将是如下所示的https://simple-graphql-node-server.herokuapp.com/playground

结论

在本文中,我们已经成功地组装了一个简单的 GraphQL 服务器,并使用 CircleCI 自动化了它的部署。GraphQL 将继续存在,并有望获得更多的采用,因为它的许多优点使它值得更多的使用。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

将 Gatsby 应用程序持续部署到 Heroku | CircleCI

原文:https://circleci.com/blog/continuous-deployment-of-gatsby-apps-to-heroku/

JAMStack 正在迅速成为 web 开发领域的一件大事。JAMStack 代表 Javascript、API 和 HTML 标记,是一种用于构建 web 应用程序的现代架构,它的一个主要特点是不需要数据库,web 服务器是可选的。JAMStack 架构旨在通过生成一个由 HTML、CSS 和 Javascript 组成的前端和一个只是返回 JSON 或 XML 的内容 API 的后端来分离前端和后端。

当今领先的 JAMStack 平台之一是 Gatsby.js 。Gatsby.js 是一个基于 React.js 的免费开源框架,帮助开发者构建高速网络应用。

在本教程中,我们将演示如何通过使用 CircleCI 构建一个持续集成管道来将 Gatsby.js 站点部署到流行的 Heroku 平台。

先决条件

为了跟进这篇文章,你需要准备一些东西:

  • Node.js > = 10.16 安装在您的系统上(您可以通过在您的终端上运行命令node -v打印出您安装的 NodeJS 版本来确认这一点)
  • Git 安装在您的系统上(您可以通过在您的终端上运行命令git来确认这一点。这应该会打印出可用的 git 命令)
  • 英雄的叙述
  • GitHub 的一个账户
  • 一个的账户

有了这些,你就准备好跟随了。

创建 Gatsby.js 项目

首先,我们需要创建我们的 Gatsby.js 项目。在系统中的适当位置,运行以下命令创建一个新项目:

npm gatsby new gatsby-heroku-deploy 

这个命令的gatsby-heroku-deploy部分是我要搭建的项目的文件夹名。您可以使用任何喜欢的名称。

这个命令将立即开始搭建一个新的 Gatsby.js 项目。你可能会被提示选择yarnnpm作为你的包管理器,在本教程中,选择npm

一旦搭建过程完成,进入您的项目的根目录(cd gatsby-heroku-deploy)并运行以下命令来启动一个服务器,以便在开发模式下为应用程序提供服务:

npm run develop 

这将为地址为http://localhost:8000/的应用程序提供服务,并显示下面的页面。

太好了!

我们现在已经有了一个功能正常的 Gatsby.js 站点。

为盖茨比设置 Heroku 应用程序

要为我们的 Gatsby 应用程序设置一个 Heroku 应用程序,我们需要登录 Heroku 并创建一个新的应用程序。

接下来,我们需要获得应用程序的名称(对于上面创建的应用程序,这将是gatsby-heroku-deploy)和 Heroku 帐户的 API 密钥。API 密匙可以在 Heroku 账户设置部分找到。

稍后,我们将需要这两个值(app name 和 API key)来为部署到 Heroku 的 CircleCI 项目创建环境变量。

我们需要在 Heroku 帐户上做的最后一件事是安装一些必要的构建包。构建包是在部署应用程序时运行的脚本。在这个练习中,我们需要两个构建包:

  • heroku/nodejs:所有 Node.js 应用程序都需要
  • heroku/Heroku-build pack-Static:Heroku build pack 静态包(因为 Gatsby.js 的构建是由静态文件组成的,这将帮助我们在 Heroku 平台上提供静态文件)

转到 Heroku 应用程序的设置页面,向下滚动到构建包(你可能已经看到heroku/nodejs构建包已经被添加)。点击 Add buildpack ,你会看到弹出一个类似下图的对话框。

对于每个构建包(heroku/nodejsheroku/heroku-buildpack-static),在文本字段中输入标识符,并点击保存更改

注意: 如果已经添加了heroku/nodejs,只需添加heroku/heroku-buildpack-static构建包即可。

在对两个构建包都做了这些之后,您将会看到现在在构建包中列出的两个构建包。

这就完成了 Heroku 所需的设置。

在 CircleCI 上设置 Gatsby.js 项目

我们的下一个任务是在 CircleCI 上建立我们的 Gatsby.js 项目。我们需要将我们的代码推送到您链接到 CircleCI 帐户的 GitHub 存储库。

接下来,转到添加项目页面来添加项目。

点击设置项目开始设置项目。这将加载下一个屏幕。

在设置页面上,点击开始构建。在构建开始之前,您将得到一个提示,要么下载并使用所提供的 CircleCI 配置文件,并将其添加到一个单独的分支中,要么手动设置一个。

选择手动添加继续。这将提示另一个对话框,检查确认您已经设置了配置文件,可以开始构建了。

点击开始构建完成设置。构建肯定会失败,因为我们还没有设置配置文件,这将在以后进行。

我们需要在 CircleCI 控制台上做的最后一件事是为我们刚刚添加的项目设置环境变量。这将使它能够对我们的 Heroku 应用程序进行身份验证访问,以进行部署。

点击管道页面上的项目设置进入您的项目设置(确保您的项目是当前选择的项目)。

这将导航到项目设置页面。在这个页面上,点击侧面菜单上的环境变量

在这个环境变量页面上,点击添加环境变量按钮

添加以下环境变量:

  • HEROKU_APP_NAME:这是您的 Heroku 应用程序的名称(在本例中为gatsby-heroku-deploy)
  • HEROKU_API_KEY:在账户设置中您的 Heroku 账户的账户标签下找到您的 Heroku 账户 API key

添加后,您将在 CircleCI 控制台上为部署到 Heroku 做好一切准备。

使用 orbs 部署 Gatbsy.js 站点

orb 是 YAML 配置的可重用包,它将重复的配置压缩成一行代码。它们使开发人员能够轻松地使用强大的管道功能,并抽象出所有的样板文件。

在本练习中,我们将使用 CircleCI 的 orb for Heroku 来配置我们的管道,以将我们的 Gatsby.js 站点部署到 Heroku。

但在此之前,我们需要创建一个文件来指导我们的 Heroku Buildpack 静态包如何部署和服务我们的应用程序。

在项目的根目录下,创建一个名为static.json的新文件,并在其中输入以下代码:

{
  "root": "public/",
  "headers": {
    "/**": {
      "Cache-Control": "public, max-age=0, must-revalidate"
    },
    "/**.css": {
      "Cache-Control": "public, max-age=31536000, immutable"
    },
    "/**.js": {
      "Cache-Control": "public, max-age=31536000, immutable"
    },
    "/static/**": {
      "Cache-Control": "public, max-age=31536000, immutable"
    },
    "/icons/*.png": {
      "Cache-Control": "public, max-age=31536000, immutable"
    }
  },
  "https_only": true,
  "error_page": "404.html"
} 

这里最重要的配置参数是root属性。当 Gatsby.js 运行它的build脚本(这也是 Heroku 用来在部署前运行构建的脚本)时,在包含应用程序生产版本的项目根目录下会生成一个public文件夹。这个生产版本是主机提供给用户的。

因此,root属性用于指向该文件夹,以便主机提供适当的代码。该配置中的其他参数只是 Gatsby.js 站点的最佳实践配置。例如,缓存指南可以在这里找到。

很好!现在我们可以编写部署脚本了。

在 Gatsby.js 项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

就是这样!

在上面的配置中,我们拉进了 Heroku orb circleci/heroku@0.0.10,它自动让我们访问一组强大的 Heroku 作业和命令。其中一个任务是heroku/deploy-via-git,它将你的应用程序直接从 GitHub repo 部署到你的 Heroku 账户。

这项工作已经负责安装 Heroku CLI、安装项目依赖项、运行构建脚本和部署应用程序。它还获取我们的环境变量,以便顺利部署到我们的 Heroku 应用程序。

现在,真相大白的时刻到了。让我们提交对 Gatsby.js 项目所做的所有更改,并推送到 repo 来触发部署。

太好了!

要查看部署的幕后操作,您可以点击进入构建。

现在,为了最终确认我们的 Gatsby.js 站点已经成功部署,请访问站点的默认 Heroku 地址https://[APP_NAME].herokuapp.com。如果你用了和我在这个练习中一样的名字,那就是https://gatsby-heroku-deploy.herokuapp.com/。您将看到一个类似于下图的屏幕(如果您使用的是不同的 Gatsby.js starter 模板,这可能会有所不同,在这里了解更多关于 starters 的信息)。

结论

在本教程中,我们使用 CircleCI orbs 将 Gatsby.js 站点部署到 Heroku。现在,当我们推送代码时,所有的更改都将自动部署。本教程还演示了 CircleCI orbs 如何简化部署。orbs 没有编写许多需要部署到 Heroku 的配置行,而是将所有这些抽象成方便我们使用的命令。

检查 CircleCI orbs 注册表中适合您的编程语言和部署目的地的 orbs。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

将节点应用持续部署到 Heroku | CircleCI

原文:https://circleci.com/blog/continuous-deployment-to-heroku/

CircleCI orbs 是 YAML 配置的可重用包,它将重复的配置压缩成一行代码。自 2018 年推出以来, CircleCI orbs registry 已经被开发者、开发团队以及希望帮助开发者将其服务无缝集成到持续集成管道中的公司所使用。

在本教程中,我们将展示如何使用 CircleCI orbs 将一个 Node.js 应用程序持续部署到 Heroku ,这是最流行的托管平台之一。

先决条件

要跟随本教程,您首先需要一些东西:

有了这些,你就准备好跟随了。

克隆 Node.js API 项目

我们希望在 Heroku 上托管的应用程序是一个简单的 Node.js API 项目,它由一个返回“todos”列表的单一路由组成。

由于本练习的重点是将应用程序部署到 Heroku 平台,因此不需要考虑创建新 Node.js 应用程序的细节。从这个回购里克隆一个现成的就行了。

要克隆项目,请运行:

git clone https://github.com/coderonfleek/simple-node-api.git 

这将把 Express.js API 项目克隆到您的系统上。这个应用程序的入口点是server.js文件,它包含以下代码:

const express = require("express");
const routes = require("./routes");

const app = express();
const port = process.env.PORT || "1337";
app.set("port", port);

app.use('/', routes);

app.listen(port, () => console.log(`Server running on localhost:${port}`)); 

这段代码在端口 1337 创建并公开了一个新的 Express.js 应用程序。然后应用程序被设置为使用在routes.js文件中定义的路线。

routes.js文件创建了一个新的 Express.js 路由器,并公开了一个单独的/todos route,它返回了一个在todo.js文件中定义的待办事项数组。然后,它导出路由器对象。代码如下:

const express = require('express');
const todos = require("./todos");

const router = express.Router();

router.get("/todos", function(req, res) {
  res.json(todos);
});

module.exports = router; 

通过首先安装依赖项来尝试这个项目。在项目的根目录中,键入:

npm install 

然后使用以下命令运行应用程序:

node server.js 

应用程序应该在地址http://localhost:1337启动并运行。打开您喜欢的浏览器,访问位于http://localhost:1337/todos/todos路线。

Screenshot of what the route returns

如上所示,该路径返回一个 todo 对象数组,每个对象都有一个idtask名称。

现在,在你的 GitHub 帐户上为这个项目创建一个新的存储库,并将项目推送到你刚刚创建的存储库中。

为部署准备 Node.js 项目

当没有指定 Heroku Procfile 时,Heroku 使用npm start命令启动 Node.js 项目。这个命令表明 Heroku 需要我们的package.json文件中的一个start脚本来启动我们的应用程序。

这意味着我们需要在我们的package.json文件中创建一个start脚本来指导 Heroku 如何启动应用程序。如果您正在使用克隆的项目,您可以跳过这一步。否则,转到package.jsonscripts部分(目前只包含一个test脚本)并添加start脚本:

….
“scripts” : {
  ……,
  “start”: “node server.js”

} 

有了这个脚本,Heroku 现在可以成功地启动我们的应用程序了。

在 Heroku 上创建应用程序

下一步是创建 Heroku 应用程序。该应用程序将映射到我们的部署,并帮助我们管理我们部署的应用程序。它将提供度量、扩展能力和访问应用程序日志等功能。

转到 Heroku 仪表盘,点击新建并选择创建新应用,创建一个新应用。

这将打开应用程序创建表单。

Screenshot of the app creation form

我们已将该应用程序命名为simple-node-api-circleci,但您可以随意给它取任何您喜欢的名称。只要确保该名称不包含空格或对 URL 不友好的字符。

点击创建应用设置您的新应用。

在 CircleCI 建立项目

将应用程序部署到 Heroku 的下一步是将 GitHub 存储库中的应用程序连接到 CircleCI。

转到您的 CircleCI 仪表板并在添加项目部分添加项目。

Screenshot of the dashboard

在你的项目旁边(这里是simple-node-api-updated,点击设置项目按钮。

在这个屏幕上,你会立即看到一个错误,说明在项目中找不到 CircleCI 配置文件

这是可以理解的,因为我们还没有包含管道配置文件。我们将在本教程的后面部分进行介绍。

现在,选择Write your own using our starter config.yml template。在该页面上,选择skip this step,然后选择Use existing config。构建将运行并失败。

在 CircleCI 上配置 Heroku 访问

为了将我们的项目从 CircleCI 推送到 Heroku,我们需要在 CircleCI 和 Heroku 之间配置一个经过认证的握手。通过在 CircleCI 项目的设置中创建两个环境变量来配置握手:

  • HEROKU_APP_NAME 是您的 Heroku 应用程序的名称(在本例中是simple-node-api-circleci
  • HEROKU_API_KEY 是你的 Heroku 账号 API 密匙。这可以在你的 Heroku 账户的账户标签下的账户设置下找到。滚动到 API 密匙部分,点击显示复制你的 API 密匙。

进入你的 CircleCI 仪表盘,点击项目的设置

CircleCI project settings

在设置页面的工具条菜单上,点击构建设置下的环境变量

Environment Variables form

在环境变量页面上,创建两个名为HEROKU_APP_NAMEHEROKU_API_KEY的变量,并输入它们的值。

有了这些,我们的 CircleCI 配置将能够对 Heroku 平台进行认证部署。

使用 Heroku orb 部署应用程序

现在来看主要的行动领域:我们的部署管道。这里有一个提示:这将是最容易的一步。你刚才笑了吗?我愿意这样相信!

这一步对于每一个 CI 工具来说都不容易,但是使用 CircleCI orbs,我们可以创造奇迹。这是因为 CircleCI orbs registry 有一个现成的 orb 用于将应用程序部署到 Heroku。我们将在 CircleCI 配置中使用 Heroku orb 来部署我们的应用程序。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。添加此配置:

version: 2.1
orbs:
  heroku: circleci/heroku@1.2.6
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git 

就这些吗?当然是了!记得吗,我告诉过你我们会变魔术的。

在上面的配置中,我们引入了 Heroku orb circleci/heroku@1.2.6,它自动让我们访问一组强大的 Heroku 作业和命令。

其中一项工作是heroku/deploy-via-git,它将你的应用程序直接从你的 GitHub repo 部署到你的 Heroku 账户。

现在,你可能有几个问题。Heroku CLI 是如何安装的?代码是什么时候从回购中签出的?身份验证凭据是如何访问的?Heroku 的部署命令是什么时候运行的?

这是球体真正力量的闪耀之处。前面问题中的所有进程都已经被 Heroku orb 抽象了。拉入 orb 确保我们有一个为部署到 Heroku 而设置的环境,同时作业检查我们的代码,将其部署到 Heroku,并启动应用程序。它抽象了过程的本质细节。

保存配置并将您的更改推送到 GitHub 存储库。这将立即触发通过 CircleCI 向 Heroku 的部署过程。

Screenshot of successful deployment

成功!要查看 Heroku orb 在幕后做了什么,请点击构建过程。

Build process details

您不必定义这些步骤,因为 orb 会为您处理一切。

要确认应用已经成功部署并且正在运行,请访问链接https://YOUR_HEROKU_APP_NAME.herokuapp.com。对于本教程,这个 URL 将是https://simple-node-api-circleci.herokuapp.com/todos。这将按预期返回我们的待办事项列表。为了更好的可视性,我使用了 Chrome 的 JSON 格式器插件。

Application using formatter plugin

现在你知道了。通过 CircleCI orbs 的强大功能,Node.js API 项目只需几个步骤就已成功部署到 Heroku。

结论

CircleCI orbs 是 CI/CD 世界的游戏规则改变者。抽象大量乏味、容易出错的样板配置代码,以提供简单、稳定和强大的抽象,这有助于开发人员专注于他们最擅长的事情:成功部署应用程序。

一定要检查 CircleCI orbs 注册表中适合您的编程语言和部署偏好的 orbs。与你的同事分享这个教程。让他们知道,有了 CircleCI 球,你不需要重新发明轮子。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

CircleCI orbs 的持续部署

原文:https://circleci.com/blog/continuous-deployment-with-circleci-orbs-automate-deploys-to-aws-gcp-k8s-and-more/

为了提高上市速度,开发团队已经开始从自动化他们的测试转移到 DevOps 成熟度的下一个阶段——自动化他们的部署。虽然这可能看起来是一个令人生畏的步骤,但我们已经构建了一组新的 orb,专门用于通过向团队的配置中添加几行预打包的代码来帮助团队进行这一大转变。通过简化自动化部署过程,团队可以更快地发布新功能,并利用尖端技术,而不必彻底检查现有基础架构。

自动化部署的团队可以更快地将代码推向市场。那么为什么不是每个团队都简单地自动部署呢?有几个关键的障碍阻止团队设置他们的 CI/CD 管道来自动部署。首先,代码覆盖率。对他们的代码没有信心的团队不会——也不应该——自动化他们的部署。为了解决这个问题,我们将特定于测试的 orb 放在一起,以帮助团队确定他们的代码覆盖率以及他们的测试在哪里缺乏。查看我们关于测试驱动开发的帖子,了解更多关于如何编写最有用的测试的想法。

一旦团队对他们的代码覆盖率有信心,他们通常会在实际自动化部署的时候陷入困境,因为不是所有的 CI/CD 工具都允许您部署到现代服务,如 AWS ECS、AWS Serverless 或 Google Cloud Run。而且,当您必须自己编写这些集成时,可能需要几个小时的开发时间。我们设计了部署球来克服这个障碍。您可以使用 orb 直接从 CI/CD 管道部署到任何测试或生产环境,设置过程只需几分钟而不是几小时。每个 orb 都是专为满足现代团队的需求而设计的——提供无缝、开箱即用的解决方案来集成尖端技术,并帮助团队达到 DevOps 成熟度的新水平。

了解如何使用 orb 自动部署到流行的服务:

使用这些 orb 部署到云服务:

安装 Heroku CLI 并将应用程序部署到 Heroku。

AWS CodeDeploy
将应用程序部署到 AWS CodeDeploy。

亚马逊 ECS
支持 EC2 和 Fargate 启动类型和蓝/绿部署。

Cloud Foundry
向 Cloud Foundry 推送部署应用。

AWS SAM 无服务器
利用 AWS 无服务器应用程序模型,在 CircleCI 上构建、测试和部署您的 AWS 无服务器应用程序。

Google Cloud Run
构建无状态映像并将其部署到 Google Cloud Run,作为无服务器应用程序。

使用这些球体部署到 Kubernetes:

OpenShift (RedHat)
自动化 Kubernetes 应用的构建、部署和管理。

Helm
用 Helm Charts 查找、分享、使用为 Kubernetes 打造的软件。

Kublr
在您的所有环境中集中部署、运行和管理大型企业的多集群 Kubernetes 部署。

当您运行 Kubernetes 集群时,在开发的早期检测漏洞和错误配置漂移。

谷歌云 GKE
部署 Kubernetes 集群,并在几秒钟内从您的 CI 渠道更新生产代码。

有了 Azure Kubernetes 服务,Azure AKS
运输速度更快、操作更轻松、扩展更自信。

亚马逊 EKS
在 AWS 上使用 Kubernetes 部署、管理和扩展容器化应用。

DeployHub
自动化您工作流程的发布步骤,为您管道中的每个状态创建 100%可重复的部署流程。

Fairwinds
提供了一个用 Docker 和 Kubernetes 构建 GitOps 工作流的框架。

VMware 代码流
发布更高质量的应用和更快的 IT 代码,同时降低运营风险。

在 CircleCI 上使用 Kubernetes 的工具集合。

Rafay Systems
使用 Rafay Systems 平台部署 CircleCI 的 Kubernetes 集群。

浏览我们的其他部署资源:

Pulumi
支持预览对 Pulumi 堆栈所做的任何云基础架构更改。

作为 CircleCI 工作流程的一部分,将您的应用程序部署到 Convox 平台。

Spinnaker
使用 Spinnaker 简化部署。

realMethods
生成 MPV 质量的应用程序,可立即部署到 CircleCI。

Quali
将 CloudShell Colony 集成到您的 CI/CD 管道中。您可以使用可用的构建任务从任何蓝图创建一个沙箱,开始您的测试,并在完成后结束沙箱。

Pantheon
将 Drupal 和 WordPress 代码推送到 Pantheon Dev 和 Multidev 环境中。

在 CI/CD 过程中,隐藏代码中对特性标志的所有引用。

Salesforce
针对 CircleCI 的 Salesforce SFDX CLI 集成。为您的 Salesforce 集成轻松创建 CI/CD 渠道。

我们的合作伙伴在说什么

谷歌云混合伙伴关系主管 Rayn Veerubhotla 表示:

“通过推出这些新的[orb],CircleCI 使开发人员能够进一步简化他们在云运行上的体验,最终帮助企业更快地为他们的客户带来新的服务和产品。”

Convox 的联合创始人 Cameron Gray 描述了他构建部署 orb 的经历:

“与 CircleCI 合作建造 Convox orb 既简单又强大。借助 Convox CircleCI orb,我们的客户能够构建应用程序、运行测试并部署到运行在多个云上的 Kubernetes 集群,所有这些都只需一个工作流程,这将为用户节省时间来做最重要的事情。”

你能做什么

对于部署,您还想做一些 orb 中没有的事情吗?orb 是开源的,所以在一个现有的 orb 上增加功能只是获得你的 PR 批准和合并的问题。查看 orbs 注册表中所有可用的 orbs。您是否有一个用例,您觉得它与当前的部署 orb 集有所不同?你可以自己创作一个并贡献给社区。我们甚至发布了为 orb 创建自动化构建、测试和部署管道的最佳实践(第 1 部分第 2 部分)来帮助您。

为了在您的部署过程中建立信心,开始使用 orb 来测试代码覆盖率,轻松地与第三方工具集成,并自动部署到您首选的测试或生产环境。与其花时间自己开发集成和复杂的部署流程,不如使用 orb 轻松地自动化部署。

开始在 CircleCI 建造部署球

零停机部署| CircleCI

原文:https://circleci.com/blog/continuous-deployment-without-downtime/

随着工程团队越来越多地采用 DevOps 作为他们的软件开发策略,他们变得更快、更高效。不幸的是,这种速度和效率可能会暴露交付系统中的裂缝以及生产力的其他瓶颈。通常,您可以找到关于如何采用 DevOps 实践的信息,如持续集成 (CI)和持续部署,但没有太多关于什么可能出错以及如何应对这些挑战的信息。

首先,让我们注意到连续交付不同于连续部署。虽然您可能会看到这些术语可以互换使用,但它们的含义是不同的。连续交付意味着主代码库总是处于准备部署的状态。尽管如此,这并不意味着有一个实际的交付或释放。将代码部署到生产环境需要一个手动步骤。

另一方面,连续部署涉及到自动化部署所需的所有步骤。它是软件开发自动化过程的最后阶段。使用连续部署,所有通过自动化 CI 测试的提交都被部署到实际的生产环境中。这既需要 CI,也需要持续交付。

为什么持续部署很重要?

传统上,软件开发团队主要关心他们的发布时间表。当有为新特性开发的代码或者现有的代码需要修复时,所有的改变都会被添加到一个大的版本中。将为更新设置一个具体的时间,并且在部署版本时通常会出现停机时间。随着现代软件开发实践的发展,现在有可能频繁地自动发布和更新。持续部署有几个好处:

  • 工程团队可以针对市场变化立即采取行动。他们不再需要等待一个主要的发布来推动他们的代码库的变化。
  • 您可以自动化从提交到部署的整个开发过程。这意味着系统管理员不必亲自执行发布,从而腾出时间来处理其他任务。
  • 随着发布日期的临近,工程师们不再紧张。这使得他们可以专注于特性驱动的开发和测试。
  • 它允许真实世界的实验。内部和外部客户可以成为 QA 流程的一部分,您可以获得即时的用户反馈,并能够在生产环境中执行 A/B 测试。

结果是,管理层将能够看到持续的进展,用户将在准备就绪后立即获得功能和补丁。然而,连续部署的成功取决于各种因素,包括测试套件的质量、部署策略和连续部署最佳实践的采用。

与持续部署相关的问题

虽然连续部署可以带来上述好处,但它也暴露了一些问题,如果没有解决方案,这些问题会影响部署的成功。这些问题包括:

  • 对管道缺乏信心。开发人员和管理人员可能对自动化部署缺乏信心,担心他们会推出质量差的代码。这是由于两个主要原因:
    • 对测试套件缺乏信心。连续部署的一个基本组成部分是自动化测试。测试带来的挑战包括易变的测试、测试时间的增加和不完整的测试覆盖。
    • 对安全检查缺乏信心,没有从流程一开始就考虑安全因素。
  • 使用外部资源和 OSS 有助于减少开发时间。但是,使用这些资源需要监控它们的状态和安全问题。
  • 工具的可用性、兼容性和配置。为测试、安全和自动化寻找合适的工具组合是一项挑战。
  • 在一个大型组织中处理多个团队是困难的,同步成为一个大问题。例如,产品团队和营销团队需要尽早调整,以消除延迟部署的最后一分钟的修订。
  • 根据您的部署策略,需要额外的基础架构会导致额外的成本。

那么,您如何确保您的团队能够实现零停机部署呢?

处理零停机的障碍

以下是一些减少或消除上述问题的建议:

  • 采用测试驱动的开发实践,为你的项目提供详尽的测试覆盖
  • 尽早并经常与安全团队协调。
  • 添加对您的操作系统和第三方服务的安全扫描。
  • 使用一流的工具,实现灵活性、兼容性和功能性。
  • 为所有相关方创建一个可见的开发时间表,并要求严格遵守。
  • 使用微服务开发架构。这种架构是向后兼容的,它隔离了变化,并且允许每个服务内部的可测试性。
  • 在对基础设施进行更改和添加之前,研究项目的最佳部署策略。

其他连续部署最佳实践

为了进一步增强零停机部署,应采用以下最佳实践:

  • 将您的基础设施视为代码。对基础设施中的变更使用版本控制和测试,就像对代码中的变更一样。
  • 建立一个可以向相关团队成员发送警报的沟通渠道。
  • 对于每一项任务,无论是功能需求还是缺陷修复,都要使用问题跟踪器。
  • 使用包含问题编号的分支命名约定。
  • 永远不要直接提交给主分支。
  • 将高可用性构建到您的基础架构中,以提供冗余和弹性系统。

包扎

当充分考虑到与持续部署相关的挑战时,这是一个提供持续改进的解决方案。快速响应市场变化,从提交到部署的整个流程实现自动化,减轻工程师的压力,并在真实环境中进行试验,可以更频繁地为您的用户提供更多价值。


Alice Njenga 喜欢将密集的技术材料转换成对外行和技术专业人员都容易理解的文章。

阅读 Alice Njenga 的更多帖子

持续的 Drupal:用 Docker、Git 和 Composer - CircleCI 维护一个 Drupal 网站

原文:https://circleci.com/blog/continuous-drupal-p1-maintaining-with-docker-git-composer/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


很久以前,我曾经在 GoDaddy 共享主机上托管过一个 Drupal 网站,用 FTP 管理文件,每隔一段时间复制一次 MySQL 数据库作为“备份”。你能在那句话里找到多少错误?在 2017 年,有许多工具和最佳实践允许我们高效地维护 Drupal 站点,并跨团队成员以及基础设施进行扩展。现在使用这些工具和实践创建一个 Drupal 网站,可以加快使用 Drupal 进行开发的速度。此外,如果您决定以后在 Drupal 站点中实现 CI,那么用这个堆栈来设置您的站点将使这成为可能。

在这个由三部分组成的系列的第一篇文章中,我们将介绍如何使用 Docker、Git、Composer 和 Drush 来智能高效地维护 Drupal 8 网站。

使用的软件

需要以下软件和工具:

  • Apache(有图片)
  • 作曲者(图像中可用)
  • docker(1 . 13 . 0 版或更新版本)
  • Docker 编写(1.10.0 版或更高版本)
  • Drupal 8(通过 Composer 安装)
  • Drush(通过 Composer 安装)
  • Git (v2.10 或更新版本)
  • MariaDB(或 MySQL,Drupal 8 通过 Docker 支持的版本)
  • PHP7(在图像中可用,技术上可以使用 PHP5,但是为什么 PHP7 要好得多)

在 Ubuntu 和其他基于 Debian 的发行版上安装

如果你用的是 Ubuntu,我建议 Ubuntu 16.04 或更新版本。

sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install docker-ce git
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose 

在 macOS 上安装

我们假设您已经安装了 Brew 。Docker 建议在 macOS 上使用他们的安装程序。这里是稳定版的下载链接。这个安装程序还包括 Docker Compose。

brew install bash-completion git 

预构建的虚拟机

DrupalVM 是一个预构建的虚拟机,包括我们想要使用的一切以及更多。本指南中的许多步骤可以跳过,因为 DrupalVM 会为您完成这些步骤。如果你仍然想了解工具选择背后的原因或者内部工作原理,通读这篇文章仍然是有用的。

设置项目目录

出于本文的目的,我们将创建一个虚构的基于 Drupal 的博客,名为“持续博客”。首先,我们需要创建一个根目录来保存我们的整个项目。这个目录将使用 Git 进行版本控制。我更喜欢把我所有基于 GitHub 的回购放在一个Repos目录下,然后放在 GitHub 用户名下。

mkdir -p ~/Repos/circleci/continuous-blog
cd ~/Repos/circleci/continuous-blog 

创建 Dockerfile 文件

现在,在开始安装 Drupal 之前,我们需要创建一些东西。第一个是 docker 文件,它将包含我们实际的 Drupal 网站。您可以将下面的Dockerfile复制粘贴到./Dockerfile中,这是我们项目的根。

FROM drupal:8.4-apache

RUN apt-get update && apt-get install -y \
	curl \
	git \
	mysql-client \
	vim \
	wget

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
	php composer-setup.php && \
	mv composer.phar /usr/local/bin/composer && \
	php -r "unlink('composer-setup.php');"

RUN wget -O drush.phar https://github.com/drush-ops/drush-launcher/releases/download/0.4.2/drush.phar && \
	chmod +x drush.phar && \
	mv drush.phar /usr/local/bin/drush

RUN rm -rf /var/www/html/*

COPY apache-drupal.conf /etc/apache2/sites-enabled/000-default.conf

WORKDIR /app 

走过码头文件

FROM drupal:8.4-apache 

我们从官方的 Docker 库 Drupal 图像开始。这意味着我们不需要自己设置 Apache 和 PHP。更具体地说,我们不必去寻找和安装 Drupal 需要的特定 PHP 插件。为 Drupal 的最新版本使用一个标签很重要,但是它不需要特别是您想要运行的 Drupal 版本。这是因为我们不打算使用图像中提供的 Drupal 文件,而是来自 Composer(在本文后面)。

RUN apt-get update && apt-get install -y \
	curl \
	git \
	mysql-client \
	vim \
	wget 

我们正在映像中安装一些我们需要的工具,以进行后续步骤。看起来是一个奇怪的选择,但是如果没有它,Drush 将无法正确连接到我们网站的数据库。提示,我们用多行、单个RUN步骤创建这样的命令,因为它改进了 Docker 层缓存。

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
	php composer-setup.php && \
	mv composer.phar /usr/local/bin/composer && \
	php -r "unlink('composer-setup.php');" 

在这里我们安装 Composer,PHP 依赖管理器。您或 Docker 映像尚未安装的所有内容都将由 Composer 处理。为用户root安装了 Composer。您会看到一个又一个警告,提示您不应该在 Composer 中使用 root 用户。我们在 Docker 映像中工作,其中唯一的常规用户是root用户。因此,尽管有警告,在这种情况下这是没问题的。在你自己的电脑上,不要使用root用户。

RUN wget -O drush.phar https://github.com/drush-ops/drush-launcher/releases/download/0.4.2/drush.phar && \
	chmod +x drush.phar && \
	mv drush.phar /usr/local/bin/drush 

我们正在安装 Drush 发射器。使在命令行上使用 Drush 变得更加容易,因为不再建议全局安装 Drush。

RUN rm -rf /var/www/html/* 

这将删除 Docker 映像附带的 Drupal 副本。如果你愿意,你可以使用它,但是在这篇文章中,我们在 Git 的帮助下用 Composer 安装和跟踪 Drupal。

COPY apache-drupal.conf /etc/apache2/sites-enabled/000-default.conf 

复制定制的 Apache VirtualHost 配置文件,告诉 Apache 我们希望在文件系统中的什么位置托管我们的网站。

创建 Apache VHost 配置

我们刚刚提到的 Apache VirtualHost 配置文件,让我们创建它。这个文件应该位于./apache-drupal.conf

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /app/web

	<Directory /app/web>
		AllowOverride All
		Require all granted
	</Directory>

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet 

创建 Docker 合成文件

我们的 Drupal 站点将由两个 Docker 图像组成。包含 Drupal 特定内容的主映像,我们用自己制作的Dockerfile来指定,还有一个数据库映像。Docker Compose 将允许使用这两个图像创建容器,并将它们相互连接,以便它们“正常工作”。

Docker Compose 文件也应该创建在我们的项目的根目录下,./docker-compose.yml

version: '3'
services:
  db:
    image: mariadb:10.2
    environment:
      MYSQL_DATABASE: drupal
      MYSQL_ROOT_PASSWORD: AppleSucks
    volumes:
      - db_data:/var/lib/mysql
    restart: always
  drupal:
    depends_on:
      - db
    build: .
    ports:
      - "8080:80"
    volumes:
      - ./app:/app
    restart: always
volumes:
  db_data: 

浏览 Docker 撰写文件

version: '3' 

互联网上许多带有示例撰写文件的博客帖子将使用版本 2。我们正在使用 Docker Compose 的新功能,因此需要版本3或更高。

services:
  db:
    image: mariadb:10.2
    environment:
      MYSQL_DATABASE: drupal
      MYSQL_ROOT_PASSWORD: WaterfallSucks 

我们在这里创建一个名为“db”的容器,它是由 MariaDB v10.2 Docker 映像制成的。可以使用 MySQL 和 Postgress,只要是 Drupal 支持的。我们还告诉 MariaDB 在启动时创建一个名为“drupal”的新数据库,并将 MariaDB root用户的密码设置为“WaterfallSucks”。随意使用您喜欢的任何数据库名称和密码。MariaDB 的 Docker Hub 页面提供了关于可用环境变量的更多信息。

 volumes:
      - db_data:/var/lib/mysql 

我们指定想要使用一个名为db_data的 Docker 卷来存储/var/lib/mysql的内容。这个目录是 MariaDB 存储它所包含的数据库信息的地方。它允许我们关闭容器并在以后再次启动它们,而不会丢失保存在数据库中的任何数据。

 drupal:
    depends_on:
      - db 

我们指定了第二个名为 Drupal 的容器。depends_on键告诉 Docker Compose 在db容器启动之前不要启动这个容器。最好不要启动 Drupal,因为数据库是存在的。

 build: . 

这告诉 Docker Compose 使用本地 Docker 文件作为我们图像的基础,而不是像我们对 DB 那样使用image然后指定 Docker 图像。这意味着我们可以在不运行单独命令的情况下进行动态构建,或者更糟的是,推到公共 Docker 存储库中进行快速开发更改。

 ports:
      - "8080:80" 

我们将本地机器上的端口 8080 映射到 Docker 容器中的端口 80。这允许我们在浏览器中访问http://localhost:8080,并在容器中查看我们正在运行的站点。端口 8080 可以是本地的任何可用端口。

 volumes:
      - ./app:/app 

我们在这里创建绑定装载,而不是传统的卷。这将获取本地机器上存储库中的./app目录,并通过 Docker 容器使其作为/app可用。这就是我们如何将 Drupal 站点以及任何主题和模块提供到容器中。

volumes:
  db_data: 

这告诉 Docker Compose 创建我们前面在配置中指定的卷。

创建应用程序目录

这是最难的一步,在我们项目的根目录下创建一个app目录。

mkdir app 

哇哇。

用 Git 保存我们的进度

这时,我们可以创建我们的 Git repo 并保存我们的进度。现在这样做是不必要的,但是它允许我们在接下来的几个步骤中可视化文件系统如何变化。

git init .
git add .
git commit -m "Initial commit." 

构建 Drupal 8 网站

随着最初的脚手架的方式,我们可以继续让我们的实际网站建设。我们首先用 Docker Compose 打开我们的容器。

docker-compose up -d --build 

--build告诉 Docker Compose 在我们运行这个命令时构建我们的Dockerfile。我们需要登录到我们的主容器来运行 Composer,但是我们需要容器名来这样做。我们可以通过运行docker-compose ps找到它。连接到端口 80 的容器是正确的。下面是一个输出示例:

$ docker-compose ps

 Name                        Command               State          Ports        
 ---------------------------------------------------------------------------------------
 continuousblog_db_1       docker-entrypoint.sh mysqld      Up      3306/tcp            
 continuousblog_drupal_1   docker-php-entrypoint apac ...   Up      0.0.0.0:8080->80/tcp 

这里正确的容器名是continuousblog_drupal_1。我们使用docker exec命令登录它。

docker exec -it continuousblog_drupal_1 bash 

这将把您放入位于/app目录的容器中。现在我们可以使用composer来安装 Drupal。

/app #  composer create-project drupal-composer/drupal-project:8.x-dev /app --stability dev --no-interaction
/app #  mkdir -p /app/config/sync
/app #  chown -R www-data:www-data /app/web 

现在在您的浏览器中访问 http://localhost:8080 并浏览 Drupal 安装程序。

一旦所有的东西都安装好了,你的站点就可以运行了,我们将使用 Drush 来导出你的 Drupal 配置。然后,退出容器。

/app #  drush config-export
/app #  exit 

我们将编辑现有的.gitignore文件,删除第 11 行和第 15 行。这些行忽略了settings.php文件和*/files/*目录。有些人会说不要这样做,在某些情况下他们是对的。为了简单起见,我们将在这里对files目录进行版本化,我们正在对settings.php进行版本化。如果您的回购将要公开,请不要对此文件进行版本控制,因为每个人都将拥有您的数据库凭据。

运行git status将显示 Composer 安装的所有内容。我们可以用 Git 提交所有东西,这样就完成了。我们现在已经保存了 Drupal 网站的初始状态。

从这里去哪里?

我们现在有了一个用 Git 和 Composer 跟踪的基本 Drupal 8 网站。我们甚至可以使用 Docker Compose 在任何位置启动和运行站点(如果我们还导出和导入数据库)。

我们本系列的下一篇博文将会介绍如何让我们的 Drupal 网站在 CircleCI 上的持续集成工作流中,在我们将网站发布到生产环境之前做一些基本的测试。

与此同时,您还可以使用我们的设置做其他事情。

更新 Drupal 8 核心

我建议在更新 Core 之前创建一个新的 Git 分支。Drupal 8 核心可以更新如下:

git checkout -b update-drupal
docker exec -it continuousblog_drupal_1 bash
/app #  composer update drupal/core --with-dependencies
/app #  drush updb  # updates the DB with any schema changes
/app #  git diff    # to check for changes
/app #  git status  # also to help review changes 

在浏览器中测试我们的站点(在我们的下一篇文章中,使用 CI ),以确保一切按预期运行。

/app #  exit
git add .
git commit -m "Updated Drupal Core."
git push 

安装“Contrib”模块或主题

“Contrib”模块是由某人发布或“贡献”到 Drupal.org 上的 Drupal 官方注册中心的公共模块。这些模块也可以用 Git 和 Composer 安装和跟踪。在这里,我们将安装 Pathauto Drupal 模块。

git checkout -b install-pathauto
docker exec -it continuousblog_drupal_1 bash
/app #  composer require drupal/pathauto
/app #  drush en pathauto -y  # enables the module with Drush rather than visiting the Drupal Admin page 

在浏览器中测试我们的站点(在我们的下一篇文章中,使用 CI ),以确保一切按预期运行。如果您为新模块做了任何配置,不要忘记用 Drush 导出您的配置。

/app #  drush config-export  # if you made config changes
/app #  exit
git add .
git commit -m "Installed Pathauto."
git push 

更新 Drupal“Contrib”模块和主题

Contrib 模块可以像 Core 一样更新。在任何时候,您都可以运行composer outdated来查看您的 Drupal 站点中有可用更新的每一部分。

git checkout -b update-pathauto
docker exec -it continuousblog_drupal_1 bash
/app #  composer update drupal/pathauto --with-dependencies
/app #  drush updb  # updates the DB with any schema changes 

在浏览器中测试我们的站点(在我们的下一篇文章中,使用 CI ),以确保一切按预期运行。

/app #  exit
git add .
git commit -m "Updated Pathauto."
git push 

维护定制的 Drupal 模块和主题

有两种方法可以做到。如果您有一个特定于该站点的模块,那么您可以将所有代码保存在同一个 Git repo 中,并在这里进行维护。但是考虑一下这一点,因为很多时候模块可以服务于同一公司内的多个站点,或者最终获得开源,这总是一件很棒的事情。主题更可能是特定于站点的,但同样的警告也适用。

对于不特定于站点的模块和主题,您可能会将它们保存在自己的存储库中,并作为子模块添加到您的 Drupal 站点,而不是在这个 repo 中维护代码。

如果您这样做了,那么每当您签出 Drupal 8 站点的存储库时,您将希望运行以下命令来确保您的定制模块也得到签出:

git submodule sync
git submodule update --init 

要更新它们,你需要cd进入它们的目录,然后运行普通的git pullgit checkout <tagname>。然后,备份到这个存储库的根目录,并提交您的更改。

问题&保持这篇文章的更新

如果你有问题,或者对这篇文章有什么补充,请通过 CircleCI 讨论让我们知道。*

连续 Drupal 第二部分:Drupal 8 CI - CircleCI

原文:https://circleci.com/blog/continuous-drupal-p2-continuous-integration/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


我们正在学习如何更快地使用 Drupal,将变更从开发人员的笔记本电脑快速转移到生产中,同时保持(如果不是提高)质量。在这个连续的 Drupal 系列的第 1 部分中,我们介绍了如何使用现代工具集来启动和运行 Drupal。在这篇文章中,我们将继续第 1 部分的内容,把我们的 Drupal 网站带入 CircleCI。我们将在网站上运行 QA 测试,以确保一切按预期运行,并可选地对我们可能有的定制模块或主题进行 PHPUnit 测试。让我们开始吧。

现在我们有了一个用 Git 和 Composer 管理的 Drupal 网站,我们可以使用 CircleCI、Behat 和 PHPUnit 等工具来自动测试我们的网站。

用 Behat 测试 Drupal 8 网站

Behat 允许用一种近似英语的语言来描述某些基于 UI 的特性应该如何工作。如果你熟悉“用户故事”的概念,Behat 让我们以一种可以自动测试的方式用代码编写用户故事,这被称为行为驱动开发。

安装行为

我们将安装 Behat 和相关工具。我们希望在 Drupal 容器内的app目录中完成这项工作。

docker-compose ps  # to get our container name
docker exec -it continuousblog_drupal_1 bash
composer require --dev behat/behat
composer require --dev behat/mink
composer require --dev behat/mink-extension
composer require --dev drupal/drupal-extension 

创建一个behat.yml文件:

default:
  suites:
    default:
      contexts:
        - FeatureContext
        - Drupal\DrupalExtension\Context\DrupalContext
        - Drupal\DrupalExtension\Context\MinkContext
  extensions:
    Behat\MinkExtension:
      goutte: ~
      selenium2: ~
      base_url: http://localhost/
    Drupal\DrupalExtension:
      blackbox: ~
      api_driver: 'drupal' 
      drupal: 
        drupal_root: 'web' 

然后我们将让 Behat 初始化并运行-dl来查看定义列表。能够查看此列表可以确认所有安装都是正确的。

vendor/bin/behat --init
vendor/bin/behat -dl 

测试用户行为

我们的 Behat 测试将放在容器中的/app/features/目录中。我们将创建一个快速的通用测试,以确保我们的系统在 demo 上工作,就像测试工作流一样。

创建文件/app/features/global.feature:

Feature: Test Features
	Some test scenarios to make sure the website is generally working.

@api
  Scenario: Run cron
    Given I am logged in as a user with the "administrator" role
    When I run cron
    And am on "admin/reports/dblog"
    Then I should see the link "Cron run completed" 

现在,我们可以使用以下工具运行我们的 Behat 测试:

vendor/bin/behat

Feature: Test Features                   
  Test scenarios to make sure the website is generally working.                      

  @api                                    
  Scenario: Run cron                                             # features/global.feature:4                                                                              
    Given I am logged in as a user with the "administrator" role # Drupal\DrupalExtension\Context\DrupalContext::assertAuthenticatedByRole()                              
    When I run cron                                              # Drupal\DrupalExtension\Context\DrupalContext::assertCron()                                             
    And am on "admin/reports/dblog"                              # Drupal\DrupalExtension\Context\MinkContext::visit()                                                    
    Then I should see the link "Cron run completed"              # Drupal\DrupalExtension\Context\MinkContext::assertLinkVisible()                                        

1 scenario (1 passed)                     
4 steps (4 passed)                        
0m1.25s (13.18Mb) 

至此,我们有了一个使用 Behat 测试 Drupal 的基本示例。下一步是将所有这些连接到 CircleCI。在我们这样做之前,让我们用 Git 保存我们的进度。

exit  # to exit the container
git status  # you should recognize everything outside of the */files/* directory
git add .
git commit -m "Configure Behat and first test." 

圆形构型

现在我们要在 CircleCI 上建立我们的 Drupal 网站。如果这是你第一次使用 CircleCI,请先阅读入门文档

CircleCI 配置文件应该位于.circleci/config.yml处,我们的配置文件应该如下所示:

version: 2
jobs:
  build:
    docker:
      - image: cibuilds/drupal:latest
      - image: mariadb:10.2
        environment:
          MYSQL_DATABASE: drupal
          MYSQL_ROOT_PASSWORD: HorriblePassword
          MYSQL_ROOT_HOST: "%"
    environment:
      BLUEMIX_ORG: CircleCI
      BLUEMIX_SPACE: dev
    working_directory: /project/app
    steps:
      - checkout:
          path: /project
      - run:
          name: "Setup Database & Server"
          command: |
            echo "127.0.0.1      db" >> /etc/hosts
            sleep 20
            mysql -uroot -pHorriblePassword -h127.0.0.1 drupal < ../db/drupal-db-dump.sql
            service apache2 start
      - run:
          name: "Install Dependencies"
          command: |
            composer install
      - run:
          name: "Behat Tests"
          command: vendor/bin/behat 

与我们在上一篇博文中使用 Docker Compose 不同,我们使用 CircleCI 及其“Docker Executor”来创建我们的执行环境。我们将在本系列的第 3 部分对 CircleCI 配置进行演练。现在,我只想简单介绍一下“设置数据库&服务器”这一步。

管理数据库

当管理多个环境时,您可能有多个数据库位置。生产、 CI 、试运行、开发等各一个。对于某些环境(比如 dev ),您可以使用一个轻量级的假 DB。很多时候你需要处理真实的数据。这是通过复制(转储)数据库并将它们导入到其他环境中来实现的。关键是数据流动的方向。

总是将数据库数据向下游移动

这是足够重要的,它保证自己的标题。当移动数据库数据的拷贝时,总是从拷贝生产数据开始。然后,您可以将它导入暂存或其他地方。然后,您甚至可以将暂存数据移动到本地计算机上。这种做法可以防止数据丢失。如果您从本地机器上的一个 DB 开始,并将其转移到生产环境中,那么您可能会丢失自上次创建本地 dev DB 以来创建的用户、帖子等。

echo "127.0.0.1 db" >> /etc/hosts -我们的 Drupal 设置文件知道我们之前设置的数据库主机名是db。这一行总是在 CircleCI 容器中工作主机名。

跟踪数据库导出

对于这个例子,我们的 repo ./db根目录下的目录被创建来保存.sql格式的 DB 转储。我们本地数据库里有一个来自mysqldump的转储。一旦您的网站投入生产,您将希望从那里检索数据库转储。对于拥有敏感数据的站点来说,在将数据库从生产环境导入到其他环境之前对其进行清理是一种常见的做法。尤其是当这些额外的环境可能不太安全时。如果数据库转储中仍然有敏感信息,您可能不想像我们在这里做的那样用 Git 跟踪它。

最后,托管我们的 Drupal 网站

在“持续的 Drupal”系列的第三部分,我们将使用 Kubernetes 找到一个托管 Drupal 网站的地方,随着我们的网站越来越受欢迎,我们可以扩展托管基础设施。

问题&保持这篇文章的更新

如果你有问题,或者对这篇文章有什么补充,请通过 CircleCI 讨论让我们知道。

通过 fastlane | CircleCI 持续集成和部署 Android 应用

原文:https://circleci.com/blog/continuous-integration-and-deployment-for-android-apps-with-fastlane/

持续集成 (CI)是软件开发中与 DevOps 相关的一个流行术语。CI 是对推送到项目中的新代码进行自动验证,以确保其正确性。CI 根据为项目编写的测试来验证代码。这意味着几乎每一个更新都必须伴随着测试。

因此,典型的 CI 流程是这样的:

  • 代码被签入到存储库中
  • CI 服务器被通知新代码已被推送
  • CI 服务器从存储库中获取代码,执行代码附带的测试,并执行您必须指定的任何其他命令
  • CI 服务器会通知您所执行命令的结果

通常情况下,软件项目往往有不止一个开发人员,因此 CI 可以方便地确保项目在每次提交到存储库之后都是稳定的。

另一方面,连续部署(CD)比 CI 更进一步。在执行测试和生成构建之后,CD 继续在生产中部署构建。这与 CI 的区别在于,对于 CD,您将在脚本中指定一个部署命令来将您的应用程序推向生产。

这些操作利用了两个重要的工具:CI 服务器和存储库。存储库是代码驻留的地方,CI 服务器是集成和部署触发的地方。在本文中,您将把代码检查到 GitHub,并将 CircleCI 用作 CI 服务器。

阅读更多关于移动应用开发持续集成的信息。

你将建造什么

在本文中,您将构建一个简单的 Android 应用程序来显示流行电影。该应用程序将从一个 API 获取数据,并显示它。在您构建这个应用程序之后,您将为这个应用程序编写测试并为它设置 CI/CD。

先决条件

这篇文章假设你已经对使用 Kotlin 进行 Android 开发有了基本的了解。如果你仍然需要补上,这里有一个很好的课程让你开始。除此之外,您还需要确保具备以下条件:

  • 最近安装了一个稳定版本的 Android Studio
  • GitHub 的一个账户
  • 一台装有 macOS 的机器。我们将在这个项目中使用 fastlane,它被官方支持在 macOS 上运行。你可以关注这一期来了解它什么时候可以用于其他平台。

检查完所有内容后,请放心继续。

构建应用程序

为了简洁起见,您不会从头开始构建应用程序。相反,您将在一个初始项目的基础上进行构建。点击下载启动项目。在继续之前,理解 starter 项目中的类和文件是很重要的。

在项目中,您有以下类/文件:

  • 这是一个用来访问翻新实例的对象。翻新是由 Square 开发和维护的用于 Android 和 Java 的类型安全 HTTP 客户端。您将使用它为您的应用程序发出网络请求。
  • 这个接口包含了你在构建应用程序的过程中将要访问的端点。该接口用于改造。
  • 这是一个适配器类,可以帮助你管理列表中的数据。这个类控制列表的大小、每行的外观以及数据如何绑定到列表。这个类使用 DiffUtilCallback 类来检查要在列表中填充的内容的唯一性。
  • MovieModel.kt:这个类包含与您将从服务器收到的响应相匹配的数据类。
  • MainActivityViewModel.kt:这是 MainActivity 用来发出网络请求并返回结果的 ViewModel。
  • 这是一个你可以连接其他班级的班级。这是您的应用程序开始的地方。

现在您已经了解了 starter 项目是如何工作的,您将添加收尾工作来使应用程序正常工作。

你需要做的第一件事是从电影数据库获取一个 API 密匙。如果您没有帐户,您需要创建一个帐户。完成帐户设置后,进入设置(点击右上方用户图像出现的下拉菜单中的设置),然后点击垂直菜单中的 API 选项卡。请求 API 密钥(如果您还没有这样做)。

之后,复制出 API KEY(v3 auth)值。

复制密钥后,打开 gradle.properties 文件并添加以下代码片段:

API_KEY = "MOVIE_DB_API_KEY" 

注意: 用刚才复制的密钥替换 MOVIE_DB_API_KEY。

现在你会看到一个通知,建议你应该同步你的 Gradle 文件。继续并同步它们。

下一步是添加从 API 获取流行电影的逻辑。您将在视图模式下执行此操作。打开MainActivityViewModel类并添加这些导入:

// app/src/main/java/dev/idee/cicdandroid/MainActivityViewModel.kt

import android.util.Log
import androidx.lifecycle.MutableLiveData
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

Then, add this snippet to the body of your class:

// app/src/main/java/dev/idee/cicdandroid/MainActivityViewModel.kt

private val movieListLiveData = MutableLiveData<List<MovieModel>>()

open fun getMovieLiveData(): MutableLiveData<List<MovieModel>> {
    return movieListLiveData
}

fun setMovieLiveData(movieModelList: List<MovieModel>) {
    movieListLiveData.value = movieModelList
}

init {
    fetchMovies()
}

private fun fetchMovies() {

    APIClient.client.create(ApiInterface::class.java).getPopularMovies(BuildConfig.API_KEY)
        .enqueue(object : Callback<MovieResponse> {
            override fun onFailure(call: Call<MovieResponse>, t: Throwable) {
                Log.e("MainActivityViewModel", t.toString())
            }

            override fun onResponse(call: Call<MovieResponse>, response: Response<MovieResponse>) {
                response.body()?.let {
                    setMovieLiveData(it.movieModelList)
                }

            }

        })

} 

初始化类时调用init块。在这个块中,您调用了fetchMovies方法。这个方法获取电影并用响应更新movieListLiveData对象。这个对象是可观察的,并将被MainActivity用来监听结果。

接下来,您将在MainActivity中设置视图模型,以查找movieListLiveData对象中的更新。您还将设置一个回收器视图来显示您获取的电影。

打开您的MainActivity.kt,并将这些导入添加到导入部分:

// app/src/main/java/dev/idee/cicdandroid/MainActivity.kt

import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

Then add these objects just before the onCreate method:

// app/src/main/java/dev/idee/cicdandroid/MainActivity.kt

private lateinit var viewModel: MainActivityViewModel
private val movieAdapter = MovieListAdapter(DiffUtilCallback()) 

这里,您声明了一个MainActivityViewModel的实例,并且初始化了将被回收器视图使用的适配器。

接下来,继续将这两个方法添加到MainActivity类中:

// app/src/main/java/dev/idee/cicdandroid/MainActivity.kt

private fun setupRecyclerView() {
    with(movieRecyclerView) {
        layoutManager = LinearLayoutManager(this@MainActivity)
        adapter = movieAdapter
    }
}

private fun setupViewModel() {
    viewModel = ViewModelProviders.of(this)[MainActivityViewModel::class.java]
    viewModel.movieListLiveData.observe(this, Observer {
        progressBar.visibility = View.GONE
        movieAdapter.submitList(it)
    })
} 
  • 这用一个布局管理器和一个适配器初始化你的 RecyclerView。
  • setupViewModel:这将初始化视图模型,并开始观察 movieListLiveData 对象以监听响应。

现在,这些方法没有被引用。将它们添加到该类的 onCreate 方法中,以便在活动启动时触发它们:

// app/src/main/java/dev/idee/cicdandroid/MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    setupRecyclerView()
    setupViewModel()
} 

这样,你的应用就准备好了。点击 Android Studio 上的运行按钮,您应该会看到如下内容:

</blog/media/2020-03-27-fastlane3.mp4>

写作测试

为了使持续集成有效,测试是必要的。这就是在项目中保持制衡的方式。在本节中,您将为您的应用程序添加一些测试。您不必担心测试依赖项,它们已经被添加到您下载的 starter 项目中了。

打开androidTest目录。大概是这样:/android-projects/ci-cd-android/app/src/androidTest/java/{package-name}。为了确认您是否在正确的目录中,您会看到一个ExampleInstrumentedTest测试文件。

创建一个名为MainActivityTest的新文件,并将下面的代码片段添加到其中:

// /app/src/androidTest/java/dev/idee/cicdandroid/MainActivityTest.kt

import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @get:Rule
    val activityRule = ActivityTestRule(MainActivity::class.java, false, false)

    @Test
    fun appLaunchesSuccessfully() {
        ActivityScenario.launch(MainActivity::class.java)
    }

} 

这个测试只是检查应用程序是否成功启动,没有错误。之后,在同一目录下创建另一个文件MainActivityViewModelTest,并添加以下代码片段:

// /app/src/androidTest/java/dev/idee/cicdandroid/MainActivityViewModelTest.kt

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityViewModelTest {

    private lateinit var viewModel: MainActivityViewModel
    private val list = ArrayList<MovieModel>()
    @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setUp() {
        viewModel = MainActivityViewModel()
    }

    @Test
    fun testWhenLiveDataIsUpdated_NewValueTakesEffect() {
        list.add(MovieModel("","",""))
        viewModel.setMovieLiveData(list)
        Assert.assertEquals(viewModel.getMovieLiveData().value!!.size,1)
    }

} 

该测试检查从 LiveData 对象更新和获取结果的方法是否以最佳方式运行。你可以运行你的测试,你应该看到他们通过。

既然您已经构建了您的 Android 应用程序并编写了测试,那么您需要将它签入远程存储库。打开您的 GitHub 概要文件并创建一个新项目(存储库)。

为您的项目输入合适的名称和描述。可以使用ci-cd-android作为项目名称。创建项目后,您将看到关于如何将本地项目推送到存储库的说明:

您现在可以跳过这些说明,转到下一部分。

使用 CircleCI 设置配置项

在本节中,您将使用 CircleCI 为您的 Android 项目设置 CI。在 Android 项目的根目录下,创建一个名为.circleci的新文件夹,并在其中添加一个名为config.yml的文件。

config.yml文件中,粘贴以下代码片段:

version: 2.1

orbs:
 android: circleci/android@0.2.0

jobs:
 build:
   executor: android/android

   steps:
     - checkout
     - run:
         command: ./gradlew build 

这是一个脚本,它包含代码被签入存储库时要对其执行的命令。这个文件可以被称为管道。

在这个代码片段中,有不同的部分。你有一个version部分,它告诉你正在使用的 CircleCI 版本。在这种情况下,它是版本 2.1,然后您有jobs部分。作业是运行构建过程的东西。定义配置文件时,您至少需要一个作业。

总之,您定义的作业检查您的代码,下载必要的依赖项,运行您的测试,并上传结果。

在这之后,接下来要做的事情是将项目的更新推送到存储库中。您可以通过依次运行以下命令来实现这一点:

# adds all the changes in the working directory to the staging area
git add .

# Save changes to the local repository
git commit -m "added tests and Circle CI config file"

# Push changes on the local repository to the remote repository
git push origin master 

接下来,您必须在 CircleCI 仪表板上设置 GitHub 项目。如果您没有 CircleCI 帐户,您可以在此轻松创建一个。

幸运的是,既然你已经有了一个 GitHub 帐户,这应该不会花很长时间。如果你已经有一个 CircleCI 账户,你应该用登录。无论哪种情况,你都会出现在仪表盘上。在您的仪表板上,选择添加项目选项卡:

搜索您之前创建的存储库,然后单击设置项目。单击该按钮后,您应该会看到如下页面:

点击开始建造。当询问您是否希望 CircleCI 为您将配置文件添加到 repo 时,选择手动添加。这是因为我们已经添加了配置文件。

为部署设置快速通道

现在,您将进一步为您的 Android 项目设置连续部署。您将使用 fastlane ,这将有助于自动将您的 Android 应用程序发布到 Google Playstore。

您应该做的第一件事是使用以下命令安装最新的 Xcode 命令行工具:

xcode-select --install 

这将打开一个许可请求,接受它并接受后面显示的条款。然后将获取并安装最新的工具:

完成后,通过运行以下命令安装 fastlane:

brew cask install fastlane 

安装 fastlane 后,您需要将它添加到您的路径中。用以下命令打开 bash 概要文件:

open -e .bash_profile 

注意: 如果文件之前不存在,可以使用命令:touch。bash_profile 创建它。

然后,将此代码片段添加到文件中:

PATH=$PATH:$HOME/.fastlane/bin 

之后,运行这个命令来刷新 bash 概要文件:

. .bash_profile 

接下来,转到您的 Android 项目终端,用以下命令初始化项目中的 fastlane:

fastlane init 

首先在目录中创建一个fastlane文件夹。然后,它继续请求您的应用程序的包名。输入应用程序的包名,然后按回车键。您可以在 app-module build.gradle文件中找到您的应用包名称。在文件中,包名被称为applicationId

之后,您将被要求提供 JSON 秘密文件的路径。输入fastlane/api.json并选择进入。最后,当被问及是否计划通过 fastlane 将信息上传到 Google Play 时,回答 n

注意: 使用 fastlane,你可以上传你的应用程序的截图和发布说明,但我们不会在本教程中考虑这一点。

下面是终端上的流程截图:

在您的项目上初始化 fastlane 之后,您现在需要从您的 Google Developers 服务帐户获得一个凭证文件。这个文件可以让你将二进制文件上传到 PlayStore。

打开 Google Play 控制台。然后,选择设置:

然后选择开发人员帐户下的 API 访问选项:

要为您的 Google Play 帐户启用 API 访问,请点击创建新项目。它将向您显示如下屏幕:

点击创建服务账户。您将看到这样一个对话框:

单击谷歌应用编程接口控制台链接。这将引导您进入谷歌云控制台上的项目:

当你到达这里时,点击创建服务账户

然后填写您的详细信息,并点击创建。当询问角色时,选择服务账户>服务账户用户,然后点击继续:

之后,您将看到以下屏幕:

点击创建密钥。确保按键类型为JSON。一旦您的密钥被创建,它将被下载到您的机器。

返回打开 Google Play 控制台的选项卡。点击对话框上的DONE。您现在将看到一个新的服务帐户。点击GRANT ACCESS,选择Release manager角色,点击ADD USER

注: 或者,您可以从角色下拉列表中选择项目负责人。请注意,选择发布管理器可以访问生产跟踪和所有其他跟踪。选择项目负责人将授权更新除生产跟踪之外的所有跟踪。

现在您已经获得了您的密钥,将密钥复制到项目的fastlane文件夹中。这次改名为api.json。这是为了使文件名和目录与设置 fastlane 时的配置相匹配。

注意: 该文件包含您 Playstore 账户的秘密,因此只能在私人存储库中使用。

浪子的文件夹里有一个名为Fastfile的文件。该文件用于配置您可以使用 fastlane 执行的任务。如果打开默认文件,您将看到三个不同的块:

  • before_all:这是在执行车道前插入要执行的指令的地方。
  • lane:这是您定义想要执行的实际任务的地方,比如部署到 PlayStore。您可以定义任意多的车道。
  • after_all:该程序块在执行通道成功时被调用。
  • error:如果任何其他程序块发生错误,将调用该程序块。

Fastfile已经有了一个生成发布版本并将其部署到 PlayStore 的通道,即playstore通道。您将稍微修改这条车道。打开你的Fastfile,像这样更新playstore车道:

lane :playstore do
 gradle(
   task: 'bundle',
   build_type: 'Release'
 )
 upload_to_play_store skip_upload_apk:true 
end 

注意: 如果您没有 playstore lane,请创建一个并添加上面的代码片段。

在这里,您将任务更新为bundle。这使得 fastlane 将构建一个 aab (Android 应用捆绑包)而不是 apk。Aab 是将工件上传到 Playstore 的推荐格式。您还在upload_to_play_store命令中添加了一个skip_upload_apk:true参数,这样只有生成的 aab 才会被使用,如果有 apk 存在,它将被丢弃。

准备部署您的应用程序

接下来,您必须对您的应用程序进行一些更新,以便为部署做准备。您必须将发布签名配置添加到您的应用程序模块build.gradle文件中。这样,fastlane 将使用您用来生成应用程序早期版本的相同密钥库来生成后续版本。

signingConfigs片段添加到您的应用程序模块build.gradle:

android {
    signingConfigs {
        release {
            keyAlias 'key0'
            keyPassword 'keyPassword'
            storeFile file('../KeyStore')
            storePassword 'storePassword'
        }
    }
    // Leave the rest untouched...
} 

注意: 用您的实际密码替换钥匙密码和存储密码。也替换密钥别名和密钥库的名称。在这个片段中,密钥库存储在项目根目录中。如果您需要为您的应用程序生成密钥库的帮助,您可以访问这个资源

之后,更新 build.gradle 文件的 buildTypes 部分,如下所示:

buildTypes {
    release {
        signingConfig signingConfigs.release
        // Leave other parts untouched...
    }
} 

至此,我们已经将应用程序配置为使用特定的密钥库。接下来,您将在您的build.gradle文件中创建函数来帮助您为您的应用程序版本生成构建号。将这段代码添加到您的应用程序模块build.gradle文件的android部分之前:

ext.versionMajor = 1
ext.versionMinor = 0
ext.versionPatch = 1
ext.versionClassifier = null
ext.isSnapShot = false
ext.minSdkVersion = 21

private Integer generateVersionCode() {
    return ext.minSdkVersion * 10000000 + ext.versionMajor * 10000 +
            ext.versionMinor * 100 + ext.versionPatch
}

private String generateVersionName() {
    String versionName = "${ext.versionMajor}.${ext.versionMinor}.${ext.versionPatch}"

    if (ext.versionClassifier == null) {
        if (ext.isSnapShot) {
            ext.versionClassifier = "SNAPSHOT"
        }
    }

    if (ext.versionClassifier != null) {
        versionName += "-" + ext.versionClassifer
    }

    return versionName
} 

在这个代码片段中,您添加了保存应用程序版本值的变量。然后,您添加了两个方法generateVersionCodegenerateVersionName来根据应用程序版本值的变化生成版本代码和版本名称。

这有助于在您修改应用程序版本时,为您的应用程序提供一种独特、渐进的方式来生成版本代码。现在,在build.gradle文件的defaultConfig部分更新这些属性,如下所示:

defaultConfig {
    versionName generateVersionName()
    versionCode generateVersionCode()
    // ... Leave others untouched

} 

现在,您将更新 CircleCI 配置文件,告诉它要执行哪个 fastlane 命令。打开config.yaml文件,并将这些步骤添加到您的文件中:

# .circleci/config.yaml
- run:
    name: Install fastlane
    command: bundle install
- run:
    name: Execute fastlane
    command: bundle exec fastlane playstore 

这里,您有两个命令,第一个安装 fastlane,第二个生成一个构建并发送到 Google PlayStore。为了让您的应用程序成功上传,您的 PlayStore 帐户上应该已经有一个包名为(dev.idee.cicdandroid)的应用程序。如果你以前没有上传你的应用程序,你可以使用这个教程作为指南。

现在,当您将新代码推送到您的存储库时,您应该会看到您的应用程序上线了!

结论

在本文中,我们讨论了持续集成和部署的基础。我们构建了一个简单的应用程序来演示这一点,并进一步了解 fastlane 的部署。现在,您可以设置您的应用程序来自动测试并直接部署到 Playstore。这只是开始。使用 CircleCI 和 fastlane,您可以做更多的事情。

黑客快乐!


Idorenyin Obong 是一名专门从事移动开发的软件工程师。他喜欢写一切关于软件的东西。

阅读 Idorenyin Obong 的更多帖子

线段-圆上的连续积分

原文:https://circleci.com/blog/continuous-integration-at-segment/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


这是联合创始人 Calvin French-Owen 撰写的 Segment 博客的转贴。Calvin 和 Segment 的团队将于本周四(6 月 30 日)在 T2 circle ci 的办公时间发表演讲。请加入我们关于谷歌的 AMP 项目的特别报告!

作为我们推动公开细分市场内部进展的一部分,我们希望分享我们如何运行我们的持续集成 (CI)构建。我们的大多数方法遵循标准实践,但是我们想要分享一些我们用来加速我们的构建管道的技巧和诀窍。

驱动我们所有构建的是 CircleCIGithubDocker Hub 。每当有推送到 Github 时,存储库就会触发 CircleCI 上的构建。如果这个构建是一个带标签的发布,并且通过了测试,我们为这个容器构建一个映像。

然后,该映像被推送到 Docker Hub,并准备好部署到我们的生产基础架构中。

2016-06-27-ci-at-segment-flow.png

CircleCI 和 Travis CI

在继续讲下去之前,我想先谈谈房间里的大象:特拉维斯·CI。几乎所有关于 CI 工具的讨论都围绕着使用 Travis CI 还是 CircleCI 展开。两者都是圆滑的,托管的,和响应的。两者都非常容易使用。

老实说,我们喜欢这两种工具。我们将 Travis CI 用于我们的许多开源库,而 CircleCI 则用于我们的许多私有回购。这两种产品都非常好用,非常好地满足了我们的需求。

然而,CircleCI 有一个我们非常喜欢的特性:SSH 访问。

大多数时候,我们在配置测试环境时没有任何问题。但是当我们这样做时,SSH 到运行代码的容器的能力是非常宝贵的。

客观地说, Segment 通过数百种不同的微服务运行我们的整个基础设施。每一个都来自不同的 repo,并通过 docker-compose 以一些不同的依赖关系运行。

我们的 CI 大部分都是相对标准的,但是偶尔设置一个新环境的服务需要一些定制工作。它在一个新的回购中,将需要它自己的依赖集和构建步骤。这就是能够在测试环境中运行命令的地方——你可以在机器上调整配置。不再有数百个“修复配置项”提交!

Dotfiles

为了处理所有这些不同的回购,我们想让设置回购变得非常简单,以便它支持 CI。我们有三个经常使用的不同的圆形命令,它们在我们的公共点文件中共享。首先,circle()设置了所有正确的环境变量,并自动启用了我们的 slack 通知。

org=$(basename $(dirname $(pwd)))
repo=$(basename $(pwd))

echo enabling project
curl "https://circleci.com/api/v1/project/${org}/${repo}/follow?circle-token=${circletoken}" \
  -X POST \
  -H "Accept: application/json" \
  --silent > /dev/null

echo enabling notifications
curl "https://circleci.com/api/v1/project/${org}/${repo}/settings?circle-token=${circletoken}" \
  -X PUT \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"slack_webhook_url": "xxxxxxxxx"}' \
  --silent > /dev/null 

此外,我们有一个 circle.open()命令,可以直接从您的浏览器中的 CLI 自动打开测试结果。

repo=$(git remote -v)
re="github.com/([^/]+/[^[:space:]]+)(.git)"
if [[ $repo =~ $re ]]; then open "https://circleci.com/gh/${BASH_REMATCH[1]}"; fi 

最后是 circle.badge()命令,用于自动将标记添加到 repo 中

org=$(basename $(dirname $(pwd)))
repo=`basename $(pwd)`

echo creating status token
response=`curl "https://circleci.com/api/v1/project/$org/$repo/token?circle-token=$circletoken" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"label":"badge","scope":"status"}' \
  --silent`
statustoken=`node -pe 'JSON.parse(process.argv[1]).token' "$response"`

badge="[![Circle CI](https://circleci.com/gh/segmentio/$repo.svg?style=svg&circle-token=$statustoken)](https://circleci.com/gh/segmentio/$repo)"

echo adding badge to Readme.md
echo $badge > temp-readme.md
cat Readme.md >> temp-readme.md
cp temp-readme.md Readme.md
rm temp-readme.md 

共享脚本

现在,考虑到我们有数百个 repos,当我们对 circle.yml 文件进行更改时,我们的任务是保持所有测试脚本和存储库同步。在几百个回购中保持相同的行为是很烦人的,但是我们决定宁愿用抽象问题(很难解决)来换取在工具上更大的投资(通常更容易)。为此,我们在一个共享的 git repo 中使用一组通用的脚本。每次测试运行时,脚本都会被拉下来,并处理共享的打包和部署。每个服务的 circle.yml 文件如下所示:

machine:
  node:
    version: 4
  services:
    - docker

deployment:
  deploy:
    tag: /[0-9]+(\.[0-9]+)*/
    commands:
      - git clone github.com/segmentio/circle-scripts.git
      - sh ./circle-scripts/node-deploy.js 

这意味着如果我们改变我们的部署方案,我们只需更新一个位置中的代码,而不是更新每个单独的 repo 的 circle.yml。然后,我们可以根据我们在单独的服务 repo 中需要的构建类型引用不同的脚本。

码头集装箱

最后,没有 Docker 容器,整个构建过程是不可能的。容器极大地简化了我们将代码推向生产、根据我们的内部服务进行测试以及本地开发的方式。

在测试我们的服务时,我们利用 docker-compose.yml 文件来运行我们的测试。这样,一个给定的服务实际上可以针对 CI 中运行的完全相同的映像进行测试。它减少了对模拟或存根的需求。

2016-06-27-ci-at-segment-docker.png

此外,当映像由 CI 构建时,我们可以将这些相同的映像下载下来,并在本地运行它们。

为了实际构建代码并将其推向生产,CircleCI 将首先运行测试,然后检查该构建是否是一个带标签的发布。对于任何带标签的版本,我们都让 CircleCI 通过 Docker 文件构建容器,然后对其进行标记并将其部署到 Docker Hub。

我们没有使用 latest everywhere,而是将标记的映像与主版本(1.x)和次版本(1.2.x)一起显式部署到 Docker Hub。

这样,我们能够在需要时指定回滚到特定版本——或者在不需要特定版本时部署某个发布分支的最新版本(对于本地开发和 docker-compose 文件非常有用)。

这样做的代码相对简单,首先我们检测版本:

tag="$(git describe --tags --always)"

# Find our individual versions from the tags
if [ -n "$(echo $tag | grep -E '.*\..*\..*')" ]
then
    major=$(echo $tag | cut -d. -f1)
    minor=$(echo $tag | cut -d. -f2)
    patch=$(echo $tag | cut -d. -f3)

    major_version_tag=$major.x
    minor_version_tag=$major.$minor.x
    patch_version_tag=$major.$minor.$patch

    tag_list="$major_version_tag $minor_version_tag $patch_version_tag"
else
    tag_list=$tag
    fi 

然后,我们构建、标记并推送我们的 docker 图像:

docker build -t segment/$CIRCLE_PROJECT_REPONAME .

# Tag the new image with major, minor and patch version tags.
for version in $tag_list
do
    echo "==> tagging $version"
    docker tag segment/$CIRCLE_PROJECT_REPONAME:latest segment/$CIRCLE_PROJECT_REPONAME:$version
done

# Push each of the tags to docker hub, including latest
for version in $tag_list latest
do
    echo "==> pushing $version"
    docker push segment/$CIRCLE_PROJECT_REPONAME:$version
done 

一旦我们的映像被推送到 Docker Hub,我们就能保证构建出正确版本的代码,这样我们就可以将其部署到生产环境中,并在 ECS 中运行。

多亏了容器,我们的 CI 渠道让我们在将微服务部署到生产中时更有信心。

兜了一圈

这就是我们的 CI 构建管道,主要由 Github、CircleCI 和 Docker 提供支持。

虽然我们一直在努力寻找使整个管道更加无缝的方法,但我们对使用第三方工具提供的低维护、并行化和隔离感到满意。

Deno APIs | CircleCI 的持续集成

原文:https://circleci.com/blog/continuous-integration-deno/

提供软件服务的开发团队面临着速度和准确性之间的不断权衡。新特性应该在尽可能短的时间内以很高的准确性提供,这意味着没有停机时间。对于您的团队用来管理代码库的任何手动集成过程来说,由于人为错误而导致的不可预见的停机时间是很常见的。这种意外的中断可能是团队接受自动化集成过程的挑战的主要驱动力之一。

持续集成(CI)在速度和精度之间找到了一个最佳平衡点,可以尽可能快地提供各种功能。如果新代码的问题导致构建失败,那么被污染的构建就不会提供给客户。结果是客户不会经历停机时间。

在本文中,我将使用 CircleCI 来演示如何将 CI 应用到一个 Deno 项目中。在配套教程中,你可以学习如何将你的 Deno 项目部署到 Heroku

Deno 是一个简单、现代、安全的 JavaScript 和 TypeScript 运行时。当您创建项目时,使用 Deno 具有以下优势:

  1. Deno 在默认情况下是安全的。除非明确启用,否则没有文件、网络或环境访问权限。
  2. 它支持现成的 TypeScript。
  3. 它作为单个可执行文件提供。

本教程的示例项目将是一个用 Oak 构建的 API。这个 API 有一个返回测验问题列表的端点。将使用 SuperOak 为端点编写一个测试用例。

先决条件

开始之前,请确保您的系统上安装了以下项目:

  • 最新安装的 Deno

对于存储库管理和持续集成,您需要:

入门指南

创建一个新目录来保存所有项目文件:

mkdir deno_circleci

cd deno_circleci 

为了简单起见,您可以在我们的项目中导入一组硬编码的问题。创建一个名为questions.ts的文件并添加如下内容:

export default [
  {
    id: 1,
    question: "The HTML5 standard was published in 2014.",
    correct_answer: "True",
  },
  {
    id: 2,
    question:
      "Which computer hardware device provides an interface for all other connected devices to communicate?",
    correct_answer: "Motherboard",
  },
  {
    id: 3,
    question: "On which day did the World Wide Web go online?",
    correct_answer: "December 20, 1990",
  },
  {
    id: 4,
    question: "What is the main CPU in the Sega Mega Drive / Sega Genesis?",
    correct_answer: "Motorola 68000",
  },
  {
    id: 5,
    question: "Android versions are named in alphabetical order.",
    correct_answer: "True",
  },
  {
    id: 6,
    question:
      "What was the first Android version specifically optimized for tablets?",
    correct_answer: "Honeycomb",
  },
  {
    id: 7,
    question:
      "Which programming language shares its name with an island in Indonesia?",
    correct_answer: "Java",
  },
  {
    id: 8,
    question: "What does RAID stand for?",
    correct_answer: "Redundant Array of Independent Disks",
  },
  {
    id: 9,
    question:
      "Which of the following computer components can be built using only NAND gates?",
    correct_answer: "ALU",
  },
  {
    id: 10,
    question:
      "What was the name of the security vulnerability found in Bash in 2014?",
    correct_answer: "Shellshock",
  },
]; 

设置 Deno 服务器

在这一节中,我们将使用 Oak 中间件框架来设置我们的 Deno 服务器。它是 Deno 的 HTTP 服务器的框架。堪比 KoaExpress 。首先,创建一个名为server.ts的文件,并向其中添加以下代码:

import { Application, Router } from "https://deno.land/x/oak@v7.5.0/mod.ts";
import questions from "./questions.ts";

const app = new Application();
const port = 8000;

const router = new Router();

router.get("/", (context) => {
  context.response.type = "application/json";
  context.response.body = { questions };
});

app.addEventListener("error", (event) => {
  console.error(event.error);
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port });
console.log(`Server is running on port ${port}`);

export default app; 

在这个例子中,我们使用从 Oak 框架导入的ApplicationRouter模块来创建一个新的应用程序,该应用程序监听端口8000上的请求。然后我们声明一个路由,该路由返回一个 JSON 响应,其中包含存储在questions.ts中的问题。我们还添加了一个事件监听器,每次发生错误时都会触发它。如果您需要在出现错误时进行调试,这将很有帮助。

运行应用程序

我们可以使用以下命令运行应用程序,看看到目前为止已经完成了什么:

deno run --allow-net server.ts 

导航至http://localhost:8000/查看响应。

API response

为应用程序编写测试

现在我们已经设置了服务器并运行了应用程序,我们可以为我们的 API 端点编写一个测试用例了。创建一个名为server.test.ts的新文件,并将以下代码添加到其中:

import { superoak } from "https://deno.land/x/superoak@4.2.0/mod.ts";
import { delay } from "https://deno.land/x/delay@v0.2.0/mod.ts";
import app from "./server.ts";

Deno.test(
  "it should return a JSON response containing questions with status code 200",
  async () => {
    const request = await superoak(app);
    await request
      .get("/")
      .expect(200)
      .expect("Content-Type", /json/)
      .expect(/"questions":/);
  }
);

// Forcefully exit the Deno process once all tests are done.
Deno.test({
  name: "exit the process forcefully after all the tests are done\n",
  async fn() {
    await delay(3000);
    Deno.exit(0);
  },
  sanitizeExit: false,
}); 

在这个例子中,我们导入了在server.ts中创建的superoak模块和应用程序。然后我们声明一个测试用例,其中我们使用 SuperOak 和我们的应用程序创建一个请求。然后,我们向应用程序索引路由发出一个GET请求,并做出如下断言:

  1. 返回一个HTTP:OK响应(200)
  2. 收到的响应是一个 JSON 响应
  3. 收到的 JSON 响应有一个名为questions的节点

在本地运行测试

导航到终端。从应用程序的根目录,使用 CTRL + C 停止服务器运行。然后发出以下命令来运行测试:

deno test --allow-net server.test.ts 

这应该是回应:

test it should return a JSON response containing questions with status code 200 ... ok (30ms)
test exit the process forcefully after all the tests are done
 ...% 

有了您的测试用例,您可以添加 CircleCI 配置。

添加 CircleCI 配置

在您的项目根目录中,创建一个名为.circleci的文件夹。将名为config.yml的文件添加到该目录中。

mkdir .circleci

touch .circleci/config.yml 

.circleci/config.yml中添加:

# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1

jobs:
  build-and-test:
    docker:
      - image: denoland/deno:1.10.3
    steps:
      - checkout
      - run: |
          deno test --allow-net server.test.ts

workflows:
  sample:
    jobs:
      - build-and-test 

在本例中,我们做的第一件事是指定 CircleCI 管道流程引擎的版本。始终指定最新的版本(撰写本文时是 2.1)。

指定 CircleCI 版本后,我们指定一个名为build-and-test作业。这项工作有两个关键环节:dockerstepsdocker块指定了我们构建过程成功运行所需的映像。在这种情况下,我们使用官方的 Deno Docker 图像。

steps模块执行以下操作:

  1. 从我们的 GitHub 库签出最新的代码
  2. 运行server.test.ts中的测试

按照workflows块中的规定执行build-and-test任务。

接下来,我们需要在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。如需帮助,请查看这篇文章:将您的项目推送到 GitHub

将项目添加到 CircleCI

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都会显示在你项目的仪表盘上。

在您的deno_circleci项目旁边,点击设置项目

CircleCI 将检测项目中的config.yml文件。点击使用现有配置,然后开始建造。您的第一个构建过程将开始运行并成功完成。

点击构建和测试查看工作步骤和每个工作的状态。

CI Result

结论

在本教程中,我向您展示了如何使用 GitHub 和 CircleCI 为 Deno 应用程序建立持续集成管道。虽然我们的应用程序很简单,只有一个端点和一个测试用例,但是我们涵盖了管道配置和特性测试的关键领域。

CI 建立在测试和版本控制的软件开发最佳实践的基础上,使向软件添加新功能的过程自动化。这消除了人为错误导致生产环境停机的风险。它还为维护的软件增加了额外的质量控制和保证。尝试持续集成,让代码库瓶颈成为团队的过去!

本教程的完整代码库可从 GitHub 上的获得。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

Adonis APIs | CircleCI 的持续集成

原文:https://circleci.com/blog/continuous-integration-for-adonis-apis/

Adonis.js 是发展最快的 Node.js 框架之一。正如其主页上所说,该框架是为测试驱动开发(TDD) 的粉丝设计的。作为这种设计的一个特性,它与一个专门的测试框架捆绑在一起。

在本教程中,您将学习如何自动测试 Adonis.js API,以便您的测试将在代码库的每次更改时运行。

先决条件

要遵循本教程,需要做一些事情:

  1. Javascript 的基础知识
  2. 安装在您系统上的node . js(>= 8.0)
  3. 全球安装的 Adonis.js CLI
  4. 一个的账户
  5. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

克隆 Adonis.js API 项目

首先,您需要克隆我们将要测试的 API 项目。在您的终端中,运行以下命令来克隆基础项目:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/adonis-api-testing.git 

这将立即将存储库的base-project分支克隆到您运行上述命令的位置。这是 API 项目的起点。它没有测试设置。

接下来,进入项目的根目录并安装所需的依赖项:

cd adonis-api-testing
npm install 

然后,在项目的根目录下创建一个名为.env(注意点.)的新文件,并粘贴如下配置:

HOST=127.0.0.1
PORT=3333
NODE_ENV=development
APP_NAME=AdonisJs
APP_URL=http://${HOST}:${PORT}
CACHE_VIEWS=false
APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis
HASH_DRIVER=bcrypt 

上面的配置指定我们处于开发环境中,并且我们将使用名为adonisSQlite数据库进行持久存储。要获得我们的数据库和数据库模式设置,请使用以下命令运行 Adonis.js 项目迁移:

adonis migration:run 

Migrations Run

我们现在已经完全设置好了我们的应用程序,可以开始试用了。通过运行以下命令启动应用程序:

adonis serve --dev 

该命令将启动在http://127.0.0.1:3333运行的 API。如果3333已经在您的系统上使用,端口可能会不同。

我们的 API 是一个简单的User账户 API。正如在./start/routes.js文件中看到的,它包含两个主要端点。

// start/routes.js

"use strict";

const Route = use("Route");

Route.get("/", () => {
  return { greeting: "Welcome to the Adonis API tutorial" };
});

//User api routes
Route.group(() => {
  Route.post("create", "UserController.create");

  Route.route("get", "UserController.fetch", ["GET", "POST"]);
}).prefix("user"); 

create路由调用UserControllercreate方法,通过提供一个usernameemailpassword来创建一个新用户。我们还有一个get端点,它简单地调用UserControllerfetch函数来返回数据库中的用户数组。这些控制器功能的实现可以在下面显示的app/Controllers/Http/UserController.js文件中找到。

// app/Controllers/Http/UserController.js

async create({ request, response }) {
    const data = request.post();

    const rules = {
        username: `required|unique:${User.table}`,
        email: `required|unique:${User.table}`,
        password: `required`
    };

    const messages = {
        "username.required": "A username is required",
        "username.unique": "This username is taken. Try another.",
        "email.required": "An Email is required",
        "email.unique": "Email already exists",
        "password.required": "A password for the user"
    };

    const validation = await validate(data, rules, messages);

    if (validation.fails()) {
        const validation_messages = validation.messages().map((msgObject) => {
            return msgObject.message;
        });

        return response.status(400).send({
            success: false,
            message: validation_messages
        });
    }

    try {
        let create_user = await User.createUser(data);

        let return_body = {
            success: true,
            details: create_user,
            message: "User Successully created"
        };

        response.send(return_body);
    } catch (error) {
        Logger.error("Error : ", error);
        return response.status(500).send({
            success: false,
            message: error.toString()
        });
    }
} //create

async fetch({ request, response }) {
    const data = request.all();

    try {
      const users = await User.getUsers(data);

      response.send(users);
    } catch (error) {
      Logger.error("Error : ", error);
      return response.status(500).send({
        success: false,
        message: error.toString(),
      });
    }
} //fetch 

用 Postman 测试 API

现在让我们通过调用我们的端点来测试我们的 API。我们将使用邮递员来测试我们的端点。确保您的应用程序正在运行。如果没有,再次运行adonis serve --dev

测试用户创建

User Creation - Succesfull

测试用户提取

User Fetch

设置测试框架

下一步是为测试 Adonis.js 应用程序建立测试框架。幸运的是,Adonis.js 有自己的专门测试包,名为 Vow 。使用以下命令安装Vow:

adonis install @adonisjs/vow 

此软件包的安装将导致您的项目发生以下变化:

  • 一个vowfile.js文件将被创建在你的项目的根目录下。
  • 将在项目的根目录下创建一个test文件夹。这是包含所有测试的地方。为了组织测试,所有的单元测试都被放在这个文件夹中的一个unit文件夹中。功能测试放在一个functional文件夹中。默认情况下,在test/unit/example.spec.js中创建一个样本单元测试套件。
  • 在项目的根目录下创建一个.env.testing文件,包含特定于测试目的的环境变量。这个文件与.env合并,所以你只需要从.env文件中定义你想要覆盖的值。

用以下配置替换.env.testing的内容:

HOST=127.0.0.1
PORT=4000
NODE_ENV=testing
APP_NAME=AdonisJs
APP_URL=http://${HOST}:${PORT}
CACHE_VIEWS=false
APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis
HASH_DRIVER=bcrypt 

完成测试设置的最后一步是将Vow提供者添加到我们的项目中。在./start/app.js文件中,将以下项目添加到aceProviders数组中:

...

const aceProviders = [
  ....,
  "@adonisjs/vow/providers/VowProvider"
];

... 

通过运行以下命令运行Vow安装附带的示例测试:

adonis test 

示例测试将成功运行。

Sample Tests

添加测试

现在是时候开始向我们的 API 项目添加一些适当的测试了。我们将添加测试来测试我们的createget端点。运行以下命令来创建一个测试套件,我们将在其中放置我们的测试:

adonis make:test User 

在运行该命令后显示的选项中,选择Functional test(使用箭头键)并按下Enter

测试套件的新文件将在test/functional/user.spec.js自动创建。在该文件中,用以下代码替换内容:

// test/functional/user.spec.js

"use strict";

const { test, trait, after } = use("Test/Suite")("User");

const User = use("App/Models/User");

trait("Test/ApiClient");

const randomString = generateRandomString();

test("Test User creation", async ({ client }) => {
  const userData = {
    username: randomString,
    email: `${randomString}@test.com`,
    password: "123456"
  };

  const response = await client.post("/user/create").send(userData).end();

  response.assertStatus(200);
}).timeout(0);

test("Fetch all users", async ({ client }) => {
  const response = await client.get("/user/get").end();

  response.assertStatus(200);
  response.assertJSONSubset([
    {
      username: randomString,
      email: `${randomString}@test.com`
    }
  ]);
}).timeout(0);

//Delete the created user
after(async () => {
  await (await User.findBy("username", randomString)).delete();
});

function generateRandomString(length = 7) {
  var result = "";
  var characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  var charactersLength = characters.length;
  for (var i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
} 

在上面的测试套件中,Adonis.js Test/ApiClient特征用于访问client对象,该对象用于调用我们的端点。generateRandomString随机数生成器功能用于为测试用户创建一个假的用户名和电子邮件。

Test User creation测试中,我们通过用适当的数据调用create端点来创建一个新用户,并检查我们是否得到了一个表明操作成功的200响应代码。

Fetch all users测试中,client对象用于调用我们的get端点。然后测试响应以确保它返回一个200状态代码。我们还检查它是否返回一个包含我们在之前的测试中创建的用户的数组。

我们将.timeout(0)附加到每个测试,以覆盖测试运行程序的超时限制,因为功能和浏览器测试可能需要一段时间才能运行。

最后,我们通过删除我们创建的测试用户来做一些清理工作。这是在两个测试都完成后进行的。

运行包含在tests文件夹中的所有测试的时间。为此,再次运行测试命令:

adonis test 

All Tests Run

将 API 项目连接到 CircleCI

我们的下一个任务是在 CircleCI 建立我们的项目。从将你的项目推送到 GitHub 开始。

接下来,转到 CircleCI 仪表板上的添加项目页面来添加项目。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

在设置页面上,单击手动添加以指示 CircleCI 我们将手动添加配置文件,而不使用显示的示例。接下来,您会得到提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始构建开始构建。这个构建将会失败,因为我们还没有设置我们的配置文件,稍后我们将会这样做。

自动化我们的测试

现在我们已经将项目连接到 CircleCI,我们可以为我们的持续集成(CI) 管道编写一个配置,这将使我们的测试过程自动化。

在项目的根目录下,创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的文件。在config.yml文件中,输入以下代码:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:11-browsers
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run Migrations
          command: |
            mv .env.testing .env
            node ace migration:run
      - run:
          name: Run tests
          command: npm run test 

在上面的配置文件中,我们从更新npm开始,以确保我们使用的是最新版本。接下来,我们安装所需的依赖项并缓存它们。然后我们创建我们的环境配置文件(.env)并运行我们的迁移。随着所有依赖项和数据库的建立,我们运行我们的测试。

保存该文件,提交并将您的更改推送到您的存储库,以触发 CI 管道运行。

Build Successful - CircleCI

让我们通过点击构建来查看我们的测试结果。

Tests Automated - CircleCI

当我们将新代码推送到我们的库时,我们的测试现在会自动运行。

结论

测试驱动开发(TDD)是一种最佳实践,许多开发人员仍在努力将其集成到他们的开发流程中。拥有一个将 TDD 内置于其核心的框架确实使采用它变得不那么麻烦。在本教程中,我们学习了如何设置 Adonis.js API 的测试,以及如何使用 CircleCI 自动完成这个过程。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

角度应用的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-for-angular-applications/

本教程涵盖:

  1. 设置示例角度应用
  2. 为您的 Angular 应用程序创建和运行测试
  3. 通过持续集成实现自动化角度测试

自动化测试是您持续集成实践的基础。自动化测试澄清了您团队的应用程序的构建过程的状态,确保测试在每个提交或拉请求时运行,并保证您可以在部署到生产环境之前快速修复错误。

在本教程中,我将向你展示如何自动化测试一个角度应用程序。Angular 是一个完全用 TypeScript 编写的框架,流行于构建任何规模或复杂度的 T2 单页应用程序。Angular 由 Google 创建并开源,为构建 web 应用程序提供了一个标准结构。

我将引导您构建一个简单的 Angular 应用程序来检索虚拟用户列表。该列表来自一个名为 JSONPlaceholder 的免费、假冒的 RESTful API,它通常用于测试和原型开发。

先决条件

对于本教程,您需要:

本教程已在以下版本中进行了测试:

  • 角度 CLI: 13.2.2
  • 节点:16.13.2
  • 软件包管理器:npm 8.1.2
  • 操作系统:达尔文 x64

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

入门指南

首先,通过运行以下命令搭建一个新的 Angular 应用程序:

ng new circleci-angular-ci 

系统会提示您回答一些问题:

  • 键入 No 忽略角度路由。本教程不需要它。
  • 选择 CSS 选项作为样式表格式。
  • 进入,等待 Angular CLI 搭建新应用。

安装完成后,将在您的开发文件夹中创建一个名为circleci-angular-ci的新文件夹(或者当您从运行上一个命令时)。转到新创建的 Angular 应用程序,使用以下命令运行它:

// navigate into the folder
cd circleci-angular-ci

// run the application
ng serve 

使用浏览器转到默认 URL: http://localhost:4200上的应用程序。

Angular homepage

创建用户服务

正如我前面提到的,这个演示应用程序从第三方 API 中检索一个虚拟用户列表。对于 Angular 应用程序,标准做法是将业务逻辑和与第三方 API 的通信抽象为服务。这种实践促进了代码的可重用性。

要使用 Angular CLI 创建服务,请运行以下命令:

ng g service service/user 

这个命令在user.service.ts中生成一个名为UserService的新服务。它还在src/app/service文件夹中创建一个名为user.service.spec.ts的测试文件。

首先修改src/app/service/user.service.ts文件的内容。替换为以下内容:

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { User } from "../user";

@Injectable({
  providedIn: "root",
})
export class UserService {
  apiURL: string = "https://jsonplaceholder.typicode.com/users";

  constructor(private httpClient: HttpClient) {}

  public getUsers() {
    return this.httpClient.get<User[]>(`${this.apiURL}`);
  }
} 

这些必需的包已导入:

  • HttpClient在 Angular 中使用,提供客户端 HTTP 协议,增强客户端和服务器之间的通信。
  • 是一个装饰器,它使得一个类可以作为依赖项被提供和注入。

定义了从中检索用户列表的端点,并创建了一个名为getUser()的方法。这个方法将从 API 返回包含用户列表的 JSON 结果。

在您可以使用HttpClient与项目中的 API 通信之前,您需要在根AppModule中导入HttpClientModule。打开src/app/app.module.ts,如下图所示进行修改:

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpClientModule } from "@angular/common/http";
import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {} 

创建用户界面

在上一节中,我们在UserService中引用了一个名为user.ts的文件。这是一个有助于识别 API 预期返回的每个属性的数据类型的接口。在src/app文件夹中创建一个名为user.ts的新文件,并用以下内容填充它:

export interface User {
  id: number;
  name: string;
  email: string;
  phone: string;
  website: string;
  address: {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
  };
} 

修改应用程序组件

通过依赖注入将UserService注入到 app 组件中来修改 app 组件。然后,使用它来检索用户列表。

import { Component } from "@angular/core";
import { User } from "./user";
import { UserService } from "./service/user.service";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  title = "List Of dummy users";

  constructor(private userService: UserService) {}

  users: User[] = [];

  ngOnInit(): void {
    this.userService.getUsers().subscribe((res) => {
      this.users = res;
      return this.users;
    });
  }
} 

显示用户列表

接下来,打开src/app/app.component.html文件,将其内容替换为:

<div class="page-content">
  <div class="container content-wrapper">
    <div class="page-title">
      <h2>{{ title }}</h2>
    </div>
    <div class="row">
      <div *ngFor="let user of users" class="col-md-4">
        <div class="card">
          <div class="card-body">
            <h5 class="card-title">{{ user.name }}</h5>
            <div class="card-text">
              <span>{{ user.address.city }}, {{ user.address.street }}</span>
            </div>
            <div>
              <p>{{ user.phone }}</p>
              <p>{{ user.email }}</p>
              <p>{{ user.website }}</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div> 

在这个代码片段中,我们遍历了列表users并将其呈现在 HTML 中。

向应用程序添加样式

为了设计本教程的应用程序,我们将使用 Bootstrap。发出以下命令通过 NPM 安装引导程序:

npm install bootstrap 

安装完成后,打开angular.json文件。将bootstrap.css文件包含在其中,如下所示:

"styles": [
      "./node_modules/bootstrap/dist/css/bootstrap.css",
      "src/styles.css"
], 

通过打开style.css包含额外的自定义样式。添加此内容:

.page-content {
  margin-top: 100px;
}
.content-wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, minimax(305px, 1fr));
  grid-gap: 15px;
} 

从终端按下 CTRL + C 停止应用程序。用ng serve重新开始,这样新的样式表就会生效。

ng server output

您已经有了用户列表,并完成了样式表更改。

List of users

您的角度应用程序已经启动并运行。现在是时候开始编写测试UserServiceAppComponent的脚本了

测试用户服务和应用组件

AppComponent中,必须创建应用程序并显示适当的标题。为了确保这一点,打开src/app/app.component.spec.ts,将其内容替换为:

import { TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
import { HttpClientModule } from "@angular/common/http";
import { UserService } from "./service/user.service";

describe("AppComponent", () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AppComponent],
      providers: [UserService],
      imports: [HttpClientModule],
    }).compileComponents();
  });

  it("should create the app", () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'List Of dummy users'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual("List Of dummy users");
  });
}); 

接下来,UserService将测试*getUsers()*方法是否按照预期返回用户列表。为了演示这一点,您将创建一个用户对象数组,它具有我们期望从 API 获得的结构。

src/app/service/user.service.spec.ts的内容替换为:

import { TestBed } from "@angular/core/testing";
import { UserService } from "./user.service";
import { HttpClientModule } from "@angular/common/http";
import { User } from "../user";

describe("UserService", () => {
  let service: UserService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
      providers: [UserService],
    });
    service = TestBed.inject(UserService);
  });

  it("should be created", () => {
    expect(service).toBeTruthy();
  });

  it("should be able to retrieve the list of users", () => {
    const dummyUsers: User[] = [
      {
        id: 1,
        name: "Oluyemi",
        email: "yem@me.com",
        phone: "43434343",
        website: "me.com",
        address: {
          street: "sample street",
          suite: "29",
          city: "Abuja",
          zipcode: "23401",
        },
      },
      {
        id: 1,
        name: "Temi",
        email: "tem@me.com",
        phone: "55242",
        website: "tems.com",
        address: {
          street: "Tems street",
          suite: "45",
          city: "LAgos",
          zipcode: "23401",
        },
      },
    ];

    service.getUsers().subscribe((users) => {
      expect(users.length).toBe(10);
      expect(users).toEqual(dummyUsers);
    });

    expect(dummyUsers).toHaveSize(2);
  });
}); 

这段代码导入了运行所有测试所需的所有包。它还在“beforeEach”方法中初始化了被测组件及其依赖项。对于getUsers()方法,我们期望长度为10并且users列表中每个对象的结构等于*dummyUsers*数组的结构。

在本地运行测试

接下来,确认您定义的测试正在按预期运行。从终端发出以下命令:

npm run test 

这将在观察模式下构建应用程序,并启动 Karma 测试运行程序。它还会打开一个 Chrome 浏览器来显示测试输出。

Karma test runner UI

虽然这在本地可能是可以接受的,但是您可能不希望在使用 CircleCI 进行自动化测试时启动浏览器。为了调整这一点,你可以使用无头 Chrome ,这是一种在没有完整浏览器 UI 的环境下运行 Chrome 浏览器的方法。像这样更新package.json文件中的脚本对象:

 "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test --no-watch --no-progress --browsers=ChromeHeadless"
  }, 

您可以通过按下 CTRL + C 来停止测试运行。使用npm run test命令再次运行。

这是终端输出:

> circleci-angular-demo@0.0.0 test
> ng test --no-watch --no-progress --browsers=ChromeHeadless

03 01 2022 06:35:55.412:INFO [karma-server]: Karma v6.3.9 server started at http://localhost:9876/
03 01 2022 06:35:55.414:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
03 01 2022 06:35:57.432:INFO [launcher]: Starting browser ChromeHeadless
03 01 2022 06:36:00.426:INFO [Chrome Headless 96.0.4664.110 (Mac OS 10.15.7)]: Connected on socket wXEU2YE-5tONdo2JAAAB with id 10965930
Chrome Headless 96.0.4664.110 (Mac OS 10.15.7): Executed 4 of 4 SUCCESS (0.026 secs / 0.059 secs)
TOTAL: 4 SUCCESS 

测试成功了。

自动化测试

既然您的应用程序已经准备好并且测试成功,那么您需要在 CircleCI 中创建自动化测试的过程。首先,创建一个名为.circleci的新文件夹,并在其中创建一个config.yml文件。将此内容添加到新文件:

version: 2.1
orbs:
  browser-tools: circleci/browser-tools@1.2.3
jobs:
  build:
    working_directory: ~/ng-project
    docker:
      - image: cimg/node:16.13.1-browsers
    steps:
      - browser-tools/install-chrome
      - browser-tools/install-chromedriver
      - run:
          command: |
            google-chrome --version
            chromedriver --version
          name: Check install
      - checkout
      - restore_cache:
          key: ng-project-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - run: npm install
      - save_cache:
          key: ng-project-{{ .Branch }}-{{ checksum "package-lock.json" }}
          paths:
            - "node_modules"
      - run: npm run test 

orb 有用于浏览器测试的工具,比如 Chrome 和 ChromeDriver。cimg/node:16.13.1-browsers Docker 映像来自 CircleCI 映像注册表,并安装了运行测试的项目的所有依赖项。

接下来,在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。查看将您的项目推送到 GitHub 以获取指导。

现在,登录你的 CircleCI 账户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

CircleCI dashboard

点击设置项目按钮。将提示您是否已经在项目中定义了 CircleCI 的配置文件。

Application branch

输入分支名称(对于本教程,我们使用main)。点击设置项目按钮完成该过程。您可以从仪表板查看构建。

Successful build

你有它!

结论

在本教程中,您从头开始构建了一个 Angular 应用程序。您编写了主要应用程序组件和服务的基本测试,创建该服务是为了从第三方 API 检索用户列表。最后,您使用 CircleCI 自动化了测试。

Angular 是目前最流行的构建单页面应用程序的框架之一,它很可能还会存在一段时间。很有可能,你和你的团队将会承担一个自动化测试 Angular 应用的任务。

我希望这篇教程对你有所帮助。完整的源代码可以在circle ci-GWP/circle ci-angular-demo上找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。由于精通技术,他的爱好包括尝试新的编程语言和框架。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

CodeIgniter APIs 的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-for-codeigniter-apis/

管理代码库是软件开发团队的一个主要瓶颈。让团队按照他们自己的节奏工作必须与确保代码库在任何时候都是有效的相平衡。许多团队使用代码库中的分支来试图保持这种平衡。一些团队为每个新特性创建一个分支,而其他团队为每个环境使用不同的分支(例如,开发、试运行、生产)。无论您使用什么方法,您都需要在某个时候合并分支,通常是在变更被批准的时候。随着应用程序复杂性的增加或团队规模的扩大,瓶颈会变得更加严重。您的团队在合并到生产分支时遇到的问题可能会延迟新功能的部署或导致意外停机,这会对客户的士气产生负面影响,并对销售产生负面影响。

持续集成(CI) 旨在解决这些问题。使用 CI,您可以通过简单地将新特性的相关代码推送到存储库的主分支来触发集成。CI 渠道让您可以维护一个每个人都可以推进的主要分支。只有在成功构建的情况下,新添加的代码才被允许进入主分支。这不仅节省了时间,而且有助于减少人为错误带来的复杂性。CI 确保软件更新能够以快速可靠的方式执行。

在本教程中,我将向您展示如何使用 CircleCI 持续集成 CodeIgniter API。

先决条件

对 CodeIgniter 的基本理解可能会有所帮助,但我将在整个教程中提供解释和官方文档的链接。如果您对任何概念不清楚,您可以在继续之前查看链接的材料。

开始之前,请确保您的系统上安装了以下项目:

  • Composer : Composer 将用于您的 CodeIgniter 项目中的依赖管理。
  • 本地数据库实例。虽然本教程将使用 MySQL,但是您可以自由选择您喜欢的数据库服务。

对于存储库管理和持续集成,您需要:

入门指南

首先,创建一个新的 CodeIgniter 项目:

$ composer create-project codeigniter4/appstarter ci-test-circleci 

这将启动一个新的 CodeIgniter 项目及其所有依赖项,安装在一个名为ci-test-circleci的文件夹中。

运行应用程序

移动到项目文件夹并运行应用程序:

$ php spark serve 

从浏览器导航到 http://localhost:8080/ 打开欢迎页面。

CodeIgniter Homepage

在您的终端中,按CTRL + C停止应用程序,同时我们继续其余的设置。

对于本教程,我们将构建一个 API 来管理博客文章。该 API 将拥有创建、阅读、更新和删除博客文章的端点。为了简单起见,一篇博客文章将有三个字段:

我们的 API 将添加一个主键(id)、一个created_at字段和一个updated_at字段。

首先,设置您的本地 env 文件和数据库。

设置本地环境

使用以下命令将env文件复制到.env文件中:

$ cp env .env 

默认情况下,CodeIgniter 在生产模式下启动。在本教程中,我们将把它改为开发模式。在.env文件中,取消对CI_ENVIRONMENT变量的注释,并将其设置为 development:

CI_ENVIRONMENT = development 

接下来,在您的本地环境中创建一个数据库。取消对以下变量的注释,以更新每个值并建立到数据库的成功连接:

database.default.hostname = localhost
database.default.database = YOUR_DATABASE_NAME
database.default.username = YOUR_DATABASE_USERNAME
database.default.password = YOUR_DATABASE_PASSWORD
database.default.DBDriver = MySQLi # this is the driver for a mysql connection. There are also drivers available for postgres & sqlite3. 

用项目特定的值替换YOUR_DATABASEYOUR_DATABASE_USERNAMEYOUR_DATABASE_PASSWORD占位符。

接下来,我们需要创建一个迁移文件,并在数据库中植入一些帖子。

迁徙和播种

现在您已经创建了一个数据库并建立了到它的连接,为post表创建迁移。

从终端,使用 CodeIgniter CLI 工具创建一个迁移文件:

$ php spark migrate:create 

CLI 将要求您命名迁移文件。然后,它将在app/Database/Migrations目录中创建迁移文件。我们正在创建的迁移的名称是add_post

迁移文件名将以数字序列为前缀,格式为 YYYY-MM-DD-HHIISS 。前往 CodeIgniter 文档获取更详细的解释。

接下来,打开位于app/Database/Migrations/YYYY-MM-DDHHIISS_add_post.php的迁移文件并更新其内容:

<?php

namespace app\Database\Migrations;

use CodeIgniter\Database\Migration;

class AddPost extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 5,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'title' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false
            ],
            'author' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false,
            ],
            'content' => [
                'type' => 'VARCHAR',
                'constraint' => '1000',
                'null' => false,
            ],
            'updated_at' => [
                'type' => 'datetime',
                'null' => true,
            ],
            'created_at datetime default current_timestamp',
        ]);
        $this->forge->addPrimaryKey('id');
        $this->forge->createTable('post');
    }

    public function down()
    {
        $this->forge->dropTable('post');
    }
} 

现在,运行您的迁移:

 $ php spark migrate 

该命令在数据库中创建一个post表,并添加迁移中列出的列。

为了使开发更容易,在数据库中植入一些虚拟客户机数据。fzaninotto faker 包是 CodeIgniter 框架中默认的dev依赖项。您可以使用它向数据库中添加随机帖子。正如您为迁移所做的那样,使用 CodeIgniter CLI 工具为 posts 创建一个种子。运行:

$ php spark make:seeder 

当 CLI 提示您输入名称时,输入PostSeeder。在app/Database/Seeds目录下会创建一个PostSeeder.php文件。打开文件并将其内容替换为:

<?php
namespace app\Database\Seeds;

use CodeIgniter\Database\Seeder;
use Faker\Factory;

class PostSeeder extends Seeder
{
    public function run()
    {
        for ($i = 0; $i < 10; $i++) { //to add 10 posts. Change limit as desired
            $this->db->table('post')->insert($this->generatePost());
        }
    }

    private function generatePost(): array
    {
        $faker = Factory::create();
        return [
            'title' => $faker->sentence,
            'author' => $faker->name,
            'content' => $faker->paragraphs(4, true),
        ];
    }
} 

接下来,用虚拟客户机播种数据库。运行:

$ php spark db:seed PostSeeder 

Database View

实体模型

我们将使用 CodeIgniter 的模型用于 API 与数据库的交互。首先,通过打开app/Models目录并创建一个名为PostModel.php的文件,为 blog 表创建一个模型。添加:

<?php

use CodeIgniter\Model;

class PostModel extends Model
{
    protected $table = 'post';

    protected $allowedFields = [
        'title',
        'author',
        'content',
    ];

    protected $updatedField = 'updated_at';

    public function getAllPosts(): array
    {
        return $this->findAll();
    }

    public function findPost($id): array
    {
        $post = $this
            ->asArray()
            ->where(['id' => $id])
            ->first();

        if (!$post) throw new Exception('Could not find post for specified ID');

        return $post;
    }

    public function savePost(array $postDetails): array
    {
        $postId = (int)$this->insert($postDetails);
        $postDetails['id'] = $postId;
        return $postDetails;
    }

    public function updatePost(int $postId, array $newPostDetails): array
    {
        $this->update($postId, $newPostDetails);
        return $this->findPost($postId);
    }

    public function deletePost($id){
        $post = $this->findPost($id);
        $this->delete($post);
    }
} 

在这个类中,我们定义了允许 API 与数据库交互的模型函数。首先指定表名和可以在数据库中更新的列。

函数getAllPostsgetPostsavePostupdatePostdeletePost让 API 根据控制器的需要执行读或写操作。

创建控制器

接下来,在app/Controllers目录中创建一个文件名Post.php。添加:

<?php

namespace app\Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use PostModel;

class Post extends BaseController
{
    public function create()
    {
        $rules = [
            'title' => 'required|min_length[6]|max_length[100]',
            'author' => 'required|min_length[6]|max_length[100]',
            'content' => 'required|min_length[6]|max_length[1000]',
        ];

        $input = $this->getRequestInput($this->request);

        if (!$this->validateRequest($input, $rules)) {
            return $this
                ->getResponse(
                    $this->validator->getErrors(),
                    ResponseInterface::HTTP_BAD_REQUEST
                );
        }

        $model = new PostModel();
        $savePostResponse = $model->savePost($input);
        return $this->getResponse(
            [
                'message' => 'Post added successfully',
                'post' => $savePostResponse
            ],
            ResponseInterface::HTTP_CREATED
        );
    }

    public function index()
    {
        $model = new PostModel();
        return $this->getResponse([
            'message' => 'Posts retrieved successfully',
            'posts' => $model->getAllPosts()
        ]);
    }

    public function show($id)
    {
        $model = new PostModel();
        return $this->getResponse([
            'message' => 'Post retrieved successfully',
            'post' => $model->findPost($id)
        ]);
    }

    public function update($id)
    {
        $input = $this->getRequestInput($this->request);
        $model = new PostModel();
        $updatePostResponse = $model->updatePost($id, $input);

        return $this->getResponse(
            [
                'message' => 'Post updated successfully',
                'post' => $updatePostResponse
            ]
        );
    }

    public function delete($id){
        $model = new PostModel();
        $model->deletePost($id);

        return $this->getResponse(
            [
                'message' => 'Post deleted successfully',
            ]
        );
    }
} 

该控制器类包含五个功能,对应于用户的路线:

  • 创建:index功能
  • 读取:indexshow功能
  • 更新:update功能
  • 删除:delete功能

对于这些函数中的每一个,我们使用在PostModel.php中声明的相关函数与数据库进行交互。

我们的控制器使用了一些需要在BaseController中声明的助手函数。打开app/Controllers/BaseController.php文件。在BaseController类内,添加:

public function getResponse(array $responseBody,
                            int $code = ResponseInterface::HTTP_OK)
{
    return $this
        ->response
        ->setStatusCode($code)
        ->setJSON($responseBody);
}

public function getRequestInput(IncomingRequest $request)
{
    $input = $request->getPost();
    if (empty($input)) {
        $input = json_decode($request->getBody(), true);
    }
    return $input;
}

public function validateRequest($input, array $rules, array $messages = [])
{
    $this->validator = Services::Validation()->setRules($rules);
    // If you replace the $rules array with the name of the group
    if (is_string($rules)) {
        $validation = config('Validation');

        // If the rule wasn't found in the \Config\Validation, we
        // should throw an exception so the developer can find it.
        if (!isset($validation->$rules)) {
            throw ValidationException::forRuleNotFound($rules);
        }

        // If no error message is defined, use the error message in the Config\Validation file
        if (!$messages) {
            $errorName = $rules . '_errors';
            $messages = $validation->$errorName ?? [];
        }

        $rules = $validation->$rules;
    }
    return $this->validator->setRules($rules, $messages)->run($input);
} 

注意 : 不要忘记为 CodeIgniter 添加这些导入语句:

use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\Validation\Exceptions\ValidationException;
use Config\Services; 

控制器路由

打开app/Config/Routes.php文件并添加:

$routes->get('posts', 'Post::index');
$routes->post('post', 'Post::create');
$routes->get('post/(:num)', 'Post::show/$1');
$routes->post('post/(:num)', 'Post::update/$1');
$routes->delete('post/(:num)', 'Post::delete/$1'); 

这些行将把在Post控制器中声明的每个函数分配给一个用户可以向其发送 HTTP 请求的端点。

运行应用程序并测试端点

运行应用程序:

$ php spark serve 

使用 Postman(或类似的应用程序)我们可以验证我们的应用程序工作正常。

获取博客文章列表

Fetch Posts

检索博客文章的详细信息

Fetch Post

写作测试

看起来我们的端点工作正常,但我们不能绝对肯定,直到我们测试了所有的可能性。为了确保我们的应用程序处理意外情况,我们应该编写模拟边缘场景的测试。

tests目录中,创建一个名为Controllers的文件夹。在tests/Controllers目录中,创建一个名为PostTest.php的类,并添加:

<?php

namespace Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Test\FeatureTestCase;

class PostTest extends FeatureTestCase
{
    public function testCreateWithValidRequest()
    {
        $result = $this->post('post', [
            'title' => 'THis is a dummy post',
            'author' => 'Janet Doe',
            'content' => 'This is a test to show that the create endpoint works'
        ]);
        $result->assertStatus(ResponseInterface::HTTP_CREATED);
    }

    public function testIndex()
    {
        $result = $this->get('posts');
        $result->assertStatus(ResponseInterface::HTTP_OK);
    }

    public function testShowWithInvalidId()
    {
        $result = $this->get('post/0'); //no item in the database should have an id of -1
        $result->assertStatus(ResponseInterface::HTTP_NOT_FOUND);
    }
} 

为了节省时间,我们在本教程中只测试 3 个场景。首先,我们尝试创建一个 post,并断言 API 成功地处理了它,并返回一个201 HTTP 响应代码。第二个测试检查当请求获取所有帖子时,API 是否返回一个200响应代码。最后一个测试是边缘情况;这是对数据库中不存在的职位的请求。我们期望系统返回一个404响应,因为我们的数据库中没有这样的帖子。

接下来,我们需要对app/Config/Database.php文件进行一些修改。修改$tests数组:

public $tests = [
   'hostname' => '',
   'username' => '',
   'password' => '',
   'database' => '',
   'DBDriver' => '',
]; 

有了您的测试用例,您可以添加 CircleCI 配置。

添加 CircleCI 配置

在您的项目根目录中,创建一个名为.circleci的文件夹。将名为config.yml的文件添加到该目录中。

$ mkdir .circleci

$ touch .circleci/config.yml 

.circleci/config.yml中添加:

version: 2
jobs:
  build:
    docker:
      - image: circleci/php:7.4-node-browsers

      - image: circleci/mysql:8.0.4

        auth:
          username: mydockerhub-user
          password: $DOCKERHUB_PASSWORD

        environment:
          MYSQL_ROOT_PASSWORD: rootpw
          MYSQL_DATABASE: test_db
          MYSQL_USER: user
          MYSQL_PASSWORD: passw0rd

    steps:
      - checkout

      - run: sudo apt update
      - run: sudo docker-php-ext-install zip

      - run:
          # Our primary container isn't MYSQL so run a sleep command until it is ready.
          name: Waiting for MySQL to be ready
          command: |
            for i in `seq 1 10`;
            do
              nc -z 127.0.0.1 3306 && echo Success && exit 0
              echo -n .
              sleep 1
            done
            echo Failed waiting for MySQL && exit 1

      - run:
          name: Install MySQL CLI
          command: |
            sudo apt-get install default-mysql-client

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: Create .env file and add db parameters
          command: |
            sudo cp env .env
            sudo chmod 777 .env
            sudo echo "" >> .env
            sudo echo "CI_ENVIRONMENT = development" >> .env
            sudo echo "" >> .env
            sudo echo "database.default.hostname = 127.0.0.1" >> .env
            sudo echo "database.default.database = test_db" >> .env
            sudo echo "database.default.username = user" >> .env
            sudo echo "database.default.password = rootpw" >> .env
            sudo echo "database.default.DBDriver = MySQLi" >> .env

      - run: phpdbg -qrr ./vendor/bin/phpunit tests/Controllers 

这里发生了几件事。指定 CircleCI 版本后,我们指定构建任务。这项工作有两个关键环节。docker构建指定了我们构建过程成功运行所需的映像。其中,我们加载了两个 Docker 映像,一个用于 PHP (7.4),另一个用于 MySQL。authenvironment块定义了允许我们的 CodeIgniter 应用程序连接到数据库的认证参数。

注意 : CodeIgniter 对 PHP 版本的最低要求是 7.2。默认情况下,CircleCI 将 Docker 映像的 PHP 版本设置为 7.1。请务必更改这一点,否则您的构建将会失败。

步骤块做几件重要的事情:

  1. 从存储库中签出代码
  2. 设置 Docker 图像
  3. 安装 MySQL
  4. 安装编写器依赖项
  5. 创建一个.env文件,并相应地更新数据库参数
  6. 运行tests/Controllers文件夹中的测试用例。参考文档,了解我们为什么使用phpdbg

接下来,我们需要在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。看到这个帖子帮助把你的项目推到 GitHub

将项目添加到 CircleCI

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都会显示在你项目的仪表盘上。

在您的ci-test-circleci项目旁边,点击设置项目

Set up Project

CircleCI 会检测您的配置文件,并为您提供使用默认 PHP 配置的选项。

点击使用现有配置然后开始建造。您的第一个构建过程将开始运行。此构建将失败。你以后会明白为什么的。

Failed Build

点击构建。您将看到作业步骤和每个作业的状态。为什么最后一步构建失败了?这是因为我们的应用程序没有通过所有的测试。

Failed Build Details

这是持续集成之美的一个例子;你可以在 i >它们投入生产之前捕捉错误和问题。这种情况下的问题是微不足道的,但是使用更复杂的数据库可以获得同样的好处

是时候通过让我们的应用程序通过最后的测试来让我们的管道构建成功了。

我们的测试失败了,因为我们的应用程序在找不到提供的 ID 的帖子时抛出了一个异常。当这种情况发生时,我们的应用程序应该捕获异常并在响应中返回一条错误消息。

为此,我们需要做一些改变。首先,在app目录中创建一个名为Exception的目录。在app/Exception目录中,创建一个名为ItemNotFoundException.php的类,并添加:

<?php

namespace app\Exception;

use Exception;

class ItemNotFoundException extends Exception
{

} 

这个类允许我们只处理在数据库中找不到的项目的异常。如果需要,我们可以为另一个异常应用不同的逻辑(例如,一个AccessDeniedException)。

接下来,在位于app/Models/PostModel.php类中的findPost函数中使用这个异常。添加:

public function findPost($id): array
{
    $post = $this
        ->asArray()
        ->where(['id' => $id])
        ->first();

    if (!$post) throw new ItemNotFoundException('Could not find post for specified ID');

    return $post;
} 

注意 : 不要忘记在文件的顶部添加这个导入语句。

use App\Exception\ItemNotFoundException; 

更改app/Controllers/Post.php类中的show函数:

public function show($id)
{
    $model = new PostModel();
    try {
        return $this->getResponse([
            'message' => 'Post retrieved successfully',
            'post' => $model->findPost($id)
        ]);
    } catch (ItemNotFoundException $e) {
        return $this->getResponse([
            'message' => $e->getMessage(),
        ],
            ResponseInterface::HTTP_NOT_FOUND);
    }
} 

注意 : 别忘了加上进口声明。

use App\Exception\ItemNotFoundException; 

提交并推动您的更改。

$ git add .
$ git commit -m "Handle ItemNotFoundException"
$ git push origin main 

一旦成功推送了更改,请返回到您的 CircleCI 仪表板。有一个新的生成进程正在运行。完成后,状态变为Success

Successful Build

结论

在本教程中,我向您展示了如何使用 GitHub 和 CircleCI 为 CodeIgniter 应用程序建立持续集成管道。虽然我们的应用程序很简单,测试覆盖面很小,但我们涵盖了 CodeIgniter 中管道配置和特性测试的关键领域。

当代码库中反映出强大的测试文化时,持续集成确实会大放异彩。通过使用确定应用程序应该如何响应预期和意外场景的测试用例,持续集成使得添加(和部署)新功能的过程更加简单。如果所有测试都通过,更新将被部署到生产服务器上。如果没有,团队会收到警报,他们可以在问题造成任何停机之前解决问题。尝试持续集成,让代码库瓶颈成为团队的过去!

本教程的完整代码库可从 GitHub 上的获得。

编码快乐!


Oluyemi 是一个技术爱好者、编程狂和热爱新技术的网络开发迷。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

Django 项目的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-for-django-projects/

本文主要关注为 Django 项目设置一个持续集成管道,但是这里的信息也可以扩展到其他 Python 项目。 Django 是一个 Python 框架,被描述为“有期限的完美主义者的 web 框架”它被认为是创建最小可行产品(MVP)的一个很好的工具,因为它很容易建立一个带有数据库的应用程序并运行测试。它交付了高质量的代码,并且拥有优秀的文档。这些功能也有利于你的用户,因为它承诺快速推出新功能。

以下是我们将采取的步骤:

  1. 创建 Django 应用程序
  2. 为应用程序创建测试
  3. 将应用程序归档
  4. 配置 CircleCI
  5. 本地运行
  6. 推送至 GitHub
  7. 添加徽章
  8. 探索缓存优化

先决条件

为了完成本教程,您需要安装以下软件:

创建 Django 应用程序

Django Girls 提供了一个关于 Django 框架的很棒的教程。我们将从一个博客应用程序开始,这是完成 Django Girls 教程并为其设置 CircleCI 的结果。对于数据库,我们将使用一个平面文件:.sqlite

要获得博客应用程序,请在您的终端中键入以下代码来克隆这个回购:

git clone https://github.com/NdagiStanley/django_girls_complete.git 

然后,通过运行以下命令进入目录:

cd django_girls_complete 

在我完成 Django Girls 的教程后,我对应用程序的代码库做了额外的修改。由于这些更改与为项目设置 CI 没有直接关系,我将只在 GitHub 中添加文件更改的链接。这些变化包括:

要查看原始版本,请运行:

git checkout original 

要获得我的更改后的代码库,请运行:

git checkout 1.1.0 

从现在开始,我将带我们一步一步地建立我们的持续集成管道。我们的应用程序的文件夹结构看起来像这样(我简单地从我的终端运行tree来得到这个):

.
├── Dockerfile
├── LICENSE
├── README.md
├── blog
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── static
│   │   └── css
│   │       └── blog.css
│   ├── templates
│   │   └── blog
│   │       ├── base.html
│   │       ├── post_detail.html
│   │       ├── post_edit.html
│   │       └── post_list.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── docker-compose.yml
├── init.sh
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── requirements.txt

7 directories, 26 files 

为应用程序创建测试

我们的 CI 管道需要进行测试,这样我们可以确保在合并任何新的提交之前,我们的自动化构建是可以的。用 Django 编写测试在这里有大量的文档

首先,将blog/tests.py中的代码替换为:

from django.contrib.auth.models import User
from django.test import TestCase
from django.utils import timezone

from .models import Post
from .forms import PostForm

class PostTestCase(TestCase):
    def setUp(self):
        self.user1 = User.objects.create_user(username="admin")
        Post.objects.create(author=self.user1,
                            title="Test",
                            text="We are testing this",
                            created_date=timezone.now(),
                            published_date=timezone.now())

    def test_post_is_posted(self):
        """Posts are created"""
        post1 = Post.objects.get(title="Test")
        self.assertEqual(post1.text, "We are testing this")

    def test_valid_form_data(self):
        form = PostForm({
            'title': "Just testing",
            'text': "Repeated tests make the app foul-proof",
        })
        self.assertTrue(form.is_valid())
        post1 = form.save(commit=False)
        post1.author = self.user1
        post1.save()
        self.assertEqual(post1.title, "Just testing")
        self.assertEqual(post1.text, "Repeated tests make the app foul-proof")

    def test_blank_form_data(self):
        form = PostForm({})
        self.assertFalse(form.is_valid())
        self.assertEqual(form.errors, {
            'title': ['This field is required.'],
            'text': ['This field is required.'],
        }) 

我们添加的是一个从django.test.TestCase扩展而来的PostTestCase类,它有四个方法:

  • 在定义为def setUp(self)setUp方法中,我们创建了一个用户self.user,以及该用户的一篇文章。
  • test_post_is_posted方法确认标题为测试的帖子正文是我们正在测试这个
  • test_valid_form_data方法确认表单保存正确:在表单上填写一个标题和文本以创建一篇文章,文章被保存,其标题和文本被确认是正确的。
  • test_blank_form_data方法确认当既没有填写标题也没有填写文本时,表单将抛出一个错误。

其次,运行以下命令:

python manage.py test 

注意: 这个命令从所有的测试用例中构建一个测试套件,在任何文件名以test开头的文件中扩展TestCase,然后运行那个测试套件。

Tests passing

测试通过了!呼吸新鲜空气。

要在添加测试后从代码库开始,运行:

git checkout tests 

将应用程序归档

接下来是对应用程序进行分类。这到底是什么意思?

Docker 化一个应用意味着使用 Docker 在一个容器中开发、部署和运行一个应用。这涉及三个关键文件:

  • 文件:一个文件对于 Docker 就像一个 T2 文件对于 Git 一样。其中列出的文件和/或文件夹将在 Docker 上下文中被忽略,并且不会在 Docker 图像中找到。看我们的.dockerignore文件这里

  • Dockerfile file:定义创建 Docker 图像的步骤。请看我们这里的Dockerfile

  • docker-compose.yml文件:无论你是运行一个服务还是多个服务, Docker compose 消除了键入一个长的docker run命令的需要,并允许你运行一行docker-compose up,在文件的上下文中旋转容器。看我们的docker-compose.yml文件这里

我还添加了一个在运行Dockerfile时使用的初始化脚本。看我们的init.sh文件这里

圆形构型

为了将 CircelCI 集成到我们的项目中,我们需要为一个 Python 应用添加一个配置文件。在项目的根目录下创建一个.circleci文件夹,并添加一个config.yml文件。把这几行复制进去:

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:3.6
    steps:
      - checkout
      - restore_cache:
          key: deps1-{{ .Branch }}-{{ checksum "requirements.txt" }}
      - run:
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: deps1-{{ .Branch }}-{{ checksum "requirements.txt" }}
          paths:
            - "venv"
      - run:
          name: Running tests
          command: |
            . venv/bin/activate
            python3 manage.py test
      - store_artifacts:
          path: test-reports/
          destination: python_app 

如果这是你第一次这样做,你会注意到四个可能不明显的步骤:

  • checkout:这个命令通过 SSH 将您的源代码提取到配置的路径(默认情况下是工作目录)。
  • restore_cache:该命令恢复之前保存的缓存。
  • save_cache:该命令生成并保存一个文件、多个文件或文件夹的缓存。在我们的例子中,我们保存了运行pip install …后获得的已安装 Python 包的缓存。
  • 这个命令存储日志、二进制文件等。以便应用程序在以后的运行中可以访问它们。

在本地运行 CircleCI 构建

我主张在将构建推送到 GitHub 并在 CircleCI 上运行之前,安装 CircleCI CLI 工具在本地运行构建。在本地运行构建使您不必向在线存储库提交代码来确认构建通过。它加快了开发周期。

您可以使用 Homebrew 通过运行以下命令来安装 CLI:

brew install circleci 

然后,开始运行circleci switch命令:circleci switch

然后运行circleci config validate来验证您的配置文件写得是否正确,并运行circleci build来构建应用程序:circleci config validate circleci build

命令的输出以成功结束!本地构建成功运行。有了这个检查,我们现在可以将代码推送到 GitHub。

要在添加测试后从代码库开始,运行:

git checkout circleci 

将项目连接到 CircleCI

我们现在需要做的就是将 CircleCI 连接到我们在 GitHub 上的代码,这样我们就可以让 CI 管道像魔咒一样工作:推送代码更改,运行测试,如果通过就合并。在浏览器中打开 GitHub 并创建一个新的资源库。如果你没有 GitHub 账号,你可以在这里创建一个。创建你的回购协议后,将你的项目推送到 GitHub

然后登录 CircleCI 查看仪表盘。如果你没有帐号,你可以在这里注册一个免费的。在您的仪表板页面上,点击添加项目,然后点击您正在使用的项目名称。我的情况是名字叫django_girls_complete

Set up project

接下来,点击Start Building选择要使用的配置文件。

因为我们已经有了一个配置文件,单击Add Manually运行作业。

成功的运行将如下所示:

Successful run

你有它!Django 项目的 CI 就是这么设置的。在您的终端中键入以下内容以运行应用程序:

docker compose up 

该应用将于<0.0.0.0:8000>上线。

添加徽章

您的代码库中可以有许多集成。在您的自述文件中包含徽章是一种最佳实践,它允许您向他人展示这些集成服务的状态。它通过让其他人知道这些服务的状态来帮助他们。要获取徽章,请导航至:https://circleci.com/gh/<Username>/<Project>/edit#badges。我的情况是:https://circle ci . com/GH/NdagiStanley/django _ girls _ complete/edit # badges。

点击项目名称旁边的 cog 图标:

然后,在侧边菜单中,点击状态徽章 : Status badges

复制嵌入代码:Embedded code

最后,将它粘贴到您的自述文件中,最好是在库的简要介绍之后,靠近顶部。从自述文件中,您将能够看到默认分支中最近作业的构建状态

探索缓存

在显示本地 CircleCI CLI 运行的屏幕截图中,您可能已经注意到红色的文本:Error: Skipping cache - error checking storage: not supported。在本地运行 CircleCI 生成时不支持缓存。

为了使 CircleI 作业(配置文件中命令的单次运行)更快,实现了缓存。更多信息请点击。在我们的设置中,我们包括了缓存步骤。为了便于学习,我运行了有缓存和没有缓存的 CircleCI 作业,向我们展示了它的优势。观察:

Caching example

从最低的构建开始,最右边告诉我们运行作业所用的时间:00:11 (11 秒),7 秒,等等。

如果没有修改requirements.txt文件,缓存有利于我们忽略pip install -r requirements.txt命令。当然,第一个作业只是设置缓存。难怪用了 11 秒。我们可以假设,在不改变requirements.txt文件的情况下,对 GitHub 的连续提交将在 7 秒的范围内运行。在第三个任务中,我删除了缓存,时间返回到第一个任务的范围:11 秒。在第四次运行时,我恢复了缓存,运行了 20 秒。最后,同一作业的重新运行持续了 7 秒钟,再次利用了缓存。

这里的秒钟可能看起来很少,无足轻重,但是随着时间的推移,失去的秒钟可以变成几分钟,甚至几小时。

结论

如果您遵循了上面的说明,那么您就已经很好地掌握了 Django 项目的持续集成。我们从创建一个 Django 应用程序并为其编写测试开始。然后我们对应用程序进行了 Dockerized,这样我们就可以在一个隔离的容器中构建它。这样做是有益的,因为它导致在任何机器上运行应用程序只需要一个依赖项:Docker。该应用程序的其他依赖项都安装在 Docker 容器中。然后,我们介绍了使用 CircleCI 的一些最佳实践:

  • 使用 CircleCI CLI 在本地运行 CI 构建
  • 添加徽章和
  • 利用缓存实现更快的构建。

关于斯坦利:从年轻的时候起,斯坦利就摆弄电子产品,用科技制造东西。现在,他是安德拉的一名工程师,他的工作涉及数据、ML 和物联网。源于他一生对 DIY 的热爱,他踏上了一段召唤内心的建造者并一路教导他人的个人旅程。他关心技术如何影响社会,并寻求与致力于创造积极影响的其他人合作。


Stanley 是一名软件工程师和技术文案,他身兼数职,包括技术团队领导和社区参与。他把自己描述成一个数字人(在数字空间中有文化)。

阅读 Stanley Ndagi 的更多帖子

Go 应用的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-for-go-applications/

本教程涵盖:

  1. 设置 Go 博客应用程序
  2. 添加和运行测试
  3. 自动化测试

Go 是一种由 Google 支持的开源编程语言,它使构建简单、可靠、高效的软件变得容易。Go 在网络服务器上的效率和友好的语法使它成为了 Node.js 的有用替代品。所有的网络应用都需要经过充分测试的特性,在 Go 中开发的也不例外。在本教程中,我们将构建测试一个简单的 Go 博客。

先决条件

要遵循本教程,需要做一些事情:

  1. 编程基础知识
  2. 安装到您的系统上(您可以在这里找到安装指南)。
  3. 一个的账户
  4. 一个 GitHub 。账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

一旦满足了所有这些要求,您就可以开始本教程了。

设置一个简单的 Go 博客项目

要设置 Go 项目,请创建项目文件夹。导航到文件夹的根目录,并输入:

mkdir go-testing
cd go-testing 

接下来,使用go mod命令初始化根目录下的项目:

go mod init go-testing 

这将用包名go-testing初始化项目。你可以使用任何你想要的包名,只要它遵循标准包命名约定。。

现在,为应用程序的入口点创建一个main.go文件:

// go-testing/main.go

package main

import "fmt"

func main(){
  fmt.Println("Good to Go!")
} 

这段代码将字符串Good to Go!打印到 CLI 界面。通过在 CLI 上运行以下命令来运行此代码:

go run main.go 

创建博客模型

在这一步中,您将为一个示例博客创建一个模型(用面向对象编程的术语来说)。这个模型创建了一个blog的新实例,并向其中添加了文章。

在项目的根目录下,创建一个名为blog.go的新的Go文件。把这个粘贴进去:

// go-testing/blog.go

package main

type Article struct {
	Title string `json:"title"`
	Body  string `json:"body"`
}

type Blog struct {
	Articles []Article
}

func New() *Blog {
	return &Blog{}
}

func (b *Blog) SaveArticle(article Article) {
	b.Articles = append(b.Articles, article)
}

func (b *Blog) FetchAll() []Article {
	return b.Articles
} 

这段代码包含两种数据类型和三个函数,工作原理如下:

  • Article struct类型包含属性TitleBody。它分别使用json绑定到titlebody
  • Blog是代表博客本身的struct类型。它由一组Article数据类型组成。
  • New()是实例化一个新的Blog的方法。换句话说,它返回了一个新的Blog实例。
  • SaveArticle方法向博客的文章集合中添加一篇新文章。
  • FetchAll是一个在数组中检索Blog实例中所有Article数据类型的方法。

要测试您刚刚创建的博客模型,请将main.go中的所有内容替换为:

// go-testing/main.go

package main

import (
	"fmt"
)

func main() {

	blog := New()

	fmt.Println(blog)

	blog.SaveArticle(Article{"My first Blog post", "Today, we will be talking about blogging"})

	fmt.Println(blog)

} 

这段代码创建了一个打印在stdout上的新博客实例。SaveArticle方法保存新的文章实例,博客再次打印到stdout。既然项目中有不止一个Go文件,那么使用这个命令来运行这两个文件:

go run blog.go main.go 

在您的 CLI 上打印出了blog实例。

Blog test - CLI

blog实例的第一份打印输出为空。第二次打印输出发生在我们添加了一篇文章之后,因此它包含了那篇文章。

向 Go 项目添加测试

是时候添加一些测试了。您将添加测试脚本,断言SaveArticleFetchAll方法按预期工作。

在项目的根目录下创建一个测试文件blog_test.go,并将它粘贴到:

// go-testing/blog_test.go

package main

import "testing"

func TestSaveArticle(t *testing.T) {

	blog := New()

	blog.SaveArticle(Article{"My title", "My Post Body"})

	if blog.Articles[0].Title != "My title" {
		t.Errorf("Item was not added")
	}
}

func TestFetchAllArticles(t *testing.T) {

	blog := New()

	blog.SaveArticle(Article{"My title", "My Post Body"})

	articles := blog.FetchAll()

	if len(articles) == 0 {
		t.Errorf("Fetch All fails")
	}
} 

在这段代码中,testing包(可以在Go的标准库中找到)被导入。然后编写两个函数分别测试SaveArticleFetchAll方法。

TestSaveArticle创建一个新的Blog实例并保存一个Article。然后检查标题,确保保存的文章包含在博客中。如果没有保存的文章,错误将导致测试失败。

TestFetchAllArticles创建一个新的Blog实例并保存一个Article。然后,它调用FetchAll来检索文章,并检查博客是否包含任何文章。如果没有文章,错误表明FetchAll未能返回新文章。

在本地运行测试

使用详细标志-v运行这些测试以获得更多信息。输入以下内容:

go test -v 

命令完成后,您将在 CLI 上看到一条消息。

Local Test - CLI

这个屏幕截图显示两个测试成功运行并通过了测试。添加-v标志显示关于每个测试如何运行的具体反馈,而不仅仅是通常的总结。

用 CircleCI 自动化测试

您的下一个任务是自动化测试过程,以便在代码被推送到远程存储库时测试能够运行。使用 CircleCI 设置测试自动化包括三个步骤:

  1. 添加 CircleCI 管道配置以自动化测试过程
  2. 创建一个远程存储库,并将项目代码推送到那里
  3. 将存储库作为项目添加到 CircleCI 上

首先创建构建管道配置文件来包含您的配置脚本。

在项目的根目录下,创建一个名为.circleci的文件夹和一个config.yml文件。在config.yml内部进入如下配置:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/go:1.15.10
    steps:
      - checkout
      - restore_cache:
          keys:
            - go-mod-v4-{{ checksum "go.sum" }}
      - run:
          name: Install Dependencies
          command: go get ./...
      - save_cache:
          key: go-mod-v4-{{ checksum "go.sum" }}
          paths:
            - "/go/pkg/mod"
      - run:
          name: Run tests
          command: go test -v 

在上面的脚本中,Go的 CircleCI Docker 映像作为测试环境被拉进来。然后将项目代码从项目的远程存储库中签出。在依赖项被安装和缓存之后,测试脚本(go test -v)运行。

提交对项目的更改,然后将项目推送到 GitHub

接下来,转到 CircleCI 仪表板上的 Add Projects 页面。

Add Project - CircleCI

点击设置项目开始。

Add Config - CircleCI

在设置项目页面上,点击fast以指示 CircleCI 您将在您的分支上使用配置文件。然后点击设置项目完成。构建应该会成功执行。

Build successful - CircleCI

您可以单击构建来查看部署的详细步骤。

结论

正如我们在本教程中所展示的,Go 提供了一个开箱即用的测试套件。使用 Go 可以通过优化收益和减少工作负载成本来更容易地实践测试驱动开发(TDD) 。这让您和您的团队有更多的时间为您的应用程序开发功能

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

针对超薄应用的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-for-svelte/

在撰写本文的时候,苗条的框架是最流行的 Javascript 框架之一。去年,根据 JS 2019 的状态,它在人气上超过了 Vue.js 。与其他框架不同,Svelte 不使用虚拟 DOM 在浏览器中进行 DOM 更新工作,而是在其构建步骤中编译 Javascript 代码,以便在状态发生变化时有效地更新 DOM。这种策略导致了它的迅速流行和采用。在本教程中,我们将创建一个自动化的持续集成(CI) 管道,自动运行为苗条应用编写的测试过程。

先决条件

要遵循本教程,需要做一些事情:

  1. Javascript 的基础知识
  2. 系统上安装的 Node.js
  3. 一个的账户
  4. GitHub 的一个账户

所有这些安装和设置,让我们开始教程。

搭建一个新的苗条项目

首先,我们需要创建我们的苗条项目。在系统的适当位置,运行以下命令:

npx degit sveltejs/template svelte-testing 

这个命令将立即开始搭建一个新的苗条项目。svelte-testing部分定义了项目文件夹的名称。您可以使用任何喜欢的名称。

一旦搭建过程完成,进入项目的根目录并安装所需的包:

cd svelte-testing
npm install 

安装完成后,运行以下命令启动服务器,为开发模式下的应用程序提供服务:

npm run dev 

这将在http://localhost:5000/ ( 5000是默认端口)为应用程序提供服务。当它在使用中时,将改为分配另一个端口)。

App first view

我们现在有了一个实用的苗条应用程序。

用 Jest 和 Svelte 测试库设置测试

为了编写和运行我们的苗条项目的测试,我们需要建立 Jest苗条测试库。为了实现这一点,我们需要在我们的项目中安装一些包。此设置需要以下库:

  • 用 Jest 作为我们的测试者
  • @babel/core@babel/preset-envbabel-jest:在我们的测试文件中启用 ES6 JavaScript 的使用
  • @testing-library/svelte:苗条测试库
  • 这个软件包可以帮助你在开玩笑之前编译这些苗条的组件
  • 在给笑话添加方便的断言时很有用

所有这些包都需要作为开发依赖项安装。使用以下命令一次性安装它们:

npm install --save-dev jest svelte-jester @testing-library/jest-dom @testing-library/svelte @babel/core @babel/preset-env babel-jest 

一旦所有这些都安装完毕,下一步就是向我们的项目添加配置,以指定这些包如何运行。

将下面的jest配置部分添加到您的package.json文件中。(参考我们的package.json文件这里

"jest": {
    "transform": {
        "^.+\\.svelte$": "svelte-jester",
        "^.+\\.js$": "babel-jest"
    },
    "moduleFileExtensions": [
        "js",
        "svelte"
    ],
    "setupFilesAfterEnv": [
        "@testing-library/jest-dom/extend-expect"
    ]
} 

接下来,在项目的根目录下,创建一个名为.babelrcBabel 配置文件,并将以下配置放入其中:

{
  "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
} 

最后,在package.json文件的scripts部分添加一个test脚本:

...

"scripts": {
    ...
    "test": "jest src"
} 

现在,您已经拥有了用 Jest 测试您的细长组件所需的一切。

向我们的苗条项目添加测试

要开始编写测试,首先创建一个简单的组件,稍后将对其进行测试。在src/components文件夹中,创建一个名为ButtonComp.svelte的新文件,并输入以下代码:

<script>
  export let name

  let buttonText = 'Button'

  function handleClick() {
    buttonText = 'Button Clicked'
  }
</script>

<h1>Hello {name}!</h1>

<button on:click="{handleClick}">{buttonText}</button> 

在上面的代码中,我们创建了一个组件,该组件显示一个包含字符串和动态name变量的头,该变量可以由父组件传递给该组件。我们还显示了一个按钮,通过用一个handleClick方法处理它的click事件,在点击时改变它的标签。

让我们在应用程序中使用这个组件,把它放在应用程序的主页中。打开src/App.svelte文件,用下面的代码片段替换全部内容:

 <script>
	export let name;

	import ButtonComp from "./components/ButtonComp.svelte";
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>

	<ButtonComp name="Svelte" />
</main>

<style>
	main {
		text-align: center;
		padding: 1em;
		max-width: 240px;
		margin: 0 auto;
	}

	h1 {
		color: #ff3e00;
		text-transform: uppercase;
		font-size: 4em;
		font-weight: 100;
	}

	@media (min-width: 640px) {
		main {
			max-width: none;
		}
	}
</style> 

在上面的代码中,我们导入了我们的ButtonComp组件,并在我们的主页中创建了它的一个实例,将Svelte值传递给了name属性。

让我们看看更新后的主页是什么样子。

App with component

我们的头显示Hello Svelte,因为我们将字符串Svelte传递给了name属性。点击按钮,观察标签从Button变为Button Clicked。我们将编写测试来断言我们的组件所展示的这些行为。

src文件夹中,创建一个新的__tests__文件夹(两边加双下划线)。这是 Jest 搜索测试套件/脚本的标准文件夹。

__tests__中,让我们通过创建一个ButtonCompTest.js文件并添加以下代码来编写我们的第一个测试套件:

import "@testing-library/jest-dom/extend-expect";

import { render, fireEvent } from "@testing-library/svelte";

import ButtonComp from "../components/ButtonComp";

test("'Hello Svelte' is rendered on the header", () => {
  const { getByText } = render(ButtonComp, { name: "Svelte" });

  expect(getByText("Hello Svelte!")).toBeInTheDocument();
});

test("Button text changes when button is clicked", async () => {
  const { getByText } = render(ButtonComp, { name: "Svelte" });
  const button = getByText("Button");

  await fireEvent.click(button);

  expect(button).toHaveTextContent("Button Clicked");
}); 

在上面的文件中,我们导入了ButtonComp组件并对其进行了测试。第一个测试通过传递字符串Svelte作为name属性的值,确认标签Hello Svelte!在我们的组件中找到。

第二个测试检查组件中按钮的行为。我们再次呈现它,并获取对其中按钮的引用。然后,我们触发按钮上的click事件,并检查其当前状态,以确认按钮的标签确实更改为预期的Button Clicked字符串。

让我们保存文件,并在终端上使用 test 命令来运行我们的测试:

npm run test 

Local test run

我们现在已经成功地运行了我们的测试,它们都通过了。

创建 CircleCI 项目

我们的下一个任务是在 CircleCI 建立我们的苗条项目。从将你的项目推送到 GitHub 开始。

然后转到 CircleCI 仪表板上的添加项目页面来添加项目。

Add Project - CircleCI

点击设置项目开始。

Add Config - CircleCI

接下来,点击手动添加。您会得到提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有设置配置文件。我们以后再做那件事。

自动化我们的测试

本教程的下一步是编写我们的 CI 管道脚本来自动化测试过程。

在项目的根目录下,创建一个名为.circle的新文件夹。在该文件夹中,创建一个config.yml文件,并将以下代码添加到其中:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test 

在上面的文件中,我们首先引入一个Node.js Docker 图像。然后我们更新npm以确保我们运行的是最新版本。接下来,我们安装依赖项并缓存它们。有了所有的依赖项,我们运行我们的测试。

将这些更改提交并推送到您的 GitHub 存储库中。这将触发管道动作并运行。这可以在 CircleCI 控制台的Pipelines页面上查看。

Build Success - CircleCI

让我们通过点击 build 查看测试结果来查看详细的过程。

Build Success - CircleCI

我们的测试运行完美!

让我们通过在__tests__文件夹中创建另一个测试套件AppTest.js,并将下面的代码放入(src/__tests__/AppTest.js)中,来持续集成更多的测试:

import "@testing-library/jest-dom/extend-expect";

import { render } from "@testing-library/svelte";

import App from "../App";

test("'Hello World' is rendered on the header", () => {
  const { getByText } = render(App, { name: "World" });

  expect(getByText("Hello World!")).toBeInTheDocument();
}); 

现在,提交您的更改,并再次将它们推送到 GitHub 存储库来运行管道。

Build Success - CircleCI

结论

完整的项目可以在 GitHub 上的这里看到。

在本教程中,我们构建了一个持续集成(CI)管道来自动测试我们的苗条应用程序。随着我们继续向项目中添加更多的特性和测试,只需要简单地推送到存储库就可以确保我们的测试运行。获得通过或失败的结果有助于指导开发,并防止我们将糟糕的代码提交给我们的 repo。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

使用 Slack 和 Twilio | CircleCI 为您的 CI 构建配置通知

原文:https://circleci.com/blog/continuous-integration-slack-twilio/

本教程涵盖:

  1. 设置示例 Node.js 应用程序
  2. 将 CircleCI 与 Slack 和 Twilio 集成
  3. CircleCI 版本的测试通知

circle ci notificationorbs的构建是为了在构建成功或失败时将消息传递到适当的通道。这有助于参与项目的每个人了解最新版本的最新状态。

在本教程中,您将探索和实现发送到 Slack 信道的通知,以及通过 SMS 发送的通知。为了完成这项任务,您将使用来自 CircleCI orb 注册表的 Slack 和 Twilio orbs。从头开始实现这个特性会很麻烦,但是使用 orbs,只需几行代码就可以完成。

先决条件

您需要这些物品来从本教程中获得最大收益:

创建 Nest.js 应用程序

第一步是构建一个返回简单消息的 Nest.js 应用程序。然后,您将编写一个测试来断言应用程序返回正确的消息。您将把代码推送到 GitHub,这将触发 CircleCI 上的构建。一旦构建成功,定制消息将作为 SMS 发送到首选电话号码和指定的 Slack 信道。如果构建失败,将重复相同的过程。

首先,运行以下命令:

nest new nest-slack-notifications 

转到新创建的项目并运行应用程序:

// change directory
cd nest-build-notifications

// run the application
npm run start:dev 

前往http://localhost:3000查看默认主页。

Homepage

这个过程返回一条Hello World!消息,这将适用于本教程中的用例。

在本地运行测试

默认情况下,Nest.js 应用程序带有一个内置的测试框架( Jest )来提供断言功能。此外,位于src/app.controller.spec.ts的测试脚本文件已被提供,以确认Hello World!已从应用程序返回。使用以下命令运行测试:

npm run test 

您将看到类似下面的输出。

> nest-slack-notifications@0.0.1 test
> jest

 PASS  src/app.controller.spec.ts
  AppController
    root
      ✓ should return "Hello World!" (6 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.183 s
Ran all test suites. 

测试通过,这意味着您可以继续学习教程。

Test passes

添加 CircleCI 配置以自动化测试

现在您可以添加配置文件来设置与 CircleCI 的持续集成。在应用程序的根目录创建一个名为.circleci的文件夹,并在其中创建一个名为config.yml的新文件。将此代码粘贴到新文件中(.circleci/config.yml):

version: 2.1
orbs:
  node: circleci/node@5.0.3
jobs:
  build-test-and-notify:
    executor:
      name: node/default
    steps:
      - checkout
      - run: sudo npm install -g npm@latest
      - run:
          name: Install dependencies
          command: npm install
      - run: npm test
workflows:
  build-and-notify:
    jobs:
      - build-test-and-notify 

在这个配置文件中,您已经从 CircleCI orbs 注册表中指定了要使用的 Node.js orb 版本,并安装了项目的所有依赖项。然后,您设置命令来运行应用程序的测试。

在 CircleCI 建立项目

将项目推给 GitHub 开始。

干得好!您已经成功地创建了项目,安装了它的所有依赖项,并设置了配置,以便在将新代码推送到 GitHub 时自动运行测试。现在您需要将项目连接到 CircleCI。为此,请使用您的帐户详细信息登录 CircleCI,并在 GitHub 上选择您的项目所在的组织。

在这种情况下,项目的名称是nest-slack-notifications。使用您在 GitHub 上定义的项目名称找到它,然后单击设置项目

Setup project

选择你的 config.yml 文件屏幕上,选择最快选项。键入main作为分支名称。CircleCI 会自动定位config.yml文件。点击设置项目启动工作流程。

这将使用项目中的配置立即触发管道。并且,您将获得一条成功构建的消息。

Pipeline success

成功!它像预期的那样工作。现在您可以开始处理指示构建过程状态的通知了。

添加松弛集成

在本节中,您将把 Slack 集成到 CircleCI 中,这样您就可以在构建过程之后轻松地设置通知。

为了实现这个集成,我在 Slack 上创建了一个名为CircleCI-Notify的工作区。

Slack workspace

添加 CircleCI 应用程序

准备好工作空间后,设置 Slack OAuth 令牌进行身份验证。使用浏览器登录你空闲的工作空间。接下来,在 Slack API 网站上访问你的应用。点击从头开始创建新应用。系统将提示您添加应用程序名称,并选择您刚刚登录的工作空间。请注意,如果您使用CircleCICircleCi-<modifier>来命名您的应用程序,这将非常有用。

Slack App

在下一页,您可以选择要添加到应用程序的功能。对于本教程,选择“Permissions”区域,在这里您将添加scopes,它将提供适当的权限。

Slack orb 只需要发布聊天消息和上传文件的能力。你可以用Oauth Scope提供这三个。点击添加 OAuth 范围选择范围。

Slack App Scopes

接下来,滚动浏览您的工作区的 OAuth 令牌,并点击安装到工作区

Generate OAuth Token

这将为您的工作区生成一个令牌。复制这个新生成的令牌并保存在手边,以便在本教程的后面使用。

Your OAuth Token

更新 CircleCI 配置

为了完成 Slack 集成,您需要更新 CircleCI 配置文件,以包含 Slack 的circle ci orb。打开.circleci/config.yml并更新其内容:

version: 2.1
orbs:
  node: circleci/node@5.0.3
  slack: circleci/slack@4.10.1
jobs:
  build-test-and-notify:
    executor:
      name: node/default
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: npm install
      - run: npm test
      - slack/notify:
          event: pass
          template: success_tagged_deploy_1
      - slack/notify:
          event: fail
          mentions: "@yemiwebby"
          template: basic_fail_1
workflows:
  build-and-notify:
    jobs:
      - build-test-and-notify 

您在这个文件中所做的更改,包括了 Slack 集成管道中的另一个 orb。一旦构建失败或成功,一个名为slack/notify的新命令会向 Slack 通道发送一条消息。

这些是您将定制消息发送到 Slack 工作区以跟踪 CircleCI 上的构建状态所需的所有更新。不过,在用最新的代码更新存储库之前,您需要与 Twilio 集成 SMS 通知。

与 Twilio 集成用于通知

在本节中,您将通过将 CircleCI 与 Twilio 集成,进一步构建通知。如前所述,CircleCI orbs registry 已经有一个 Twilio orb,用于为 CircleCI 作业发送自定义 SMS 通知。

要与 Twilio 集成,您需要:

  • TWILIO_FROM是您拥有的 Twilio 电话号码,格式为+和国家代码,例如+16175551212 (E.164 格式)
  • TWILIO_TO是一个参数,用于确定 SMS 消息的目的电话号码,其格式与TWILIO_FROM相同
  • TWILIO_ACCOUNT_SID您的 Twilio Account SID可以在您的 Twilio 帐户仪表板上找到吗
  • TWILIO_AUTH_TOKEN是你的 Twilio AUTH TOKEN
  • body是将显示的自定义消息,而不是默认的通知消息

在 CircleCI 上添加环境变量

从您的 Twilio 帐户仪表板收集上一节提到的所有凭证。回到 CircleCI 上的项目设置页面。点击侧面菜单上的环境变量。然后点击添加变量,输入你的 Twilio 凭证。

Environment Variables

完成后,返回到项目设置页面。

更新 CircleCI 配置文件

现在您需要用 CircleCI Twilio orb 细节和所需的命令更新 CircleCI 配置文件。打开.circleci/config.yml文件,用以下内容更新其内容:

version: 2.1
orbs:
  node: circleci/node@5.0.3
  slack: circleci/slack@4.10.1
  twilio: circleci/twilio@1.0.0
jobs:
  build-test-and-notify:
    executor:
      name: node/default
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: npm install
      - run: npm test
      - slack/notify:
          event: pass
          template: success_tagged_deploy_1
      - slack/notify:
          event: fail
          mentions: "@yemiwebby"
          template: basic_fail_1
      - twilio/sendsms:
          body: Successful message from Twilio
      - twilio/alert:
          body: Send error message
workflows:
  build-and-notify:
    jobs:
      - build-test-and-notify 

您通过包含 Twilio orb 和以下命令对此文件进行了更改:

  • twilio/sendsms是用于在构建成功后发送短信的命令
  • twilio/alert是一个命令,只有在构建过程中出现错误时才会触发该命令来发送 SMS

注意 : 在您的 Twilio 帐户的消息地理权限设置中启用您所在的地区,以接收此处记录的短信。此外,确保您的电话号码已经过验证,尤其是如果您使用的是试用账户。

成功构建的测试

提交所有代码更改,并将其推送到 GitHub。这将自动触发 CircleCI 控制台上的构建。一旦流程完成并成功构建,您将通过您为松弛工作区选择的渠道和 SMS 接收自定义消息。

测试失败的构建

要使 CircleCI 上的构建失败,以便您可以看到它工作,请打开src/app.service.ts文件并更新返回消息:

import { Injectable } from "@nestjs/common";

@Injectable()
export class AppService {
  getHello(): string {
    return "Hello World";
  }
} 

这将更改消息,从而导致测试失败。现在,添加所有新的更改,提交它们,并将其推送到 GitHub。这将再次触发构建,但这次会失败。你将会在 Slack 和 Twilio 上收到一条消息,和预期的一样。

以下是我设置的出现在CircleCI-Notify工作区的#general通道中的消息。

Slack custom message

这是 Twilio 发来的短信。

Twilio custom message

结论

您在本教程中学到的最好的一点是,实现这些通知功能是如此简单和无缝。这些通知可以很容易地集成到您现有的任何项目中。

此外,这个过程并不局限于 Node.js 应用程序。无论您使用的是哪种框架或编程语言,您都可以轻松地将在此获得的知识转移并用于其他项目。查看 CircleCI orbs 注册表以了解更多关于其他可用软件包的信息。

完整的源代码可以在 GitHub 的这里找到。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

Symfony 应用程序与 Behat | CircleCI 的持续集成

原文:https://circleci.com/blog/continuous-integration-symfony-behat/

Behat 是一个支持行为驱动开发的开源测试框架。专注于需求沟通,它以帮助工程师构建伟大的系统而闻名,而不是构建系统并测试它们的伟大。Symfony 仍然是顶级的 PHP 框架之一。它是不可知的,允许你使用任何测试框架。在本教程中,我们将为 Symfony 应用程序建立一个持续集成管道,并对 Behat 提供的功能进行测试。该应用程序将返回一个客户列表。为了简单起见,我们不与数据库交互。相反,我们将硬编码客户的详细信息。

按时间顺序,我们将:

  1. 创建新的 Symfony 应用程序
  2. 通过 Composer 安装 Behat,并在我们的应用程序中初始化它
  3. 创建 GitHub 存储库
  4. 用默认数据创建一个端点,并为其编写一个测试
  5. 在本地运行测试,然后配置 CircleCI 使其自动化

先决条件

为了成功实现本教程的目标,您需要以下内容:

安装 Symfony 应用程序

使用 Composer,通过运行以下命令创建新的 Symfony 应用程序:

composer create-project symfony/website-skeleton symfony-behat 

一旦安装过程完成,您将拥有一个新的应用程序,它的所有依赖项都安装在一个名为symfony-behat的文件夹中。

进入项目文件夹,安装所需的工具,以便通过运行以下命令对应用程序进行功能测试:

// move into project
cd symfony-behat

// install web server and Behat
composer require behat/behat symfony/web-server-bundle --dev ^4.4.2 

上面的最后一个命令将安装:

  • behat/behat:Behat 的最新版本
  • symfony/web-server-bundle:本地运行 Symfony 应用程序的网络服务器

初始化行为

安装 Behat 后,要做的第一件事是在我们的应用程序中初始化它。这是至关重要的。它附带了一个样板文件,我们可以在上面构建,它配置了测试套件,将告诉 Behat 在哪里找到以及如何测试我们的应用程序。使用以下命令初始化:

vendor/bin/behat --init 

您将看到以下输出:

+d features - place your *.feature files here
+d features/bootstrap - place your context classes here
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here 

Behat 创建了一个features目录来保存特性的测试脚本。它还会在features/bootstrap文件夹中创建一个FeatureContext类。

行为中的一些重要概念

如果您是 Behat 的新手,以下定义会很有帮助:

  • 特性(Feature):表示一个功能单元的文件,包括定义、场景和步骤,以便于测试特定的功能。
  • 场景:重新创建条件和用户行为模式的步骤集合。
  • :设置先决条件、触发事件或断言应用程序的简单语言模式。步骤负责站点的真实行为。
  • 关键字:一组特定的单词,用作步骤模式的开始,以提高可读性,并将步骤分组为前提条件、动作和断言,例如GivenWhenThen
  • Context :这个类提供了与应用程序交互的新方法。这主要意味着提供额外的步骤。

为客户端点创建特征文件

我们的应用程序的一个预期特性是,任何用户都应该能够以未经身份验证的用户身份访问customer端点,然后能够毫无问题地查看客户列表。这是特性的故事,在这一节中,我们将创建一个特性文件,详细说明我们希望customer端点如何工作。导航到features文件夹,并在其中创建一个名为customer.feature的文件。将以下内容粘贴到新文件中:

Feature: List of customers
  In order to retrieve the list of customers
  As a user
  I must visit the customers page

  Scenario: I want a list of customers
    Given I am an unauthenticated user
    When I request a list of customers from "http://localhost:8000/customer"
    Then The results should include a customer with ID "1" 

Behat 用来描述应用程序中特性集的预期行为的语言被称为小黄瓜。它是一种商业可读的领域特定语言,专门为行为描述而创建。从上面的文件中,我们描述了我们的应用程序所期望的特性之一,并创建了一个上下文来描述所提议的特性将为我们的系统带来的商业价值。然后我们使用Scenario关键字来定义该特性的可确定的业务情况。下面的步骤描述了实现该特性需要做的事情。

创建功能场景的步骤定义

既然我们已经恰当地概括了应用程序的特性,我们需要在FeatureContext中定义步骤定义。如果您现在使用以下命令执行 Behat:

vendor/bin/behat 

您将看到以下输出:

Feature: List of customers
  In order to retrieve the list of customers
  As a user
  I must visit the customers page

  Scenario: I want a list of customers
    Given I am an unauthenticated user
    When I request a list of customers from "http://localhost:8000/customer"
    Then The results should include a customer with ID "1"

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.02s (9.58Mb)

 >> default suite has undefined steps. Please choose the context to generate snippets:

  [0] None
  [1] FeatureContext
 > 

输出表明,Behat 通过我们定义的三个步骤识别了我们的场景。然而,FeatureContext类有一些缺失的方法,它们代表了在customer.feature文件中创建的每个步骤。Behat 提供了一条路线,通过称为步骤定义的实际方法轻松映射每个场景步骤。

您可以手动创建这些方法,也可以让 Behat 自动为您生成这些方法。对于本教程,我们选择后者。要继续,选择选项1

--- FeatureContext has missing steps. Define them with these snippets:

    /**
     * @Given I am an unauthenticated user
     */
    public function iAmAnUnauthenticatedUser()
    {
        throw new PendingException();
    }

    /**
     * @When I request a list of customers from :arg1
     */
    public function iRequestAListOfCustomersFrom($arg1)
    {
        throw new PendingException();
    }

    /**
     * @Then The results should include a customer with ID :arg1
     */
    public function theResultsShouldIncludeACustomerWithId($arg1)
    {
        throw new PendingException();
    } 

复制这些方法并用它们更新FeatureContext.php文件:

<?php

use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Behat\Behat\Tester\Exception\PendingException;
use Symfony\Component\HttpClient\HttpClient;

/**
 * Defines application features from the specific context.
 */
class FeatureContext implements Context
{
    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct()
    {
    }

    /**
     * @Given I am an unauthenticated user
     */
    public function iAmAnUnauthenticatedUser()
    {
        throw new PendingException();
    }

    /**
     * @When I request a list of customers from :arg1
     */
    public function iRequestAListOfCustomersFrom($arg1)
    {
        throw new PendingException();
    }

    /**
     * @Then The results should include a customer with ID :arg1
     */
    public function theResultsShouldIncludeACustomerWithId($arg1)
    {
        throw new PendingException();
    }
} 

这些只是从customer.feature文件中的每一步派生的方法的定义。正确定义了方法之后,我们仍然需要添加所需的代码来完成我们的场景。用以下代码替换features/bootstrap/FeatureContext.php的内容:

<?php

use Behat\Behat\Context\Context;
use Symfony\Component\HttpClient\HttpClient;

/**
 * Defines application features from the specific context.
 */
class FeatureContext implements Context
{
    protected $response;
    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct()
    {
    }

    /**
     * @Given I am an unauthenticated user
     */
    public function iAmAnUnauthenticatedUser()
    {
        $httpClient = HttpClient::create();
        $this->response = $httpClient->request("GET", "http://localhost:8000/customer");

        if ($this->response->getStatusCode() != 200) {
            throw new Exception("Not able to access");
        }

        return true;
    }

    /**
     * @When I request a list of customers from :arg1
     */
    public function iRequestAListOfCustomersFrom($arg1)
    {
        $httpClient = HttpClient::create();
        $this->response = $httpClient->request("GET", $arg1);

        $responseCode = $this->response->getStatusCode();

        if ($responseCode != 200) {
            throw new Exception("Expected a 200, but received " . $responseCode);
        }

        return true;
    }

    /**
     * @Then The results should include a customer with ID :arg1
     */
    public function theResultsShouldIncludeACustomerWithId($arg1)
    {
        $customers = json_decode($this->response->getContent());

        foreach($customers as $customer) {
            if ($customer->id == $arg1) {
                return true;
            }
        }

        throw new Exception('Expected to find customer with an ID of ' . $arg1 . ' , but didnt');
    }
} 

从在customer.feature文件中创建的场景开始,我们首先为第一步创建一个名为iAmAnUnauthenticatedUser()的方法。这将决定是否已经创建了customer端点,以及未经身份验证的用户是否可以访问它。

public function iAmAnUnauthenticatedUser()
{
    $httpClient = HttpClient::create();
    $this->response = $httpClient->request("GET", "http://localhost:8000/customer");

    if ($this->response->getStatusCode() != 200) {
        throw new Exception("Not able to access");
    }

    return true;
} 

接下来,我们创建了一个方法来断言我们可以从customer端点检索客户列表。

public function iRequestAListOfCustomersFrom($arg1)
{
    $httpClient = HttpClient::create();
    $this->response = $httpClient->request("GET", $arg1);

    $responseCode = $this->response->getStatusCode();

    if ($responseCode != 200) {
        throw new Exception("Expected a 200, but received " . $responseCode);
    }

    return true;
} 

最后,为了确保检索到的客户列表包含预期的记录,我们将编写另一个方法来检查带有特定id的条目。

public function theResultsShouldIncludeACustomerWithId($arg1)
{
    $customers = json_decode($this->response->getContent());

    foreach($customers as $customer) {
        if ($customer->id == $arg1) {
            return true;
        }
    }

    throw new Exception('Expected to find customer with an ID of ' . $arg1 . ' , but didnt');
} 

现在运行 Behat 肯定会失败。我们还没有创建customer端点来返回适当的记录。

创建客户控制器

通过运行以下命令为客户端点生成控制器:

php bin/console make:controller CustomerController 

用以下代码替换src/Controller/CustomerController.php文件的内容:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class CustomerController extends AbstractController
{
    /**
     * @Route("/customer", name="customer")
     */
    public function index()
    {
        $customers = [
            [
                'id' => 1,
                'name' => 'Olususi Oluyemi',
                'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation',
            ],
            [
                'id' => 2,
                'name' => 'Camila Terry',
                'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation',
            ],
            [
                'id' => 3,
                'name' => 'Joel Williamson',
                'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation',
            ],
            [
                'id' => 4,
                'name' => 'Deann Payne',
                'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation',
            ],
            [
                'id' => 5,
                'name' => 'Donald Perkins',
                'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation',
            ]
        ];

        $response = new Response();

        $response->headers->set('Content-Type', 'application/json');
        $response->headers->set('Access-Control-Allow-Origin', '*');

        $response->setContent(json_encode($customers));

        return $response;
    }
} 

这里,我们定义了一个路由/customer,创建了一个默认的客户列表,并以 JSON 格式返回它。

在本地运行功能测试

我们的特性测试要求我们调用一个特定的端点。为此,我们需要保持服务器运行。运行以下命令启动服务器:

php bin/console server:run 

完成后,在终端的另一个选项卡或窗口中,使用以下命令执行 Behat:

vendor/bin/behat 

您将看到以下输出。

Feature: List of customers
  In order to retrieve the list of customers
  As a user
  I must visit the customers page

  Scenario: I want a list of customers
    Given I am an unauthenticated user
    When I request a list of customers from "http://localhost:8000/customer"
    Then The results should include a customer with ID "1"

1 scenario (1 passed)
3 steps (3 passed)
0m0.10s (10.01Mb) 

我们的测试现在像预期的那样运行。是时候创建一个 GitHub 存储库,并将这个应用程序的代码库推送给它了。遵循这个指南,学习如何将项目推送到 GitHub

添加 CircleCI 配置

我们首先需要更新.env.test文件,因为我们的管道需要它。将内容替换为以下内容:

# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
APP_ENV=dev
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7 

要添加 CircleCI 配置,在应用程序的根目录下创建一个.circleci文件夹,并在其中添加一个名为config.yml的新文件。打开新创建的文件并粘贴以下代码:

version: 2
jobs:
  build:
    docker:
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run: sudo apt update
      - run: sudo docker-php-ext-install zip

      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "composer.json" }}
            - v1-dependencies-

      - run:
          name: "Create Environment file"
          command: mv .env.test .env

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor
      - run:
          name: Run web server
          command: php bin/console server:run
          background: true

      # run Behat test
      - run:
          name: "Run Behat test"
          command: vendor/bin/behat 

在上面的配置文件中,我们从 CircleCI 映像注册表中提取了circleci/php:7.4-node-browsers Docker 映像,并安装了测试环境所需的所有包。然后,我们继续为我们的项目安装所有的依赖项。

包括启动本地服务器并在后台运行它的命令。

- run:
    name: Run web server
    command: php bin/console server:run
    background: true 

最后一部分是为我们的特性测试执行 Behat 的命令。

# run Behat test
- run:
    name: "Run Behat test"
    command: vendor/bin/behat 

继续用新代码更新存储库。在下一节中,我们将在 CircleCI 上设置我们的项目。

将项目连接到 CircleCI

CircleCI 上登录您的帐户。在控制台中,找到在 GitHub 上创建的项目,点击设置项目

Set up project

您将被重定向到一个新页面。点击开始建造

Start building

将出现一个提示,让您选择是将配置文件添加到存储库的新分支,还是手动添加。点击手动添加继续。

Add config manually

点击开始建造

Added config

这将成功运行,没有故障。

Successful build

单击作业以查看构建的详细信息。

Successful build result

结论

在本教程中,我们遵循行为驱动开发(BDD)的基本原理,用 Behat 构建 Symfony 应用程序,并用 CircleCI 进行自动化测试。对于您的应用程序,您可以使用 Behat 做更多的事情。这里的信息足以让您开始为自己的应用程序构建合适的特性,同样的方法也可以用于任何其他 PHP 项目。


Oluyemi 是一个技术爱好者、编程狂和热爱新技术的网络开发迷。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

值得信赖的持续集成| CircleCI

原文:https://circleci.com/blog/continuous-integration-that-you-can-trust/

在 CircleCI,我们关心安全性——2018 年,我们成为第一个符合政府机构要求的严格安全和隐私标准的 CI/CD 工具,以获得 FedRAMP 授权。现在,CircleCI 通过了 SOC 2 Type II 认证,又增加了一项业界公认的安全认证。

什么是 SOC 2 Type II?

SOC 2 Type II 认证是美国注册会计师协会 (AICPA)的服务组织控制报告平台的组成部分。SOC 2 Type II 为我们提供了满足(并超越)行业标准的机会,并为我们的用户提供了获得行业认可的标准化报告,他们可以在我们的空间中跨服务进行比较。我们的认证意味着我们已经制定并遵循了降低风险所必需的程序和政策,并且我们的流程可以被要求和审核。

这对 CircleCI 用户意味着什么

CircleCI 现在可以提供截至 2020 年 10 月 22 日的 SOC 2 Type II 和 FedRAMP 附录 B 报告(FedRAMP 为 2019 年 10 月 14 日)。这些报告概述了我们在组织监督、供应商管理、内部公司治理和风险管理流程以及监管监督方面的政策。它们涵盖了从我们如何加密客户数据到我们如何培训员工的方方面面。用户对我们的信任非常重要,因此我们必须让他们了解我们的安全升级和事故报告流程。这让我们的用户有信心使用我们的产品,知道他们的数据受到保护。

在 CircleCI,保护您的数据对我们来说至关重要,这样您就可以专注于为您的用户打造最佳产品。凭借我们的 SOC 2 Type II 认证,我们很自豪地向您,我们的用户正式做出这一承诺。

如需索取 SOC 2 Type II 或 FedRAMP 附录 B 报告的副本,请联系我们此处

持续集成 Code Climate 的自动化代码评审| CircleCI

原文:https://circleci.com/blog/continuous-integration-with-code-climates-automated-code-review/

在本教程中,我们将学习如何将 CircleCI 与 Code Climate 的质量工具集成。将质量集成作为持续集成 (CI)管道的一部分有很多好处。这些优势包括能够:

  • 根据设定的参数监控代码质量和语法,以易于理解和透明的格式呈现问题
  • 以公开、透明的方式显示测试覆盖报告
  • 设置测试覆盖阈值,当超过阈值时,确定我们的管道中的问题是否被触发,如果是,暂停我们的部署过程
  • 设置 GitHub 状态检查,检查开放拉取请求(PRs)的覆盖范围和代码语法,保护我们的部署不会失败

先决条件

出于本教程的目的,在我们开始之前,您需要了解一些事情。这些是:

  1. 基本了解安装了 Node 的 Node.jsnpm
  2. 已安装 ESLint
  3. 一个 CircleCI 账户
  4. 一个 GitHub 账户
  5. 一个代码气候账户

我正在用 JavaScript 语言开发用作演示的应用程序,但可以随意使用任何其他代码环境和 CircleCI 支持的语言,如这里的和这里的所示。

在本地构建应用程序

这是我们的文件夹结构最终的样子。为了跟进,您可以从这里的派生或克隆整个项目。

.
├── .circleci
│   └── config.yml
├── src
│   └── index.js
├── test
│   └── string.js
├── .codeclimate.yml
├── .eslintrc.json
├── .gitignore
├── License
├── README.md
├── initialconfig.yml
├── package-lock.json
└── .package.json 

属国

我们唯一需要的全局安装是 Node.js,所以确保您已经安装了它。我运行的是 12.13.0 版本。如果您安装了 nvm ,您可以使用以下命令下载并切换到 12.13.0:

nvm install 12.13.0 

之后,让我们安装本地项目依赖项。运行以下命令来初始化我们的package.json文件:

npm init --yes 

通过运行以下命令安装我们所有的依赖项:

npm install chai mocha nyc eslint --save-dev 

我们将使用:

  • ESLint 作为我们的林挺工具
  • 作为我们的测试框架
  • 作为我们的断言库为摩卡
  • 伊斯坦布尔用于报道
 "devDependencies": {
    "chai": "^4.2.0",
    "eslint": "^6.6.0",
    "mocha": "^6.2.2",
    "nyc": "^14.1.1"
  } 

要设置林挺,请在您的终端中我们应用程序的根文件夹下运行以下命令:

eslint --init 

按照步骤创建您的.eslintrc.json文件。

项目代码

既然设置已经完成,让我们开始开发我们的应用程序。我们将通过写出额外的原型方法来扩展 JavaScript 的字符串功能。在我们的src/index.js文件中,添加:

String.prototype.isQuestion = function isQuestion() {
  /* Returns true if the last letter of the input string a question mark. 
   false otherwise
  */
  const questionable = new RegExp(/\?$/);
  return questionable.test(this);
};
String.prototype.hasVowels = function hasVowels() {
  // Returns true if a vowel exists in the input string. Returns false otherwise.
  const vowels = new RegExp('[aeiou]', 'i');
  return vowels.test(this);
};
String.prototype.toUpper = function toUpper() {
  /* Replace all lowercase letters in the input string with their uppercase
  * analogs by converting each letter's ASCII value to decimal then back to
  * ASCII
  */
  const upper = new RegExp('[a-z]', 'g');
  return this.replace(upper, function transform(letter) {
    return String.fromCharCode(letter.charCodeAt(0) - 32);
  });
};
String.prototype.toLower = function toLower() {
  const lower = new RegExp('[A-Z]', 'g');
  return this.replace(lower, function transform(letter) {
    return String.fromCharCode(letter.charCodeAt(0) + 32);
  });
}; 

接下来,让我们编写一些测试来确保我们的方法按预期工作。在我们的test/string.js文件中,添加以下几行:

const expect = require('chai').expect;
require('../src/index');
describe('String extension tests', function () {
  describe('isQuestion', function () {
    it('Should return true if given a question', function () {
      expect('To be or not to be, that is the question'.isQuestion()).to.equal(false);
      expect('Quis custodiet ipsos custodes?'.isQuestion()).to.equal(true);
    });
  });
  describe('hasVowels', () => {
    it('should return false if the string has no vowels', () => {
      expect('N VWLS'.hasVowels()).to.equal(false);
      expect('n vwls'.hasVowels()).to.equal(false);
      expect('@#$^&*%12345'.hasVowels()).to.equal(false);
      expect(' '.hasVowels()).to.equal(false);
    });
    it('should return true if the string has vowels', () => {
      expect('Has vowels'.hasVowels()).to.equal(true);
      expect('HAS VOWELS'.hasVowels()).to.equal(true);
      expect('H@s v0wels'.hasVowels()).to.equal(true);
      expect('@#$^&*% 12345 e'.hasVowels()).to.equal(true);
    });
    it('should return a boolean value', () => {
      expect(typeof ('1234'.hasVowels())).to.equal('boolean');
    });
  });
  describe('toUpper', () => {
    it('should return a string', () => {
      expect(typeof 'Lowercase'.toUpper()).to.equal('string');
    });
    it('should return the string passed in uppercase', () => {
      expect('lowercase'.toUpper()).to.equal('LOWERCASE');
      expect('LowerCase'.toUpper()).to.equal('LOWERCASE');
      expect('L0werCAs3& letters'.toUpper()).to.equal('L0WERCAS3& LETTERS');
      expect(''.toUpper()).to.equal('');
    });
  });
  describe('toLower', () => {
    it('should return a string', () => {
      expect(typeof 'Lowercase'.toLower()).to.equal('string');
    });
    it('should return the string passed in lowercase', () => {
      expect('LOWERCASE'.toLower()).to.equal('lowercase');
      expect('LowerCase'.toLower()).to.equal('lowercase');
      expect('L0werCAs3& letters'.toLower()).to.equal('l0wercas3& letters');
      expect(''.toLower()).to.equal('');
    });
  });
}); 

要运行我们的测试,将以下内容添加到我们的package.json文件的scripts部分:

 "scripts": {
    "test": "mocha"
  }, 

从现在开始,我们可以通过执行以下命令来运行我们的测试:

npm test 

如你所见,我们所有的测试现在都通过了。

新闻报道

现在,让我们通过伊斯坦布尔设置覆盖报告。这很简单。我们需要做的就是向我们的package.json文件添加另一个脚本命令:

"scripts": {
    "test": "mocha",
    "cover": "nyc --reporter=lcov --reporter=text mocha"
  }, 

现在,运行命令:

npm run cover 

将运行我们的测试并生成测试覆盖报告,该报告将显示在我们的终端上。

执行该命令后,您会注意到两个新文件夹,./.nyc_output./coverage被添加到我们的文件夹结构中。

我们的覆盖报告的 HTML 版本可以通过打开文件./.coverage/lcov-report/index.html找到。如果你不喜欢赌桌,不用担心😀,coverage reporter 的输出格式可以通过几种方式修改

与 CircleCI 的持续集成

在 CircleCI 建立

CircleCI 使用我们的应用程序根目录下的./.circleci文件夹中的config.yml文件作为蓝本来设置和运行任何构建。让我们用一些初始语句填充文件,以确保运行测试的构建将被正确配置。

version: 2
jobs: 
  build: 
    working_directory: ~/repo
    docker:
      - image: circleci/node:12.13.0
    steps:
      - checkout
      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      - run:
          name: Install dependencies
          command: npm install

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}
      - run:    
          name: Run tests
          command: npm test
      - run:
          name: Generate coverage report
          command: npm run cover
      - store_artifacts: # Save tests results as artifacts
          path: test-results.xml
          prefix: tests
      - store_artifacts: # Save coverage reports as artifacts
          path: coverage
          prefix: coverage 

我们在这里所做的就是指导 CircleCI 安装我们项目的依赖项,运行我们的测试,然后生成我们的覆盖报告。

推送至 GitHub

为了让 CircleCI 上的东西运行起来,我们将不得不把我们的工作提交给 GitHub。首先,让我们将项目初始化为一个 git 存储库,并提交我们到目前为止所做的所有工作。

在我们项目的根目录下,在您的终端中执行以下命令。

git init 

在我们的.gitignore文件中,让我们添加,

# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Dependency directories
node_modules/ 

然后,运行以下命令:

git add .
git commit -m "Add code, tests, coverage" 

在 GitHub 上为项目创建一个存储库,然后使用以下命令将我们的本地副本推送到 GitHub:

git remote add origin <add-repo-url>
git push -u origin master 

如果你是新手,我的朋友斯坦的这个教程应该会有所帮助。跳到 GitHub 推送部分,快速了解如何操作。

触发构建

如果你还没有注册 CircleCI 的话。

通过 GitHub 登录,或者如果您已经有帐户,请转到您的仪表板。从那里,导航到仪表板中的相关组织,然后导航到窗口左侧的 ADD PROJECTS 选项卡,如下所示。

点击建立项目,然后开始建立下一页的。这将使用我们之前推送到 GitHub 的config.yml文件触发我们项目的构建。让我们确认我们初出茅庐的构建通过。

的确如此。接下来,让我们看看如何利用 Code Climate 提供的独特功能来优化我们的构建管道,以实现持续集成。

将代码气候与 CircleCI 联系起来

点击这里获得一个帐户并通过 GitHub 登录。

一旦我们通过认证,我们将被重定向到我们的代码气候仪表板,如下所示。

幸运的是,Code Climate 对开源项目是免费的,所以只要你的项目是免费的,点击在开源选项下添加一个库。如果您希望整合一个私人回购,请使用另一个选项开始试用该产品。

继续将 GitHub 组织和存储库添加到 Code Climate 中。

设置测试覆盖率报告

我们已经在本地生成了测试的覆盖报告,所以我们现在需要做的是指示 CircleCI 将报告发送给 Code Climate。我们将通过我们的.circleci/config.yml文件做到这一点。进行以下修改:

# https://circleci.com/docs/collect-test-data/#mochajs
# https://github.com/codeclimate/test-reporter/issues/342
version: 2
jobs:
    build:
        docker:
            - image: circleci/node:12.13.0
        working_directory: ~/repo
        steps:
            - checkout
            # Update npm
            - run:
                name: update-npm
                command: 'sudo npm install -g npm@latest'
            # Download and cache dependencies
            - restore_cache:
                keys:
                    - v1-dependencies-{{ checksum "package-lock.json" }}
                    # fallback to using the latest cache if no exact match is found
                    - v1-dependencies-
            - run: 
                name: Install dependencies
                command: npm install
            - save_cache:
                paths:
                    - node_modules
                key: v1-dependencies-{{ checksum "package-lock.json" }}
            # - run: mkdir reports
            # Run mocha tests
            - run:
                name: Run tests
                command: npm test
                when: always
            # Run coverage
            - run:
                name: Run coverage
                command: npm run cover
                when: always
            # Run coverage report for Code Climate
            - run:
                name: Setup Code Climate test-reporter
                command: |
                    # download test reporter as a static binary
                    curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
                    chmod +x ./cc-test-reporter
                    ./cc-test-reporter before-build
                when: always
            - run:
                name: Send coverage report to Code Climate
                command:
                    # nyc report requires that nyc has already been run,
                    # which creates the .nyc_output folder containing necessary data
                    ./cc-test-reporter after-build -t lcov
                when: always
            # Upload results
            - store_artifacts: # upload test coverage as artifact
                path: ./coverage/lcov.info
                prefix: tests 

现在,在测试运行之后,我们指示 CircleCI 执行命令

npm run cover 

当前在我们的package.json文件中,该文件在/.nyc_output文件夹中生成一个到我们的覆盖报告的链接。然后,我们下载 Code Climate test reporter 包,并使用它通过 Code Climate API 发送我们的覆盖报告。

在我们将更改推送到 GitHub 之前,这将自动触发在 CircleCI 上运行测试,我们需要允许 CircleCI 将我们的覆盖报告发送给 Code Climate。我们通过在 CircleCI 中添加我们的代码 Climate Test Reporter 键作为环境变量来实现这一点。

我们会在这里找到钥匙。

我们需要将它添加到 CircleCI 上,作为一个名为CC_TEST_REPORTER_ID的环境变量。

我们现在要做的就是推进 GitHub,确保我们的构建通过,并关注我们的代码气候覆盖报告。这是我的代码气候破折号目前的样子。

设置过磅报告

您会很高兴地知道,我们还可以建立代码风格的代码氛围报告。我们可以通过我们自己的.eslintrc文件或者通过我们的 Code Climate dashboard 进行配置。

让我们看看这两种方法。

通过 Code Climate 仪表板进行报告

导航到回购设置,然后导航到仪表板上的可维护性选项卡,如下所示。

在这里,您可以根据需要打开或关闭不同的设置。

报告通过。eslintrc 文件

更详细的风格分析,让我们使用 Code Climate 的 ESLint 插件。我们使用 ESLint 主要是因为我们的代码是用 JavaScript 编写的,但是你可以随意使用任何适合你项目的插件。

如图所示,导航到仪表板上的插件选项卡。

选中 ESLint 框,然后点击页面底部的 Save 按钮,打开插件。为了配置 ESLint 插件,我们需要在我们的根项目文件夹中创建一个.codeclimate.yml文件。

version: "2"         # required to adjust maintainability checks
plugins: 
  eslint:
    enabled: true
    channel: "eslint-5"
    config:
      extensions:
      - .js 

我们正在使用ESLint v5.7.0,并指示代码气候只审查.js文件。默认情况下,它会这样做,但我已经明确地添加了这一点,以向您展示我们如何对其他文件扩展名也这样做。

您可以通过安装代码气候 CLI,然后运行以下命令来验证您的代码气候.yml文件:

codeclimate validate-config 

这是我的.eslintrc.json文件:

{
    "env": {
        "browser": true,
        "node": true,
        "commonjs": true,
        "es6": true,
        "mocha": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "eqeqeq": [
            0
        ],
        "indent": [
            2,
            2
        ],
        "linebreak-style": [
            2,
            "unix"
        ],
        "prefer-arrow-callback": [
            0
        ],
        "quotes": [
            2,
            "single"
        ],
        "semi": [
            2,
            "always"
        ]
    }
} 

我们通过使用0禁用规则或使用2启用规则来启用和禁用.eslintrc文件中的规则。我们需要这样做,以便代码环境将启用的规则视为问题。我们现在需要做的就是将我们的更改推送到 GitHub,以便在 CircleCI 上触发新的构建。

关于可以用来扩展 ESLint 配置的插件列表,请查看 Code Climate ESLint 文档。如果你想实验,也有很多其他插件

使用 GitHub 构建监控

我们可以利用 Code Climate GitHub 集成做更多的事情,使我们的工作流程更好,代码更有弹性。让我们把 Code Climate 的 PR 评审意见加入到 GitHub 中。Code Climate 将分析每一个公开的 PR,并对它发现的任何风格问题留下内联评论,以及它发现的问题的一般摘要,包含总覆盖率%和 PR 将引入的覆盖率变化。要启用这些功能,请导航到 Code Climate 仪表板上回购设置下的 GitHub 选项卡。接下来,启用 拉式请求备注 功能、汇总备注行内问题备注功能,如下图所示。

最后,在页面的 Connections 部分下,安装 GitHub webhook,它会在任何 PRs 打开时通知代码气候。

现在,我们来自 Code Climate 的公关分析被发布到我们的公开 GitHub PR 上。

让我们也加入公关状态检查。通过状态检查,我们可以确保在将任何打开的 PR 合并到所需分支之前满足某些标准。在这种情况下,我们希望:

  • 在我们允许合并之前通过我们的测试
  • 我们的测试覆盖率保持在 50%以上
  • 公关没有风格问题

幸运的是,我们可以通过 GitHub 和 Code Climate 的一个简单过程来执行所有这些规则。

测试必须通过检查

我们将展示如何保护主分支。然而,这可以在任何分支上实现。在您的 GitHub 存储库中,导航到设置水平选项卡下的分支垂直选项卡,如下所示。

编辑分支保护规则,在规则设置下,启用要求状态检查通过后再合并,然后启用 ci/circleci: Build Errorci/circleci: build checks 。最后,保存我们到目前为止所做的更改。

现在,每次打开新的 PR,GitHub 都会阻止我们合并到受保护的分支,直到我们的测试在 CircleCI 构建中通过。为了复制我们的测试如何在本地运行,在 CircleCI 上配置我们的环境以匹配我们的本地构建过程是非常重要的。

覆盖率阈值和样式检查

设置这些将确保所有开放的 pr 满足我们预定义的林挺规则和测试覆盖标准。导航到仪表板上回购设置选项卡下的 GitHub 选项卡。接下来,启用 拉动请求状态更新 选项,如图所示。

最后,我们遵循与上一节相同的过程,这一次也启用了所有的代码环境检查。

这是支票在公开的 PR 上的外观。

一旦他们通过,我们就可以自由合并我们的公关了。当 Code Climate 发现任何值得研究的问题时,我们可以通过 Code Climate 上的特定公关问题页面进行跟进。

为了设置我们的覆盖率阈值,在该阈值下代码环境将触发一个问题,我们导航到仪表板上 Repo Settings 选项卡下的 Test Coverage 选项卡。我们可以编辑我们的差异覆盖率阈值,并强制执行总覆盖率规则,该规则将在我们的总覆盖率下降时触发一个问题。

结论

在本指南中,我们看到了如何轻松地将 Code Climate 的质量工具与 CircleCI 和 GitHub 集成,利用它们来优化我们的 CI 渠道。使用这些工具,我们已经使我们的代码具有弹性,我们的构建过程透明,允许我们专注于编写更好的、准备好发布的代码。

如果你有兴趣看一看,可以在这里找到本指南的所有代码。如果你有什么建议,请留下你的简历。😀


Mabishi Wakio 是一名软件开发人员,不幸的是,他发现产品管理的诱惑太难以抗拒。当他们不听用户的话、不创建线框或不写代码时,可以发现他们在看书、举重或与拳击教练对打,很少获胜。

阅读 Mabishi Wakio 的更多帖子

持续集成对交付周期的影响| CircleCI

原文:https://circleci.com/blog/continuous-integrations-impact-on-lead-time/

毫无疑问,速度对 DevOps 很重要。但是,如果没有可靠和一致的质量,速度就没有什么意义。高速度固然很好,但是高信心也同样重要。这两种品质结合在一起,就形成了所谓的高性能 DevOps。

挑战在于知道如何衡量开发团队的表现,因为速度只是故事的一部分。一个高绩效的团队实际上是什么样子的,你如何知道你的团队与其他 DevOps 团队相比做得好不好?“快”是什么样子的?

在我们最近的报告数据驱动的 CI 案例:实践中 3000 万个工作流揭示了 DevOps 什么中,我们深入研究了揭示 DevOps 性能的数据和指标。在这篇博客文章和接下来的三篇文章中,我们将聚焦于被认为是描述 DevOps 性能的行业标准的四个关键指标。

我们还分享我们的运行数据,这些数据支持 DevOps 专业人员已经熟悉的关键指标,这些专业人员跟踪来源,如 2019 年 DevOPs 状态报告。感谢我们从 3000 万个 CircleCI 工作流中收集的信息,我们可以充满信心地说,这些数据支持将这些指标作为成功的基准——而持续集成(CI) 让团队能够自动化软件交付中耗时的手动步骤,从而发挥作用。

我们是 CI 的忠实粉丝,是采用 CI 方法的 DevOps 团队的啦啦队长,这不会让你感到惊讶。根据 Puppet 的 2016 年开发运维状况报告,高性能开发运维团队的部署频率比其他团队高 200 倍,故障恢复速度快 24 倍,交付周期短 2,555 倍。

CI 对交付周期的影响

在本帖中,我们将讨论前置时间:工作流从第一次触发到完成所需要的时间长度。通过“交付时间”,我们不一定意味着“部署到生产”;我们的意思是让你的工作流程达到预期的最终状态。提前期是根据任何特定工作流运行所需的时间来衡量的。

提前期是指你能多快收到信号。如果你想要一个短的交付时间,你需要尽可能最大化自动化。这就是 CI 派上用场的地方:如果您尽可能多地自动化管道,您可以将部署时间表从传统的几周和几个月缩短到几小时甚至几分钟。

为了了解观察到的开发行为如何与行业标准进行比较,我们查看了 2019 年 6 月 1 日至 8 月 30 日期间运行的超过 3000 万个工作流的 CircleCI 数据。工作流代表:

  • 每天运行 160 万个作业
  • 超过 40,000 个组织
  • 超过 150,000 个项目

以下是我们的发现:

  • 记录的最短提前时间是 2.1 秒
  • 最长准备时间为 3.3 天
  • 3000 万个工作流中的 80%在 10 分钟内完成
  • 交付周期的第 50 百分位是 3 分 27 秒
  • 第 95 百分位是 28 分钟

从我们的数据中有什么收获?

没有放之四海而皆准的答案:这取决于您的工作流程。在 2.1 秒内完成的工作流不会做太多事情:很可能它们是关于发送通知,或者打印和返回一条消息并返回 exit 0。我们注意到的 3.3 天的最大交付周期可能显示了大量的测试,如回归套件、集成套件,可能还有针对多个平台的交叉编译。

正如我们所说的,交付周期取决于你想要完成的目标。对于一些团队和工作流来说,28 分钟(第 95 百分位)对于等待信号来说太长了;在其他情况下,在 28 分钟内完成一个工作流程可能是一个巨大的成功。你的测试的复杂性,你正在构建的软件的类型,以及测试集成的深度,所有这些因素都会影响到交付时间。

如果你能加入任何能减少工作流程时间的优化,你就走在了正确的方向上。

有没有运行长的工作流?还是慢了?

正如我们一开始就注意到的,当速度与质量相匹配时,速度是很重要的。但是我们并不是说工作流运行三天以上的 DevOps 组织很慢。该组织可能是一个有数百个相互依赖的项目的组织,这一点没有反映在我们的数据中。

但是尽管交付时间可能因为许多原因而变化,但是与不使用 CI 的团队相比,这是交付时间的巨大减少。这就是团队使用持续集成来自动化构建和测试的原因,也是使用 CI 的团队能够比他们的同行减少四个数量级的交付时间的原因。

CI 采用的增量价值

我们不相信工作流交付时间的通用标准。团队不应该担心达到通用基准——他们应该专注于寻找减少工作流程长度的内部机会。

采用 CI 并不是一蹴而就的,但好消息是即使有一点点帮助(而且你不必做得完美)。这是一个很好的起点:简单地采用 CI 原则就能让您踏上提高绩效的道路。

在接下来关于我们的工作流数据的三篇博客文章中,我们将分享对更多关键 DevOps 指标的见解:部署频率平均恢复时间变更失败率

连续包发布,第二部分:使用 CircleCI 和 packagecloud - CircleCI 的自动 npm 发布

原文:https://circleci.com/blog/continuous-package-publishing-part-ii-automated-npm-publishing-with-circleci-and-packagecloud/

概观

在本帖中,我们将回顾构建基本软件交付管道的每个步骤。为了做到这一点,我们将提供一些例子来演示使用 CircleCI 作为持续集成服务器和 packagecloud 上的包存储库进行持续交付的自动化软件交付过程。

要了解更多关于软件包和包管理的基本概念,以及如何将它们与 CI/CD 结合起来构建软件交付管道,请阅读上一篇文章连续包发布,第一部分:CI/CD 中的包管理简介

构建软件交付管道

作为一个例子,我们将构建一个 NodeJS 包,它提供一个打印“Hello World”和相应测试的函数。我们将把代码签入到一个git存储库中,使用 CircleCI 作为一个持续集成服务器,并将包发布到 packagecloud.io 上的一个存储库中。

开始前

如果你想按照这篇文章中的例子来创建一个软件交付管道的例子,请阅读下面的内容:

  1. 用示例代码克隆 GitHub repo
  2. CircleCI 上创建或登录帐户。
  3. packagecloud 上创建或登录一个帐户,并按照说明创建一个存储库。示例中将使用 packagecloud 凭据。
  4. 在 CircleCI 中设置一个名为NPM_TOKEN的环境变量,其值为 packagecloud 用户 API 令牌。

首先,让我们用我们正在构建的包的元数据创建package.json文件。

 package.json
{
  "name": "hello-world",
  "version": "0.1.0",
  "description": "Print 'Hello world!'",
  "main": "hello.js",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^22.2.1"
  },
  "author": "Armando Canals",
  "license": "MIT"
} 

main字段指向 NodeJS 程序的主入口点。这种情况下,hello.js以及我们的软件包将包含的相应程序:

hello.js
function hi() {
  return "Hello world!";
}

module.exports = hi; 

如您所见,该程序定义了一个命名函数,该函数返回一个字符串,然后将该函数导出为 NodeJS 应用程序中的一个模块。

module.exports代码是需要这个包所必需的,这将在后面演示。让我们添加一个测试来涵盖这个功能。我们将使用 Jest 测试库(在package.jsondevDependencies中列出),因为它很简单,不需要配置。

hello.test.js
const hi = require('./hello.js');

describe('hi', () => {
  it('should return Hello world!', () => {
    expect(hi()).toBe("Hello world!");
  });
}); 

有了测试,我们就可以确保我们的代码准备好被打包分发了。

配置 CircleCI 以运行测试

在下一步中,我们将使用 CircleCI 2.0 配置在 CircleCI 中配置运行测试和分发 npm 包的工作流。

首先,在 CircleCI UI 中,找到并点击Add Projects按钮,从可用项目列表中选择一个项目,然后点击Start building按钮。

continuouspackage1.png

接下来,在项目根目录中,创建一个名为.circleci,的文件夹,并在该文件夹中创建一个名为config.yml的文件。

 .circleci/.config.yml
# Javascript Node CircleCI 2.0 configuration file
#
# Check {{ '/language-javascript' | docs_url }} for more details
#
version: 2

defaults: &defaults
  working_directory: ~/repo
  docker:
    - image: circleci/node:8.9.1

jobs:
  test:
    <<: *defaults  
    steps:
      - checkout

      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "package.json" }}
          - v1-dependencies-

      - run: npm install
      - run:
          name: Run tests
          command: npm test

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}

      - persist_to_workspace:
          root: ~/repo
          paths: .

workflows:
  version: 2
  only_test:
    jobs:
      - test 

该工作流配置将在每次提交时运行jobs下的test作业。

要查看关于此配置的更多细节,请查看使用 CircleCI 2.0 发布 npm 包的

此时,我们可以使用git签入我们的源代码。

$ git add .
$ git commit -m 'Update circle config'
$ git push origin feature_branch 

continuouspackage2.png

continuouspackage3.png

我们软件交付管道的测试部分已经完成!现在,每次提交源代码库时,都会运行测试套件,以确保最近的更改没有破坏任何东西。

使用 CircleCI 2.0 发布 NodeJS 应用程序

既然我们已经有了一个为 NodeJS 包运行测试的自动化方法,我们需要在发布版本准备好发布时将包部署到 npm 注册中心。

首先,让我们在 packagecloud 上创建一个 npm 注册表:

登录 packagecloud 后,点击“创建存储库”按钮创建一个 npm 注册表。下面的 CircleCI 配置中将使用该存储库的名称以及帐户的用户名。

接下来,我们来修改一下前面的。circleci/config.yml 文件,以在配置中的作业下包含部署作业:

 # Javascript Node CircleCI 2.0 configuration file
#
# Check {{ '/language-javascript' | docs_url }} for more details
#
version: 2

defaults: &defaults
  working_directory: ~/repo
  docker:
    - image: circleci/node:8.9.1

jobs:
  test:
    <<: *defaults  
    steps:
      - checkout

      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "package.json" }}
          - v1-dependencies-

      - run: npm install
      - run:
          name: Run tests
          command: npm test

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}

      - persist_to_workspace:
          root: ~/repo
          paths: .

  deploy:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/repo
      - run:
          name: Set registry URL
          command: npm set registry https://packagecloud.io/armando/node-test-package/npm/
      - run:
          name: Authenticate with registry
          command: echo "//packagecloud.io/armando/node-test-package/npm/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
      - run:
          name: Publish package
          command: npm publish

workflows:
  version: 2
  test-deploy:
    jobs:
      - test
      - deploy:
          requires:
            - test
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/ 

此工作流配置将运行我们的测试,并将我们的包部署到已配置的 npm 注册表。将 packagecloud 用户和 repo 替换为您在 packagecloud 上的用户/repo。

  • 每次提交分支时,test作业将运行npm test
  • 当包含v前缀的标记提交被推送到git(即v0.1.0)时,deploy作业将向已配置的 npm 注册表发布一个版本。

在我们继续之前,让我们从 packagecloud 获取配置中使用的NPM_TOKEN。所需的令牌是 packagecloud API 令牌,可以通过以下方式找到:

  1. 登录 packagecloud 并点击侧边栏上的“API Token”按钮。

  2. 或者,通过在运行npm login命令的机器上配置 packagecloud npm 注册表之后使用npm login,这可能是用于获取凭证的本地开发机器。在包云文档中阅读更多关于npm login的信息。

接下来,使用 packagecloud 中的令牌,让我们在 CircleCI 中设置一个名为NPM_TOKEN的环境变量。发布包时,此令牌将用于在 packagecloud npm 注册表中进行身份验证。

continuouspackage4.png

或者,如果您喜欢将您的敏感环境变量签入 git,但是加密,您可以遵循在 circleci/encrypted-files 中概述的过程。触发包发布

使用上面的 CircleCI 配置,当包含v前缀(即v0.1.0)的标记提交被推送到git库时,可以触发发布。

$ git tag v0.1.0
$ git push origin v0.1.0 

continuouspackage5.png

现在,我们在每次提交时都运行测试套件,并且在将标签推送到 git 时部署软件版本。

continuouspackage6.png

使用部署的 NodeJS 包

现在我们已经在一个存储库中有了我们的包,用户将需要安装包所在的存储库。让我们安装我们在 CircleCI 工作流中部署v0.1.0包的包存储库。

我们可以通过在主目录或项目根目录中创建一个.npmrc文件来实现这一点,这取决于您是需要npm在系统范围内使用这个存储库,还是特定于一个项目。

~/.npmrc
registry=https://packagecloud.io/armando/node-test-package/npm/ 

这个存储库包含我们之前创建的 hello-world 应用程序。因此,在其系统上将此配置为 npm 注册表的用户现在可以运行以下命令:

$ npm install hello-world 

这个命令将安装我们在上一节中创建并发布的 hello-world 包。

$ node
> hello = require('hello-world');
[Function: hi]
> hello()
'Hello world!' 

结论

手工软件交付过程的自动化可以显著减少软件开发周期时间。通过创建部署管道,团队可以以快速、可重复和可靠的方式发布软件。

packagecloud

连续包发布,第一部分:CI/CD - CircleCI 中的包管理介绍

原文:https://circleci.com/blog/continuous-package-publishing/

这是关于 CI/CD 中的包管理的两部分系列的第一部分,由 packagecloud 的联合创始人 Armando Canals 撰写。

概观

在本帖中,我们将解释一些关于软件包和包管理的基本概念,以及如何将它们与 CI/CD 结合起来构建软件交付管道。

我们将讨论软件包和存储库,持续交付,以及软件开发过程的构建、测试和发布阶段的自动化。

什么是软件包?

软件包是安装在服务器、计算机和个人设备上的可分发文件。

想到。Windows 上的 EXE 文件。Mac 上的 dmg 文件,或者。转速和。Linux/Debian 系统上的 deb 文件——这些都是不同平台使用的软件包。

这些软件包有一系列不同的格式,并且有许多不同的方式来描述它们的元数据。尽管如此,我们还是可以为大多数软件包定义一些通用的步骤:

* Write code
* Define metadata (name, version, dependencies, etc.)
* Archive code and metadata for distribution 

上面提到的最后一个步骤,归档代码,创建一个发布的软件包。这个档案,或者工件,在发布阶段被分发到存储库,我们将在后面的文章中回顾。

自动化软件包的构建、测试和发布

为了创建交付管道,我们希望尽可能多地自动化手动流程。在我们的项目中实现自动化发布将有助于减少软件开发过程中的摩擦和人为错误。

让我们回顾一下在创建软件交付管道时将要接触到的一些概念、过程和工具。

连续累计

持续集成是软件开发人员团队同意经常集成到共享代码库中的实践。通常,会使用一个版本控制系统,比如 git 或 Subversion,并在每次代码库集成时运行自动化单元测试。

Continuous_package1.png

我们将使用git版本控制系统和 CircleCI 作为我们的持续集成服务器。

我们希望每个人都能:

1\. run any build tasks necessary to run tests.
2\. run automated tests to ensure code quality. 

通过在每个集成中运行这些步骤,我们可以尽快检测到这些阶段中的任何错误并修复它们。

连续交货

持续交付是持续集成合乎逻辑的下一步。它描述了一种通过使软件部署成为简单的、可重复的任务来快速将软件从开发发布的方法。

构建软件包

实际的包构建过程因包类型而异。包的类型从操作系统级别的包到在浏览器中运行前端代码的包,每种包的构建方式都不同。

每种包类型都有一个工具或者一个特定的过程,来将包从源代码构建到一个归档中以供分发。例如,npm带有npm pack命令,该命令将代码库归档到一个 tarball 中,以便在 npm 注册表上使用。

对于我们的示例,我们希望处理必要的任务,以便从 CI 服务器内部构建包。这个过程将允许我们在发布阶段将包发布到存储库中。

自动化发布

为了触发生成新版本的构建,我们需要告诉 CI 服务器一个版本已经准备好了。为了做到这一点,我们使用git tag特性作为触发器,告诉 CI 服务器一个构建已经准备好发布了。

例如,下面的第一个命令将标记最近的提交,第二个命令将新创建的标记推送到远程 git 存储库,触发我们在 CI 中的构建:

git tag v0.1.0
git push origin v0.1.0 

将标签推送到远程 repo 会触发 CircleCI 配置的部署工作流,我们将在后面的章节中对此进行介绍。

Continuous_package2.png

如上所述,一个带版本号的被推送到远程存储库的带标签的提交将触发我们的持续集成系统中的一个构建。这个过程会将代码归档成一个包格式(Deb、RPM、JAR 等)。)然后将它分发到工件/包存储库中。

构建一次软件包

当在不同的环境中测试代码时(开发、试运行、生产),包应该是相同的。为 CI 系统中的每个环境构建包可能会引入难以发现的错误,并产生额外的工作,因此理想的做法是在测试过程的早期构建一次包,并将它们上传到存储库中,以便在测试的后期使用,并一直到生产。

分发软件包

一旦发布就绪,就需要以一种安全、可靠的方式提供给计算机。对我们来说,这意味着将软件包发布到一个包存储库中。

CircleCI 允许我们通过支持使用命令行工具和安全配置存储库凭证的能力来发布我们的包。

仓库

软件包资料库是元数据和包对象的混合体,与软件包管理器一起用于向计算机提供最新的软件。这些存储库是包及其元数据的仓库,可以是公共的,也可以是私有的。

公共软件库的一些例子是用于npm和 NodeJS 包npmjs.org的注册表,或者用于 Java/Maven 工件类型的 Maven Central 库。

对于我们的例子,我们将使用 packagecloud,它为许多不同的包类型托管私有和公共包存储库,并与不同的包管理器无缝协作。

Packagecloud 生成各种包管理器使用的存储库元数据,从操作系统级包到浏览器中使用的 JavaScript 包。

continuous_package3.png

包管理器

软件包管理器是与包含软件的软件包仓库进行交互的工具。开发人员使用它们来搜索、安装和管理存储库中的包。

例如,如果你使用基于 Debian 的操作系统,你可能熟悉高级打包工具- APT ,或者如果你使用基于 RedHat 的系统,你可能习惯于使用 yum/dnf 工具。

另一个例子是,如果你是一名 JavaScript 开发人员,使用npm开发 NodeJS 包,或者是一名 Python 程序员,使用pip开发 Python 编程语言——包管理器的列表非常广泛,从操作系统级别的包到浏览器中运行的 JavaScript 代码。

Continuous_package4.png

结论

将软件开发过程的活动部分集成到 CI/CD 系统中,可以显著减少开发软件时的周期时间和人为错误。通过理解软件管道的不同部分并自动化软件交付过程,团队可以更快更有效地工作。

我们的下一篇文章中,我们将使用 CircleCI 构建一个全功能的软件交付管道,以构建包、运行测试并将工作包上传到 packagecloud 上的存储库中。

移动应用的持续性能测试| CircleCI

原文:https://circleci.com/blog/continuous-performance-testing-for-mobile-apps/

截至 2021 年,应用商店中大约有 570 万个移动应用程序,其中 220 万个用于 iOS,348 万个用于 Android 用户。鉴于数量庞大,客户有各种各样的选择。

有如此多的应用可用,客户满意度是最重要的,这意味着避免客户流失和留住用户。有什么比看着你的客户转向你的竞争对手,因为他们有一个更强大、更一致的类似应用程序版本更令人沮丧的吗?

抓住用户注意力的最好方法是给他们一个直观的高性能的用户体验。性能测试只是您可以用来获得这一结果的有用策略之一。

移动应用性能测试可主动识别应用中的故障、漏洞和性能瓶颈,帮助您保持用户满意度。毕竟,移动应用程序用户如果对它的性能不满意,会毫不犹豫地删除你的应用程序。在将应用程序投入生产之前进行有效的性能测试对于避免这种情况至关重要。

在本文中,我们将探索移动应用持续性能测试的好处,重点介绍持续性能测试工具,并了解如何将性能测试添加到 CI 渠道中。

什么是性能测试?

性能测试是一个非功能性测试过程,用于衡量应用在不同工作负载下的表现。性能测试分析系统指标,如速度、响应性和稳定性。有了这些测试的结果,您就可以在发布软件之前识别并纠正与性能相关的瓶颈。这些测试检查各种指标,如响应时间、并发用户、加载时间、CPU 寿命和瓶颈,以预测用户在使用您的应用程序时可能会遇到的问题。

如果没有性能测试,用户可能会遇到可用性问题或体验不一致的服务。性能停机不仅会导致收入损失,还会损害您的市场声誉。

在性能测试过程中,您可以进行几个子类别的测试。这五种主要类型是:

  • 峰值测试—峰值测试确定您的系统如何应对突发流量。峰值测试是通过快速增加用户生成的音量并测量软件对此的响应来完成的。
  • 负载测试—负载测试在真实负载条件下评估应用程序。负载测试的主要目的是观察应用程序如何应对变化的额外负载以及软件可以承受的最大负载(用户数量)。
  • 容量测试——容量测试的主要目的是监控软件是否能够处理数据库中的大量数据。容量测试有助于您识别系统中的突破点。
  • 压力测试——压力测试评估应用程序在超出正常使用模式时的运行情况。这是通过用许多并发事务或用户加载软件并测试其稳定性来实现的。
  • 浸泡测试—也称为耐久性测试,浸泡测试测试您的软件和应用程序在长时间暴露于高流量时的性能。它揭示了通常在几天或几周后发生的可靠性问题。

这些测试中的每一个都使您能够更好地理解应用程序不同组件的可执行性和可靠性。然而,运行移动性能测试是相当苛刻的。这是因为测试人员必须考虑各种各样的可用设备,每个设备都有多个 UI 选项和不同级别的网络连接。

总之,这些因素使得移动测试变得复杂。因此,为了帮助执行这些测试,开发人员经常求助于以下工具来对移动应用进行性能测试:

  • load ninja——load ninja 是一个基于云的测试工具,运行负载和性能测试。这个工具让用户使用真正的浏览器,而不是负载模拟器。其易于使用的界面提供了实时分析选项,并将脚本创建时间减少了 60%。
  • Apache JMeter——Apache JMeter 是一个基于 Java 的开源测试工具,用于测量应用程序的性能。其高度可扩展的核心使开发人员能够测试各种用例。除此之外,它还兼容多种网络协议,如 HTTP、HTTPS、POP3、SOAP、LDAP 和 FTP 服务。
  • WebLOAD — WebLOAD 是一款流行的企业级移动应用测试工具,它将所有性能监控元素整合到一个流程中,以简化应用测试。使用 WebLOAD,测试人员可以检查可伸缩性和性能,并执行验证测试。

性能测试移动应用的优势

如果你希望你的应用程序长期成功,市场饱和和用户期望使得高性能是不可协商的。一个移动应用程序只有在稳定可靠的情况下才会获得五星评级。通过测量应用程序软件可用性的重要属性,如速度、准确性和稳定性,性能测试可以确保只有高质量的应用程序才能投入生产。通过优先考虑用户体验,您可以帮助您的团队为客户提供符合性能预期的市场就绪型应用。

例如,当开发人员运行负载测试时,他们试图了解应用程序在高负载下的行为。该测试让他们了解了应用程序的流量负载和最大用户数。类似地,当测试人员进行性能回归测试时,它让他们深入了解应用程序的局限性,并提供其速度和准确性的现实视图。

简而言之,性能测试在应用上线之前就暴露了它的缺点,使你能够主动工作,并确保没有任何问题或错误会影响你的应用的整体性能和声誉。

CI 管道中的连续性能测试

传统的瀑布式测试方法需要数周时间来交付结果。它根本不适合应对当前快速测试和发布的挑战,而移动应用需要新版本、更新和更多集成。

持续集成/持续开发(CI/CD) 自动化测试构建的集成和部署,并确保用户尽快获得应用的最新测试版本。此外,通过将性能测试整合到 CI 管道中,可以精确地知道每个变化对你的应用性能的影响。

您可以在 CI 管道中实施性能测试,以测试功能性和非功能性测试指标,如速度、可伸缩性或响应性,从而确保负载模式是真实的,并与现实环境相协调。

在持续的性能测试中,测试被分解成更小的反馈循环,这样开发人员可以更快地收到测试结果,并且可以更快地检测和修复错误。以下是将持续性能测试集成到 CI 渠道中时要遵循的一般步骤:

  1. 选择要测试的关键绩效指标(KPI)。根据您的目标,您的 KPI 可能是错误率、并发性、吞吐量、崩溃、加载时间等等。
  2. 组织数据并编写性能测试。
  3. 选择合适的测试平台环境。理想情况下,您应该选择基于云的 CI/CD 平台,以便在需要时扩展测试。
  4. 在 CI 管道中执行性能测试。我们建议使用 CLI 进行测试;速度更快,效率更高。
  5. 为了获得更快的反馈并优化您的管道,您还可以运行测试套件的并行执行。
  6. 当您提交代码时,CI 测试床环境将运行该代码,并检查新代码是否导致了任何回归。
  7. 当您的系统通过测试后,它将被部署到生产环境中。如果失败,结果将被发回给开发人员进行反馈和审查。借助 CI 工具强大的分析功能,工程师可以可视化问题并进行根本原因分析。
  8. 最后,在执行完成后,销毁测试环境。这可以防止新测试运行时出现假阳性。

使用上面的步骤,您可以持续测试您的移动应用程序的关键质量,以确保它在发布给用户时是高性能和可靠的。当性能测试作为 CI 管道的一部分来实现时,它会变得更加有效。

此外,CI 管道中的持续性能测试可缩短应用的上市时间,使您的团队能够更快地解决问题,帮助修复 bug,防止中断,让您在各种情况下执行多个测试,并帮助您比较多个测试的性能结果。

结论

得知你的应用已经崩溃的最糟糕的时候是当它已经在生产的时候。糟糕的应用程序性能会导致客户流失,并影响应用程序的长期未来。为了跟上竞争并获得(和保留)用户,尝试使用性能测试,这有助于确保您的应用程序满足性能预期。

对于您的团队来说,性能测试可能感觉像是一个额外的步骤,可能会使他们放慢速度。然而,通过使持续的性能测试成为 CI 管道中的一个步骤,您可以使您的测试过程更加简化和高效。简化的连续测试过程可以减少重复性的手工任务,将开发人员解放出来从事其他工作。自动化测试和应用程序代码在 CI 管道中的移动确保您的团队不会错过任何测试。

CircleCI 支持高性能、可靠的移动应用程序开发,并使向 CI 渠道添加性能测试变得容易。为了持续提供快速可靠的移动应用体验,立即注册

CircleCI 和休斯顿- CircleCI 连续发布

原文:https://circleci.com/blog/continuous-release-with-circleci-and-houston/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


将 CircleCI、GKE 和休斯顿结合起来,简单、安全、快速地将代码提供给客户。

本文由 Turbine Labs,Inc .首席执行官 Mark McBride 撰写

概观

工程团队的存在不是为了手动执行发布过程或紧张地盯着生产指标;他们的存在是为了制造并向客户交付产品。我们开发发布过程以确保我们构建的产品是好的,理解花费在这种保证上的时间是有代价的:花费在考虑发布上的时间就是花费在不考虑产品上的时间。在涡轮实验室,我们花了很多时间思考如何改善这种平衡,这也是我们建造休斯顿的原因。在这里,我们将向您展示如何使用 CircleCI、GKE 和休斯顿的组合为 web 应用程序创建高质量、低开销的连续发布管道。

发布过程的目标是快速安全地将新代码提供给客户。在理想情况下,这一过程是即时的,没有风险。在现实世界中,每一次发布都伴随着一些风险,所有的过程都需要时间。这是一种微妙的平衡。花太多时间来增加你对一个版本的信心,你的执行速度会慢得像爬行一样。过分热衷于发布,产品质量会受到影响,这也会使执行陷入停顿。

几十年前,代码/构建/验证/部署/发布周期被验证。这个过程中的大多数步骤都是由人来完成的,而且这个过程很慢。持续集成系统自动化单元测试和封装的兴起。最近,云和容器编排基础设施使得自动将代码推送到硬件变得前所未有的容易。但是正如我们已经看到的,如果没有单独的发布流程,持续部署会增加客户面临的巨大风险。

传统发布流程中的压力和谨慎源于几个因素:

  1. 发布新代码的成本
  2. 恢复失败版本的成本
  3. 难以确定一个版本的质量
  4. 发布缺陷的广泛范围

正确的工具有助于建立一个工作流程来解决这些问题。执行成本低、恢复成本低、检查简单且影响有限的版本极大地加速了工程团队。

我们将通过一个具体的例子,使用 CircleCI 进行持续集成, GKE 进行持续部署,以及休斯顿进行高可信度发布。这个例子中的所有代码都可以在 https://github.com/turbinelabs/circle-ci-integration/的获得。您可以派生这个 repo,并按照自述文件中的说明自己完成示例。

The release pipeline

释放管道

在我们的工作流中,开发人员推送由 CircleCI 构建、测试和打包的分支,circle ci 会自动将它们作为部署推送到 GKE。工程师可以在向广大受众发布之前测试这些部署,在生产中验证代码,而不会向客户公开。发布工程师推送标签,这些标签也由 CircleCI 打包,然后推送到 GKE。这些部署可以增量地发布给客户,并与现有代码进行行为比较。如果新部署看起来不好,可以立即关闭。从 git 推送到代码出现在生产环境中的总时间大约是 2 分钟。

GKE 构型

这个例子基于我们的 Kubernetes 集成指南。我们在 GKE 部署了一个简单的服务,使用休斯顿来管理入口流量。这种设置允许我们将部署与发布分开,支持增量蓝绿色部署、生产中的验证以及跨不同代码版本的应用行为的轻松比较。就其本身而言,这解决了我们的两个挑战:休斯顿以客户为中心的方法来衡量应用健康意味着它可以直接确定新代码是否表现良好,休斯顿的发布控制意味着恢复发布就像扳动开关一样简单

要求

我们希望它易于部署和发布代码,但我们不一定要将每个提交都推送到 git。在本例中,我们使用以下约定:

  • 每次提交都有自动化测试运行。

  • 与/server-dev-命名方案匹配的分支。*/被推到 GKE,每个分支都有自己的部署。当对分支进行修改时,相应的部署也会更新。这使得开发人员可以在不污染 GKE 环境的情况下迭代正在进行的分支。

  • 与/server-prod-命名方案匹配的标记。*/被推送到 GKE,每个标签都有自己的部署。

CircleCI 2.0 提供了工作流,可以让您为不同的 git 活动编写不同的执行步骤。在我们的 config.yml 中,我们定义了三个工作流:

  • “构建”工作流在每次提交时执行,并且只运行自动化测试。

  • “dev_deploy”工作流是在对匹配 regex /server-dev-的分支进行更新时触发的。*/.它运行自动化测试,构建 docker 映像,将其推送到 GCR,然后创建一个标有“开发”阶段的 kubernetes 部署。

  • “prod_deploy”工作流在表单/server-prod-的标签推送时触发。*/.它像 dev_deploy 一样测试、打包和推送映像到 GCR。但是,在创建部署之前,它还包括一个批准步骤,并将其部署标记为“生产”阶段。

Workflows in action

运行中的工作流

连续累计

我们的示例项目是一个简单的节点应用程序,因此集成只是执行一个自动化测试套件。CircleCI 2.0 引入了使用定制容器映像的能力,通过允许用户从预先配置的映像开始,极大地缩短了构建/测试时间,从而节省了每次构建时下载和安装所需软件的成本。我们从正式的 node:8.4.0 映像开始,运行 npm install 和 npm test,而不是从普通的 Linux 安装开始。

# note: the docker image versions here are almost certainly out of date.
# See https://github.com/turbinelabs/circle-ci-integration/blob/master/.circleci/config.yml
jobs:
  build:
    docker:
      - image: node:8.4.0
        environment:
          DEBIAN_FRONTEND: noninteractive
          steps:
            - checkout
            - run: cd server && npm install && npm test 

连续交货

持续交付更加复杂,因为我们需要配置 CircleCI 来与 GKE 一起工作。我们使用项目环境变量来将秘密保存在我们的存储库中,设置以下值

  1. GCLOUD_CLUSTER_NAME —可在 GCLOUD 容器集群列表中找到
  2. GCLOUD_COMPUTE_ZONE —也可以在 GCLOUD 容器集群列表中找到
  3. GCLOUD_PROJECT ID —此处描述了
  4. 谷歌地球

为了避免每次构建都从一个新的 Ubuntu 映像开始,这需要每次构建都下载、安装和配置 GCloudSDK 和 Docker,我们将它打包到一个映像中,这个映像已经预装了软件,通过环境变量进行了配置。这在每次构建过程中节省了大量时间。有了这些,我们的推送开发服务器作业运行 docker build,标记构建的映像,并将其推送到 GCR。

# note: the docker image versions here are almost certainly out of date.
# See https://github.com/turbinelabs/circle-ci-integration/blob/master/.circleci/config.yml
  push-dev-server:
    docker:
      - image: turbinelabs/gcloud-build:0.12.0
    environment:
      DEBIAN_FRONTEND: noninteractive
    steps:
      - checkout
      - setup_remote_docker
      - run: openrc boot
      - run: docker build -t gcr.io/${GCLOUD_PROJECT_ID}/all-in-one-server:$CIRCLE_BRANCH server
      - run: docker tag gcr.io/${GCLOUD_PROJECT_ID}/all-in-one-server:$CIRCLE_BRANCH gcr.io/${GCLOUD_PROJECT_ID}/all-in-one-server:la
      - run: gcloud docker -- push gcr.io/${GCLOUD_PROJECT_ID}/all-in-one-server:$CIRCLE_BRANCH 

持续部署

既然我们的代码已经打包并发送到 GCR,我们就可以在 GKE 创建部署了。我们已经为开发和生产部署构建了 yaml 模板,替换了分支、标记、git sha、stage(开发或生产)和部署版本的字符串。一个简单的 shell 脚本使用 CircleCI 环境提供的信息将模板转换成有效的 kubernetes 规范。我们使用相同的 gcloud-build-base 映像来调用 kubectl,我们的部署在 GKE 创建或更新。

note: the docker image versions here are almost certainly out of date. # See https://github.com/turbinelabs/circle-ci-integration/blob/master/.circleci/config.yml deploy-dev-server:

docker:

  • image: turbinelabs/gcloud-build:0.12.0
    steps:
  • checkout
  • run: openrc boot
  • run: ./deploy.sh dev server/dev-deploy-template.yaml

请注意,这将部署代码,而不是发布代码

在生产中验证

现在我们的工作流已经就绪,我们可以创建一个开发分支,进行更改,推动它,并在大约 2 分钟内看到生产中的新部署。部署完成后,我们可以使用休斯顿来预览新的变更,并在它们发布给客户之前进行验证。为了简化这个过程,我们的前端开发人员创建了一个 chrome 插件(源代码也可以从 github 上的获得)让你选择一个服务版本来查看。

我们首先将休斯顿配置为寻找名为“Tbn-All-in-one-server-Version”的 cookie,如果这样的 cookie 存在,则将流量路由到版本标签与 cookie 值匹配的服务实例。有了它,我们的插件可以向休斯顿 API 请求一个已部署服务版本的列表,并设置一个 cookie,将浏览器会话固定到该版本的代码。这让您可以与其他团队成员共享推送的代码,而没有干扰客户的风险。如果你需要改变,只需在你的分支上做出改变,然后推动它。CI 管道构建、打包并推送新代码。

Select a deployed, but unreleased version of code to verify

选择已部署但未发布的代码版本进行验证

生产版本略有不同。当您想要创建一个可发布的版本时,创建一个标签并将其推送到您的 git 源。

git tag server-prod-v1.1
git push origin server-prod-v1.1 

这将触发 CircleCI 中的 prod_deploy 工作流。这遵循与 dev_deploy 工作流相同的步骤,但是在 GKE 中创建部署之前包括一个额外的批准步骤。这不是必需的,但是如果您希望在创建生产实例之前有一个轻量级的批准步骤,CircleCI 提供了一个很好的解决方案。

创建部署后,您可以使用 Houston 向客户逐步发布新版本。从一个小的百分比开始,以最小化一个缺陷漏过的影响。然后,比较新旧版本的延迟和成功率。如果出现任何问题,只需关闭新版本,客户就会回到以前的版本。

Incremental blue green releases made easy

轻松实现蓝色/绿色增量发布

包裹

有了这条管道,我们可以轻松安全地构建、测试、部署、验证和发布软件。创建部署就像推送分支或标记一样简单。这些部署可以发布给单独的开发人员,或者逐渐地发布给用户群的一部分,极大地减少了缺陷的影响。当事故发生时,恢复到以前的状态就像按动电灯开关一样简单。所有这一切意味着工程团队花更少的时间强调做出改变,而更多的时间专注于构建产品。

如何持续部署 Chrome 扩展- CircleCI

原文:https://circleci.com/blog/continuously-deploy-a-chrome-extension/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


谷歌浏览器是互联网上最常用的浏览器。人们正在为各种用例创建 Chrome 扩展。在 Twitter 宣布他们的 280 个字符的推文测试后的 24 小时内,新的 Chrome 扩展诞生了,将推文压缩回 140 个字符。在波多黎各遭受飓风袭击后的几天内,一个名为捐赠给波多黎各的 0.99 美元的推广活动被创建,展示了该岛的美丽图片,所有收入都将捐给慈善机构,帮助该岛重建。

在这种快速发展的环境中,我们可以通过持续部署来自动交付特性、错误修复和安全补丁。谷歌的 Chrome 开发者文档有很多信息,但没有包括任何关于持续集成的内容,也没有提供自动化部署的例子。我们将在这篇文章中讨论一个例子,以及我们如何应对开发环境的挑战和版本控制。

入门指南

注意: 这篇博文中的例子和截图是基于我为 CircleCI 编写的 Chrome 扩展,名为“无意义”。你可以在谷歌 Chrome 商店GitHub 找到它。如果你在某些命令或截图中看到“无意义”这个名字,它指的是 Chrome 扩展的名字。在适当的时候应该用你自己的来代替。

首先,我们需要创建并配置一个谷歌 API 项目,并收集一些关键信息。在您的系统上安装curljq将使完成这些步骤更加容易。

  1. 创建一个新的 Google API 项目。这里有一个直接链接Step 1 Screenshot

  2. 确保刚刚创建的项目是屏幕顶部下拉列表中当前选择的项目。Step 2 Screenshot

  3. 在 API 搜索栏中搜索“Chrome Web Store API”并选择该 API。Step 3 Screenshot

  4. 启用 API。Step 4 Screenshot

  5. 单击“创建凭据”蓝色按钮。Step 5 Screenshot

  6. 不要创建 API 键(默认)。相反,请单击“客户端 ID”链接。Step 6 Screenshot

  7. 对于“应用程序类型”,选择“其他”,并在出现的文本字段中,将此新客户端命名为。请随意使用“CircleCI”。Step 7 Screenshot

  8. 将提供“客户 ID”和“客户密码”。保存该信息。他们很快就会被需要。

  9. 在浏览器中访问以下 URL,用上一步中的“客户端 ID”替换$CLIENT_ID

    https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.googleapis.com/auth/chromewebstore&client_id=$CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob 
    
  10. 接受权限弹出窗口后,你会看到谷歌简单称之为“代码”的东西。保存该信息

在这一点上,我们应该有 3 个关键位的信息。一个client_IDclient_secret和一个code。接下来需要的是所谓的“刷新令牌”,我们可以通过在终端中使用curljq来获得它。在终端中输入以下命令,用实际值替换$CLIENT_ID$CLIENT_SECRET$CODE:

curl "https://accounts.google.com/o/oauth2/token" -d "client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&code=$CODE&grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq '.refresh_token' 

将返回一个“刷新令牌”。保存该信息

需要的最后一项实际上是最容易得到的,Chrome 扩展的应用 ID。访问 Chrome 商店中的 Chrome 扩展。看看浏览器中的 URL,路径的最后一部分(最后一个正斜杠后的文本)是应用程序 ID。保存该信息

Chrome Store Screenshot

基本设置

让我们从一个基本的例子开始:通过主分支自动部署一个 Chrome 扩展。完整、基本的 CircleCI 配置文件示例可在附录 A 中找到。

启动配置文件

我们将从 CircleCI 配置文件开始,如下所示:

version: 2
jobs:
  build:
    docker:
      - image: ubuntu:16.04
    environment:
      - APP_ID: <INSERT-APP-ID> 

我们使用 CircleCI 2.0 和 Ubuntu 16.04 Docker 镜像作为我们的执行环境。我们在“入门”一节中获得的“应用程序 ID”将在这里设置,以便我们可以在构建过程中使用它。确保用您的实际应用 ID 替换<INSERT-APP-ID>

拯救我们的秘密

我们的构建需要几个环境变量来工作。是这些变量之一,我们在配置中设置它,因为它不被认为是秘密的。Chrome 扩展的应用 ID 是众所周知的。我们还需要设置三个,但出于安全原因,我们将对这些内容保密。确保通过 CircleCI UI 创建并设置私有环境变量CLIENT_ID``CLIENT_SECRETREFRESH_TOKEN

我们配置的主体

 steps:
      - checkout
      - run:
          name: "Install Dependencies"
          command: |
            apt-get update
            apt-get -y install curl jq
            # You can also install Yarn, NPM, or anything else you need to use to build and test your extension. 

像 CircleCI 上的大多数作业一样,我们首先签出代码,然后安装依赖项。这里,我们将前面使用的相同工具curljq安装到我们的容器中,以便我们可以在发布过程中使用它们。测试所需的任何工具也应该安装在这里。

 - run:
          name: "Run Tests"
          command: echo "Run any tests here." 

这是您运行测试的地方。因为这篇博文是关于部署的,测试策略会受到你的具体项目的影响,并且是一个大话题,所以我们不打算在这篇博文中涉及它。如果有足够的兴趣,我们可能会在将来单独发布一篇文章来测试 Chrome 扩展和 Firefox 插件。

 - run:
          name: "Package Extension"
          command: git archive -o pointless.zip HEAD 

Google Chrome Store API 需要将扩展打包成. ZIP 文件才能上传。这里的git子命令archive用于从项目中创建 zip 文件。这里使用git archive而不是zip来创建 zip 文件是有益的,因为前者会忽略.git目录,我们不希望它出现在我们的包中,因为它只是膨胀。少安装一个命令。

出版

 - run:
          name: "Upload & Publish Extension to the Google Chrome Store"
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
              curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T pointless.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
              curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
            fi 

Chrome Store API 使用“访问令牌”进行认证操作。然而,访问令牌仅在 40 分钟内有效。幸运的是,我们有一个“刷新令牌”,用来从 Chrome 商店请求一个新的访问令牌。有了这个访问令牌,我们就可以上传 zip 文件,最后将上传的文件作为我们的新版本发布。

整个事情都包装在 Bash if 块中,这样我们只有在通过主分支构建时才发布扩展的新版本。

这是一个使用持续部署发布 Google Chrome 扩展的基本例子。同样,完整的示例配置文件可以在附录 A 中找到。如果你想看一些更先进的技术,请继续阅读。

高级设置

保持环境隔离

在一个扩展的开发过程中,它很可能会在 Chrome“未打包”的情况下进行测试。这会导致一个恼人的问题。当扩展的开发版本与“生产”版本一起加载时,扩展会重复出现。相同的浏览器动作图标、版本号等将会出现。我们可以解决这个问题。

在扩展的manifest.json文件中,即 git 中提交的文件,可以设置图标的开发版本。这可以是一个不同颜色的图标,或者只是某种变化,以表明它不是扩展的产品发布。版本也可以这样做。开发版本可以是通用的,在发布之前可以加入生产版本号。这里有一个利用这种方法的manifest.json文件的片段和一个 CircleCI“步骤”,展示了如何在构建期间使扩展“生产就绪”。

manifest.json 代码片段

{
	"manifest_version": 2,
	"name": "Pointless - a CircleCI Chrome Extension",
	"short_name": "Pointless",
	"version": "9.9.9.9",
	"version_name": "dev-version",

	"browser_action":{
		"default_icon": "development-icon.png"
	}
} 

CircleCI 配置示例步骤

 - run:
          name: "Make Extension Production Ready"
          command: |
            jq '.version = "new-version"' manifest.json | sponge manifest.json
            jq '.browser_action.default_icon = "icon.png"' manifest.json | sponge manifest.json 

您可以通过附录 B 中的高级设置技术查看完整的 CircleCI 配置示例。

使用 Git 标记部署

不必在每次成功构建 master 时都部署新版本的 Chrome 扩展,可以在推送新的 Git 标签时完成。为了实现这一点,CircleCI 配置需要针对工作流进行调整。单个“作业”可以分成“构建”和“发布”作业,以及用于指示我们想要使用的 Git 标签类型的工作流过滤器。以下是该工作流的具体线条:

workflows:
  version: 2
  main:
    jobs:
      - build:
          filters:
            tags:
              only: /.*/
      - publish:
          requires:
            - build
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /.*/ 

在本例中,扩展 ZIP 将在build作业中构建,并在publish作业中发布。CircleCI 的工作区用于在工作流程中移动 ZIP 文件。您可以在附录 B 中查看完整的 CircleCI 配置示例和高级设置技术。

自定义 Docker 图像

基本设置示例ubuntu:16.04中使用的 Docker 图像是一个相当大的图像。使用更小、更有针对性的映像意味着更快的构建。一个预先做好的 Docker 镜像,基于 Docker Alpine,可以通过使用cibuilds/chrome-extension用于 Chrome 扩展。更多信息,请查看 Docker Hub 页面GitHub repo

附录

附录 A

完整的 CircleCI 配置文件(.circleci/config.yml)演示了将 Chrome 扩展部署到 Google Chrome 商店所需的最小设置。

version: 2
jobs:
  build:
    docker:
      - image: ubuntu:16.04
    environment:
      - APP_ID: <INSERT-APP-ID>
    steps:
      - checkout
      - run:
          name: "Install Dependencies"
          command: |
            apt-get update
            apt-get -y install curl jq
            # You can also install Yarn, NPM, or anything else you need to use to build and test your extension.
      - run:
          name: "Run Tests"
          command: echo "Run any tests here."
      - run:
          name: "Package Extension"
          command: git archive -o pointless.zip HEAD
      - run:
          name: "Upload & Publish Extension to the Google Chrome Store"
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
              curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T pointless.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
              curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
            fi 

附录 B

workflows:
  version: 2
  main:
    jobs:
      - build:
          filters:
            tags:
              only: /.*/
      - publish:
          requires:
            - build
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /.*/

version: 2
jobs:
  build:
    docker:
      - image: cibuilds/chrome-extension:latest
    steps:
      - checkout
      - run:
          name: "Install Dependencies"
          command: echo "You can also install Yarn, NPM, or anything else you need to use to build and test your extension."
      - run:
          name: "Make Extension Production Ready"
          command: |
            jq '.version = "new-version"' manifest.json | sponge manifest.json
            jq '.browser_action.default_icon = "icon.png"' manifest.json | sponge manifest.json
      - run:
          name: "Run Tests"
          command: echo "Run any tests here."
      - run:
          name: "Package Extension"
          command: git archive -o pointless.zip HEAD
      - persist_to_workspace:
          root: /root/project
          paths:
            - pointless.zip

  publish:
    docker:
      - image: cibuilds/chrome-extension:latest
    environment:
      - APP_ID: <INSERT-APP-ID>
    steps:
      - attach_workspace:
          at: /root/workspace
      - run:
          name: "Publish to the Google Chrome Store"
          command: publish /root/workspace/pointless.zip 

使用 CircleCI - CircleCI 将 Python 包持续部署到 PyPI

原文:https://circleci.com/blog/continuously-deploying-python-packages-to-pypi-with-circleci/

来自出版商的说明:您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。尝试在我们的文档博客中搜索最新信息。


Python 包索引通常被称为 PyPI,是 Python 编程语言的软件仓库。每次运行pip install $PACKAGE时,你都在使用 PyPI。在这篇文章中,您将学习如何使用 git 标签和 CircleCI 将您自己的 Python 包持续部署到 PyPI。

在过去的几周里,我一直在为 CircleCI API 开发一个 Python 包装器。这个项目使用了我们将要在这里讨论的同样的方法。

属国

这种方法在标准 Python 库之外唯一需要的依赖项是 twine 包。

假设

本教程假设以下情况属实:

  1. 你在 PyPI 上有账户。如果没有,很容易上手。可以在这里注册。
  2. 您的项目设置中有一个名为PYPI_PASSWORD的环境变量,它引用您的 PyPI 密码。
  3. 您有一个遵循标准打包指南的 Python 包。如果没有,Python 提供了一些关于如何打包和分发项目的优秀文档。
  4. 您正在使用 git 标签来创建一个发布。
  5. 您正在使用带有工作流的 CircleCI 2.0。

发布工作流

下面描述了高级发布工作流。

  1. 一旦您准备好剪切项目的新版本,您就可以在setup.py中更新版本,并使用git tag $VERSION创建一个新的 git 标签。
  2. 一旦用git push --tags将标签推送到 GitHub,就会触发一个新的 CircleCI 构建。
  3. 您运行一个验证步骤来确保 git 标记与您在上面的步骤 1 中添加的 my project 的版本相匹配。
  4. CircleCI 执行你所有的测试(你有测试对不对?).
  5. 一旦所有测试都通过,您就可以创建一个新的 Python 包,并使用 twine 将其上传到 PyPI。

完整演练

有了所有这些假设,并在头脑中有了一个高层次的概述,我们就可以开始研究真实世界的例子了。

setup.py

setup.py 文件是任何 Python 包中最重要的部分。它为 PyPI 提供元数据,处理所有打包任务,甚至允许您添加定制的打包相关命令。我们的示例项目中的文件如下所示:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:copyright: (c) 2017 by Lev Lazinskiy
:license: MIT, see LICENSE for more details.
"""
import os
import sys

from setuptools import setup
from setuptools.command.install import install

# circleci.py version
VERSION = "1.1.1"

def readme():
    """print long description"""
    with open('README.rst') as f:
        return f.read()

class VerifyVersionCommand(install):
    """Custom command to verify that the git tag matches our version"""
    description = 'verify that the git tag matches our version'

    def run(self):
        tag = os.getenv('CIRCLE_TAG')

        if tag != VERSION:
            info = "Git tag: {0} does not match the version of this app: {1}".format(
                tag, VERSION
            )
            sys.exit(info)

setup(
    name="circleci",
    version=VERSION,
    description="Python wrapper for the CircleCI API",
    long_description=readme(),
    url="https://github.com/levlaz/circleci.py",
    author="Lev Lazinskiy",
    author_email="lev@levlaz.org",
    license="MIT",
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: Developers",
        "Intended Audience :: System Administrators",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
        "Topic :: Software Development :: Build Tools",
        "Topic :: Software Development :: Libraries :: Python Modules",
        "Topic :: Internet",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.6",
        "Programming Language :: Python :: 3 :: Only",
    ],
    keywords='circleci ci cd api sdk',
    packages=['circleci'],
    install_requires=[
        'requests==2.18.4',
    ],
    python_requires='>=3',
    cmdclass={
        'verify': VerifyVersionCommand,
    }
) 

有几点需要注意:

  1. VERSION被设置为文件顶部的常数。这应该总是与您最新的 git 标签相匹配。
  2. setup()函数相当标准,提供了 PyPI 需要的所有必要的元数据。
  3. 我们有一个名为verify的定制命令,它将确保我们的 git 标签与我们的VERSION相匹配。我们将此命令作为工作流程的一部分来运行,以确保我们发布的是正确的版本。

这个项目的 CircleCI 配置文件中有趣的部分如下所示。你可以在这里看到完整的文件。

部署作业

一旦我们安装了所有的项目依赖项,为打包做好准备,我们就运行定制的verify命令(在上一节中讨论过)来确保 git 标签与我们即将发布的版本相匹配。

 - run:
          name: verify git tag vs. version
          command: |
            python3 -m venv venv
            . venv/bin/activate
            python setup.py verify 

接下来,我们使用在项目设置中设置的PYPI_PASSWORD环境变量创建一个.pypirc文件。

 - run:
          name: init .pypirc
          command: |
            echo -e "[pypi]" >> ~/.pypirc
            echo -e "username = levlaz" >> ~/.pypirc
            echo -e "password = $PYPI_PASSWORD" >> ~/.pypirc 

然后我们创建所有的包。

 - run:
          name: create packages
          command: |
            make package 

为了方便起见,这个项目使用了一个 Makefile 文件。如果您不想使用 Makefile,您可以在本节中手动运行命令。

创建包的命令有:

# create a source distribution
python setup.py sdist

# create a wheel
python setup.py bdist_wheel 

最后,我们使用 twine 将刚刚创建的包上传到 PyPI。

 - run:
          name: upload to pypi
          command: |
            . venv/bin/activate
            twine upload dist/* 

我们配置的部署作业只有在构建是 git 标记时才被触发,并且依赖于我们测试作业的通过。此类设置的配置如下所示:

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build:
          filters:
            tags:
              only: /.*/
      - deploy:
          requires:
            - build
          filters:
            tags:
              only: /[0-9]+(\.[0-9]+)*/
            branches:
              ignore: /.*/ 

摘要

就是这样!现在,每当你为你的项目推一个新的 git 标签时,你将自动创建一个新的包,并把它上传到 PyPI。这允许您拥有一个完全独立的、可重复的连续部署管道。最重要的是,通过使用测试、持续集成和持续部署,您能够确保您的包的用户在运行pip install $YOUR_PACKAGE时只获得最高质量的包。

使用 Jest 和 Enzyme | CircleCI 实现 React 应用的持续集成

原文:https://circleci.com/blog/continuously-testing-react-applications-with-jest-and-enzyme/

本教程涵盖:

  1. 克隆并设置一个 React 和 Redux 示例应用程序
  2. 为应用程序编写测试
  3. 为自动化测试配置持续集成

React 仍然是许多 UI 开发人员的首选 web 框架,在2021 Stack Overflow Developer Survey中超过 jQuery 成为最受欢迎的框架。它为构建数据驱动的用户界面提供了一个直观的模型,并在数据发生变化时有效地更新 DOM。React 与 Redux 很好地结合在一起,管理 React 呈现接口所需的数据。Redux 提供了一种可预测的方式来构建和更新这些前端应用程序中的数据。

在本教程中,我将引导您设置一个 React 和 Redux 应用程序示例,并为其编写测试。我将向您展示如何用 CircleCI 配置持续集成来自动化测试,并确保您添加的任何新代码都不会破坏现有的功能。

先决条件

要按照教程进行操作,需要做一些事情:

  1. 安装在您机器上的节点v 12 . 22 . 12(lts/铒)
  2. 基本熟悉反应还原
  3. 一个 CircleCI 账户
  4. GitHub 账户

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

入门指南

该项目建立在 Redux 异步示例应用的基础上。使用这个应用程序将向您展示如何将测试添加到一个真实的应用程序中,这在您构建自己的应用程序时会有所帮助。

在添加测试之前,您需要了解应用程序的功能。最好的方法是克隆应用程序并在本地运行。要克隆应用程序并安装其依赖项,请运行:

git clone --depth 1 --branch template https://github.com/CIRCLECI-GWP/react-jest-enzyme.git
npm install
npm start 

Local NPM start - terminal

跟踪 Git 和 GitHub 的进展

当你在你的应用上工作时,你需要用 Git 来跟踪变化。删除.git目录,然后运行git init

从克隆的存储库的根目录,运行:

rm -rf .git
git init 

这将创建一个新的 git 存储库。参考创建回购了解如何在 GitHub 上推送和跟踪您的变更。

CI-Jest-Enzyme

示例应用程序功能的演练

该示例应用程序旨在通过从 Reddit API 获取数据来显示选定子编辑中的当前标题。用户可以选择他们想要查看标题的子编辑。标题被加载并显示在屏幕上。用户也可以通过点击刷新按钮来更新当前所选子编辑显示的数据。

这个简单的应用程序是一个很好的例子,因为它拥有大多数真实应用程序所需的所有组件,包括:

  • 从 API 获取数据
  • 用户交互
  • 同步和异步操作
  • 表示和容器组件

到目前为止,你应该已经了解了这个示例应用程序的功能,并且你应该在自己的 GitHub 帐户上有一个副本。下一步是添加测试。

测试反应组件

如果您检查package.json文件,您会注意到您已经配置了一个test命令。

 "test": "react-scripts test" 

react-scriptsjest一起安装和配置,因此您不需要再次安装它。不过,您确实需要安装enzyme,以及适用于您的 React 版本的适配器:

npm install --save-dev enzyme enzyme-adapter-react-16 

接下来,您需要配置enzyme来使用适配器。react-scripts支持在src/setupTests.js文件中配置测试工具。

创建该文件并输入:

import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";

Enzyme.configure({ adapter: new Adapter() }); 

我推荐使用快照测试来跟踪组件的变化。在这种方法中,您可以拍摄组件的快照,当组件的渲染输出发生变化时,您可以轻松地检测到所做的更改。快照也是可读的,因此这是验证组件是否呈现预期输出的一种更简单的方法。

要使用快照技术,您需要安装enzyme-to-json包,以便在测试期间将 React 组件转换成快照:

npm install --save-dev enzyme-to-json 

您还需要配置jest来使用这个包作为快照序列化程序。通过添加以下内容在package.json中进行配置:

"jest": {
    "snapshotSerializers": [
      "enzyme-to-json/serializer"
    ]
  } 

您现在可以开始测试了。

组件测试

首先为App组件编写测试。一个好的起点是添加一个快照测试,以确保组件在给定所需属性的情况下呈现预期的输出。

首先需要导入 App 组件,但是App.js中唯一的导出是组件的 Redux -connected 版本:export default connect(mapStateToProps)(App)。您想要测试组件的呈现,而不是它与redux的交互,所以您还需要导出底层的App组件。通过将这个片段添加到App.js中来实现:

export { App }; 

概括地说,src/containers/App.js文件应该包含以下内容:

import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
  selectSubreddit,
  fetchPostsIfNeeded,
  invalidateSubreddit,
} from "../actions";
import Picker from "../components/Picker";
import Posts from "../components/Posts";

class App extends Component {
  static propTypes = {
    selectedSubreddit: PropTypes.string.isRequired,
    posts: PropTypes.array.isRequired,
    isFetching: PropTypes.bool.isRequired,
    lastUpdated: PropTypes.number,
    dispatch: PropTypes.func.isRequired,
  };
  componentDidMount() {
    const { dispatch, selectedSubreddit } = this.props;
    dispatch(fetchPostsIfNeeded(selectedSubreddit));
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.selectedSubreddit !== this.props.selectedSubreddit) {
      const { dispatch, selectedSubreddit } = nextProps;
      dispatch(fetchPostsIfNeeded(selectedSubreddit));
    }
  }
  handleChange = (nextSubreddit) => {
    this.props.dispatch(selectSubreddit(nextSubreddit));
  };
  handleRefreshClick = (e) => {
    e.preventDefault();
    const { dispatch, selectedSubreddit } = this.props;
    dispatch(invalidateSubreddit(selectedSubreddit));
    dispatch(fetchPostsIfNeeded(selectedSubreddit));
  };
  render() {
    const { selectedSubreddit, posts, isFetching, lastUpdated } = this.props;
    const isEmpty = posts.length === 0;
    return (
      <div>
        <Picker
          value={selectedSubreddit}
          onChange={this.handleChange}
          options={["reactjs", "frontend"]}
        />
        <p>
          {lastUpdated && (
            <span>
              Last updated at {new Date(lastUpdated).toLocaleTimeString()}.{" "}
            </span>
          )}
          {!isFetching && (
            <button onClick={this.handleRefreshClick}>Refresh</button>
          )}
        </p>
        {isEmpty ? (
          isFetching ? (
            <h2>Loading...</h2>
          ) : (
            <h2>Empty.</h2>
          )
        ) : (
          <div style={{ opacity: isFetching ? 0.5 : 1 }}>
            <Posts posts={posts} />
          </div>
        )}
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  const { selectedSubreddit, postsBySubreddit } = state;
  const {
    isFetching,
    lastUpdated,
    items: posts,
  } = postsBySubreddit[selectedSubreddit] || {
    isFetching: true,
    items: [],
  };
  return {
    selectedSubreddit,
    posts,
    isFetching,
    lastUpdated,
  };
};

export default connect(mapStateToProps)(App);

export { App }; 

按照惯例,jest会在任何名为tests的文件夹下找到名称以.test.js结尾的测试文件。这意味着您需要创建容器文件夹,然后创建tests目录,并在其下创建一个 App .test.js文件。

App现在被导出,所以您现在可以通过添加到您的测试文件来导入它:

import { App } from '../App' 

因为您正在独立于redux测试App组件,所以由redux提供的任何东西(例如,组件的props)都需要被明确地提供。您可以添加一些渲染测试来看看这在实践中是如何工作的。在App.test.js里面,添加你的第一个测试:

import React from "react";
import { shallow } from "enzyme";
import toJson from "enzyme-to-json";
import { App } from "../App";

describe("App", () => {
  it("renders without crashing given the required props", () => {
    const props = {
      isFetching: false,
      dispatch: jest.fn(),
      selectedSubreddit: "reactjs",
      posts: [],
    };
    const wrapper = shallow(<App {...props} />);
    expect(toJson(wrapper)).toMatchSnapshot();
  });
}); 

在这个测试中,您想要验证在给定所有必需的props的情况下App是否呈现。通过提供道具,你可以模拟redux在实际应用中会做什么。jest提供了一个模拟函数,您可以在测试中使用它来代替真实函数。对于本教程,您可以用它来模拟dispatch函数。在您的测试中,这个函数将被调用来代替实际的dispatch函数。

您可以使用npm test命令运行测试。jest测试运行程序启动,运行测试,并打印出测试运行总结。

Test run summary - Terminal

如果你打开src/containers/__tests__/__snapshots__/App.test.js.snap,你应该会找到组件的快照版本,显示组件的渲染输出。

继续添加几个测试来检查App的渲染行为。首先,添加一个测试来确保selectedSubreddit属性总是被传递给Picker组件。将此测试添加到现有测试的正下方:

// Add this import
import Picker from "../../components/Picker";

it("sets the selectedSubreddit prop as the `value` prop on the Picker component", () => {
  const props = {
    isFetching: false,
    dispatch: jest.fn(),
    selectedSubreddit: "reactjs",
    posts: [],
  };
  const wrapper = shallow(<App {...props} />);
  // Query for the Picker component in the rendered output
  const PickerComponent = wrapper.find(Picker);
  expect(PickerComponent.props().value).toBe(props.selectedSubreddit);
}); 

这个测试展示了如何轻松地使用enzyme来查询嵌套组件(在本例中是Picker),并断言它是用正确的props呈现的。我强烈推荐深入研究 enzyme 的文档,看看它提供的测试工具的范围。

接下来,添加另一个测试来检查基于某些条件呈现的元素。在这种情况下,您将验证当isFetching属性为false时,是否呈现了刷新按钮:

it("renders the Refresh button when the isFetching prop is false", () => {
  const props = {
    isFetching: false,
    dispatch: jest.fn(),
    selectedSubreddit: "reactjs",
    posts: [],
  };
  const wrapper = shallow(<App {...props} />);
  expect(wrapper.find("button").length).toBe(1);
}); 

最后,添加一个处理一些用户交互的测试。该测试可以验证当点击刷新按钮时,它会分派正确的动作:

// Add this import
import * as actions from "../../actions";

// .. other tests

it("handleRefreshClick dispatches the correct actions", () => {
  const props = {
    isFetching: false,
    dispatch: jest.fn(),
    selectedSubreddit: "reactjs",
    posts: [],
  };
  // Mock event to be passed to the handleRefreshClick function
  const mockEvent = {
    preventDefault: jest.fn(),
  };
  // Mock the actions we expect to be called
  actions.invalidateSubreddit = jest.fn();
  actions.fetchPostsIfNeeded = jest.fn();

  const wrapper = shallow(<App {...props} />);
  // Call the function on the component instance, passing the mock event
  wrapper.instance().handleRefreshClick(mockEvent);

  expect(mockEvent.preventDefault).toHaveBeenCalled();
  expect(props.dispatch.mock.calls.length).toBe(3);
  expect(actions.invalidateSubreddit.mock.calls.length).toBe(1);
  expect(actions.fetchPostsIfNeeded.mock.calls.length).toBe(2);
}); 

从导入actions开始,这样您就可以模仿它提供的一些功能。在测试中,您提供通常的props和一个mockEvent对象。使用mockEvent对象模拟按钮被点击时浏览器发送的点击事件。被嘲弄的事件需要包含一个preventDefault属性。这个属性应该是一个函数,因为它将在handleRefreshClick函数内部被调用。如果没有它,您将得到一个关于丢失属性的错误:e.preventDefault is not a function.

使用shallow呈现组件后,手动调用handleRefreshClick,传递模拟事件来模拟在应用程序中调用该函数时会发生什么。断言应用程序的以下属性:

  • event.preventDefault应该叫过一次
  • props.dispatch应该已经叫了 3 遍了
  • actions.invalidateSubreddit应该叫过一次
  • actions.fetchPostsIfNeeded应该叫了两次
    • 第一次通话发生在componentDidMount
    • 第二个呼叫发生在handleRefreshClick内部

为了确保您对componentDidMount函数调用的期望是正确的,您可以在handleRefreshClick函数调用之前包含这些断言。

const wrapper = shallow(<App {...props} />);
// The next assertions are for functions called in componentDidMount
expect(props.dispatch.mock.calls.length).toBe(1);
expect(actions.fetchPostsIfNeeded.mock.calls.length).toBe(1);

wrapper.instance().handleRefreshClick(mockEvent);

//... rest of test omitted for brevity 

至此,您已经测试了代码中最具挑战性的部分,这将为您轻松地为任何其他组件功能添加测试提供一个良好的起点。

测试 Redux 功能

在本节中,您将为您的应用程序的redux相关部分添加一些测试,特别是动作和减少器。

测试动作创建者

从动作创作者开始。这个应用程序有异步和同步动作创建器。异步动作创建器与redux-thunk一起使用,支持不会立即产生结果的异步操作,比如获取数据。同步动作创建者返回普通对象。本教程涵盖了如何测试这两者。

作为参考,这是您的src/actions/index.js文件应该包含的内容:

export const REQUEST_POSTS = "REQUEST_POSTS";
export const RECEIVE_POSTS = "RECEIVE_POSTS";
export const SELECT_SUBREDDIT = "SELECT_SUBREDDIT";
export const INVALIDATE_SUBREDDIT = "INVALIDATE_SUBREDDIT";

export const selectSubreddit = (subreddit) => ({
  type: SELECT_SUBREDDIT,
  subreddit,
});

export const invalidateSubreddit = (subreddit) => ({
  type: INVALIDATE_SUBREDDIT,
  subreddit,
});

export const requestPosts = (subreddit) => ({
  type: REQUEST_POSTS,
  subreddit,
});

export const transformResponseBody = (json) => {
  return json.data.children.map((child) => child.data);
};

export const receivePosts = (subreddit, json) => ({
  type: RECEIVE_POSTS,
  subreddit,
  posts: transformResponseBody(json),
  receivedAt: Date.now(),
});

// const fetchPosts = (subreddit) => (dispatch) => {
//   dispatch(requestPosts(subreddit));
//   return fetch(`https://www.reddit.com/r/${subreddit}.json`)
//     .then((response) => response.json())
//     .then((json) => dispatch(receivePosts(subreddit, json)));
// };

export const fetchPosts = (subreddit) => (dispatch) => {
  dispatch(requestPosts(subreddit));
  return fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then((response) => response.json())
    .then((json) => dispatch(receivePosts(subreddit, json)));
};

const shouldFetchPosts = (state, subreddit) => {
  const posts = state.postsBySubreddit[subreddit];
  if (!posts) {
    return true;
  }
  if (posts.isFetching) {
    return false;
  }
  return posts.didInvalidate;
};

export const fetchPostsIfNeeded = (subreddit) => (dispatch, getState) => {
  if (shouldFetchPosts(getState(), subreddit)) {
    return dispatch(fetchPosts(subreddit));
  }
}; 

您的下一个任务是创建支持测试的文件。在src/actions/中,创建一个名为__tests__的文件夹,并在其中创建一个名为actions.test.js的文件。

同步动作创建器是简单的纯函数,接受一些数据并返回一个动作对象。您的测试应该检查给定必要的参数,动作创建者返回正确的动作。您可以通过对selectSubreddit动作创建者的测试来演示这一点,它接受一个subreddit作为参数,然后返回一个动作。

import * as actions from "../index";

describe("actions", () => {
  const subreddit = "reactjs";

  describe("selectSubreddit", () => {
    it("should create an action with a given subreddit", () => {
      const expectedAction = {
        type: actions.SELECT_SUBREDDIT,
        subreddit,
      };
      expect(actions.selectSubreddit(subreddit)).toEqual(expectedAction);
    });
  });
}); 

对于大多数同步动作创建者来说,这就是你所需要做的。

为了在测试异步动作创建器时使您的工作更容易,您也可以为receivePosts动作创建器添加一个测试。该函数是这样工作的:

export const receivePosts = (subreddit, json) => ({
  type: RECEIVE_POSTS,
  subreddit,
  posts: json.data.children.map(child => child.data),
  receivedAt: Date.now()
}) 

在返回的动作中,posts属性发生了转换。将其提取到一个新的函数调用中,该函数调用带有json参数并执行您需要的转换。注意,您必须导出新的助手函数,以便您可以在稍后的测试中访问它。这里显示了新版本的receivePosts功能:

export const transformResponseBody = (json) => {
  return json.data.children.map(child => child.data);
}

export const receivePosts = (subreddit, json) => ({
  type: RECEIVE_POSTS,
  subreddit,
  posts: transformResponseBody(json),
  receivedAt: Date.now()
}) 

您可能会注意到,在返回的操作中,有一个返回Date.now()receivedAt属性。在您的测试中,您将跳过对该属性的测试,因为每次调用该函数时它都会发生变化。您可以通过模仿Date.now函数来测试这一点,但是出于本教程的目的,您可以跳过这一步。

既然您已经选择了您需要做的范围,那么您需要为receivePosts动作创建者添加测试:

describe("actions", () => {
  const subreddit = "reactjs";
  // Add the mockJSON response
  const mockJSON = {
    data: {
      children: [{ data: { title: "Post 1" } }, { data: { title: "Post 2" } }],
    },
  };

  // ... other tests...

  describe("receivePosts", () => {
    it("should create the expected action", () => {
      const expectedAction = {
        type: actions.RECEIVE_POSTS,
        subreddit,
        posts: actions.transformResponseBody(mockJSON),
      };
      expect(actions.receivePosts(subreddit, mockJSON)).toMatchObject(
        expectedAction
      );
    });
  });
}); 

注意,您正在使用toMatchObject来匹配返回的动作对象的一个子集,这不包括匹配receivedAt键。

对其余同步动作创建器的测试遵循相同的过程,其中给定一些数据,测试是否返回正确的动作。

是时候测试异步动作创建者了,特别是fetchPosts动作创建者。您需要做的第一件事是导出该函数,您将通过向该函数添加export来做到这一点,因此它变成:

export const fetchPosts = (subreddit) => (dispatch) => {
  dispatch(requestPosts(subreddit));
  return fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then((response) => response.json())
    .then((json) => dispatch(receivePosts(subreddit, json)));
}; 

安装一些新的软件包:

npm install --save-dev fetch-mock redux-mock-store 

使用fetch-mock模仿使用fetchredux-mock-store发出的 HTTP 请求。这有助于您创建一个模拟商店以在测试中使用。添加测试:

// Add the new imports

import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import configureMockStore from "redux-mock-store";

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe("actions", () => {
  const subreddit = "reactjs";
  // Add the mockJSON response
  const mockJSON = {
    data: {
      children: [{ data: { title: "Post 1" } }, { data: { title: "Post 2" } }],
    },
  };

  // ... other tests...

  describe("fetchPosts", () => {
    afterEach(() => {
      // restore fetch() to its native implementation
      fetchMock.restore();
    });

    it("creates REQUEST_POSTS and RECEIVE_POSTS when fetching posts", () => {
      // Mock the returned data when we call the Reddit API
      fetchMock.getOnce(`https://www.reddit.com/r/${subreddit}.json`, {
        body: mockJSON,
      });

      // The sequence of actions we expect to be dispatched
      const expectedActions = [
        { type: actions.REQUEST_POSTS },
        {
          type: actions.RECEIVE_POSTS,
          subreddit,
          posts: actions.transformResponseBody(mockJSON),
        },
      ];

      // Create a store with the provided object as the initial state
      const store = mockStore({});

      return store.dispatch(actions.fetchPostsIfNeeded(subreddit)).then(() => {
        expect(store.getActions()).toMatchObject(expectedActions);
      });
    });
  });
}) 

从所有必要的导入开始,包括redux-thunk。对于这种情况,您需要配置一个实际的商店,这意味着您也将把中间件应用到模拟商店。

您有一个在每次测试后运行的afterEach函数,它确保您恢复原来的fetch实现。这就是为什么您的模拟实现不会在其他测试中使用。

接下来,模拟您期望发出的请求,并提供一个将作为响应体返回的模拟body。然后定义调用fetchPosts时您期望采取的动作顺序。这个序列意味着当fetchPosts被调度时,它应该生成一个REQUEST_POSTS动作,然后是RECEIVE_POSTS请求的子编辑的帖子。像前面的测试一样,在RECEIVE_POSTS动作中排除receivedAt属性,并像前面一样,在posts键中添加转换后的响应体。

接下来,创建商店,给它一些初始状态,然后分派fetchPosts。最后,断言应用于商店的动作列表应该与您的expectedActions数组中的序列相匹配。

此时重新运行您的测试应该可以确认一切都通过了。干得好!

您的动作创建者测试到此结束。接下来是如何测试减速器。

测试减速器

缩减器是 Redux 的核心,因为它们是更新整个应用程序状态的方式。reducer 测试应该有助于验证您调度的每个操作都按照预期更新了状态。

下面是您将要测试的reducers/index.js文件的内容:

import { combineReducers } from "redux";
import {
  SELECT_SUBREDDIT,
  INVALIDATE_SUBREDDIT,
  REQUEST_POSTS,
  RECEIVE_POSTS,
} from "../actions";

const selectedSubreddit = (state = "reactjs", action) => {
  switch (action.type) {
    case SELECT_SUBREDDIT:
      return action.subreddit;
    default:
      return state;
  }
};
const posts = (
  state = {
    isFetching: false,
    didInvalidate: false,
    items: [],
  },
  action
) => {
  switch (action.type) {
    case INVALIDATE_SUBREDDIT:
      return {
        ...state,
        didInvalidate: true,
      };
    case REQUEST_POSTS:
      return {
        ...state,
        isFetching: true,
        didInvalidate: false,
      };
    case RECEIVE_POSTS:
      return {
        ...state,
        isFetching: false,
        didInvalidate: false,
        items: action.posts,
        lastUpdated: action.receivedAt,
      };
    default:
      return state;
  }
};
const postsBySubreddit = (state = {}, action) => {
  switch (action.type) {
    case INVALIDATE_SUBREDDIT:
    case RECEIVE_POSTS:
    case REQUEST_POSTS:
      return {
        ...state,
        [action.subreddit]: posts(state[action.subreddit], action),
      };
    default:
      return state;
  }
};
const rootReducer = combineReducers({
  postsBySubreddit,
  selectedSubreddit,
});
export default rootReducer;
export { postsBySubreddit, selectedSubreddit }; 

您的 reducer 文件有两个 reducer,每个 reducer 管理自己的状态部分。最终,它们将使用combineReducers合并成一个根缩减器。通过将以下代码片段添加到reducers/index.js中,导出单个 reducer 函数以使测试更加方便:

export { postsBySubreddit, selectedSubreddit } 

reducers下创建一个__tests__目录。然后在该目录下创建一个reducers.test.js文件。这是你测试的地方。因为是两者中比较简单的,所以先测试一下selectedSubreddit减速器。

import {
  SELECT_SUBREDDIT,
  INVALIDATE_SUBREDDIT,
  REQUEST_POSTS,
  RECEIVE_POSTS,
} from "../../actions";
import { postsBySubreddit, selectedSubreddit } from "../index";

describe("app reducer", () => {
  describe("selectedSubreddit", () => {
    it("should return the default state", () => {
      expect(selectedSubreddit(undefined, {})).toBe("reactjs");
    });

    it("should update the selectedSubreddit", () => {
      const subreddit = "frontend";
      const action = {
        type: SELECT_SUBREDDIT,
        subreddit,
      };
      expect(selectedSubreddit(undefined, action)).toBe(subreddit);
    });
  });
}); 

第一项测试检查selectedSubreddit减速器是否正确初始化状态。当给定一个undefined状态或一个空动作时,它应该返回默认值,该值被设置为reactjs。下一个检查验证当 reducer 接收到一个有效的 action 对象时,它是否正确地更新了状态。

现在你可以继续使用postsBySubreddit减速器了。

describe("postsBySubreddit", () => {
  const subreddit = "frontend";

  it("should return the default state", () => {
    expect(postsBySubreddit(undefined, {})).toEqual({});
  });

  it("should handle INVALIDATE_SUBREDDIT", () => {
    const action = {
      type: INVALIDATE_SUBREDDIT,
      subreddit,
    };
    expect(postsBySubreddit({}, action)).toEqual({
      [subreddit]: {
        isFetching: false,
        didInvalidate: true,
        items: [],
      },
    });
  });

  it("should handle REQUEST_POSTS", () => {
    const action = {
      type: REQUEST_POSTS,
      subreddit,
    };
    expect(postsBySubreddit({}, action)).toEqual({
      [subreddit]: {
        isFetching: true,
        didInvalidate: false,
        items: [],
      },
    });
  });

  it("should handle RECEIVE_POSTS", () => {
    const posts = ["post 1", "post 2"];
    const receivedAt = Date.now();
    const action = {
      type: RECEIVE_POSTS,
      subreddit,
      posts,
      receivedAt,
    };
    expect(postsBySubreddit({}, action)).toEqual({
      [subreddit]: {
        isFetching: false,
        didInvalidate: false,
        items: posts,
        lastUpdated: receivedAt,
      },
    });
  });
}); 

首先测试它是否正确初始化了状态。在这种情况下,默认状态是一个空对象,如第一个测试所示。

其余动作的测试是相似的;您验证给定一个动作,reducer 返回预期的状态更新。应该将subreddit设置为返回对象的键,并且应该根据 reducer 中的规则更新嵌套对象。

您可能会注意到 reducers 的共同主题是,给定一组特定的输入(初始状态和一个动作),应该返回一个新的状态。您对返回的状态进行所有断言,以确保它是您所期望的。

完成本教程的这一部分后,您已经了解了 React 和 Redux 应用程序的许多相同组件,您需要在典型的应用程序中测试这些组件。

Run all test suites

与 GitHub 和 CircleCI 的持续集成

这是本教程中添加与 CircleCI 持续集成的部分。持续集成有助于确保您对代码所做的任何更改都不会破坏任何现有的功能。测试将在您推送新代码的任何时候运行,无论是通过向现有分支添加新的提交,还是通过打开 pull 请求将新分支合并到主分支。这有助于在开发过程的早期发现错误。

圆形构型

您需要添加的第一件事是一个配置文件,它将告诉 CircleCI 如何测试您的应用程序。配置文件需要在根文件夹的.circleci目录中。命名为config.yml

以下是用于示例应用程序的配置文件:

version: 2.1
orbs:
  node: circleci/node@5.0.2
jobs:
  build-and-test:
    docker:
      - image: "cimg/base:stable"
    steps:
      - checkout
      - node/install:
          node-version: "12.22"
      - node/install-packages
      - run:
          command: npm run test
workflows:
  build-and-test:
    jobs:
      - build-and-test 

集成 CircleCI 和 GitHub

花点时间确保您已经将所有的更改都推送到您之前创建的 GitHub 存储库中。现在,您可以设置 CircleCI 来测试您的代码,无论何时您做出任何新的更改。

以下是将项目添加到 CircleCI 的方法:

在 CircleCI 项目视图中,点击设置项目

Setup project CircleCI

在下一个屏幕上,选择包含配置文件的分支。

Select config branch CircleCI

检查build-and-test工作流程中的所有步骤。

All steps view in CircleCI

单击某个步骤的名称可获得有关该步骤的更多详细信息。例如,打开npm run test步骤显示所有的测试。

Step details in CircleCI

恭喜你!您的 CI 流程已经设置好了,存储库中任何新的提交都将触发一个测试运行,以确保您的更改不会破坏构建。在您所做的更改导致测试失败的情况下,您会得到通知,并且您可以准确地跟踪哪个提交导致了失败。如果您觉得本教程有用,请务必与您的团队分享您所学到的内容。感谢您的宝贵时间!


Dominic Motuka 是 Andela 的 DevOps 工程师,在 AWS 和 GCP 支持、自动化和优化生产就绪部署方面拥有 4 年多的实践经验,利用配置管理、CI/CD 和 DevOps 流程。

阅读多米尼克·莫图卡的更多帖子

宣布 CircleCI 的 Jenkinsfile 转换器| CircleCI

原文:https://circleci.com/blog/convert-jenkinsfile-circleci-config/

作为开发人员,我们都能体会到快速使用新工具或框架的经历,以及挫折(和快乐!)那随它来。就个人而言,我可以很容易地回忆起我第一次构建 CircleCI config.yml文件的经历,包括 1.0 和 2.0,以及在单独的屏幕上打开 CircleCI 配置参考时经历的多次迭代。有比我愿意承认的更多的尝试和错误。

自 CircleCI 2.0 发布以来,我们的团队一直在不断改进 onboarding 体验,以使其更容易上手,包括在 CircleCI CLI 中的功能,以在本地验证config.yml文件。我们最新发布的 Jenkinsfile 转换器让这种体验变得更好。

什么是 Jenkinsfile 转换器?

Jenkinsfile 转换器是一个将 Jenkinsfile 转换成 CircleCI config.yml文件的工具。通过将 Jenkinsfile 加载到转换器中,您将立即收到一个格式化的config.yml文件,该文件将带您通过 CircleCI 获得最佳配置。

值得注意的是,尽管该工具尽最大努力转换尽可能多的 Jenkinsfile,但仍有一些关键的差异无法通过该工具解决。例如,CircleCI 没有插件的概念,我们无法支持 Jenkins 宇宙中超过 1400 个不断发展的插件。虽然为了让你的体形达到最佳状态还需要做一点额外的工作,但是 Jenkinsfile converter 会让你一切顺利。让我们看一个例子。

这里我们有一个简单的 Python 项目的 Jenkinsfile:

pipeline {
    agent none
    options {
        skipStagesAfterUnstable()
    }
    stages {
        stage('Build') {
            agent {
                docker {
                    image 'python:2-alpine'
                }
            }
            steps {
                sh 'python -m py_compile sources/add2vals.py sources/calc.py'
            }
        }
        stage('Test') {
            agent {
                docker {
                    image 'qnib/pytest'
                }
            }
            steps {
                sh 'py.test --verbose --junit-xml test-reports/results.xml sources/test_calc.py'
            }
            post {
                always {
                    junit 'test-reports/results.xml'
                }
            }
        }
    }
} 

将这个 Jenkinsfile 加载到转换器后,我们看到了下面的config.yml文件:

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/base:2020.08
    steps:
      - run:
          command: python -m py_compile sources/add2vals.py sources/calc.py
  test:
    docker:
      - image: cimg/base:2020.08
    steps:
      - run:
          command: py.test --verbose --junit-xml test-reports/results.xml sources/test_calc.py

workflows:
  version: 2
  build-and-test:
    jobs:
      - build
      - test:
          requires:
            - build 

这里,发布测试结果的post命令没有被传输到 CircleCI config.yml文件。我们需要将store_test_results 添加到我们的config.yml文件中,以便将该功能引入 CircleCI。

 test:
    docker:
      - image: qnib/pytest
    steps:
      - run:
          command: py.test --verbose --junit-xml test-reports/results.xml sources/test_calc.py
	- store_test_results:
	    path: test-reports 

Jenkinsfile 转换器位于 CircleCI 开发者中心,现在对所有人开放。这个工具将帮助您节省开始使用 CircleCI 的关键时间,无论您只是尝试它,还是转移一个具有 2,000 行 Jenkinsfile 的项目,并且将您从使用试错法来解决两个服务之间的配置问题的诱惑中解救出来。

注意 : 目前只支持声明式的 Jenkins file,目前还没有计划支持脚本化的 Jenkins file。这是我们的第一个版本,当我们继续迭代这个工具的功能时,我们希望听到你的反馈关于什么工作得好,什么工作得不好。

要了解有关此功能的更多信息,请访问以下链接:

使用 CircleCI webhooks | CircleCI 创建可定制的体验

原文:https://circleci.com/blog/create-customizable-experiences-with-circleci-webhooks/

在过去的 10 年里,CircleCI 的客户一直使用我们的平台来定制他们的软件开发流程。orb 通过可重用的配置包帮助标准化和扩展 CI/CD 管道。 CircleCI API 允许用户为他们的开发人员创建健壮的内部工具,并与其他产品集成以进行更精细的监控。

时至今日,CircleCI 用户还有另一种方式来对事件做出反应,并使用 webhooks 定制他们的软件交付体验。

以前,如果开发人员想要接收关于 CircleCI 中特定事件的信息,他们需要完全依赖 API。现在,使用 webhooks,客户可以扩大他们对 CircleCI 内发生的事件做出反应的范围。通过这些事件,开发人员可以创建有效且有目的的可定制体验。

CircleCI 合作伙伴如何使用 webhooks

CircleCI 的合作伙伴 Datadog 和 Sumo Logic 已经加入了通过 webhooks 扩展 CircleCI 的努力。

Datadog 在他们的 CircleCI 集成中使用 webhooks 将分析数据呈现给用户,使他们能够在管道中发生事件时做出明智的决策。

“作为一个长期的合作伙伴,我们对 CircleCI webhooks 的发布感到兴奋,”Datadog 的产品总监 Borja Burgos 说。“最大限度地提高开发人员的时间和工作效率从未如此重要。多亏了 CircleCI webhooks,我们能够在 Datadog 中构建新的集成,以提供对 CI 的卓越可见性和洞察力。这种整合将为我们的客户提供巨大的价值,并加强我们与 CircleCI 社区的联系。”

通过 webhooks 的 CircleCI 和 Sumo Logic 集成从 CircleCI 收集事件,包括工作流和作业完成状态,允许团队更好地跟踪持续集成和部署管道的性能和健康状况。

Sumo Logic 业务开发总监 Drew Horn 表示:“收集、丰富和关联现代 DevOps 工具链中不同来源的数据是当今工程团队面临的最大挑战之一。“借助 CircleCI webhooks,开发人员现在只需点击几下鼠标,即可将详细的自动化管道数据推送至 Sumo Logic 的持续智能平台,通过对软件开发生命周期的深入洞察和实时分析,对其软件交付性能进行基准测试和优化。”

CircleCI webhooks 将为合作伙伴集成提供更多机会,并扩展对技术用例的支持。请继续关注其他可用的集成。

circle CI web hooks:CI/CD 管道更加灵活

Webhooks 可用于在作业失败时向内部通知系统发出警报,或者使用 Airtable 等工具连接 CircleCI,以汇总已完成工作流或作业的数据。有了 webhooks,客户能够在他们的软件开发管道中创造更多的灵活性。

要创建您的第一个 webhook,请导航到 CircleCI UI,访问现有项目中的项目设置,并选择左侧菜单栏中的“Webhooks”项。阅读我们的文档了解更多关于如何创建网页挂钩的信息。

下一步是什么?

这仅仅是个开始——如果你有兴趣从 webhooks 看到更多,请访问 Canny 浏览或提交新想法。前往我们的社区论坛,讨论,让我们知道你是如何使用 webhooks 的,访问文档获取更多关于实现 webhooks 的信息,查看本教程学习如何设置你的第一个 web hooks。

创建自定义 Docker 映像以运行您的 CI 构建| CircleCI

原文:https://circleci.com/blog/creating-a-custom-docker-image-to-run-your-ci-builds/

本指南提供了关于如何创建一个有效的 Docker 映像作为 Docker 执行器中的主映像的提示。作业中的所有步骤,包括 orb 命令,都将在该映像中执行。然而,本指南并不包括如何构建和发布 Docker 映像,然后在您的生产环境中使用或发布您的开源应用程序。

欲了解更多 Docker 内容,请访问CI/CD 管道 Docker 使用指南

我希望你问问自己,你确定要这么做吗?有许多人不想在运行时安装几个包,因为这会增加他们 20-45 秒的构建时间。然后,他们花几个小时想办法建立自己的 Docker 形象,然后花几个月甚至几年的时间来维护这个形象。有时候不值得努力。

在需要安装大量软件包、需要编译源代码或通过慢速连接下载的情况下,这是您自己的自定义 Docker 映像大放异彩的最佳时机。

还感兴趣吗?太好了,我们开始吧。

编写 Dockerfile 文件

一切都以 Dockerfile 开始和结束。这是创建 Docker 图像的蓝图。有关 Docker 文件的更多信息,请访问 Docker 的入门-第 2 部分指南。

基本图像

Dockerfile 通常以声明新图像将使用的基础图像的FROM语句开始。虽然不是必须的,但我们强烈建议使用 CircleCI base 便利图像作为您的基本图像。

FROM cimg/base:stable 

CircleCI 基础映像是所有下一代便利映像的基础。你可以在这里了解更多关于所有新图片的信息。它完全是为了在 CircleCI 上工作而设计的。它拥有我们大多数客户需要的所有必需的工具以及最常用的工具。例如, checkout 特殊步骤要求git安装在 Docker 映像中。 save_cachepersist_workspace 特殊步骤,以及它们的加载等价物,需要安装targzip。我们的基本映像包括所有这些工具,并且我们确保随着额外需求的增加或删除,软件列表得到维护。

这个图像在我们的平台上也相当受欢迎,这意味着通过将您的图像基于我们的基础图像可以获得缓存优势。您可以在 GitHub 页面上了解更多关于 CircleCI base 便利图片以及如何使用它的信息。如果你选择使用不同的基本映像,我建议你至少访问一下 GitHub 页面看看我们安装了什么包,这样你就可以自己复制这个列表了。

安装和下载软件

RUN sudo apt-get update && sudo apt-get install -y \
        bison \
        llvm \
        zlib1g-dev \
        xz-utils && \
    rm -rf /var/lib/apt/lists/* 

在本例中,我们可以学习几个最佳实践:

  • 当使用不以 root 用户身份运行的映像时,需要在一些命令前加上前缀sudo
  • 在脚本场景中,总是使用apt-get而不是apt。前者更适合没有人类的环境,而后者更适合在你自己的本地计算机上摆弄。
  • 当用软件包管理器安装时,当软件包管理器试图询问一个问题时,像-y这样的标志被用来假定“是”。
  • 每行按字母顺序列出一个包。使用 git 和 GitHub 创造奇迹。这使得阅读源代码更加愉快,当查看 PR 差异时,可以非常清楚地看到哪些包被添加或删除。
  • 最后一行删除图像中的 Apt 缓存。这有助于缓存,但更重要的是减小了图像的大小。
ENV GO_VERSION=1.14.1
RUN curl -sSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | \
    sudo tar -xz -C /usr/local/ 

这个例子给了我们另外两个提示:

  • 将频繁变化的字符串设置为环境变量,可以更直观地管理变化。尤其是因为 ENV 指令在 Docker 中不算一个层。当该值在即将到来的RUN指令中被多次使用时,这种技术大大提高了效率。
  • 在这里,cURL 下载了一个 tarball,并将其直接传送到下一个命令 tar 中。这允许我们避免将文件保存到文件系统中,这样速度更快,也避免了事后清理 tarball。在你不能使用这个技巧的情况下,不要忘记用rm删除 tarball、zip 包等来清理自己。

缓存和效率

对于较大的图像,指令的顺序很重要。您希望RUN变化频繁的步骤靠近 docker 文件的底部,而变化不频繁的步骤应该靠近顶部。这是因为 Docker 按层缓存图像。每当一个层发生变化时,该层及其下的层都需要重新缓存。这种行为在 Docker 的 Dockerfile 最佳实践指南中有更详细的解释。

另一个效率项目是图像层应该尽可能的精简。不需要的文件应该在创建它的RUN步骤中删除,这样可以使该层的整体尺寸更小。

维护形象

创建自定义图像只是工作的一半。一旦该映像存在,就需要进行维护。随着您的 CI 需求的变化和增长,您的形象也需要随之调整。

你的 Dockerfile 的家

让你的 docker 文件处于版本控制之下。有些人喜欢将这个 docker 文件保存在与他们需要它的项目相同的存储库中。其他人,比如我自己,更喜欢把它放在自己的仓库里。

我更喜欢单独的回购途径,因为:

  • 当它与项目在同一个 repo 中时,该项目的 CircleCI 配置会变得更加复杂。您不希望在每次提交主项目时都构建 Docker 映像。那是浪费时间,代价昂贵。为了避免这种情况,您需要在构建项目和图像时进行逻辑分离。
  • 作为一个独立的回购,它使得在您的公司或团队的多个项目中使用该图像变得更加容易。

保持 Dockerfile 文件最新

当在 docker 文件中添加或删除内容时,用您的更改创建一个新的分支,检查并合并。标准的东西。Docker 映像的某些部分会在 Docker 文件之外进行更新。我们如何保持这些部分的更新?

您的基础映像是它自己的项目,并按照自己的时间表进行更新。例如,CircleCI base 便利映像每个月会进行一次稳定的更新。如果您下载并安装文件名为example.com/download/some-thing-4.3.x.tar.gz的软件,您将需要确保使用该软件可用的最新补丁版本更新您的映像。我们通过 CircleCI 计划工作流来实现这一点。

使用预定的工作流程发布您的自定 Docker 图像可让您定期更新图像的背景部分,而无需您进行任何手动操作。这样做的频率取决于你的个人需求。CircleCI 基础映像每月 2 日更新。如果你以此为基础,你自己在 3 号或 5 号安排的每月工作流程会很好。

托管图像

您可以选择在哪里托管您发布的 Docker 图像。存放 Docker 映像的地方称为 Docker 注册表。托管 Docker 映像的实际位置是 Docker Hub。如果你对其他任何注册表都不熟悉,坚持使用 DockerHub 就没问题了。它可以免费使用,即使是私人图像。

如果您使用领先的云提供商进行托管,他们可能会有一个 Docker 注册表供您使用,我们可能会为该提供商提供一个 CircleCI orb ,使所有这些设置更加容易。以下是主要的 Docker 注册表和相应的 orb:

讨论和反馈

要进一步讨论这个话题或提出问题,请访问我们的 CircleCI 讨论论坛。你可以找到我和 CircleCI 的用户,他们就像你一样,随时准备讨论和提供帮助。

为 orbs 创建自动化构建、测试和部署工作流第 2 部分| CircleCI

原文:https://circleci.com/blog/creating-automated-build-test-and-deploy-workflows-for-orbs-part-2/

在我的上一篇文章中,我讨论了如何为一个新的 orb 建立一个理想的自动化 CI/CD 流程。这个过程的目标是版本控制、多级测试和自动化部署。现在,让我们看一个例子。我将使用我们上个月刚刚发布的 Azure CLI orb ,因为它足够小和简单,相当容易理解,但也有点复杂,因为它确实与第三方云提供商微软 Azure 集成。

用这个过程构建、测试和部署的 orb 的config.yml文件并不复杂。我们的 Orb 工具 orb 已经抽象出自动化 Orb 开发的大部分基本机制。Azure CLI orb 的 config.yml 包含两个不同的工作流。一个工作流用于基本验证和dev发布,另一个用于集成/使用测试和可能的 orb 生产部署:

workflows:
  lint_pack-validate_publish-dev:

  # …

  integration_tests-prod_deploy: 

这种设置允许您在单个config.yml文件、单个存储库中对 orb 进行使用和集成测试,并绑定到单个git提交。它允许绕过一些替代的 orb 测试方法,这些方法可能更复杂或更麻烦(尽管绝不是无效的),例如内联您的 orb ,使用外部存储库进行测试,或者在本质上是运行在 CircleCI 上的本地作业中评估您的新 orb 源代码,使用machine执行器。

默认情况下,config.yml文件中定义的所有工作流程同时运行。然而,我们已经设置了这两个,一个在每次提交时运行,另一个仅由 git 标签触发。通过控制如何创建这些 git 标签,我们可以确保我们的集成测试工作流仅在我们完成了基本的林挺/验证并发布了我们想要进一步测试的 orb 的dev版本之后才运行。

lint_pack-validate_publish-dev

我们的第一个工作流程完全是圆形的!也就是说,除了调用我们的 orb 工具 orb 中定义的一系列作业并向它们传递必要的参数之外,您不需要做任何事情就可以在您自己的 Orb 库的配置文件中使用它。让我们快速浏览一下这些工作,一个接一个:

 - orb-tools/lint: 

orb-tools/lint作业将使用yamllint CLI 工具【lint 给定目录下的所有 YAML 文件,由singapore/lint-condo Docker 镜像打包。您可以提供一个定制的.yamllint配置文件,或者使用作业中包含的一些基本默认值。

 - orb-tools/pack:
      requires:
        - orb-tools/lint 

orb-tools/pack是为那些用解构 YAML 格式写球体的人制作的,我强烈推荐使用!对于任何具有一个或两个以上独立命令或作业的 orb,以这种方式组织 orb 源代码将使您的代码更容易解析、调试和开发。这类似于将一个整体分成更小、更模块化的服务,或者编写 React 组件,而不是充斥着 HTML、JavaScript 和 CSS 代码的单个 HTML 文件。

orbs的设计利用了一些更高级的对象类型(即命令、执行器、作业、示例),非常直观地形成了一个文件系统树,其中每种类型都有自己的文件夹,每个文件夹都包含该类型的所有实例,每个实例都在自己的 YAML 文件中。一个单独的@orb.yml文件作为这个系统的“入口点”,包含版本信息和描述。您的 orb 引用的其他 orb 也可以放在这里,或者每个 orb 也可以在一个orbs目录中有自己的文件(因为 orb 可以引用其他 orb)。看一看我们的 Azure CLI orb 示例的/src目录,应该会清楚这一切看起来像什么。

默认情况下,orb-tools/pack将执行以下操作:

  1. 签出您的项目
  2. 将 orb 源文件夹打包到一个文件中
  3. 验证这个新的 orb.yml 文件(circleci orb validate orb.yml)
  4. orb.yml文件保存到一个工作空间,这样它就可以在下游作业中使用

正如您从上面的 YAML 片段中看到的,该作业有默认值,因此您通常可以简单地调用该作业,不带任何参数,orb 会处理其余的事情。

- orb-tools/publish-dev:
    orb-name: circleci/azure-cli
    context: orb-publishing
    requires:
      - orb-tools/pack 

我们的第一个工作流程就要结束了。这个作业只是发布了我们的 orb 的一个dev版本,它已经在之前的作业中对其 YAML 进行了标记和验证。同样,提供了合理的缺省值,因此可能只需要为orb-name参数提供一个值。我们已经为这项工作附加了一个上下文,因为发布一个 orb 需要一个 CircleCI API 令牌,但是如果你已经将你的令牌存储为一个项目环境变量,那么上下文可能就不需要了。

  • 工作 4 : orb-tools/trigger-integration-workflow
- orb-tools/trigger-integration-workflow:
    name: trigger-integration-dev
    context: orb-publishing
    ssh-fingerprints: 23:d1:63:44:ad:e7:1a:b0:45:5e:1e:e4:49:ea:63:4e
    requires:
      - orb-tools/publish-dev
    filters:
      branches:
        ignore: master

- orb-tools/trigger-integration-workflow:
    name: trigger-integration-master
    context: orb-publishing
    ssh-fingerprints: 23:d1:63:44:ad:e7:1a:b0:45:5e:1e:e4:49:ea:63:4e
    tag: master
    requires:
      - orb-tools/publish-dev
    filters:
      branches:
        only: master 

在这里,事情变得有点复杂。我们已经完成了所有基本的 orb 验证,并发布了 orb 的dev版本。现在我们要触发第二个工作流,它只有在 git 标签被推回到我们的 orb 存储库时才会运行。这正是这份orb-tools工作所要做的!这就是为什么该作业需要 SSH 指纹作为参数。该作业创建一个 git 标记,并通过 SSH 将它推回我们的 orb 存储库。

我们的文档介绍了创建一个密钥并将其添加到 CircleCI 的过程,但简而言之:生成一个无密码的 OpenSSL(不是 OpenSSH)密钥对,将公共部分作为读/写密钥存储在您的 GitHub 或 Bitbucket 存储库中,将私有部分存储在 CircleCI 中。这将使您的 CircleCI 作业能够对您的 repo 进行更改,例如,推送 git 标记。

这种类型的方法是必要的,因为在内部,我们的构建系统在运行时处理配置文件,包括 orb。因此,如果没有一个新的 webhook 事件(比如一个 git 标签),我们将无法测试 orb 的新版本dev,除非手动进行第二次提交。git 标签使我们的系统重新处理我们的config.yml文件,拉入我们的 orb 的刚刚发布的dev版本。

至关重要的是,因为 git 标签与我们从本地机器推送的初始提交附加在同一个提交上,所以这个新的 CircleCI 构建被视为同一套 VCS 状态检查的一部分。也就是说,对于特定的拉请求,两组工作流作业将被附加到相同的提交,这使得开发人员可以很容易地评估给定的代码更改。

您会注意到我们调用了两次trigger-integration-workflow作业。这是因为我们的集成测试工作流使用正则表达式过滤来控制哪个作业将运行,这取决于 git 标签的内容。这样,我们可以实现以下目标:

  1. 每当提交被推送到非主分支时,只运行我们的集成测试作业;而对主分支的提交将触发集成测试作业,如果测试作业成功,还可能触发生产部署。
  2. 根据我们的 orb 源代码的哪些部分在给定的提交中被修改,动态地发布补丁、次要版本或主要版本。(例如:一个新的命令或作业将触发一个可能的主要版本;一个新的执行者,或者一个 orb 的整体description域的改变,只会触发一个小的释放。)这个特性在默认情况下是打开的,但是如果您想要更多地手动控制何时发布各种类型的 orb 版本,可以禁用它(将use-git-diff参数设置为false)。

第二个trigger-integration-workflow作业,只在提交给 master 时运行,本质上是将字符串“master”附加到我们的 git 标签上,导致我们完整的第二个工作流(可能还有生产部署作业)运行。

integration-tests_prod-deploy

Orb 集成测试总是比你在我们的第一个工作流中看到的更加自由,基本上可以拖放到几乎任何 orb repo 的 CircleCI 配置文件中。我的基本方法是,作为一个管理大型(而且还在增长!)的目的是关注使用测试,覆盖尽可能多的边缘情况。

换句话说,使用我们刚刚在之前的工作流中发布的 orb 的dev版本,并运行它的所有命令和作业。我们这样做是为了确保他们不会失败,并确保他们按照我们的期望行事。如果一个命令或任务可以以多种截然不同的方式使用,我们就运行它多次,这样我们就可以覆盖所有的方式。如果一个命令或作业涉及到与第三方服务的交互,在这种情况下,微软 Azure,我们建立一个测试环境,这样我们就可以有信心 orb 将为其他用户工作。

您甚至可以采用我们在这个 Azure CLI orb 上采用的方法,在不同的运行时环境中测试相同的命令或作业(如果作业被配置为采用自定义执行器的话)(下面,您将看到我们使用基于 Go 的 Docker 映像、基于 Python 的 Docker 映像和微软自己的第三方 Azure Docker 映像来测试 orb 的命令)。

由于这个例子的细节可能不适用于其他 orb,所以我将包括第二个工作流的完整 YAML,但是在继续讨论生产部署工作之前,只简单地触及几个要点,生产部署工作也被抽象到我们的 orb 工具 Orb 中,并且被设计为可供任何希望实现类似开发过程的 Orb 开发人员使用。

在我们的集成测试工作中使用的 YAML 锚定过滤器:

integration-dev_filters: &integration-dev_filters
  branches:
    ignore: /.*/
  tags:
    only: /integration-.*/

integration-master_filters: &integration-master_filters
  branches:
    ignore: /.*/
  tags:
    only: /master-.*/

prod-deploy_requires: &prod-deploy_requires
  [test-orb-python_master, test-orb-azure_master, test-orb-golang_master] 

完整的集成-测试和部署工作流程:

integration-tests_prod-deploy:
  jobs:
    # triggered by non-master branch commits
    - test-orb-python:
        name: test-orb-python_dev
        context: orb-publishing
        filters: *integration-dev_filters

    - test-orb-azure-docker:
        name: test-orb-azure_dev
        context: orb-publishing
        filters: *integration-dev_filters

    - test-orb-golang:
        name: test-orb-golang_dev
        context: orb-publishing
        filters: *integration-dev_filters

    # triggered by master branch commits
    - test-orb-python:
        name: test-orb-python_master
        context: orb-publishing
        filters: *integration-master_filters

    - test-orb-azure-docker:
        name: test-orb-azure_master
        context: orb-publishing
        filters: *integration-master_filters

    - test-orb-golang:
        name: test-orb-golang_master
        context: orb-publishing
        filters: *integration-master_filters

    # patch, minor, or major publishing
    - orb-tools/dev-promote-prod:
        name: dev-promote-patch
        orb-name: circleci/azure-cli
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-patch.*/

    - orb-tools/dev-promote-prod:
        name: dev-promote-minor
        orb-name: circleci/azure-cli
        release: minor
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-minor.*/

    - orb-tools/dev-promote-prod:
        name: dev-promote-major
        orb-name: circleci/azure-cli
        release: major
        context: orb-publishing
        requires: *prod-deploy_requires
        filters:
          branches:
            ignore: /.*/
          tags:
            only: /master-major.*/ 

各种test-orb任务的内容在这个 orb 的config.yml文件中已经定义好了。如果你好奇,我鼓励你去看一看。现在,我只想说明我们对 YAML 锚的使用,主要是为了简化一些不太干燥的分支/标签过滤器。我们使用这些过滤器来确保我们在第一个工作流中推送的 git 标签将正确地控制我们的集成测试工作是否会导致生产部署。

最重要的是,我想检查一下我们称之为三次分开的工作。

为什么我们给这份工作打了三次电话?

这项工作旨在将dev orb 升级为语义/生产 orb 版本。因此,它需要一个参数来确定升级后的版本是补丁(0.0.x)、次要版本(0.x.0)还是主要版本(x.0.0)。因为我们如何在trigger-integration-workflow作业中使用 git 标签来确定最终发布的版本类型,我们需要调用这个作业三次,每次使用不同的正则表达式过滤 git 标签。

除此之外,这份工作超级直接!它的设计要求尽可能少的样板文件。所需要的只是一个 orb 名称和一个 CircleCI API 令牌。如果没有提供发布类型,则默认为patch

包扎

这就是我们自动化 orb 开发工作流程的结束!我们只需对包含 orb 源代码的存储库进行一次提交,通过林挺、基本 orb 验证、发布dev orb 版本、通过 git 标签触发整个第二次集成测试工作流,以及动态插入patchminormajor orb 版本的生产部署。

几个结束语:

这个过程使用@dev:alpha标签的来标记你的球体。鉴于生产 orb 版本是不可变的,dev版本可以被覆盖。这使得我们的配置可以用这个 orb 的旧版本@dev:alpha处理一次,然后在我们用新版本@dev:alpha推送 git 标签后重新处理。因此,您将需要手动发布一个初始的@dev:alpha orb 版本来“引导”这个过程,以便您的配置可以被处理。

如果提交给定的提交已经改变了您的 orb 源,以至于现在正在调用先前的@dev:alpha版本中不存在的作业和命令,那么您可能还需要在提交给定的提交之前偶尔重新发布@dev:alpha版本。如果无法处理 CircleCI 作业的配置,它们将不会运行,并且调用尚不存在的 orb 命令将导致配置处理错误。

因为这里描述的大多数测试工作都驻留在我们的 orb 工具 Orb 中,所以我们在这个库的 README 中添加了这个过程的完整示例。

感谢阅读!


阅读更多信息:

为 orbs | CircleCI 创建自动化的构建、测试和部署工作流

原文:https://circleci.com/blog/creating-automated-build-test-and-deploy-workflows-for-orbs/

距离 CircleCI orbs 的发布已经过去了 15 周,在orbs Registry中有超过 405 个 orbs。也就是说每周有 25 个以上的新球体!

令人惊讶的是,这些球体中的大部分都是由您,我们令人惊叹的用户社区,或者是由我们新的技术合作伙伴计划中的人们贡献的。然而,CircleCI 已经发布了近 30 个 orb,作为 CircleCI 的第一个社区&合作伙伴工程师,我在过去的几个月里一直致力于为这些 orb 开发一个可持续发展的管道——这个管道将允许在尽可能少的人工干预下进行构建、测试和部署。

这是一个不小的壮举,因为每个球都是不同的。有些安装和配置第三方工具和服务。有些允许您部署到各种云提供商,或者在外部环境中测试您的代码。而且,有些只是现有 CircleCI 配置特性的语法糖,让人们更容易使用我们的平台。

我们可以使用哪种持续集成和持续部署工具来支持所有这些不同类型的 orb 的开发?更重要的是,如何在自己的 orb 开发中利用这个工具呢?在一天结束的时候,没有人希望每次有人将一个拉请求合并到一个 orb 存储库时,坐在他们的计算机前输入git pullcircleci orb publish

如果你正在读这篇文章,我会假设你已经知道一些关于球体的知识。也许你已经出版了一本,或者你已经开始在你的 CircleCI 项目中使用它们。如果没有,我鼓励你看看我们以前的一些 orbs 博客帖子!支持工程师 Kyle Tryon 为编写你的第一个球体提供了一些奇妙的技巧;他还发表了一篇关于他创造 CircleCI 的 Slack orb ( GitHub link )的经历的概述,这是迄今为止我们最受欢迎的 orb。对于那些寻找更基础的入门者来说,创建球体重用配置文档非常有帮助。

在这篇文章中,我将讨论为一个新的 orb 设置理想的自动化 CI/CD 过程。在我的下一篇文章中,我将全面介绍 Azure CLI orb 和我们内置的自动化。

现在,让我们进入新 orb 的自动化流程。这样一个过程可能有什么样的目标?

版本控制

CircleCI 位于 DevOps 生态系统的中心,是“基础设施即代码”概念的核心信奉者。orb 是基础设施,表示为代码,因此它们应该像任何其他软件一样被构建、测试和部署。为此,您的 orb 需要驻留在 git 存储库中。

有些球体非常小。你的球可能只会做一件简单的事情。其他的要大得多(查看我们的 AWS ECS orb )。对于较小的 orb,尤其是那些不太依赖外部服务的 orb,某种 orbs monorepo 可能是合适的;实际上有很多东西可以测试,即使是在有多个 orb 的存储库中。你可以在我们的 circleci-orbs 知识库中看到这种方法的例子,这里是一些 circleci 发布的第一批 orbs 的所在地。

随着时间的推移,我们很快发现,对于许多更复杂的 orb,例如那些专注于第三方部署的 orb,需要更多的粒度。在这些情况下,1:1-orb:repository 关系是理想的。请记住,我们所有的 CI/CD 逻辑都必须存在于我们的config.yml文件中,并且试图在一个配置文件中管理多个大型 orb 的集成测试和自动化发布将很快变得难以承受。

多级测试

正如您会在开发管道的多个点上以多种方式测试一个网站或应用程序一样,我们也应该对 orb 做同样的事情。在 orbs 生态系统中,这些不同级别的测试是什么?

套用最初的 orbs SDK 预览版:

  1. 模式验证:这可以用一个 CLI 命令来完成,它检查 orb 是否是用格式良好的 YAML 编写的,是否符合 orb 模式。
  2. 运行时测试:这需要建立单独的测试,并在 CircleCI 构建中运行它们。
  3. 集成测试:这可能是相当高级的 orb 或专门设计为第三方服务的公共、稳定接口的 orb 所需的唯一测试。进行 orb 集成测试需要定制构建和您自己的外部测试环境。

还有扩展测试的概念:由于 orb 本质上是 CircleCI 配置的抽象片段,我们可以测试给定的 orb 是否生成了我们期望的 CircleCI 配置语法。在实践中,我发现成本效益比有点低,因为不仅需要维护 orb 源代码本身,还需要维护其相应的所需扩展配置语法,然后手动保持两者同步,以便扩展测试保持准确。不过,对某些球体来说,这可能是有帮助的。CircleCI 的解决方案总监 Eddie Webb 发布了一篇关于这种方法的精彩的解释 ( 并附有示例!)在 CircleCI 讨论上的一个测球讨论中。

自动化部署

对我来说,这是自动化 orb 开发过程的最终目标。没有这种自动化,我不可能维持 CircleCI 不断增长的球体舰队!

作为一个新的 orb 作者,一开始简单地手动发布可能很有诱惑力。毕竟,我们只是在讨论一些简单的 shell 命令。让我警告你不要屈服于这种诱惑。在内部,我们已经回答了 orb 作者的多个问题,他们想知道为什么在将一个 pull 请求合并到他们的 orb 存储库后,他们在注册表中看不到他们更新的 orb,只是意识到他们从未真正自动化他们的 orb 测试和发布过程的部署部分。相信我,因为根据我的经验,从一开始就实现自动化比回头再添加要顺利得多。

自动化 orb 部署有一些细微差别。CircleCI CLI 提供了三种不同的发布生产 orb 的方式:

  1. 您可以通过circleci orb publishorb.yml文件直接发布到注册表中
  2. 您可以 通过circleci orb publish promote 一个先前发布的dev版本的 orb 提升为一个语义版本化的生产 orb
  3. 你可以 增加 一个现有的生产 orb 版本,例如,从foo/bar@1.1.0foo/bar@1.1.1(补丁发布),或者1.2.0(小版本),或者2.0.0(大版本)

在实践中,我发现devorb 升级为生产 orb 是最有用的自动化部署机制。它允许您在每次提交时对 orb 源代码进行基本的测试/验证,然后有条件地发布 orb 的dev版本,然后在dev orb 上运行更广泛的测试,最后,有条件地将您刚刚发布的dev orb 升级到生产版本。

把所有的放在一起

那么,对于以这种方式构建、测试和部署的 orb 来说,config.yml看起来像什么呢?首先,没那么复杂!我们已经将自动化 orb 开发的大部分基本机制抽象成了一个 orb 。要查看功能示例,请查看我们上个月发布的 Azure CLI orb ,因为它足够小和简单,容易理解,但也有点复杂,因为它确实与第三方云提供商 Microsoft Azure 集成。这个 orb 的 config.yml 包含两个不同的工作流:一个用于基本验证和dev发布,另一个用于集成/使用测试和可能的 orb 生产部署。在的后续文章中,我将对 Azure CLI orb 和这两个工作流做一个全面的介绍。

Orbs 是 CircleCI 不断增长的开源生态系统的核心,因此,当参与进来时,它们会更好。用我们的circleci-orbs 话题标签在 GitHub 或 Bitbucket 上标记你的宝珠,这样其他人可以更容易地发现它们,并且请在 CircleCI 讨论的宝珠部分发表任何问题或评论。


阅读更多信息:

使用 CircleCI Happo orb 进行跨浏览器屏幕截图测试

原文:https://circleci.com/blog/cross-browser-screenshot-testing-with-the-circleci-happo-orb/

Happo 帮助您更快、更自信地构建用户界面(UI)。在不同浏览器和不同屏幕尺寸下拍摄的自动截图让你确切地知道当你按下一个新的提交时 UI 的哪些部分发生了变化。使用 CircleCI 的组织现在可以使用 Happo CircleCI orb 直接整合 Happo。

Happo showing a diff on a  component in different browsers. Diffs can be visualized in a few different ways, allowing you to see exactly what changed.

快乐圆环球

通过抽象在持续集成(CI)环境中设置 Happo 运行所涉及的一些样板文件,orb 允许您将.circleci/config.yml文件中的配置简化为:

version: 2.1
orbs:
  happo: happo/happo@1.0.0
workflows:
  version: 2.1
  run_all:
    jobs:
      - happo/run 

API 令牌应该在HAPPO_API_KEYHAPPO_API_SECRET环境变量中定义。你可以在 happo.io 的账户设置中找到这些代币。

如果您需要在 Happo 运行之前或之后执行其他步骤,那么您可以使用happo/run_happo命令来代替happo/run作业,为您提供对执行环境的更多控制。以下是在运行一些自定义准备步骤后使用happo/run_happo命令的配置示例:

version: 2.1
orbs:
  happo: happo/happo@latest
jobs:
  happo:
    docker:
      - image: circleci/node:10
    steps:
      - checkout
      - npm install
      - npm run build-assets
      - happo/run_happo 

一旦你在 happo.io 上注册并且建立了你的测试套件,运行 Happo CircleCI orb 将确保为你推送的每个提交生成截图。对于拉请求构建和分支,您将获得一个链接,链接到基本提交和分支/PR 头的截图之间的比较。

Example log from the CircleCI UI

您的 Happo 帐户与 Github 存储库配对时,构建状态会发布到您的 pull 请求和提交中,让您可以快速查看是否有差异。详情链接将带您进入 Happo 报告,您可以在这里接受或拒绝差异。

Example of a Happo status posted to a pull request on github.com.

最后

Happo 为您的应用程序带来了强大且响应迅速的跨浏览器屏幕截图测试。Happo CircleCI orb 使与 CI 环境的集成变得前所未有的简单。我们非常自豪能够成为 CircleCI 的技术合作伙伴计划的一部分,我们对 orbs 如何帮助我们当前和未来的用户让生活变得更加轻松感到兴奋。


Henric Trotzig 是一名软件工程师,对优秀的用户界面充满热情。他是跨浏览器截图测试服务 happo.io 的创始人。

用 Cypress | CircleCI 测试 React 组件

原文:https://circleci.com/blog/cypress-react-component-testing/

本教程涵盖:

  1. 创建和设置 React 应用程序
  2. 用 Cypress 测试 React 组件
  3. React 的自动化组件测试

组件是可重用的代码,在大多数情况下,它们独立工作和运行。如果您想确信组件工作正常,您需要测试它们。很方便,Cypress.io 设计了他们的测试框架,包括组件测试。本教程说明了端到端(E2E)和组件测试之间的区别,以及使用这些方法时需要考虑的事项。然后,您将学习如何使用 Cypress 进行组件测试。

先决条件

要轻松跟进,您需要:

  1. Node.js 已安装
  2. CircleCI 账户
  3. GitHub 账户和对 Git 的理解
  4. 扎实的 JavaScript 和 React 知识
  5. 了解管道的工作原理
  6. 演示版 React 应用的克隆版

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

注意:React 组件已经开发完成,可以在src/components/App.js下的克隆存储库中找到。为了简单起见,本教程的重点是测试已经开发的 React 组件。

在下一节中,我们将看看组件测试以及它与端到端测试的比较。

什么是组件测试?

组件是软件程序中可区分的部分。表单、行动号召和网站搜索都是 web 应用程序组件的例子。Web 组件可以是任何东西,从简单的动作按钮到完整的注册表单提交。

组件测试也称为程序或模块测试。该过程包括从主应用程序中独立地验证和确认特定组件的功能、性能和符合性。这种测试仅限于特定的组件,并且由于易于测试的特性而变得简单。

组件测试与端到端测试

E2E 测试决定了应用程序的流程从开始到结束是否按预期进行。E2E 测试包括测试与第三方 API 和服务的集成。关键功能在整个应用程序中得到测试。Cypress 使用浏览器运行端到端测试,就像用户与应用程序交互一样。应用程序的基本端到端测试可能包括用户注册、确认电子邮件、登录、配置文件更新和注销。

端到端测试比组件测试更全面,更慢,也更容易flakiness。组件测试是专门的、快速的和可靠的。由于范围的原因,端到端测试通常需要一个复杂的设置阶段。组件测试不需要复杂的配置。

Cypress 中的端到端测试可以由开发人员、专业测试工程师或质量保证团队编写。通常,组件开发人员自己编写组件测试。开发人员可以在构建组件时轻松验证组件所需的功能。当您在本教程的后面编写实际的组件测试时,您会注意到 Cypress 端到端测试中的初始化命令是cy.visit(url)。组件测试使用cy.mount(<MyComponent />)。一个测试良好的应用程序包括端到端的组件测试,每组测试都专注于它们执行得最好的任务。

注意: 如果代码或测试本身没有任何变化,测试通过或失败就被认为是“易变的”。这意味着要么是不稳定的系统,要么是糟糕的设计/编写的测试。在中了解如何减少不稳定的测试失败

组件测试的优势包括:

  • 检测模块缺陷
  • 组件是独立测试的,而不是作为整个应用程序的一部分
  • 有限的范围使它们快速可靠
  • 减少开发时间
  • 易于设置特定场景
  • 不需要外部系统

要了解更多关于比较测试方法的信息,请访问 Cypress 测试类型

现在您已经理解了 Cypress 组件测试,接下来的部分将向您展示如何在应用程序中配置 Cypress,如何编写测试,以及如何运行它们。

使用 Cypress 进行组件测试

要使用 Cypress E2E 测试来测试 React 应用程序,您可以在本地开发服务器上运行应用程序,而 Cypress 在单独的终端上运行。Cypress 使用命令cy.visit(url)访问您的应用程序,并在加载的页面上运行断言。

Cypress 的最新版本包括一个内置的开发服务器,在执行组件测试时不需要本地开发服务器。Cypress 中的内置服务器负责在浏览器中安装和呈现组件。它只安装和呈现独立于主应用程序的组件。

Testing in Cypress overview

下图显示了 Cypress 按照以下顺序运行组件测试:

  • cy.mount()命令安装组件
  • 呈现组件
  • 使用 Cypress 命令测试零部件属性

特定点的失败会导致测试失败。

编写组件测试

本节详细介绍了如何配置 Cypress 并为克隆的新闻简报订阅组件编写测试。在编写组件测试之前,您需要安装 Cypress 并将其与 React 集成。

要安装 Cypress,请输入:

npm install cypress --save-dev 

这将在本地安装 Cypress 作为项目的开发依赖项。

要打开应用程序,请输入:

npx cypress open 

此命令打开 Cypress launchpad。

选择组件测试

Select testing option

确认前端框架和捆绑器,点击下一步。

Select frontend framework

开发依赖项已经安装。点击继续进入下一步。

Install dev dependencies

然后 Cypress 为您选择的测试类型生成配置文件。点击继续按钮。

Cypress generated configuration files

单击您喜欢的浏览器继续。

Choose a browser

您想从头开始编写测试,所以选择 Create new empty spec

Create a spec

给规格命名,然后点击创建规格按钮。

Path to spec

现在您有了一个包含示例代码的规范文件。运行文件。

Run spec

现在您可以开始为您的时事通讯订阅表单创建测试了。测试应验证这些操作是否发生:

  • 组件安装正确
  • 输入字段中有一个占位符
  • 订阅后会返回一条成功消息

在刚刚创建的规范文件中,用以下代码替换生成的代码:

// cypress/component/NewsLetterSubscription.cy.js file

import App from "../.././src/components/App";

describe("NewsLetterSubscription.cy.js", () => {
  describe("NewsLetterSubscription.cy.js", () => {
    it("Check input field for placeholder", () => {
      cy.mount(<App />); // mount the component
      cy.get("input").should(
        "have.attr",
        "placeholder",
        "Subscribe to our newsletter"
      ); // check the placeholder in the input field
    });
    it("test newsletter subscription", () => {
      cy.mount(<App />); // mount the component
      cy.get('[data-test="email-input"]').type("test@gmail.com"); // Type email
      cy.get('[data-test="submit-button"]').click(); // Click on submit button
      cy.get('[data-test="success-message"]')
        .should("exist")
        .contains("Thank you for subscribing to our newsletter"); // Check if success message is displayed
    });
  });
}); 

这个测试套件首先用cy.mount()命令安装组件。然后使用cy.get() 函数检查输入字段是否有一个包含文本的占位符。

当您输入电子邮件地址并单击 subscribe 按钮时,应该会返回一条成功消息。

转到 Cypress 浏览器以确保测试通过。

Passing tests

恭喜你,你有一个经过测试的 React 组件!下一步是将您的测试集成到一个 CI/CD 管道中。为此,您可以使用 CircleCI。

配置 CircleCI

要开始 CircleCI 配置,在项目的根目录下创建一个.circleci文件夹。在其中,创建一个名为config.yml的文件。为了使配置简单一点,你可以使用 CircleCI orbs

注意: 在你的项目中使用赛普拉斯圆环宝珠之前,从组织设置中,允许使用未经认证的宝珠。Settings -> Security -> Allow uncertified orbs

将以下内容输入到config.yml文件中:

version: 2.1 # Use 2.1 to make use of orbs and other features
orbs: # An orb is a reusable package of CircleCI configuration that you may share
  # across projects, enabling you to create encapsulated, parameterized commands, jobs, and
  # executors that can be used across multiple projects.
  cypress: cypress-io/cypress@1
workflows: # Workflows are a declarative way to orchestrate jobs and their run order.
  build:
    jobs:
      - cypress/run: # Run the cypress/run job from the cypress orb
          command: npx cypress run --headless --component # Run the cypress run command in headless mode 

将项目添加到 GitHub,然后登录到您的 CircleCI 帐户。

在“项目”选项卡的列表中查找项目。点击设置项目

Select a project

输入main作为包含 CircleCI 配置的 GitHub 分支的名称。点击设置项目

Enter the branch where config lives

CircleCI 启动您的管道,它将运行测试。几分钟后,您的测试应该会通过。单击绿色成功徽章查看详细信息。

Successful workflow

太好了!您已经成功地设置了每次更改代码时都要运行的测试。

结论

在这篇文章中,你学习了什么是组件测试,它与 E2E 测试有什么不同,它有什么好处。您学习了如何使用 Cypress 进行组件测试,在 React 项目中配置 Cypress,在 Cypress 测试中测试组件,以及如何运行测试。您通过设置 CircleCI 来自动执行组件测试,从而完成了项目。我希望你喜欢阅读这篇文章,就像我喜欢创作它一样。直到下次,继续学习!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

有效 Python 应用程序的数据结构

原文:https://circleci.com/blog/data-structures-for-python-applications/

本教程涵盖:

  1. 什么是 Python 数据结构以及如何使用它们
  2. 如何实现由 Python 数据结构支持的 API
  3. 如何自动化 Python API 的测试

因为计算机依赖数据来执行指令,所以计算总是需要数据交互。在现实世界的应用程序中,数据量可能是巨大的,因此开发人员必须始终如一地设计方法,以编程的方式快速有效地访问数据。

对于专门开发工具和系统的团队来说,对数据结构的深刻理解是一个很大的优势。以最佳方式组织数据可最大限度地提高效率,并使数据处理变得简单无缝。在本教程中,您将了解 Python 中的数据结构,如何使用它们来构建高效、高性能的应用程序,以及如何使用持续集成来自动测试您的 Python 应用程序。

先决条件

完成本教程需要以下项目:

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

什么是数据结构?

数据结构是一种组织和管理内存中数据的方法,可以有效地对数据执行操作。使用不同的数据类型构建数据结构和定义保存数据的变量有一些最佳实践。

为什么需要 Python 数据结构?

随着系统复杂性的增长,数据也在增长。如今,处理大量数据经常会导致处理器速度问题、数据搜索和排序效率低下,以及处理多个用户请求时的问题。这些问题对性能至关重要,必须加以解决,以实现任何系统的最高效率。

数据结构用来决定一个程序或系统如何运行。按顺序搜索阵列中的数据将会非常耗时且耗费资源。这可以通过使用诸如散列表的数据结构来解决。

数据抽象隐藏了数据结构的复杂细节,因此客户端程序不必知道实现细节。这是通过抽象数据类型完成的,它为您的应用程序提供了抽象。

Python 中的数据结构概述

Python 提供了内置的数据结构,比如列表、字典、集合和元组。Python 用户可以创建自己的数据结构,并最终控制它们的实现方式。栈、队列、树、链表和图形都是用户定义的数据结构的例子。

本教程将重点介绍列表和字典,以及开发人员如何使用它们来优化应用程序中的数据存储和检索。

下一节的重点是使用列表和字典的优化操作来存储、处理和检索数据结构中的数据。

列表

列表是元素的有序集合。因为列表是可变的,所以它们的值可以改变。项目是包含在列表中的值。

注:Python 数据结构的类型决定了它的可变性。可变对象可以改变它们的状态或内容,而不可变对象不能。

使用方括号来表示 Python 列表。下面是一个空列表的示例:

categories = [ ]

逗号(,)用于分隔列表中的项目:

categories = [ science, math, physics, religion ]

列表还可以包含列表项目:

scores = [ [23, 45, 60] , [67, 69, 90] ]

index,仅仅是值在列表中的位置,是用来访问列表中的元素的。以下是如何访问列表中各种项目的示例:

categories = [ science, math, physics, religion ]

输出:

categories [0]  # science
categories [1]  # math
categories [2]  # physics 

您还可以使用负索引来访问从列表末尾开始的项目。例如,要到达前面列表中的最后一项:

categories [-1] # religion

您可以添加、删除和修改列表中的项目,因为列表是可变的。

要更改列表中项的值,请引用该项的位置,然后使用赋值运算符:

categories [ 0 ] = “geography” # modifies the lists, replacing “science” with “geography”

要向列表中添加新项目,请使用append()方法,该方法将项目添加到列表的末尾:

categories .append( “linguistics” )

您可以在列表上使用的另一个方法是insert(),它在列表中的随机位置添加项目。其他列表对象包括del()pop()clear()sort()

字典

字典是 Python 内置的key-value对数据类型的集合。与列表不同,字典是由关键字索引的,关键字可以是字符串、数字或元组。通常,字典键可以是任何不可变的类型。

字典的键必须是不同的。花括号{}用来表示字典。

键使得使用字典和存储各种类型的数据变得简单,包括列表甚至其他字典。您可以使用字典的键对字典进行访问、删除和其他操作。关于字典要记住的一件重要事情是,用一个已经存在的键存储数据将会覆盖以前与那个键相关联的值。

下面是一个使用字典的例子:

student = { “name”: “Mike”, “age”: 24, “grade”: “A” }

要访问上述字典中的项目:

student[ ‘name’ ] # Mike

向字典中添加数据就像 Dict[key] = value 一样简单:

student[ ‘subjects’ ] = 7

Python 字典方法有len()pop()index()len()popitem()

下面的常用方法允许它从字典中返回值。

dict.items()     # return key-value pairs as a tuple
dict.keys()      # returns the dictionary's keys
dict.get(key)  # returns the value for the specified key and returns None if the key cannot be found. 

下图显示了内置和用户定义的不同类型的 Python 数据结构。

Python data structures

在本教程的下一节中,您将使用刚刚学到的知识创建一个简单的 API,它将允许您存储、操作和检索数据结构中的数据。

API 流程图和存储

现在您已经知道了什么是列表和字典,您可以使用它们来创建一个 API 端点,它具有登录功能,并且数据只存储在数据结构中。您可以观察数据如何在应用程序中流动,以及如何在 API 中使用数据结构。

Python API data flows

这个 API 图显示了一个数据存储,它是一个 Python 字典。它是用示例用户数据初始化的,因为这允许您充分探索数据结构的功能。步骤被标记为14以显示通过 API 的数据流。

步骤一让用户通过输入他们的first namelast nameusernamedate of birth来创建账户。这些细节都保存在users字典里。

第二步检索系统中的所有用户。在发送回数据之前,它会从嵌套字典转换为排序列表。

第三步用一个Id和一个username认证一个用户。

第四步是数据结构在向客户端发回响应之前进行实际处理的地方。

既然您已经知道了 API 是如何工作的,那么您就可以将数据结构放到实际的应用程序中了。

用数据结构实现 API

用数据结构实现由以下步骤组成:

  • 设置 API 框架
  • 正在初始化用户
  • 创建用户
  • 正在检索用户

设置 API 框架

为了继续本教程,我鼓励您克隆应用程序。这样,您可以浏览应用程序并理解教程中没有完整记录的部分。

git clone https://github.com/CIRCLECI-GWP/python-api-with-datastructures

cd python-api-with-datastructures 

要安装 Python 依赖项,您需要使用以下命令设置虚拟环境:

视窗操作系统

py -3 -m venv venv;

venv\Scripts\activate; 

Linux/macOS

python3 -m venv venv

source venv/bin/activate 

从 requirements.txt 文件安装需求:

pip install -r requirements.txt 

要启动 API,请运行:

python main.py 

设置和启动 API 框架的工作非常出色!下一步是修改您的路由并创建一个链表来处理用户验证和数据转换。

正在初始化用户

考虑到您的应用程序状态仅在服务器运行时持续,您将创建一个用户字典,该字典将用示例数据进行初始化。为此,在 Flask 应用程序配置之后,手动将数据添加到用户字典的main.py文件中。

修改后的字典应该是这样的:

# main.py

users = {
   1: {"fname": "John", "lname": "Doe", "username": "John96", "dob":    "08/12/2000"},
   2: {
       "fname": "Mike",
       "lname": "Spencer",
       "username": "miker5",
       "dob": "01/08/2004",
   },
} 

现在,即使您的服务器停止运行,当您测试您的端点或创建新的应用程序数据时,您也将始终有内存中的数据可供参考。

创建用户

初始化用户数据字典后,创建一个create user函数来创建用户。使用请求库,因为这将是一个 API 请求,用户凭证将通过提交进入。

使用请求库- data = request.get_json()中的get_json()方法解析传入的 JSON 请求数据,并将其存储在一个变量中。任何系统都不应该允许重复记录,您的 API 也不例外。因此,在创建新用户时,请确保新用户的详细信息与任何可用记录都不匹配。如果相同的数据已经可用,则通知用户并暂停该过程。复制这段代码并粘贴到main.py文件中:

# main.py
@app.route("/user", methods=["POST"])
def create_user():

   data = request.get_json()

   if data["id"] not in users.keys():
       users[data["id"]] = {
           "fname": data["fname"],
           "lname": data["lname"],
           "username": data["username"],
           "dob": data["dob"],
       }
   else:
       return jsonify({"message": "user already exists"}), 401

   return jsonify({"message": "user created"}), 201 

这段代码首先通过在用户字典的关键字中搜索相似的 id 来确定用户id是否已经存储在用户数据存储中。检查id的可用性不是程序化的;相反,您可以在生产中检查用户的电子邮件。

如果检查通过,新的用户信息将被输入到字典中,使用惟一的用户 id 作为键。当根据用户 id 存储字典时,这种模式会产生一个嵌套字典。

Flask 包含一个名为jsonify的函数,允许您将数据序列化为 JSON 格式,您将使用它来格式化发送回客户端的消息。

正在检索用户

获取用户可以像返回users字典一样简单,但是有一种更好的方法。相反,为什么不按降序返回所有用户,把最近创建的用户放在最上面呢?

不幸的是,在 Python 3 中字典不再是可排序的,所以它们不能被排序。相反,您可以使用以下代码片段:

# main.py
@app.route("/users", methods=["GET"])
def get_users():

   all_users = []

   for key in users:
       all_users.append(users[key])
       users[key]["id"] = key

   all_users.sort(key=lambda x: x["id"], reverse=True)

   return jsonify(users), 200 

这将在前面的代码块中创建一个空列表,然后遍历users字典值,将每个值追加到列表中。此外,每个用户都需要一个惟一的标识符,所以在列表中添加一个id是一个好主意。

记住追加到列表之后,你就有了一个字典列表,你不能通过把你的嵌套字典转换成字典列表来欺骗 Python。这就是为什么您应该使用 lambda 函数将 id 指定为 sort 方法的键。结果是一个按照用户的id值降序排列的字典列表。

最后,在创建用户并实现一个按顺序检索用户的功能后,添加认证功能- /user/login -将会非常好。

# main.py
app.route("/users/login", methods=["POST"])
def login_user():

   data = request.get_json()

   id = data["id"]
   username = data["username"]

   if id in users.keys():
       if users[id]["username"] == username:
           return jsonify(f"Welcome, you are logged in as {username}"), 200

   return jsonify("Invalid login credentials"), 401 

在使用idusername之前,通过将发布的 id 与记录进行比较,确保这样的用户存在。如果存在匹配,您可以验证用户名。如果用户输入了有效的登录信息,请让他们登录,并显示一条带有他们的用户名的欢迎消息。相比之下,失败的登录只会显示一条消息,通知他们登录失败。

开始测试您刚刚创建的三个端点:create a userlog them inretrieve all users added。如果出现任何问题,您总是可以参考位于克隆存储库中的main.py文件。

创建用户的 API 调用

Creating a user

用户登录的 API 调用

User login

检索所有用户的 API 调用

Retrieving all users

使用在列表和字典中存储数据的能力,您可以验证 API 是否按预期工作。

为您的 API 编写测试

没有经过测试的代码已经被破坏了,这可能是乏味和耗时的,但是向应用程序中添加测试从来都不是真正的损失。教程的这一部分包括对您刚刚创建的 API 端点的用户创建、多用户创建、登录和用户检索的测试。我将指导您使用 Python 应用程序测试工具Pytest测试您的端点。您将编写的第一个测试是创建一个用户:

# tests/test_app.py
def test_create_user(client):

    response = client.post(
        "/user",
        json={
            "id": 4,
            "fname": "James",
            "lname": "Max",
            "username": "Maxy",
            "dob": "08/12/2000",
        },
    )

    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 201 

这个片段中的代码创建了一个新用户,id 为4,名字为James,姓氏为Max。然后,它断言响应的内容类型是 JSON,并且对于创建的资源,状态代码是201

接下来创建一个测试,以验证该测试可以获取创建的用户:

 def test_fetch_users(client):

    response = client.get("/users")

    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 200 

该测试验证端点是否返回 JSON 响应,以及成功请求的状态代码是否为200。这两个测试只是一个开始;在文件tests/test_app.py的根目录下有更多的测试。从命令行运行pytest来执行您的测试。

Successful PyTest execution

通过测试验证了从 Python 数据结构创建的 API 端点的行为方式与使用实际数据库的 API 端点的行为方式相同。

既然您的测试已经在本地通过,那么将它们与您的持续集成环境集成,以确保部署到 GitHub 存储库的更改不会破坏应用程序。对于教程的这一部分,我们将使用 CircleCI 作为 CI 环境。

与 CircleCI 集成

要将 CircleCI 配置添加到您的项目中,请在项目文件夹的根目录下创建一个名为.circleci的新目录。在该目录中,创建一个名为config.yml的文件。将此配置添加到.circleci/config.yml文件中:

version: 2.1
orbs:
  python: circleci/python@1.5.0
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.10.2
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
      - run:
          name: Run tests
          command: pytest
workflows:
  sample:
    jobs:
      - build-and-test 

这个 CircleCI 配置是如何配置 CircleCI 来运行测试的简单示例。它指定您正在使用 Python Docker 映像,然后使用pip包管理器安装 Python 包,并使用pytest命令运行您的测试。

使用 Git 提交所有更改过的文件,使用将您的更改推送到现有的 GitHub 存储库中。

设置 CircleCI

现在您已经有了远程 GitHub 分支的代码,您可以设置 CircleCI 来运行您的测试。进入 CircleCI 仪表盘并选择项目选项卡。在列表中找到您的存储库。对于本教程,它是python-api-with-datastructures存储库。

Project repository

选择设置项目选项。因为您已经将 CircleCI 配置推送到了远程存储库,所以您只需要键入包含该配置的分支的名称,然后单击设置项目

Configuring CircleCI

坐下来,看着您的测试在 CircleCI 中执行。

Successfull CI test execution

您的测试成功通过,这只能意味着一件事:是时候庆祝了!

结论

通过学习本教程,您已经对 Python 数据结构、为什么需要它们以及如何在 Python 中使用列表和字典数据结构有了深入的了解。您还学会了只使用数据结构来编写端点。您为 API 端点编写了测试,以避免破坏现有的更改。您学习了如何集成 CircleCI,并观察了 CircleCI 在 CI 平台上执行您的测试。

一如既往,我很高兴为您创建本教程,我希望您会发现它很有价值。直到下一个,继续学习,继续建设!


Waweru Mwaura 是一名软件工程师,也是一名专门研究质量工程的终身学习者。他是 Packt 的作者,喜欢阅读工程、金融和技术方面的书籍。你可以在他的网页简介上了解更多关于他的信息。

阅读更多 Waweru Mwaura 的帖子

使用 Rookout orb | CircleCI 调试 CircleCI 环境

原文:https://circleci.com/blog/debug-your-circleci-environments-using-the-rookout-orb/

我们在 Rookout 的目标,快速调试解决方案,是帮助开发人员快速有效地调试,即使在其他地方很难。动态创建的试运行或测试环境在每次创建时都可能有不同的配置,这会导致不可预测的结果和无法重现的错误。调试这样的环境有时就像在生产中调试一样困难和令人沮丧。CircleCI orbs 使得与 Rookout 的集成变得很容易 Rookout 是一种解决方案,它使得在生产、登台或测试环境中进行调试就像在本地机器上进行调试一样容易。

有了 Rookout,您可以保持您的 CI/CD 流的新鲜和稳定,允许您的团队无所畏惧地不断推出特性和修复。

了望基础知识

当您运行 CircleCI 工作流时,Rookout 会等待您的调试指令。可以把 Rookout 想象成一个基于 web 的 IDE,当它在调试模式下运行时,附加到您的代码上。

CircleCi Rookout Diagram

当您在代码中添加 Rookout 断点时,代码不会中断。反而会照常继续运行。当它到达断点时,调试数据将被异步发送到 Rookout IDE。这允许您在不中断 CI/CD 流的情况下调试代码。

CircleCI 设置

一旦你创建了一个 Rookout 项目并导入了你的源代码,从管理设置页面检索你的ROOKOUT_TOKEN。然后,将您的ROOKOUT_TOKEN设置为 CircleCI 项目中的环境变量。使用 Rookout orb 很简单,只需将它导入到你的.circleci/config.yml文件中:

orbs:
  rookout-node: circleci/rookout-node@0.0.2 

调用rookout-node/run_script命令:

jobs:
  my_job:
    docker:
      - image: circleci/node:10
    steps:
      - rookout-node/run_script:
          users_script: %YOUR_NODE_COMMAND% 

现在,您可以在希望调试的步骤设置一个 Rookout 断点,就像您在自己的 IDE 中本地调试它一样。一旦您再次触发您的构建,调试消息将被获取并发送到您的 Rookout IDE:

</blog/media/2019-02-06-CircleCI-Rookout-min.mp4>

在上面的例子中,您可以看到设置断点和调试代码就像在本地机器上调试本地代码一样简单。

包扎

我们很高兴成为 CircleCI 的技术合作伙伴计划的一部分,帮助开发人员调试他们自己的构建和测试步骤,并使开发周期更快、更有效。


Liran Haimovitch 是 Rookout 的首席技术官和联合创始人。他是现代软件方法论的倡导者,他的秘密热情是理解软件实际上是如何工作的。可以跟着他 @Liran_Last

使用 SSH 访问调试 CI/CD 管道

原文:https://circleci.com/blog/debugging-ci-cd-pipelines-with-ssh-access/

在 AWS re:invent 和 KubeCon 等行业活动中,我与许多开发人员进行了交流。开发人员经常讲述阻碍他们快速有效工作的事情。许多涉及与系统管理员、SREs 或 DevOps 同事的令人沮丧的互动。我听过好几次的一个故事涉及到这样一段对话:

dev: 嘿,SRE 队。我的构建失败了,我不知道构建节点中的应用程序发生了什么。它在 CI/CD 平台上失败了,但是构建脚本在我的开发环境中运行良好。我可以通过 SSH 访问平台上的构建节点,以便实时调试吗?

SRE: 一小时后怎么样?

dev: 一个小时?我需要完成这个,这样我就可以开始为下一个版本开发新功能了。您能否授予我访问构建节点的权限,以便我可以在构建失败的实际资源上进行调试?

SRE: 我们的 CI/CD 平台没有 SSH 访问,给你我的管理员凭证是违反安全的。

有时对话会继续:

dev: 我们安装的 SSH 插件怎么样了?这让我们可以向构建器节点发送控制台命令,这样我们就可以在系统日志中捕获响应。

SRE: 安全部门根据 CVE-2017-2648 将其标记为易受攻击,这使得中间人攻击成为可能。安全部门禁止它在我们的 CI/CD 平台上使用。我们没有进入节点的 SSH 功能来帮助您实时调试。抱歉。

它以开发人员的想法结束:

如果我只使用单元测试和堆栈跟踪日志,我将永远无法调试这个版本。我也可能是在猜测。

虽然这个对话是一个概括,但它是基于我在职业生涯中经历的和在活动中听到的真实互动和情况。这是非常常见的情况,大多数团队都经历过类似的事情。

当我从 CircleCI 用户那里听到这个故事时,我有一个很好的解决方案:一个有用且强大的 SSH 调试功能。这个特性允许开发人员在构建失败的资源上对构建进行故障排除和调试。让我告诉你它是如何工作的。

访问管道作业

在资源上和开发人员正常开发环境之外的环境中调试代码会带来挑战,会消耗宝贵的时间。如果没有使开发人员能够访问失败的构建并对其进行故障排除的特性,CI/CD 平台本身就会成为开发人员和 SRE 团队的巨大障碍。由于不能通过 SSH 访问构建节点,开发人员不得不求助于在构建实际失败的 CI/CD 环境之外调试失败的构建。他们必须尝试在其开发环境中复制 CI/CD 环境,以准确识别问题,然后尝试仅使用应用程序、堆栈跟踪和系统日志来解决问题。这种情况对所有相关人员来说都是巨大的时间浪费。

SSH 调试允许开发人员在发生故障的资源上轻松、安全地实时调试失败的构建。使用 CircleCI,SSH 调试还为开发人员提供了自助机制,使他们能够安全地访问执行环境,而不必依赖其他团队。这对开发人员和运营团队来说都是巨大的时间节省。

对失败的管道作业进行故障排除

当您使用 CircleCI 时,使用 SSH 访问构建作业很容易。我将通过一个常见的构建失败场景来展示如何对失败的构建作业进行故障排除。这里有一个示例config.yml供我们在这个场景中使用:

version: 2.1
workflows:
  build_test_deploy:
    jobs:
      - build_test
      - deploy:
          requires:
            - build_test
jobs:
  build_test:
    docker:
      - image: cimg/python:3.10.0
    steps:
      - checkout
      - run:
          name: Install Python Dependencies
          command: |
            pip install --user --no-cache-dir -r requirements.txt
      - run:
          name: Run Tests
          command: |
            python test_hello_world.py
  deploy:
    docker:
      - image: cimg/python:3.10.0
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: false
      - run:
          name: Build and push Docker image
          command: |       
            pip install --user --no-cache-dir -r requirements.txt          
            ~/.local/bin/pyinstaller -F hello_world.py
            echo 'export TAG=0.1.${CIRCLE_BUILD_NUM}' >> $BASH_ENV
            echo 'export IMAGE_NAME=python-cicd-workshop' >> $BASH_ENV
            source $BASH_ENV
            docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
            echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
            docker push $DOCKER_LOGIN/$IMAGE_NAME 

示例config.yml指定了一个双作业工作流管道,该管道测试代码,基于应用程序构建 Docker 映像,并将该映像发布到 Docker Hub 。要将新映像发布到 Docker Hub,该作业需要 Docker Hub 凭据。这些凭证是高度敏感的,并且在$DOCKER_LOGIN$DOCKER_PWD安全环境变量中被安全地表示。这些变量被设置为项目级环境变量。如果$DOCKER_LOGIN$DOCKER_PWD环境变量在项目级不存在,这个构建将在构建推送 Docker 映像步骤失败。

识别我们失败的工作

Failed build

失败的作业日志显示失败发生在 Docker 构建过程的开始,如该日志条目所示。

invalid argument "/python-cicd-workshop" for "-t, --tag" flag: invalid reference format
See 'docker build --help'.
Exited with code 125 

现在我们知道了管道中的问题所在,我们可以轻松地使用 SSH 访问重新运行这个失败的构建。当使用 SSH 访问重新运行作业时,管道将再次运行并失败,就像以前一样。但是这一次,运行时节点保持活动状态,并向用户提供有关如何访问失败的构建资源的详细信息。

使用 SSH 重新运行作业

要获得对失败构建的 SSH 访问,请从 CircleCI 仪表板中重新运行失败的作业。

  1. 登录 CircleCI 仪表板
  2. 点击失败的作业
  3. 单击仪表板右上角的向下按钮,然后选择使用 SSH 重新运行作业

Rerun failed build with ssh

该图显示了开发人员使用 SSH 访问资源时必须使用的访问细节的示例。

2019-02-26-amr-ssh-details.jpg

SSH 访问详细信息在构建结束时提供:

ssh -p 64535 100.27.19.200 

访问执行环境

现在我们可以通过 SSH 访问执行环境,我们可以对我们的构建进行故障排除了。如前所述,日志条目表明该问题与部署步骤的 Docker 构建部分有关。错误消息的invalid argument "/python-cicd-workshop"部分告诉我"/python-cicd-workshop"中缺少 Docker 用户名。

Docker 图像名称在config.yml文件的这一行中定义,由环境变量组成:

docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG . 

我们知道 Docker 映像构建失败是因为一个不合适的映像名称,并且我们知道该名称是由环境变量组成的。这表明失败与不正确或不存在的环境变量有关。打开一个终端,通过 SSH 进入执行环境。我们想运行一个命令来测试我们关于环境变量的假设是否正确:

$ printenv |grep DOCKER_LOGIN 

printenv |grep DOCKER_LOGIN命令告诉系统显示$DOCKER_LOGIN环境变量及其值。这个命令的输出将告诉我们是否设置了$DOCKER_LOGIN变量。如果命令没有返回值,那么我们知道系统在构建的初始执行时没有设置$DOCKER_LOGIN变量。在这种情况下,没有返回值。那是我们失败的原因。

修复构建

我们现在已经验证了我们丢失了$DOCKER_LOGIN环境变量。我们可以通过使用 CircleCI 仪表板将缺失的$DOCKER_LOGIN$DOCKER_PWD变量添加到项目中来修复构建。由于这些变量的值非常敏感,因此必须在 CircleCI 平台上定义并安全存储它们。您可以按照以下说明设置变量:

  1. 点击左侧菜单中 CircleCI 仪表板上的添加项目
  2. 在项目列表中找到并点击项目名称,点击设置项目
  3. 点击 CircleCI 仪表盘右上方的项目重心
  4. 在构建设置部分,点击环境变量
  5. 点击添加变量

Add an Environment Variable对话框中,定义该构建所需的环境变量:

  • 名称:DOCKER_LOGIN值:Your Docker Hub User Name
  • 名称:DOCKER_PWD值:Your Docker Hub Password

正确设置这些环境变量对于构建成功完成至关重要。

重新运行生成

既然我们已经为构建设置了所需的环境变量,我们可以重新运行失败的构建来测试我们的更改是否有效。转到 CircleCI 仪表板,点击重新开始开始项目的重建。等待您的构建成功完成。

2019-02-26-amr-ssh-build-success.jpg

包扎

在这篇文章中,我强调了对 CI/CD 管道进行 SSH 调试的必要性。我展示了 CircleCI 的 SSH 特性的强大功能,以及任何用户如何安全、轻松地访问他们的执行环境,并实时调试失败的构建。该特性使开发人员能够快速识别和修复他们的不完整构建,以便他们可以将时间和注意力集中到构建用户需要的新的和创新的特性上。

与工作流并行运行作业以减少构建时间| CircleCI

原文:https://circleci.com/blog/decrease-your-build-times-by-running-jobs-in-parallel-with-workflows/

持续集成/持续部署(CI/CD) 管道中运行构建是自动化重复部署和测试任务的一个很好的方式。然而,如果您有大量的测试、构建步骤或其他缓慢的设置任务,它会严重阻碍您的构建,并使反馈持续时间达到顶点!长的反馈周期对任何人都没有好处,而且会减慢开发生命周期,所以下一步合乎逻辑的就是尽可能地找到缩短持续时间的方法。我们中的一些人可能会在我们的构建步骤中并行运行测试,或者为了节省时间而走不必要的捷径,尽管这些解决方案最终可能并不总是最佳地工作。让我们看看如何利用 CircleCI 2.0 的工作流的力量,以及并行运行作业如何为我们的团队和我们打开一个全新的速度世界。

平行什么?

对于那些可能不熟悉的人来说,大多数常见的构建步骤,比如测试,都是顺序运行的:也就是说,每一步都单独运行,并且只在前一步之后发生。如果我们将我们漫长的、单一的构建过程分成不同的步骤,并同时运行它们,那么这些步骤可以说是并行或并发运行的。一个构建有五个步骤,每个步骤需要一分钟来运行,当按顺序运行时,需要五分钟来完成。如果我们采取这五个步骤中的每一个并且同时运行它们,我们的构建将只需要一分钟就能运行!与顺序运行相比,这是一个巨大的增长。

为了帮助说明顺序和并行的区别以及它们需要的时间,下面是一个构建的时间表:

连续的

 0:00------1:00------2:00------3:00------4:00------5:00
    Step1-----Step2-----Step3-----Step4-----Step5----- 

平行

 0:00------1:00
    Step1-----
    Step2-----
    Step3-----
    Step4-----
    Step5----- 

快得多

连续开始

在我们进入奇妙的工作流世界之前,让我们看一下一个演示项目,它有一些模拟的公共构建步骤。我们将花大部分时间使用 CircleCI 2.0 config.yml并观察仪表盘上的东西是如何工作的。我们将从查看配置开始:

 version: 2
    jobs:
      build:
        docker:
           - image: circleci/ruby:2.4.1
        working_directory: ~/repo
        steps:
          - checkout
          - restore_cache:
              keys:
              - v1-dependencies-{{ checksum "Gemfile.lock" }}
              - v1-dependencies-
          - run:
              name: Dependencies
              command: |
                bundle install --jobs=4 --retry=3 --path vendor/bundle
          - save_cache:
              name: Save Cache
              paths:
                - ./vendor/bundle
              key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          - run:
              name: Stage Deploy
              command: make build_stage
          - run:
              name: Database
              command: |
                bundle exec rake simulated_db_create
                bundle exec rake simulated_db_load
          - run:
              name: RSpec
              command: |
                bundle exec rake simulated_rspec
          - run:
              name: Cucumber Non-integrated
              command: |
                bundle exec rake cucumber
          - run:
              name: Selenium UI Checks
              command: |
                bundle exec rake simulated_selenium
          - run: 
              name: Deploy Prod
              command: |
                bundle exec rake simulated_deploy 

我们上面的build工作负责我们需要做的一切:管理我们的 gem 依赖项,部署到我们的登台环境,为单元测试/非集成测试旋转本地数据库,运行这些测试,运行 Selenium UI 检查,如果全部通过,部署到生产环境。这是一个非常简单的设置,确保我们在部署之前已经完成了所有的工作,尽管它是连续的,因此很慢。

这样的时代

Workflow step times

我们上面的工作在 6:43 开始,虽然这并不可怕,但我们想要最快的反馈,我们在这里是为了学习并行性,所以让我们开始吧!

我们跑的步数最长的是硒和黄瓜,分别用了 3:00 和 2:04。在 Ruby 中,有一个parallel_tests gem,我们可以用它来并行运行我们的 Cucumber 测试,这对于我们等待的两分钟来说应该是一个简单的修复。

Parallel Cucumber step

时间上的进步真大!这一增加并行化的小变化将我们的总时间降低到了 5:23 !但是,缺点是,当我们在测试期间查看控制台输出时,它看起来像这样:

</blog/media/2018-11-09%20gif%201.mp4>

我们的构建输出曾经是整洁的、易于遵循的,并且可能对调试有所帮助,但现在却是一团乱麻,除了看到发生了某些事情之外没有任何用处。examples 部分应该只包含正在运行的特定场景的字符串、整数或浮点数,但是因为所有场景都在同时运行,所以它们的输出也在同时显示。这种不理想的结果是一些并行化路线如何以牺牲易用性为代价来减少我们的运行时间的主要例子。

相比之下,下面是没有运行 cumber 和parallel_tests gem 时的输出:

</blog/media/2018-11-09%20gif%202.mp4>

我们的 3:00 Selenium 工作仍然占据了我们构建时间的绝大部分,我们将通过重构来处理这个问题。

平行区

如果作业是步骤的集合,那么工作流可以被认为是作业的集合。类似于我们的顺序与并行的例子,让我们看一下仅使用单个构建作业的配置与使用工作流的配置的比较。我们将使用前面五个作业的例子,每个作业运行一分钟,并使它成为一个真正的设置。

 version: 2
    jobs:
        build: 
            docker:
                - image: circleci/ruby:2.4
            steps:
                - run: sleep(60) && echo 1
                - run: sleep(60) && echo 2
                - run: sleep(60) && echo 3
                - run: sleep(60) && echo 4
                - run: sleep(60) && echo 5 

以下是仪表板中的内容:

One job five steps

如上所述,工作流是作业的集合,因此我们的项目看起来像是利用工作流的并行化而重构的:

 version: 2
    jobs:
        one:
          docker:
                - image: circleci/ruby:2.4
          steps:
            - echo 1
        two:
          docker:
                - image: circleci/ruby:2.4
          steps:
            - echo 2
        three:
          docker:
                - image: circleci/ruby:2.4
          steps:
            - echo 3
        four:
          docker:
                - image: circleci/ruby:2.4
          steps:
            - echo 4
        five:
          docker:
                - image: circleci/ruby:2.4
          steps:
            - echo 5

    workflows:
        version: 2
        build:
            jobs:
                - one
                - two
                - three
                - four
                - five 

虽然上面的重构没有针对 dry 进行优化,但是它展示了一个非常基本的配置重构,以使用工作流。每一个原始步骤都被移动到上半部分的单个作业中,然后在下半部分,我们告诉 CircleCI 我们希望它们作为工作流集合运行。

那么这给我们带来了什么?它将我们从在 5:02 开始的连续构建步骤减少到总构建时间 1:03

Five jobs as workflow

这看起来与我们的顺序作业非常相似,那么区别在哪里呢?这就是 CircleCI 在工作流中显示作业的方式。虽然看起来很相似,但它们都是单独的作业,包含步骤、环境等,都是同时运行的。现在,我们已经完成了工作流速成课程,让我们来看看更复杂的工作流设置,然后回到我们的示例。

将这一切结合在一起

让我们一起来看看工作流的强大力量能为我们的项目做些什么。简单回顾一下,我们最初的项目是按顺序运行的,没有尝试并行运行子测试。那个版本的构建花了大约 10 分钟。

通过工作流并行运行我们的工作将我们的构建时间削减到大约 1:50!

从 10 分钟到不到 2 分钟,通过一些重构来利用并行作业!我们甚至没有并行运行所有的作业!这是我们的项目及其工作流中新重构的并行作业:

Parallel jobs in workflow

Woah”~山谬·里维****

**上面的每个条目都是一个作业,每个作业都有一组步骤。

Workflow job steps

上面的工作流程说明了一些不同的 CircleCI 概念:

  • 共享资源 -在多个任务中重用依赖关系/数据以节省时间。
  • 将作业放在其他作业之后——要求其他作业在其他作业之前完成,以确保我们的依赖项已经下载,或者其他需求已经完成了它们的配置。
  • 小型独立作业——将 Selenium 和 Cucumber 测试分解成更小的块,使我们能够获得并行性的强大功能,同时仍能获得我们需要的有用数据,允许尽可能多的作业在失败的情况下通过,还允许我们使用工作流方便的“从失败中重新运行”选项更快地重新运行。

现在让我们看看重构后的配置文件。请注意,这是缩写。你可以在这里的 gist 文件中找到整个配置。

 aliases:
        - &restore_gem_cache
          ... # repetitive data from the previous config has been omitted for length
        - &save_gem_cache
          ...
        - &bundle_install
          ...
        - &attach_workspace
            attach_workspace:
                    at: ~/data
        - &dependencies
            - checkout
            - *attach_workspace
            - restore_cache: *restore_gem_cache
            - run: *bundle_install
            - save_cache: *save_gem_cache
            - persist_to_workspace:
                root: .
                paths:
                    - vendor/bundle
        - &db_setup
          ...
        - &database
            - checkout
            - *attach_workspace
            - run: *bundle_install
            - run: *db_setup
        - &run_rspec
          ...
        - &rspec_steps
            - checkout
            - *attach_workspace
            - run: *bundle_install
            - run: *run_rspec
        - &cucumber
            name: Cucumber
            command: |
                echo "cucumber feature - ${FEATURE}"
                bundle exec cucumber ${FEATURE}
        - &selenium
          ...
        - &selenium_steps
            - checkout
            - *attach_workspace
            - run: *bundle_install
            - run: *selenium
        - &cucumber_steps
            - checkout
            - *attach_workspace
            - run: *bundle_install
            - run: *cucumber
        - &stage_deploy
          ...
        - &deploy_stage
            - checkout
            - run: apk add --update make
            - run: *stage_deploy
        - &prod_deploy
          ...
        - &deploy_prod
            - checkout
            - *attach_workspace
            - run: *bundle_install
            - run: *prod_deploy
        - &deps
            - "Dependencies"
        - &deps_and_deploy
            - "Dependencies"
            - "Stage Deploy"
        - &deps_and_db
            - "Dependencies"
            - "Database"
        - &all_test
            - "RSpec"
            - "Cucumber Number Addition"
              ...
            - "Selenium Firefox"
              ...
        - &deploy_environment
            working_directory: ~/data
            docker:
                - image: alpine:latest
        - &test_environment
            working_directory: ~/data
            docker:
                - image: ruby:2.4
    version: 2
    workflows:
        version: 2
        master:
            jobs:
                - "Stage Deploy"
                - "Dependencies"
                - "Database":
                    requires: *deps
                - "RSpec":
                    requires: *deps_and_db
                - "Cucumber Number Addition":
                    requires: *deps_and_db
                  ...
                - "Selenium Firefox":
                    requires: *deps_and_deploy
                  ...
                - "Deploy Prod":
                    requires: *all_test
    jobs:
        "Stage Deploy":
            <<: *deploy_environment
            steps: *deploy_stage
        "Dependencies":
            <<: *test_environment # All following jobs will use this environment, however its been excluded for length
            steps: *dependencies
        "Database":
            steps: *database
        "RSpec":
            steps: *rspec_steps
        "Cucumber Number Addition":
            environment:
                FEATURE: features/number_addition.feature
            steps: *cucumber_steps
        ...
        "Selenium Firefox": 
            environment:
                platform: firefox
                version: latest
            steps: *selenium_steps
        ...
        "Deploy Prod":
            steps: *deploy_prod 

现在,我们的配置文件中有很多内容,所以让我们仔细阅读一下,这样我们会感觉舒服一些。有些是 YAML 特性,有些是 CircleCI & workflow 特性。

亚姆

别名别名在整个配置中被用作变量或指向我们不想重复输入的信息的指针。它们都在我们的配置的上半部分声明,由一个符号(&)引导,并使用一个星号(*)引用。人们也可以使用 CircleCI 2.1 的特性来编写可重用执行器,而不是使用 YAML 别名的

哈希合并哈希合并用于合并我们配置中的两个键值数据集合。上面使用了这种技术来添加我们想要的 Docker 容器。

CircleCI 和工作流功能

跨作业持久保存数据工作流有一个工作空间,可用于存储数据,供随后的作业使用。在我们的例子中,我们持久化我们的依赖项,这样我们就不必继续下载它们了。持久化数据与缓存相结合使得依赖关系处理速度更快!

如上所述,我们可以在管理我们依赖关系的作业之后安排作业。我们还使用门控来确保在部署到生产环境之前通过所有测试。如果没有门控,它都在同一时间运行。

环境变量我们使用分配环境变量用于跨 Cucumber 和 Selenium 作业的命令,以准确传递我们想要为每个作业运行的内容。在黄瓜测试中,它们不是一片混乱,而是各自独立运行,输出是原始的。

类似地,我们将单个 Selenium 任务重构为多个任务,集中在特定的平台组合上进行测试。这样,如果一个平台出现故障,其他平台将继续运行,不会出现问题。

多棒的旅行啊!

我们从 Sequentialville 开始,到 Parallel Paradise 结束,这都要感谢 CircleCI 的工作流和并行作业。我们的构建现在运行快了将近 74 %,因此我们能够从更快的反馈中获益。少一些等待,多一些成就!通过更频繁地运行构建,使您的增量步骤更小,并使您自己能够更安全地撤销!

通过并行运行您的作业,您的团队可以削减多少构建时间?


Gemini Smith 是一名不断学习的软件工程师,对测试、交流和改进软件开发生命周期充满热情。主要用 Go 写作,她还通过公开演讲、宣传、咨询和指导对软件和测试社区做出贡献。

阅读更多 Gemini Smith 的文章**

macOS 专用主机现已推出| CircleCI

原文:https://circleci.com/blog/dedicated-hosts-for-mac-os/

CircleCI 现已推出 macOS 专用主机。这一新的支持层专为 macOS 构建,并在 CircleCI 上为苹果开发者提供前所未有的存储、安全性和可扩展性。

通过保留专用主机,团队可以解锁对裸机实例的访问,该实例提供对整个主机 24 小时的独占访问。与运行在虚拟机上的其他 CI/CD 提供商的解决方案不同,专用主机提供对 GPU 的访问,从而实现音频和视频测试。

我们的主机具有 200GB 的存储和 12 个 vCPU 核心,以支持最高的构建量。这意味着团队可以在 CircleCI 上扩展他们的 macOS 和 iOS 版本,而不用担心存储限制。所有的作业都将在一个独立的主机上运行,以保证代码的安全性。我们的专用主机支持与我们的虚拟机相同的 Xcode 版本,因此您将能够在您的应用程序需要的版本上运行您的构建。

CircleCI 内容营销总监 Gillian Kieser 介绍 macOS 专用主机。

所有付费计划都提供专用主机,价格为 100 信用点/分钟,最短租期为 24 小时。要了解更多关于 macOS 专用主机的信息,请访问文档。你也可以在这里阅读更多关于 CircleCI 如何支持 macOS 的信息

深入 CircleCI 工作区- CircleCI

原文:https://circleci.com/blog/deep-diving-into-circleci-workspaces/


这篇文章是我们关于在工作流中保存数据的概述的后续。要了解如何最好地使用工作区、缓存和工件,请在这里阅读我们的介绍性帖子


工作空间是工作流的一项功能,用于将数据从工作流中的作业移动到后续作业。

Diagram-v3-Workspaces.png

“workspace”这个名字可能会让人联想到一个单一存储位置的图像,作业可以随意添加或删除该位置。然而,工作流会在构建过程中引入大量的并发性,并且并发作业的运行没有固定的顺序。在这些情况下,单个可变的存储位置会导致作业在不同的工作流运行中反复失败,因为它们从未获得工作区内容的一致视图。如果您的工作流有并发作业,这将显著影响您获得可重复构建过程的能力。所以工作空间需要从不可变的层构建(我们使用存储在 blob-store 中的 gzipped tarballs)。每个作业可以使用persist_to_workspace命令向工作空间添加一个新的不可变层。

但是这还不够,为了可重复性,我们需要能够计算出当一个作业使用attach_workspace命令时,它应该能够看到什么工作空间内容。

例如,以下工作流程:

workflows:
  version: 2
  jobs:
    - compile-assets
    - compile-code
    - test:
        requires:
          - compile-assets
          - compile-code
    - code-coverage
    - deploy:
        requires:
          - test
          - code-coverage 

生成此图(我在作业名称之外用一个字母标记了作业):

 +---------------------+
 |  code-coverage (D)  +-------------------------+
 +---------------------+                         |
                                                 |    +--------------+
                                                 +---->  deploy (E)  |
                                                 |    +--------------+
 +----------------------+                        |
 |  compile-assets (A)  +---+                    |
 +----------------------+   |                    |
                            |   +------------+   |
                            +--->  test (C)  +---+
                            |   +------------+
 +--------------------+     |
 |  compile-code (B)  +-----+
 +--------------------+ 

作业 C 肯定会看到作业 A 和作业 B 增加的工作空间,但是作业 D 呢?有时作业 D 可能在作业 C 之前运行,有时可能在作业 C 之后运行,没有任何保证。

幸运的是,我们可以求助于工作流图,规则是一个作业只能看到工作流图中上游作业添加到工作区的数据。所以作业 C 永远看不到来自作业 d 的数据。

另一个考虑是,一个作业可以“覆盖”前一个作业存储在工作区中的数据,如果作业 B 和作业 C 都将一个名为code.jar的文件以不同的内容存储在工作区中,作业 E 应该看到哪个?

我们再次转向工作流图,由上游作业产生的工作空间层以拓扑顺序被应用,即由作业的祖先产生的层总是在由作业产生的层之前被应用。将工作空间附加到作业 E 时,应用层的一个有效顺序是 A、B、C、D,但另一个完全有效的顺序是 D、B、A、C。

在所有情况下,来自作业 C 的层在来自作业 A 的层之后被应用,因此我们可以知道作业 E 将会看到来自作业 C 的code.jar

最后要考虑的是,当两个并发作业(例如,作业 A 和 B,或者作业 C 和 D)将同一个文件添加到工作区时会发生什么?如果作业 C 和 D 都存储了code.jar,作业 E 应该看哪个?工作流图在这里没有帮助,工作是并发的,但是我们希望避免模糊性,并且绝对避免在不同的工作流运行中反复无常。因为我们的目标是拥有可重复的工作流,所以我们标记了这种情况,并失败了attach_workspace命令,错误突出显示了不明确的文件。

使用工作空间重新运行工作流

上面强调的问题和实现可能听起来非常理论化,对于没有任何并发性的简单工作流没有太多实际好处,但是所有这些不变性和如何应用层的仔细排序意味着我们可以在工作流运行之间共享部分工作区!

这就是仅重新运行工作流中失败的作业的工作方式,我们可以确定上次运行的成功作业的所有工作空间层,并在重新运行时使用这些层。这将节省您的时间和资源,因为我们不需要再次运行成功的作业。

概括起来

  • 工作空间旨在将作业期间生成的数据移动到工作流中的后续作业
  • 我们可以通过使用不变性和工作流图来确定工作流中的每个作业可以看到哪些数据,从而实现可重复性。
  • 工作空间并不神奇;最终它会创建 tarballs 并将它们存储在 blob 存储中。附加工作空间需要为将数据持久化到工作空间的每个上游作业下载和解包 tarballs。

这听起来很棒,但是仍然有很多选择——我应该用哪个特性做什么?

首先要记住的是,无论使用哪种特性,您存储的所有内容都有存档、上传和下载的代价。带宽不是无限的,延迟不是零。不要忘记文件系统操作,将几万到几十万个小文件打包和解包到一个归档中需要时间。[脚注:在我的多核 Macbook Pro dev 机器上,采用固态硬盘,没有虚拟化开销,为典型客户的节点包集创建node_modules目录的归档只需要 23 秒。]

工作区提供了诱人的便利,将您的整个结帐和依赖关系都放在工作区中,但是这种便利也有缺点:

  • 导致所有依赖项的存档和上传
  • 导致存档和上传您的整个 git repo(包括历史)
  • 即使您已经恢复了缓存,也会导致重新下载这些文件

为了加快工作流程,我们希望最大限度地减少存档、上传、下载和解包操作。

你该怎么办?

  • 将工作空间用于在作业中生成的、需要供后续作业使用的数据。这是 workspaces 的面包和黄油,在重新运行时工作良好。
  • 将依赖关系保存在 CircleCI 缓存中,并使用restore_cache将它们复制到容器中。如果您键入依赖清单的校验和(packages.json、Gemfile.lock、project.clj 等),那么您可以避免在每个工作流中归档和上传依赖数据。CircleCI 缓存机制只会在依赖清单的内容发生变化时保存新的缓存。由于依赖关系可能很大和/或有大量文件,这可以节省大量时间。
  • 具体说明您在工作空间中保存了什么,以避免存储后续作业不需要的数据。例如,如果您不需要在下游作业中使用git,请避免将.git添加到您的工作区,它可能包含大量数据。
  • 不要在缓存和工作区存储相同的数据,你会付出两次下载的代价。
  • 如果你确实需要完整的 git 回购协议,可以考虑使用checkout从你的 VCS 供应商那里获得回购协议。这比其他建议更不明确,因为一般网络“天气”对 VCS 提供商到 CircleCI 的转移有更大的影响。
  • 对于工作流生成的任何数据,如果您希望在工作流完成后能够在 CircleCI 系统之外访问这些数据,请使用工件。

使用 Terraform 部署到 AWS -部署 Clojure 应用程序| CircleCI

原文:https://circleci.com/blog/deploy-a-clojure-web-application-to-aws-using-terraform/

这是关于构建、测试和部署 Clojure web 应用程序的系列文章的第三篇。你可以在这里找到第一个帖子,在这里找到第二个

在本帖中,我们将重点关注如何使用 HashiCorp Terraform 建立一个相当复杂的基础设施,用 PostgreSQL 容器托管我们的 web 应用 Docker 容器,然后使用 CircleCI 在零宕机的情况下部署到我们的基础设施。如果你不想从头开始创建前两篇文章中描述的 web 应用程序,你可以通过分叉这个库并检查part-2分支来获得源代码。

尽管我们正在构建一个 Clojure 应用程序,但是只需要有限的 Clojure 知识就可以完成本系列的这一部分。

先决条件

为了构建这个 web 应用程序,您需要安装以下软件:

  1. Java JDK 8 或更高版本——clo jure 运行在 Java 虚拟机上,实际上只是一个 Java 库(JAR)。我用版本 8 构建了这个,但是一个更好的版本应该也可以。
  2. Leiningen - Leiningen,通常被称为 lein(读作‘line’)是最常用的 Clojure 构建工具。
  3. Git -无处不在的分布式版本控制工具。
  4. Docker——一个工具,旨在通过使用容器来简化应用程序的创建、部署和运行。
  5. Docker Compose -一个定义和运行多容器 Docker 应用程序的工具。
  6. HashiCorp Terraform -以可预测和可复制的方式创建和改变基础设施的工具。这个博客是用 V0.12.2 版测试的。
  7. SSH 作为命令行实用程序安装。如果你还没有安装 SSH 的话,你可能需要使用搜索引擎来获得安装说明,因为它依赖于你的操作系统。

您还需要注册:

  1. CircleCI 账号 - CircleCI 是一个持续集成和交付平台。
  2. GitHub 账户 - GitHub 是一个基于网络的托管服务,使用 Git 进行版本控制。
  3. Docker Hub 帐户 - Docker Hub 是一个基于云的存储库,Docker 用户和合作伙伴可以在其中创建、测试、存储和分发容器映像。
  4. AWS 账户——亚马逊网络服务提供按需计算平台。

注意: 我们将要构建的基础设施在支持我们所需的 AWS 服务方面会涉及少量成本。如果您离开服务大约一个小时来完成本教程,费用将在$0.50 到$1.00 之间。

你还需要设置你的 CircleCI、Docker Hub 和 Web 应用 Github 账户,如本系列的第 1 部分和第 2 部分所述。

创建 AWS 帐户和凭据

首先,我们需要注册一个 AWS 账户。虽然你可以选择“免费”账户,但由于我们将使用的资源,仍然需要付费。一旦你完成了,你会想要拆除基础设施,我会在这篇博客中告诉你怎么做。

拥有 AWS 帐户后,以 root 用户身份登录。您用来注册的电子邮件是您的用户名,但请确保您点击作为根帐户登录。登录后,从服务菜单中选择 IAM (身份访问和管理)。

从屏幕左侧的导航栏中选择用户,点击添加用户。创建一个用户,并确保您给它编程和控制台访问。

Add user

通过创建新角色或直接附加现有策略,为用户提供Administrator Access

Admin access

您可以保留默认设置并接受设置向导中的其他内容,但是在您离开添加用户成功屏幕之前,请务必仔细记下AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY

Add user success

创建 IAM 用户后,记下您的帐户 id ( 如何找到您的 AWS 帐户 id ),选择帐户名称或号码旁边的向下箭头并从下拉菜单中选择“注销”,注销 root 用户。

现在,以新创建的 IAM 用户身份登录您的帐户。如果您看到根用户登录,请确保选择登录不同的账户。使用您之前记下的 AWS 帐户 id、您设置的用户名(在我的例子中是 filmappuser)以及您为管理控制台访问设置的密码。

登录后,从服务下拉菜单中选择 EC2 ,并点击 EC2 仪表板上资源中的密钥对

点击创建密钥对并输入密钥对名称。我在地形中使用了film_ratings_key_pair,所以如果你不想编辑地形,就用它作为名字。将自动下载的.pem文件复制到您的.ssh/目录下,并对该文件设置权限,如下所示:

chmod 400 ~/.ssh/film_ratings_key_pair.pem 

让应用程序等待数据库

这一步不是绝对必要的,但是它加速了弹性容器服务(ECS)资源的设置,所以值得一做。当 web 应用程序在其 ECS 任务容器中启动时,它必须通过负载平衡器连接到数据库任务容器。我们将在下一节看到更多这方面的内容。然而,负载平衡器可能需要几分钟来注册数据库容器,如果我们让 web 应用程序尝试立即连接,它将会失败,然后 ECS 将不得不销毁应用程序容器并启动一个新的容器,这需要更多时间。

为了尽量减少这种情况,我们将添加一个借用脚本来等待数据库连接。在 web 应用项目的根目录下创建一个wait-for-it.sh文件,并将该文件的内容剪切并粘贴到您新创建的文件中。

然后将电影分级 web 应用程序项目中的Dockerfile改为如下所示:

FROM openjdk:8u181-alpine3.8

WORKDIR /

RUN apk update && apk add bash

COPY wait-for-it.sh wait-for-it.sh

COPY target/film-ratings.jar film-ratings.jar
EXPOSE 3000

RUN chmod +x wait-for-it.sh

CMD ["sh", "-c", "./wait-for-it.sh --timeout=90 $DB_HOST:5432 -- java -jar film-ratings.jar"] 

这确保了wait-for-it.sh脚本运行并反复尝试连接到端口 5432 上的DB_HOST。如果它在 90 秒内连接上,它就会运行java -jar film-ratings.jar

另外,将您的project.clj文件中的版本更新为0.1.1:

(defproject film-ratings "0.1.1"
... 

完成这些更改后,将它们添加并提交到 Git,然后推送到电影分级项目的 GitHub 存储库:

$ git add . --all
$ git commit -m "Add wait for it script"
[master 45967e8] Add wait for it script
2 files changed, 185 insertions(+), 1 deletion(-)
create mode 100644 wait-for-it.sh
$ git push 

您可以在您的 CircleCI 仪表板中检查 CircleCI 构建是否运行正常。

现在将您的更改标记为0.1.1push,这样 CircleCI 就会将它发布为最新版本:

$ git tag -a 0.1.1 -m "v0.1.1"
$ git push origin 0.1.1
...
 * [new tag]         0.1.1 -> 0.1.1 

在 CircleCI 上检查build_and_deploy工作流是否将 Docker 图像发布到 Docker Hub。

Deploy successful

使用 Terraform 的 AWS 基础设施

我们将使用 Terraform 在 AWS 中构建一个类似生产的基础设施。这是相当多的 Terraform 配置,所以我不打算遍历我已经定义的每个资源。您可以在闲暇时随意查看这些代码。

首先,使用库标题右边的 fork 按钮将我的film-ratings-terra formrepo 分支到 GitHub 中,并将分支后的版本克隆到本地机器上。

$ git clone <the github URL for your forked version of chrishowejones/film-ratings-terraform> 

既然您已经派生并克隆了 Terraform 存储库,让我们来看看它的一些重要部分。Terraform 的总体功能是什么?

下图是基础设施中几个更重要部分的极其简化的版本。

Simplified AWS Infrastructure

简化的 AWS 基础设施

这表明我们将设置两个名为film_ratings_appfilm_ratings_db的 ECS 任务,它们将在 ECS 容器中运行,包含两个应用程序实例和一个数据库实例。app 和 db 任务将在它们自己的服务中运行,类似地分别称为film_ratings_app_servicefilm_ratings_db_service(图中未显示)。

以下是应用服务和应用任务定义:

resource "aws_ecs_service" "film_ratings_app_service" {
  name            = "film_ratings_app_service"
  iam_role        = "${aws_iam_role.ecs-service-role.name}"
  cluster         = "${aws_ecs_cluster.film_ratings_ecs_cluster.id}"
  task_definition = "${aws_ecs_task_definition.film_ratings_app.family}:${max("${aws_ecs_task_definition.film_ratings_app.revision}", "${data.aws_ecs_task_definition.film_ratings_app.revision}")}"
  depends_on      = [ "aws_ecs_service.film_ratings_db_service"]
  desired_count   = "${var.desired_capacity}"
  deployment_minimum_healthy_percent = "50"
  deployment_maximum_percent = "100"
  lifecycle {
    ignore_changes = ["task_definition"]
  }

  load_balancer {
    target_group_arn  = "${aws_alb_target_group.film_ratings_app_target_group.arn}"
    container_port    = 3000
    container_name    = "film_ratings_app"
  }
} 

film-ratings-app-service.tf

注: 最低健康百分比为 50%。这允许服务停止一个容器任务(只留下一个在运行),以便在我们进行滚动部署时使用释放的资源来启动容器任务的新版本。

data "aws_ecs_task_definition" "film_ratings_app" {
  task_definition = "${aws_ecs_task_definition.film_ratings_app.family}"
  depends_on = ["aws_ecs_task_definition.film_ratings_app"]
}

resource "aws_ecs_task_definition" "film_ratings_app" {
  family                = "film_ratings_app"
  container_definitions = <<DEFINITION
[
  {
    "name": "film_ratings_app",
    "image": "${var.film_ratings_app_image}",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 3000,
        "hostPort": 3000
      }
    ],
    "environment": [
      {
        "name": "DB_HOST",
        "value": "${aws_lb.film_ratings_nw_load_balancer.dns_name}"
      },
      {
        "name": "DB_PASSWORD",
        "value": "${var.db_password}"
      }
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "film_ratings_app",
          "awslogs-region": "${var.region}",
          "awslogs-stream-prefix": "ecs"
        }
    },
    "memory": 1024,
    "cpu": 256
  }
]
DEFINITION
} 

film-ratings-app-task-definition.tf

应用程序实例需要通过端口 5432 与数据库实例通信。为了做到这一点,他们需要通过网络负载平衡器(film-ratings-nw-load-balancer)路由他们的请求,因此当我们设置film_ratings_app任务时,我们需要将网络负载平衡器的 DNS 名称传递给容器,以便容器中的应用程序可以使用它作为与数据库对话的DB_HOST

...
resource "aws_lb_target_group" "film_ratings_db_target_group" {
  name                = "film-ratings-db-target-group"
  port                = "5432"
  protocol            = "TCP"
  vpc_id              = "${aws_vpc.film_ratings_vpc.id}"
  target_type         = "ip"

  health_check {
    healthy_threshold   = "3"
    unhealthy_threshold = "3"
    interval            = "10"
    port                = "traffic-port"
    protocol            = "TCP"
  }

  tags {
    Name = "film-ratings-db-target-group"
  }
}

resource "aws_lb_listener" "film_ratings_nw_listener" {
  load_balancer_arn = "${aws_lb.film_ratings_nw_load_balancer.arn}"
  port              = "5432"
  protocol          = "TCP"

  default_action {
    target_group_arn = "${aws_lb_target_group.film_ratings_db_target_group.arn}"
    type             = "forward"
  }
} 

network-load-balancer.tf

应用程序负载平衡器(film-ratings-alb-load-balancer)是我们将用来指向我们的浏览器。它将负责将 HTTP 请求路由到film_ratings_app容器的两个实例之一,为我们将默认端口 80 映射到端口 3000。

...
resource "aws_alb_target_group" "film_ratings_app_target_group" {
  name                = "film-ratings-app-target-group"
  port                = 3000
  protocol            = "HTTP"
  vpc_id              = "${aws_vpc.film_ratings_vpc.id}"
  deregistration_delay = "10"

  health_check {
    healthy_threshold   = "2"
    unhealthy_threshold = "6"
    interval            = "30"
    matcher             = "200,301,302"
    path                = "/"
    protocol            = "HTTP"
    timeout             = "5"
  }

  stickiness {
    type  = "lb_cookie"
  }

  tags = {
    Name = "film-ratings-app-target-group"
  }
}

resource "aws_alb_listener" "alb-listener" {
  load_balancer_arn = "${aws_alb.film_ratings_alb_load_balancer.arn}"
  port              = "80"
  protocol          = "HTTP"

  default_action {
    target_group_arn = "${aws_alb_target_group.film_ratings_app_target_group.arn}"
    type             = "forward"
  }
}

resource "aws_autoscaling_attachment" "asg_attachment_film_rating_app" {
  autoscaling_group_name = "film-ratings-autoscaling-group"
  alb_target_group_arn   = "${aws_alb_target_group.film_ratings_app_target_group.arn}"
  depends_on = [ "aws_autoscaling_group.film-ratings-autoscaling-group" ]
} 

application-load-balancer.tf

所示基础设施的另一个重要部分是,film_ratings_db容器装载了一个卷,该卷被映射到一个弹性文件系统卷,以便在运行容器的 EC2 实例之外持久存储数据。我们这样做是为了,如果我们必须放大或缩小实例(或者如果实例死亡),我们不会丢失数据库中的数据。

resource "aws_ecs_task_definition" "film_ratings_db" {
  family                = "film_ratings_db"
  volume {
    name = "filmdbvolume"
    host_path = "/mnt/efs/postgres"
  }
  network_mode = "awsvpc"
  container_definitions = <<DEFINITION
[
  {
    "name": "film_ratings_db",
    "image": "postgres:alpine",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 5432
      }
    ],
    "environment": [
      {
        "name": "POSTGRES_DB",
        "value": "filmdb"
      },
      {
        "name": "POSTGRES_USER",
        "value": "filmuser"
      },
      {
        "name": "POSTGRES_PASSWORD",
        "value": "${var.db_password}"
      }
    ],
    "mountPoints": [
        {
          "readOnly": null,
          "containerPath": "/var/lib/postgresql/data",
          "sourceVolume": "filmdbvolume"
        }
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "film_ratings_db",
          "awslogs-region": "${var.region}",
          "awslogs-stream-prefix": "ecs"
        }
    },
    "memory": 512,
    "cpu": 256
  }
]
DEFINITION
} 

film-ratings-db-task-definition.tf

启动配置确保当 EC2 实例启动时,通过user_data条目挂载 EFS 卷。

...
  user_data                   = <<EOF
                                  #!/bin/bash
                                  echo ECS_CLUSTER=${var.ecs_cluster} >> /etc/ecs/ecs.config
                                  mkdir -p /mnt/efs/postgres
                                  cd /mnt
                                  sudo yum install -y amazon-efs-utils
                                  sudo mount -t efs ${aws_efs_mount_target.filmdbefs-mnt.0.dns_name}:/ efs
                                  EOF 

启动-配置. tf

创建 AWS 基础设施

我们几乎已经准备好运行我们的 Terraform 了,但在此之前,我们需要确保我们有各种变量的所有正确值。我们来看一下terraform.tfvars文件。

# You may need to edit these variables to match your config
db_password= "password"
ecs_cluster="film_ratings_cluster"
ecs_key_pair_name="film_ratings_key_pair"
region= "eu-west-1"
film_ratings_app_image= "chrishowejones/film-ratings-app:latest"

# no need to change these unless you want to
film_ratings_vpc = "film_ratings_vpc"
film_ratings_network_cidr = "210.0.0.0/16"
film_ratings_public_01_cidr = "210.0.0.0/24"
film_ratings_public_02_cidr = "210.0.10.0/24"
max_instance_size = 3
min_instance_size = 1
desired_capacity = 2 

泰若人

如果需要,您可以编辑密码,但是对于这个演示来说,这并不是真正必要的。您也可以通过设置环境变量TF_VAR_db_password来覆盖密码。ecs_key_pair_name值必须与您之前在.ssh/目录中为 AWS 用户创建的密钥对的名称相匹配。您还必须将film_ratings_app_image更改为您的图像的 Docker Hub 存储库图像名称,而不是我的(确保您已经发布了名称正确的 Docker Hub 存储库图像——如果您已经完成了本博客系列的第 2 部分,您应该已经发布了)。

如果您想使用与我正在使用的不同的 AWS 区域,您将需要更改region值。data.tf文件确保${data.aws_ami.latest_ecs.id}变量被设置为适合您所在地区的 ECS 优化 AMI 映像

不应该有理由去改变其他任何东西。

运行地形

一旦正确设置了terraform.tfvars值,就需要在克隆的film-ratings-terraform目录中初始化 Terraform:

$ terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
...
Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary. 

然后,您可以运行一个 Terraform 计划来检查应用该计划时将会创建哪些资源。系统将提示您输入 AWS 访问密钥 id 和 AWS 秘密访问密钥。如果您厌倦了输入这些,您可以在您的终端会话中设置环境变量TF_VAR_aws_access_key_idTF_VAR_aws_secret_access_key

$ terraform plan
var.aws_access_key_id
  AWS access key

  Enter a value: ...
...
Refreshing Terraform state in-memory prior to plan...
...
Plan: 32 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

To actually build the AWS resources listed in the plan, use the following command (enter `yes` when prompted with `Do you want to perform these actions?`):

$ terraform apply
data.aws_iam_policy_document.ecs-instance-policy: Refreshing state...
data.aws_availability_zones.available: Refreshing state...
data.aws_iam_policy_document.ecs-service-policy: Refreshing state...
...
Plan: 32 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes
...
aws_autoscaling_group.film-ratings-autoscaling-group: Creation complete after 1m10s (ID: film-ratings-autoscaling-group)

Apply complete! Resources: 32 added, 0 changed, 0 destroyed.

Outputs:

app-alb-load-balancer-dns-name = film-ratings-alb-load-balancer-895483441.eu-west-1.elb.amazonaws.com
app-alb-load-balancer-name = film-ratings-alb-load-balancer
ecs-instance-role-name = ecs-instance-role
ecs-service-role-arn = arn:aws:iam::731430262381:role/ecs-service-role
film-ratings-app-target-group-arn = arn:aws:elasticloadbalancing:eu-west-1:731430262381:targetgroup/film-ratings-app-target-group/8a35ef20a2bab372
film-ratings-db-target-group-arn = arn:aws:elasticloadbalancing:eu-west-1:731430262381:targetgroup/film-ratings-db-target-group/5de91812c3fb7c63
film_ratings_public_sg_id = sg-08af1f2ab0bb6ca95
film_ratings_public_sn_01_id = subnet-00b42a3598abf988f
film_ratings_public_sn_02_id = subnet-0bb02c32db76d7b05
film_ratings_vpc_id = vpc-06d431b5e5ad36195
mount-target-dns = fs-151074dd.efs.eu-west-1.amazonaws.com
nw-lb-load-balancer-dns-name = film-ratings-nw-load-balancer-4c3a6e6a0dab3cfb.elb.eu-west-1.amazonaws.com
nw-lb-load-balancer-name = film-ratings-nw-load-balancer
region = eu-west-1 

Terraform 可能需要五分钟才能运行,负载平衡器和 ECS 服务可能还需要五分钟才能正确识别彼此。您可以通过登录 AWS 控制台并从服务下拉列表中选择 ECS ,选择您的集群(默认情况下名为film_ratings_cluster,并检查附加到每个服务的日志来检查进度。film_ratings_app_service可能需要一点时间来连接,有时,即使使用wait-for-it.sh脚本,第一个启动的任务可能无法连接到数据库,您必须等待自动缩放来启动另一个任务实例。

一旦 ECS 服务和任务启动,您就可以使用app-alb-load-balancer-dns-name值作为 URL,尝试通过您的浏览器连接到应用程序(在上面的输出中显示为film-ratings-alb-load-balancer-895483441.eu-west-1.elb.amazonaws.com,但是您的值会有所不同)。

如果您最初看到 HTTP 状态 502 或 503,请不要担心,因为即使在 ECS 服务启动后,应用程序负载平衡器也需要几分钟时间来检测一切是否正常。最后,如果您在浏览器中输入 alb 负载平衡器 DNS 名称的 URL,在我的示例中为film-ratings-alb-load-balancer-895483441.eu-west-1.elb.amazonaws.com,您应该会看到这个。

Film ratings page

在这一点上,值得注意的是,要拆除所有这些资源,您可以发出terraform destroy命令(如果您真的想销毁所有资源,在提示时输入‘yes’)。

让 CircleCI 部署到 ECS 集群

接下来,我们希望使用我们的持续集成服务 CircleCI 来为我们发布 Docker 实例。

到目前为止,在我们的 CircleCI 配置中,每当我们向 GitHub 推送更改时,我们都在构建和测试我们的应用程序,每当我们在 GitHub 中标记我们的项目时,我们都在构建、测试、打包为 Docker 容器,并将 Docker 容器发布到 Docker Hub。

在本文的这一部分,我们将添加配置,以便在标记项目时将打包的 Docker 容器推送到我们的 ECS 集群,但是我们将添加一个手动批准步骤,该步骤允许我们控制开始部署到 ECS 的时间。由于我们已将 ECS 服务设置为使用滚动部署,因此该流程将在零宕机的情况下部署我们已更改、已标记的应用程序。

现在我们的.circleci/config.yml有三份工作,buildbuild-dockerpublish-docker。在publish-docker下面再加一个工作叫deploy

 ...
  deploy:
    docker:
      - image: circleci/python:3.6.1
    environment:
      AWS_DEFAULT_OUTPUT: json
      IMAGE_NAME: chrishowejones/film-ratings-app
    steps:
      - checkout
      - restore_cache:
          key: v1-{{ checksum "requirements.txt" }}
      - run:
          name: Install the AWS CLI
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: v1-{{ checksum "requirements.txt" }}
          paths:
            - "venv"
      - run:
          name: Deploy
          command: |
            . venv/bin/activate
            ./deploy.sh
... 

。圆形/config.yml

该作业将使用pip(Python 包管理器)通过读取 CLI 包要求的requirements.txt文件来安装 AWS CLI 工具。然后运行步骤Deploy运行一个我们还没有创建的deploy.sh脚本。记住将 IMAGE_NAME 更改为您的映像而不是我的映像的 Docker Hub 存储库映像名称。在我们继续之前,让我们将我们需要的额外工作添加到build_and_deploy工作流程的末尾。

...
      - hold:
          requires:
            - publish-docker
          type: approval
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^\d+\.\d+\.\d+$/
      - deploy:
          requires:
            - hold
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^\d+\.\d+\.\d+$/ 

。圆形/config.yml

我们已经向build_and_deploy工作流添加了两个新任务。第一个是hold,要求前面的publish-docker步骤在执行之前完成,类型为“批准”。“批准”类型是一个内置的 CircleCI 类型,它将停止工作流,并要求用户单击批准以继续任何后续步骤。

deploy工作依赖于hold被批准。

注意: build_and_deploy工作流中的每一个步骤一样,这些步骤只有在语义版本样式标签被推送时才会被触发(如 0.1.1)。

让我们在项目根目录中添加requirements.txt文件,以确保 CircleCI 安装了 AWS CLI 工具。该文件的内容应该是:

awscli>=1.16.0 

requirements.txt

让我们通过在我们项目的根目录中创建这个新文件来添加在deploy作业中引用的deploy.sh脚本。

#!/usr/bin/env bash

# more bash-friendly output for jq
JQ="jq --raw-output --exit-status"

configure_aws_cli(){
        aws --version
        aws configure set default.region eu-west-1 # change this if your AWS region differs
        aws configure set default.output json
}

deploy_cluster() {

    family="film_ratings_app"

    make_task_def
    register_definition
    if [[ $(aws ecs update-service --cluster film_ratings_cluster  --service film_ratings_app_service --task-definition $revision | \
                   $JQ '.service.taskDefinition') != $revision ]]; then
        echo "Error updating service."
        return 1
    fi

    # wait for older revisions to disappear
    # not really necessary, but nice for demos
    for attempt in {1..15}; do
        if stale=$(aws ecs describe-services --cluster film_ratings_cluster --services film_ratings_app_service | \
                       $JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$revision\") | .taskDefinition"); then
            echo "Waiting for stale deployments:"
            echo "$stale"
            sleep 45
        else
            echo "Deployed!"
            return 0
        fi
    done
    echo "Service update took too long."
    return 1
}

make_task_def(){
        task_template='[
                {
                    "name": "film_ratings_app",
                    "image": "%s:%s",
                    "essential": true,
                    "portMappings": [
                      {
                          "containerPort": 3000,
                          "hostPort": 3000
                      }
                    ],
                    "environment": [
                      {
                        "name": "DB_HOST",
                        "value": "%s"
                      },
                      {
                        "name": "DB_PASSWORD",
                        "value": "%s"
                      }
                    ],
                    "logConfiguration": {
                      "logDriver": "awslogs",
                      "options": {
                        "awslogs-group": "film_ratings_app",
                        "awslogs-region": "eu-west-1",
                        "awslogs-stream-prefix": "ecs"
                      }
                    },
                    "memory": 1024,
                    "cpu": 256
                }
        ]'

        task_def=$(printf "$task_template" $IMAGE_NAME $CIRCLE_TAG $DB_HOST $DB_PASSWORD)
}

register_definition() {

    if revision=$(aws ecs register-task-definition --container-definitions "$task_def" --family $family | $JQ '.taskDefinition.taskDefinitionArn'); then
        echo "Revision: $revision"
    else
        echo "Failed to register task definition"
        return 1
    fi

}

configure_aws_cli
deploy_cluster 

deploy.sh

这个脚本将为我们的film_ratings_app任务创建一个新的任务定义。如果你看一下配置,它反映了film-ratings-app-task-definition.tf中的地形定义。然后,它将这个新任务部署到集群中。

请注意,make_task_def函数中设置的task_def会替换名为$IMAGE_NAME$CIRCLE_TAG$DB_HOST$DB_PASSWORD的变量的值。稍后我们将在 CircleCI 项目环境变量中设置这些变量。

注意: 如果您使用的是与eu-west-1不同的 AWS 区域,您需要更改第 8 & 67 行的条目。

在继续之前,让我们使这个新的脚本文件可执行。

$ chmod +x deploy.sh 

现在,让我们将这些变量和我们需要的 AWS 变量添加到 CircleCI 配置中。

进入你的 CircleCI 仪表盘选择你的电影分级项目的设置,在构建设置下选择环境变量,输入以下变量:

CircleCI environment variables

注意: 这里应该已经设置了DOCKERHUB_PASSDOCKERHUB_USERNAME

使用创建用户时获得的 AWS 访问密钥 id 和 AWS 秘密访问密钥(与运行 Terraform 时设置的值相同)。记住你可以在这里找到你的 AWS 账户 id。将您的DB_PASSWORD设置为您在terraform.tfvars文件中使用的任何值。

DB_HOST值需要是将 TCP 请求路由到film_ratings_db任务实例的网络负载平衡器的 DNS 名称。您可以在terraform apply命令的输出中找到这一点,或者登录到您的 AWS 管理控制台,从服务导航到 EC2 ,并从那里导航到负载平衡器,如果您选择film-ratings-nw-load-balancer条目,您应该会看到 DNS 名称(在基本配置部分)。

nw-lb-load-balancer-dns-name = film-ratings-nw-load-balancer-4c3a6e6a0dab3cfb.elb.eu-west-1.amazonaws.com
nw-lb-load-balancer-name = film-ratings-nw-load-balancer
region = eu-west-1 

terraform 应用命令的输出

在这里的例子中,CircleCI 中的DB_HOST变量应该设置为film-ratings-nw-load-balancer-4c3a6e6a0dab3cfb.elb.eu-west-1.amazonaws.com

注意: 如果您已经销毁了您的 AWS 资源,您将需要使用film-ratings-terraform目录中的terraform apply命令重新设置它们,以设置 AWS 资源来获得网络负载平衡器 DNS。

一旦设置了环境变量,就可以提交对 CircleCI 配置和新部署脚本文件的更改,并将它们推送到 GitHub。

$ git add . --all
$ git commit -m "Added config to deploy to ECS"
$ git push origin master 

更改应用程序并部署

现在,我们将通过向我们的应用程序添加一些功能,然后对其进行标记并将更改部署到 ECS 群集,来证明 CircleCI 配置正在工作。

让我们在应用程序中添加一个搜索特性。

首先,将路由和处理程序键的映射添加到resources/film_ratings/config.edn文件中,以显示搜索表单并处理搜索表单的 post:

... 
:duct.module/ataraxy
 {\[:get "/"\] [:index]
  "/add-film"
  {:get [:film/show-create]
   \[:post {film-form :form-params}\] [:film/create film-form]}
  \[:get "/list-films"\] [:film/list]
  "/find-by-name"
  {:get [:film/show-search]
   \[:post {search-form :form-params}\] [:film/find-by-name search-form]}}

 :film-ratings.handler/index {}
 :film-ratings.handler.film/show-create {}
 :film-ratings.handler.film/create {:db #ig/ref :duct.database/sql}
 :film-ratings.handler.film/list {:db #ig/ref :duct.database/sql}
 :film-ratings.handler.film/show-search {}
 :film-ratings.handler.film/find-by-name {:db #ig/ref :duct.database/sql}
... 

resources/film _ ratios/config . edn

接下来,让我们将show-searchfind-by-name键的处理程序添加到src/film_ratings/handler/film.clj文件的底部:

...

(defmethod ig/init-key :film-ratings.handler.film/show-search [_ _]
  (fn [_]
    [::response/ok (views.film/search-film-by-name-view)]))

(defmethod ig/init-key :film-ratings.handler.film/find-by-name [_ {:keys [db]}]
  (fn [{[_ search-form] :ataraxy/result :as request}]
    (let [name (get search-form "name")
          films-list (boundary.film/fetch-films-by-name db name)]
      (if (seq films-list)
        [::response/ok (views.film/list-films-view films-list {})]
        [::response/ok (views.film/list-films-view [] {:messages [(format "No films found for %s." name)]})])))) 

src/film _ ratings/handler/film . clj

让我们在src/film_ratings/views/index.clj文件的索引视图中添加一个新按钮:

(defn list-options []
  (page
    [:div.container.jumbotron.bg-white.text-center
     [:row
      [:p
       [:a.btn.btn-primary {:href "/add-film"} "Add a Film"]]]
     [:row
      [:p
       [:a.btn.btn-primary {:href "/list-films"} "List Films"]]]
     [:row
      [:p
       [:a.btn.btn-primary {:href "/find-by-name"} "Search Films"]]]])) 

src/film _ ratings/handler/index . clj

现在我们需要在src/film_ratings/views/film.clj文件底部的search-films-by-name-view视图:

...

(defn search-film-by-name-view
  []
  (page
   [:div.container.jumbotron.bg-light
    [:div.row
     [:h2 "Search for film by name"]]
    [:div
     (form-to [:post "/find-by-name"]
              (anti-forgery-field)
              [:div.form-group.col-12
               (label :name "Name:")
               (text-field {:class "mb-3 form-control" :placeholder "Enter film name"} :name)]
              [:div.form-group.col-12.text-center
               (submit-button {:class "btn btn-primary text-center"} "Search")])]])) 

src/film _ ratings/views/film . clj

数据库边界协议和相关实现需要在find-by-name处理程序中引用的fetch-films-by-name函数。在src/film_ratings/boundary/film.clj文件中为协议添加适当的功能和协议扩展,如下所示:

...
(defprotocol FilmDatabase
  (list-films [db])
  (fetch-films-by-name [db name])
  (create-film [db film]))

(extend-protocol FilmDatabase
  duct.database.sql.Boundary
  (list-films [{db :spec}]
    (jdbc/query db ["SELECT * FROM film"]))
  (fetch-films-by-name [{db :spec} name]
    (let [search-term (str "%" name "%")]
     (jdbc/query db ["SELECT * FROM film WHERE LOWER(name) like LOWER(?)" search-term])))
  (create-film [{db :spec} film]
    (try
     (let [result (jdbc/insert! db :film film)]
       (if-let [id (val (ffirst result))]
         {:id id}
         {:errors ["Failed to add film."]}))
     (catch SQLException ex
       (log/errorf "Failed to insert film. %s\n" (.getMessage ex))
       {:errors [(format "Film not added due to %s" (.getMessage ex))]})))) 

src/film _ ratings/boundary/film . clj

另外,将项目文件中构建的版本号更改为0.2.0

(defproject film-ratings "0.2.0"
... 

现在,您应该能够通过运行以下命令对此进行测试:

$ lein repl
nREPL server started on port 43477 on host 127.0.0.1 - nrepl://127.0.0.1:43477
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.9.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_191-b12
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (dev)
:loaded
dev=> (go)
:duct.server.http.jetty/starting-server {:port 3000}
:initiated
dev=> 

如果你打开浏览器到 http://localhost:3000/ ,你应该在索引上看到搜索电影

Search films added

现在将一些电影添加到您的测试(SQLite)数据库中,并使用搜索电影将您带到搜索表单。填写你的一部或多部电影的部分名称。

Search for a film by name

按下搜索,查看搜索结果是否符合您的预期。

Film search results

也可以尝试非匹配搜索。

Non-matching search results

是时候将您的更改提交到 GitHub 了。

$ git add . --all
$ git commit -m "Added search for films"
$ git push origin master 

您可以在您的 CircleCI 仪表板中检查 CircleCI 构建是否运行正常。

接下来,我们将用新版本0.2.0标记我们的构建。

$ git tag -a 0.2.0 -m "v0.2.0"
$ git push origin 0.2.0         
...
 * [new tag]         0.2.0 -> 0.2.0 

这将触发 CircleCI 中的build_and_deploy工作流

大约几分钟后,您应该看到工作流完成发布到 Docker Hub,然后继续等待。

Workflow complete

如果您单击暂停作业,然后批准它,工作流将继续并部署到 ECS。部署步骤可能需要几分钟,AWS 弹性容器服务可能需要五分钟来滚动部署这两个实例。您可以在 ECS 控制台中检查 ECS 部署的进度。如果您尝试在大约 5 分钟后在浏览器中输入应用程序负载平衡器 DNS 名称,您应该会看到新的搜索按钮,您可以添加电影并搜索它们。

运行 film-ratings-terraform 目录中的 terraform destroy 命令,不要忘了销毁 AWS 资源。

摘要

恭喜你!如果您已经阅读了整个博客系列,那么您已经使用 PostgreSQL 数据库创建了一个 Clojure web 应用程序,测试了它,构建了它,将其打包到 Docker 容器中,发布了该容器,建立了一个 AWS 弹性容器服务,并使用 Terraform 部署到集群🎉!

这是一个相当现实的设置,虽然有点简单,但可以用在实时 web 应用程序中。您可能希望添加一个指向应用程序负载平衡器的 web 域,使用 TLS 证书保护通信,并添加更多的监控,但是您已经构建了所需的大部分内容。


Chris Howe-Jones 是顾问 CTO、软件架构师、精益/敏捷蔻驰、开发人员和 DevCycle 的技术导航员。他主要从事 Clojure/ClojureScript、Java 和 Scala 方面的工作,客户从跨国组织到小型创业公司。

阅读更多克里斯·豪-琼斯的文章

将 Dockerized Laravel 应用程序部署到 Azure | CircleCI

原文:https://circleci.com/blog/deploy-a-dockerized-laravel-app/

本教程涵盖:

  1. 编写一个样例 Laravel 应用程序
  2. 创建连续部署配置来构建和部署容器映像
  3. 将 Laravel 应用程序部署到 Azure 容器注册中心

随着 web 应用程序变得越来越复杂,软件工程团队必须依赖许多不同的产品和服务来创建最佳的开发人员体验。应用程序开发生态系统已经超越了版本控制和托管部署。手动管理所有服务中新特性的部署会在软件开发生命周期中产生严重的瓶颈。它也引入了人为错误的风险。

在本文中,我将描述如何通过 CircleCI 使用 GitHub 进行版本控制,将 Dockerized Laravel 应用程序部署到 Microsoft Azure。您将获得一个先前构建的 Laravel API 项目,从中构建一个容器映像,然后将该映像推送到 Azure 容器注册中心。然后,您将学习如何创建一个 Azure web 服务作为应用程序,并将容器映像连接到它。

微软 Azure Web Apps 是一个平台即服务 (PaaS) ,它让你发布在多个框架上运行的、用不同编程语言编写的 Web 应用。然后,您可以使用 CI/CD 工具来构建、测试和部署 web 应用程序,以获得更快的发布周期、更高效的开发和更高质量的代码。

先决条件

除了对 Laravel 和 PHP 有一个基本的了解之外,您还需要以下物品来从本教程中获得最大的收益:

  • Docker 桌面安装在您的本地机器上。你可以按照 Docker 的教程去做 Windows 或者 macOS
  • 一个 GitHub 账户。
  • 一个 CircleCI 账户。
  • 一个天蓝色的账户。

我们的教程是平台无关的,但是使用 CircleCI 作为例子。如果你没有 CircleCI 账号,请在 注册一个免费的

克隆演示项目

通过运行以下命令克隆样本 Laravel 项目:

git clone https://github.com/yemiwebby/laravel-azure-api.git 

接下来,转到刚刚创建的新文件夹的根目录。通过运行以下命令设置项目:

cd laravel-azure-api

composer install 

cp .env.example .env

php artisan key:generate

php artisan serve 

这些命令将从终端将目录更改为laravel-azure-api项目,并且:

  • 安装项目的所有依赖项
  • 创建一个.env文件并将.env.example文件的内容复制到其中
  • 生成应用程序密钥
  • 运行应用程序

默认情况下,您的申请将送达http://127.0.0.1:8000/。转到该端点查看 json 响应。

View application locally

您可以使用此命令来运行单元测试:

php vendor/bin/phpunit 

审查部署策略

既然您已经在本地运行了应用程序,现在是时候回顾一下您的部署策略了。除了包含配置文件来设置 CircleCI 的部署之外,不需要对项目代码进行任何更改。

按照时间顺序,您需要:

  1. Azure container registry(ACR)上创建一个注册表来托管您的容器映像,并获取访问密钥。ACR 是微软拥有的一个私人注册中心,用于托管 Docker 图像。
  2. 为项目创建一个容器映像,构建,然后在本地运行该容器。
  3. 将 Docker 映像发布到 Azure 容器注册中心。
  4. 创建一个 Azure web 应用程序,并将其与发布的容器映像链接。
  5. 启用连续部署并创建一个配置文件,以便在 CircleCI 上构建和部署容器映像。
  6. 将项目推送到 GitHub,连接 CircleCI。
  7. 将容器映像部署到 Azure 容器注册表。

创建 Azure 容器注册表

如果您还没有帐户,在 Azure 上创建一个帐户。转到 Azure 门户仪表盘,点击创建资源。

Create Resource

选择容器>容器注册表来创建一个新的注册表。

Create registry

在注册表创建页面上,输入所需的详细信息。

Create container registry

注: 您可以为此项目创建新的资源组,也可以使用现有的资源组。

点击审核+创建。您将被重定向到可以查看注册表信息的页面。点击创建来设置一个新的注册中心实例。

从注册表获取访问密钥

在本节中,您将在 Azure 容器注册表中启用 Docker 访问。这一步对部署过程至关重要。启用 Docker 访问允许您通过 CLI 远程登录 Azure container registry 并向其推送图像。

打开注册表,在设置部分找到访问键链接。

这将显示注册表名称和登录服务器。使用切换按钮启用管理员用户。然后,复制用户名和任何密码,最好是第一个。把这个放在手边的某个地方;在教程的后面部分,您将会用到它。

Enable admin

本地容器化应用程序

下一步是编写一个定制的 Docker 文件,它将允许您构建一个容器映像。从应用程序的根目录,创建一个名为Dockerfile的新文件。打开文件并将以下内容粘贴到其中:

FROM php:8.0.20

RUN curl -sS https://getcomposer.org/installer | php -- \
     --install-dir=/usr/local/bin --filename=composer

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

RUN apt-get update && apt-get install -y zlib1g-dev \
    libzip-dev \
    unzip

RUN docker-php-ext-install pdo pdo_mysql sockets zip

RUN mkdir /app

ADD . /app

WORKDIR /app

RUN composer install

CMD php artisan serve --host=0.0.0.0 --port=8000

EXPOSE 8000 

PHP 版本 8 用作基础映像。随后的命令安装 composer,Laravel 应用程序的扩展,并启用 PHP PDO 和 MySQL 扩展。

安装完成后,将工作目录设置为app,并将文件从本地机器复制到容器的工作目录中。然后运行composer install并包含运行应用程序的命令。

建立码头工人形象

运行以下命令为项目构建 Docker 映像:

docker build -t laravelapidemo.azurecr.io/laravelapidemo:latest . 

该命令将在项目中查找 docker 文件,并根据包含的指令构建容器映像。请注意,上面的命令使用前面创建的注册表中的注册表名称和登录服务器。这种方法的好处是很容易将容器映像映射到 Azure 容器注册中心。

Docker build output

运行 Docker 映像

现在,我们已经成功地构建了本地版本的容器映像,使用以下命令运行它以确保它能够工作:

docker run -d -p 8000:8000 laravelapidemo.azurecr.io/laravelapidemo 

这将在端口 800 上运行应用程序。请访问http://localhost:8000进行验证。

将 Docker 映像推送到 Azure 注册表

接下来,登录之前创建的 Azure 容器注册表,并将容器映像推送到其中。从终端发出以下命令:

docker login -u DOCKER_USER -p DOCKER_PASS laravelapidemo.azurecr.io 

用您的值替换这些占位符:

  • DOCKER_USER:获取容器注册表的用户名。
  • DOCKER_PASS:来自容器注册表的密码。

登录后,使用以下命令将映像推送到 Azure 注册表:

docker push laravelapidemo.azurecr.io/laravelapidemo:latest 

View repository

为容器创建 Azure web 应用程序

接下来,您需要创建一个 Azure Web App ,并将其与容器映像连接。进入 Azure 门户主页,点击创建资源

选择Containers>Web App for Containers创建一个新的 Web App 服务实例。

Create Web app for container

您将被重定向到创建 Web 应用程序页面。选择 Azure 订阅和资源组。如果需要,创建一个新的资源组。Docker container默认为选中,如果为否,点击选中。

Create web app

从 Docker 选项卡中,选择图像源及其各自的 Docker 图像。

Select Docker image

当您点击查看+创建时,您将被重定向到一个页面,在此您可以查看 web 应用程序的详细信息。点击创建来设置一个新的 Azure web 应用。

当这个过程完成后,你可以访问 URL ,查看部署到 Azure 的应用程序。

Laravel app live

支持持续部署

每次你的 Docker 图片更新时,你都希望应用程序能收到更新。为此,您需要为 web app 服务启用连续部署。

点击 web 应用名称,然后点击部署中心。在设置选项卡上,滚动到连续部署按钮并点击选择。点击保存保存更改。

Enable continuous deployment

选择连续部署后,每当在 Azure Container Registry 上重建 Docker 映像时,该 web 应用程序都会触发 Laravel 应用程序的新部署。

自动化部署

在此步骤中,您将为 CircleCI 添加管道配置。该管道自动测试并运行构建容器映像并将其推送到 Azure 容器注册中心的命令。

在项目的根目录下,打开.circleci/config.yml文件。如下所示更新其内容:

version: 2.1
orbs:
  docker: circleci/docker@2.1.2
jobs:
  build-and-test:
    description: Setup laravel application and run tests
    docker:
      # Specify the version you desire here
      - image: cimg/php:8.0-browsers

    steps:
      - checkout

      - run:
          name: "Prepare environment"
          command: |
            sudo apt update
      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Create .env file and generate app key"
          command: |
            mv .env.example .env
            php artisan key:generate

      - run:
          name: "Run tests"
          command: php vendor/bin/phpunit

  build-docker-image:
    executor:
      name: docker/docker
      tag: "3.6"
    steps:
      - checkout
      - docker/install-docker-tools
      - setup_remote_docker:
          version: 20.10.14
          docker_layer_caching: true
      - run:
          name: "Build and push Docker image"
          command: |
            docker build -t laravelapidemo.azurecr.io/laravelapidemo:latest .
            docker login -u $DOCKER_USER -p $DOCKER_PASS laravelapidemo.azurecr.io
            docker push laravelapidemo.azurecr.io/laravelapidemo:latest

workflows:
  test-and-deploy:
    jobs:
      - build-and-test
      - build-docker-image:
          requires:
            - build-and-test 

这是配置的明细。

CircleCI 配置总是从版本开始。对于本教程,我们使用版本2.1

配置的下一部分指定了两个不同的作业:

  • build-and-test
  • build-docker-image

build-and-test作业使用 CircleCI PHP 8 Docker 映像,从 GitHub 签出我们的项目,安装项目的依赖项,设置环境变量,并运行应用程序的测试。

build-docker-image作业为从 Github 存储库中提取的代码创建一个 Docker 映像,并用容器的最新版本更新 Azure 容器注册表。requires键指定在build任务完成之前build-docker-image不会运行。

将应用程序连接到 CircleCI

现在您需要在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。查看将项目推送到 GitHub 以获取指示。

登录您的 CircleCI 帐户。如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。

点击laravel-azure-api项目旁边的设置项目

Select project

系统将提示您编写新的配置文件,或者在项目中使用现有的配置文件。选择现有的分支,并输入您的代码在 GitHub 上所在的分支的名称。点击走吧

Select configuration file

您的第一个工作流将开始运行。

build-docker-image作业将失败,因为您尚未提供您的 Azure container 注册表凭据。

要解决这个问题,您需要添加用户名和密码环境变量。点击项目设置

点击环境变量。创建这些变量:

  • DOCKER_USERNAME变量是为容器注册中心获得的用户名。
  • DOCKER_PASS变量是容器注册表中的密码。

回到仪表板。点击从开始重新运行工作流程。

这一次,您的工作流将成功运行。现在,您可以在本地对代码库进行更改,并使用持续部署将其推送到 GitHub。

结论

现在你知道了。使用 Docker、CircleCI 和 Microsoft Azure,您能够自动化 Laravel 应用程序的部署过程。您执行了设置过程,以确保每次更新项目并将更改推送到 GitHub 时,CircleCI 都会使用配置细节来构建新的容器映像并更新相关的 Azure 容器注册表。随着 Azure web app 上启用持续部署,webhook 资源将被触发,以将新版本的容器映像推送到 web app。

我希望本教程对你有所帮助。点击这里查看您构建的项目的完整源代码。


Oluyemi 是一名拥有电信工程背景的技术爱好者。出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。作为技术专家,他的爱好包括尝试新的编程语言和框架。

阅读更多 Olususi Oluyemi 的帖子

使用工作流将 API 部署到多个环境中

原文:https://circleci.com/blog/deploy-an-api-to-multiple-environments/

在将应用程序部署到生产环境之前,将应用程序部署到测试环境是一种常见的做法。首先进行测试以确保功能正常工作是一种最佳实践,许多开发人员、初创公司和组织都是这样部署他们的应用程序的。

我发现,每次对我的应用程序进行更新时,手动操作很快就会变得既耗时又低效。自动化这个过程有多好?在本教程中,我们将在试运行和生产环境中部署一个 API。这个过程也称为多重部署。

在我们的多部署流程中,我们将创建一个工作流,首先将一个 API 部署到一个暂存环境,然后使用 Postman 对暂存的 API 运行自动化测试。如果测试通过,我们的工作流将在没有我们干预的情况下将 API 部署到生产环境中。如果测试没有通过,API 将不会被部署到生产环境中。

先决条件

要遵循本教程,需要做一些事情:

  1. 系统上安装的 Node.js
  2. Postman for Desktop 安装在您的系统上(您可以在这里下载它
  3. Heroku 或其他应用托管平台
  4. 一个的账户
  5. GitHub 的一个账户

安装并设置好所有这些之后,是时候开始本教程了。

克隆 API 项目

首先,您需要克隆 API 项目。我们将使用一个简单的 Node.js API 应用程序,它有一个根端点和另外两个端点,用于创建和获取用户。通过运行以下命令克隆项目:

git clone --single-branch --branch base-project https://github.com/coderonfleek/multi-deploy-api.git 

克隆过程完成后,转到项目的根目录并安装依赖项:

cd multi-deploy-api
npm install 

接下来,运行应用程序:

npm start 

应用程序将开始监听默认端口3000

打开 Postman 并向http://localhost:3000/users/get端点发出一个GET请求。这将返回一个用户数组。

Get Users - Postman

我们的 API 已经可以部署了。

在 Heroku 上设置登台和部署环境

下一个任务是创建部署环境。您将为staging(应用程序的更新将被部署,用于测试)和deploy(生产环境)创建一个。在本教程中,我们将在 Heroku 上创建这些环境,但是您可以使用您喜欢的托管平台。

进入你的 Heroku 账户仪表盘,点击新建,然后创建新应用。在应用创建页面上,创建staging应用环境。

Staging App - Heroku

现在,重复相同的过程来创建生产应用程序。

Production App - Heroku

现在已经为部署应用程序设置好了环境。最后,在 Heroku 上,从账户设置页面上的账户选项卡中获取 API 密钥。

用 Postman 集合设置 API 测试

在它被部署到staging环境之后,我们希望对 API 进行测试。对于本教程,我们将使用 Postman 来设置可以自动化的 API 测试。

第一步是为 API 请求创建一个专用环境。在 Postman 桌面上,单击管理环境齿轮图标(位于右上角)。管理环境对话框显示任何已经存在的环境。点击添加创建新环境。

在“新环境”对话框中,输入新环境的名称。将登台环境 API 基本 URL ( https://users-api-staging.herokuapp.com)作为环境变量(api_url)填入。您在INITIAL VALUE中输入的内容会被复制到CURRENT VALUE中。保持那样;使用CURRENT VALUE超出了本教程的范围。

Create environment - Postman

点击添加完成环境创建。使用屏幕右上角的下拉菜单切换到新环境。

创建收藏

下一步是为您将要测试的 API 的用户端点创建一个 Postman 集合。

点击左侧边栏中的收藏。然后点击新收藏

Create collection button - Postman

新收藏对话框中,填写您收藏的名称(Users)。如果您想添加有关该收藏的更多信息,也可以添加说明。

Create Collection - Postman

点击创建完成集合设置。新收藏会立即显示在左侧栏的收藏下。

向集合中添加请求

现在是时候向您的集合添加请求了。API 由两个端点组成:

  • (GET):获取用户配置文件列表
  • {{api_url}}/users/create (POST):创建新的用户配置文件

您将为每个端点添加一个请求。若要开始,请单击集合旁边的弹出菜单(箭头图标)。

点击添加请求。在新请求对话框中,为{{api_url}}/users/get端点创建一个请求。点击保存到用户-API-集合将其保存到您之前创建的集合中。

Add Request - Postman

现在请求已经创建,加载了一个新的请求选项卡。使用您创建的api_url变量,在地址栏({{api_url}}/users/get)中输入请求的端点。确保选择获取作为请求方法。点击保存

接下来,为{{api_url}}/users/create端点创建一个请求。我们需要获得随机值,以便POST请求可以创建测试用户。为此,我们需要一个Pre-request剧本。

打开预请求脚本选项卡,添加以下脚本:

let random = +new Date();

pm.globals.set("name", `Test-User-${random}`); 

这个脚本使用当前时间戳为每个触发的请求随机创建名称。随机变量name被设置为请求实例的全局变量。

现在用动态name变量编写请求体。

Dynamic parameters - Postman

动态的name参数将被用于对/users/create端点的每个请求。

添加测试

是时候向您刚刚创建的请求添加一些测试了。

单击{{api_url}}/users/get请求(在左侧栏中)以确保它已被加载。点击测试选项卡。在窗口中,添加:

pm.test("Request is successful with a status code of 200", function () {
  pm.response.to.have.status(200);
});

pm.test("Check that it returns an array", function () {
  var jsonData = pm.response.json();
  pm.expect(jsonData).to.be.an("array");
}); 

第一个测试检查请求是否成功返回,状态代码为200。第二个测试确保它也返回一个数组。

点击用户创建请求测试选项卡进行添加:

pm.test("User creation was successful", function () {
  pm.expect(pm.response.code).to.be.oneOf([200, 201, 202]);
});

pm.test("Confirm response message", function () {
  var jsonData = pm.response.json();
  pm.expect(jsonData.message).to.eql("User successfully registered");
}); 

这里的第一个测试通过断言状态代码200201202来检查用户创建是否成功。第二个测试确保返回正确的响应消息。

注意: 每当您对此集合中的任一请求进行更改时,请务必点击保存

用 Newman 配置自动化测试

接下来,我们将使用 Postman 的 CLI 工具 Newman 来自动化测试的运行方式。

您的收藏将需要一个公共 URL。此链接将指向您在邮递员服务上托管的收藏版本。使用此链接而不是将您的收藏导出为json的主要优点是,只要您登录 Postman,您的收藏的更改将始终可用。当您在桌面上登录时,Postman 会将您所有的本地收藏同步到您的 Postman 帐户。如果你需要一个邮差账户,登录页面会引导你创建一个。

要获取您的公共 URL,请从您的收藏弹出菜单中单击共享。您收藏的公共链接显示在获取链接选项卡下。

Get Link - Postman

使用此链接,您不再需要导出和移动收藏文件。您确实需要在环境文件更新时将其导出,但是这种情况不太常见。

点击管理环境图标,下载你的邮递员环境。点击下载获取文件。文件名类似于User-API-Tests.postman_environment.json(取决于您如何命名环境)。将此环境文件添加到项目的根目录。

接下来,通过运行以下命令,在项目的根目录下安装 Newman CLI 工具:

npm install --save-dev newman 

package.json中的测试脚本替换为:

"scripts": {
    ...
    "test": "npx newman run [YOUR_COLLECTION_PUBLIC_URL] -e ./[YOUR_ENVIRONMENT_FILENAME].json"
} 

这个测试脚本使用npx对集合的 URL 运行newman。集合 URL 使用环境的文件名来指定测试运行的位置。分别用您的集合公共 URL 和环境文件名替换值[YOUR_COLLECTION_PUBLIC_URL][YOUR_ENVIRONMENT_FILENAME]。Newman 针对请求运行集合中定义的测试,并挑选任何已经定义的请求前脚本。

现在您的测试已经为自动化设置好了。

将您的项目连接到 CircleCI

将你的项目推送到 GitHub 开始。

现在,转到 CircleCI 仪表板上的添加项目页面来添加项目。

Add Project - CircleCI

点击设置项目

Add Config - CircleCI

在设置页面上,点击 Use Existing Config 以指示您正在手动设置配置文件,并且不使用显示的示例。接下来,您会得到一个提示,要么下载管道的配置文件,要么开始构建。

Build Prompt - CircleCI

点击开始建造。这个构建将会失败,因为我们还没有设置配置文件。我们将在下一步中这样做。

在离开 CircleCI 控制台之前,您需要设置环境变量,以便将应用程序部署到 Heroku 上的两个环境(登台和生产)中。

管道页面,选择您的应用程序,然后点击项目设置

Project Settings - CircleCI

项目设置侧菜单中,点击环境变量,然后点击添加环境变量

Add Environment variable - CircleCI

添加这些变量:

  • HEROKU_STAGING_APP_NAME:暂存环境的 Heroku 应用程序名称(在本例中为users-api-staging)
  • HEROKU_PRODUCTION_APP_NAME:生产环境的 Heroku 应用程序名称(在本例中为users-api-production)
  • 你的 Heroku API 密钥

现在,您已经准备好部署到 Heroku 上的临时环境和生产环境了。

配置阶段->测试->部署工作流

正如本教程开始时所承诺的,我们的目标是创建一个自动化的工作流,其中应用程序:

  1. 部署到暂存环境
  2. 在分段地址上测试(如果测试通过)
  3. 部署到生产环境中

我们的下一个任务是编写执行这些步骤的部署管道脚本。首先在项目的根目录下创建一个名为.circleci的文件夹。在刚刚创建的文件夹中添加一个名为config.yml的配置文件。输入以下代码:

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
jobs:
  stage:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git:
          app-name: $HEROKU_STAGING_APP_NAME

  test:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test

  deploy:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git:
          app-name: $HEROKU_PRODUCTION_APP_NAME

workflows:
  stage_test_deploy:
    jobs:
      - stage
      - test:
          requires:
            - stage
      - deploy:
          requires:
            - test 

这是一个很好的剧本,对吧?让我分解一下,补充一些细节。首先,拉起heroku球体:

orbs:
  heroku: circleci/heroku@0.0.10 

然后我们创建三个jobs:

  • stage:从 repo 中签出项目代码,并使用 Heroku CircleCI orb 将应用程序部署到临时环境中。
  • test:安装需要安装newman CLI 的项目依赖项。然后它运行package.json中的test脚本,调用newman来运行集合中针对刚刚部署的临时应用程序的测试。
  • deploy:将应用程序部署到 Heroku 上的生产环境中。

orb是抽象常见任务的可重用管道包。我们正在使用的 Heroku orb 抽象了安装和设置heroku CLI 工具来部署应用程序的过程。

最后,脚本定义了stage_test_deploy工作流。该工作流运行stage作业。一旦该作业成功运行,test作业就会对刚刚部署到 staging 的应用程序运行测试。当试运行应用程序上的所有测试都通过后,脚本运行deploy作业将应用程序部署到生产环境中。这是一个平稳、简单的过程。

是时候测试我们的工作流程了。提交所有更改并将代码推送到远程存储库以运行管道脚本。

当前活动的job正在运行,而其他的在等待。

Workflow running - CircleCI

成功的结果是一个接一个地运行作业。

Workflow complete - CircleCI

单击工作流程(stage_test_deploy)链接查看流程中的步骤及其持续时间。

Workflow complete - CircleCI

您还可以单击每个构建来了解它如何运行的详细信息。

Test Job Details - CircleCI

干得好!

结论

自动化现实生活中的手动任务可以帮助您节省时间和精力,提高您的生产力。在本教程中,我们已经自动化了将应用程序暂存、测试和部署到生产环境的过程。您可以将在此学到的知识应用到更复杂的场景中,并进一步提高工作流程的效率。

编码快乐!


Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。你也可以在 Udemy 上查看他的视频课程。

阅读 Fikayo Adepoju 的更多帖子

使用 Quali Torque orb | CircleCI 按需部署应用环境

原文:https://circleci.com/blog/deploy-application-environments-with-torque/

大多数开发者关心的是构建下一个大东西。自动化您的构建、测试和发布过程允许您继续关注创新和向您的用户交付价值。通过将同类最佳的 CI/CD 工作流编排功能与托管环境即服务相结合,开发人员可以专注于构建未来。

Quali Torque orb 允许开发者直接从他们的 CircleCI 管道按需触发应用环境。通过利用 Torque orb 的预定义命令,CircleCI 用户可以轻松地将 Torque 环境即服务平台集成到他们的项目中。在本教程中,我们将演示如何设置 Torque 帐户和应用程序环境,以及如何使用 Torque orb 从 CircleCI 管道中自动部署您的环境。

在 Torque UI 中设置应用程序环境

您只需要遵循几个简单的步骤,就可以建立并运行一个全功能的环境。首先,注册一个免费的 Torque 账户并设置密码,然后按照下面的步骤操作。

在 Torque 中定义您的云帐户

下一步通常由管理员完成,就是在 Torque 中定义一个云帐户。从左侧菜单导航到管理控制台。在云账户管理部分点击连接云,然后按照说明在 AWSAzureKubernetes 上设置账户。

Cloud account admin area in Torque

在这个例子中,我们将使用 AWS 作为我们的云提供商。

添加蓝图存储库

在 Torque 中,应用环境的模板被称为蓝图。Torque 遵循开发人员的最佳实践,所有蓝图都存储在一个源代码控制存储库中,并自动同步。我们目前支持 GitHub 和 Bitbucket,并且正在积极增加更多的支持。检查扭矩位置的最新集成

在下面的例子中,我们将使用 GitHub。

在 GitHub 中,创建一个存储库。复制存储库的 URL。我们建议通过分叉示例存储库(在示例空间中)来创建存储库。

接下来,在扭矩中,执行以下步骤:

  1. 访问您的共享空间。
  2. 打开设置页面。
  3. 单击“存储库”选项卡。您可以在空间中设置两个不同的存储库,一个用于沙盒,另一个用于生产环境。在我们的例子中,我们将为沙盒环境设置一个 repo。
  4. 点击添加一个存储库
  5. 选择您希望使用的源代码管理工具(GitHub ),并将复制的 URL 粘贴到“添加 GitHub 存储库”弹出窗口中。
  6. 点击连接
  7. 授权 Torque 访问您的 GitHub 库。

Adding a blueprint repository in the Torque UI

为你的蓝图建模

DevOps 工程师为代表所需应用程序及其基础架构的蓝图建模。每个蓝图都可以有输入参数、自定义标记,并且可能会因管道所处的阶段而有所不同。

有几种方法可以在 Torque 中创建蓝图:

  • 使用示例库中的 YAML 本地语言,其中包含您可以用来创建新蓝图的示例
  • 通过导入现有的地形模板
  • 通过导入现有的舵图

让我们回顾一下我们将在本项目的其余部分使用的示例应用程序。这个应用程序被称为促销管理器。这是一个简单的电子商务销售工具,产生促销优惠。

DevOps 团队希望在 Torque 中建模 Promotions Manager 应用程序环境,将其集成到自动化的 CircleCI 工作流中,并使用它来自动测试最新版本。

在这种情况下,我们使用 YAML 方法,并借鉴我们团队正在开发的现有蓝图。使用您最喜欢的 IDE 编辑 YAML 代码,并将其签入您在上一步中在 Torque 中定义的 Git 存储库中。

Blueprint repository in GitHub

作为参考,这个蓝图可以在本教程的演示库中找到。

我们的蓝图包含三种应用服务:

  1. 运行在 MongoDB 上的后端数据库
  2. API 服务器
  3. 网络服务器

我们将在 AWS 上部署这个应用程序,AWS 是我们在 cloud account settings 一节中定义的云提供商。

Promotions Manager application in Torque

一旦您的蓝图出现在 Torque blueprint 目录中,您就可以使用切换功能发布它,并在将它包含到您的 CircleCI 工作流中之前测试它以确保它正确部署。

Use the published toggle to publish and test your environment

要测试它,导航到沙盒环境并从目录中选择蓝图。接下来,设置沙盒参数。您可以将所有参数保留为默认值,并输入一个名称。

Sandbox parameters interface

最后,启动沙箱并验证它是否正确部署。

A fully deployed sandbox ready to use

您还可以点击快速链接,并检查您的应用程序的 web UI 前端。

The front end of the Promotions Manager application

现在您确信您的应用程序在 Torque 中正确部署了,您可以结束沙箱或者让它按照沙箱持续时间参数中的定义自动结束。

作为 CI 工作流的一部分,自动化应用程序环境的部署

下一步是在 CircleCI 中创建一个项目,并将来自 Torque orb 注册表的命令包含在您的配置文件中。如果你还没有 CircleCI 账户,你需要注册一个。

在我们的例子中,我们将使用 GitHub 帐户登录 CircleCI,并连接为我们的 Torque 蓝图定义的 Git 存储库。

我们将创建一个简单的项目,该项目将启动一个沙箱来部署我们的 Promotions Manager 蓝图,运行一个测试,并终止该沙箱。

注意: 在一个典型的场景中,您会想要测试您的应用程序代码的最新版本。这意味着您还将添加一个构建步骤作为工作流的触发点,并使其成为项目的第一步。

一旦您的存储库在项目列表中列出,您将配置环境变量来定义您的 Torque 上下文

配置您的 CircleCI 管道

在 CircleCI web 应用程序中,导航到“项目”部分,并从 Promotions Manager 存储库旁边的菜单中选择项目设置。然后导航到环境变量页面。

Environment variables page in the CircleCI web app

接下来,添加以下三个变量:

  • TORQUE_SERVER :你的 TORQUE 服务器的 URL(https://app.qtorque.io)
  • Torque_SPACE :您在 TORQUE 中的空间名称,当您登录 TORQUE 时会出现在屏幕的左上角
  • TORQUE_TOKEN :在 TORQUE 中,导航到 Settings > Integrations 选项卡并选择 CircleCI。点击连接,然后点击新令牌。输入为此变量生成的值。

New token menu in the Torque Integrations tab

接下来,您就可以生成 CircleCI 项目的 config.yml 文件了。回到您的项目,选择配置文件

Configuration File option in the project menu

我们将使用扭矩 orb 库中定义的步骤:

  • start-sandbox :部署扭矩沙箱
  • end-sandbox :终止扭矩沙箱

我们还将运行一个快速测试来检索应用程序端点,您可以根据需要随意添加自己的运行状况检查、安全测试或性能验证。

在我们的推广应用程序示例中,配置文件如下所示(直接取自我们的示例报告):

version: 2.1

orbs:
  torque: quali/torque@1.0.0
  aws-s3: circleci/aws-s3@1.0.11
jobs:
  test:
    docker:
      - image: circleci/ruby:2.4.1
    steps:
      - torque/start-sandbox:
          sandbox-name: "circleci-test"
          blueprint: "promotions-manager-all-aws-dev"
          inputs: "{'AWS_INSTANCE_TYPE': 'm5.large','PORT':'3000','API_PORT':'3001', 'RELEASE_NUMBER':'none', 'API_BUILD_NUMBER':'none'}"
          artifacts: "{'promotions-manager-ui':'artifacts/latest/promotions-manager-ui.master.tar.gz','promotions-manager-api':'artifacts/latest/promotions-manager-api.master.tar.gz', 'mongodb': 'artifacts/test-data/test-data-db.tar' }"
      - run:
          name: Functional test
          command: |
            SB_ENDPOINT="SB_${SANDBOX_ID}_SHORTCUT_1"
            echo "Checking Sandbox ${!SB_ENDPOINT}"
      - torque/end-sandbox:
          sandbox-id: SANDBOX_ID
workflows:
  testpromotionmanager:
    jobs:
      - test 

确保指定所有输入参数(逗号分隔的列表)来启动沙箱。第一次运行这个流程时,使用与在 Torque 中直接测试应用程序部署时相同的值。

在后端,这个工作流调用 Torque REST API 来根据蓝图名称和可选参数触发沙盒环境的部署。类似地,一旦测试完成,沙盒环境终止,基础设施被重置为其初始状态。

运行管道

最后一步是运行您的持续集成管道。您可以直接从 CircleCI UI(第一次创建或修改配置文件时)或者根据合并到主分支的最新代码自动完成这项工作。

A successful pipeline run in CircleCI

结论

只需几个步骤,您就能够以完全自动化的方式自动化应用程序的部署和测试。您可以使用类似的方法非常快速地构建和运行更复杂的管道,从开发到测试,一直到生产,发布您自己的多服务云应用程序。

欲了解更多信息,请访问 Quali 网站上的 Torque 社区CircleCI 合作伙伴简介

使用 AWS CDK | CircleCI 部署自动缩放自托管跑步者

原文:https://circleci.com/blog/deploy-autoscaling-self-hosted-runners-using-aws-cdk/

本教程涵盖:

  1. 定义 AWS CDK 应用程序和 AWS Lambda 处理器
  2. 创建自宿主运行程序资源类
  3. 部署应用程序,然后自动化部署

您可以使用 CircleCI 的云资源来运行您的 CI/CD 作业,但有时您可能希望在您的基础架构上运行它们。如果您的团队强加了特权访问和控制要求,自托管基础架构可能最适合运行您的工作。CircleCI 的自主跑步者让你做到这一点。很容易开始并开始使用自托管运行器。

如果您发现您的团队的资源需求每天都在波动,那么您可以考虑实现一个自动伸缩的解决方案来根据需求增加资源。

在本教程中,您将学习如何使用由 AWS 开发的基础设施代码(IaC)工具 AWS CDK 来设置自动缩放。如果你对使用 AWS GUI 控制台实现自动缩放的感兴趣,有一个配套教程。

先决条件

请参考此列表来设置本教程所需的一切。

创建新的 AWS CDK 项目

为您的 CDK 项目创建一个新目录,并导航到其中。

mkdir circleci-self-hosted-runner-autoscaling
cd circleci-self-hosted-runner-autoscaling 

使用 CDK CLI 运行cdk init命令,该命令使用 Typescript 创建一个新的 CDK 项目。app参数指定您将用于初始化项目的模板。

cdk init app --language typescript 

这个命令用几个文件创建一个新的 CDK 项目。

确保aws-cdk-lib >= 2.32.0package.json中定义。如果现有版本较低,请手动更新 CDK 库版本。

为自动缩放添加 Lambda 函数

在本节中,您将定义一个 AWS Lambda 函数来根据当前需求设置所需的运行实例数量。

在 CDK 项目的根目录下创建一个lambda/auto-scaling-lambda目录。在lambda/auto-scaling-lambda目录中,添加一个用于定义依赖关系的package.json文件。

package.json文件中,定义项目的名称并添加您的处理程序将使用的node-fetch依赖项。

{
  "name": "auto-scaling-lambda",
  "version": "0.1.0",
  "dependencies": {
    "node-fetch": "^2.6.7"
  }
} 

一旦添加了依赖项,运行lambda/auto-scaling-lambda目录中的npm install命令来安装软件包。

接下来,在lambda/auto-scaling-lambda目录中添加一个index.js文件来定义 Lambda 处理程序。该函数将调用 CircleCI runner tasks API 来获取挂起作业的数量。根据响应,它将更新 AWS 自动缩放组中的实例计数。

将此代码添加到index.js文件中:

const AWS = require("aws-sdk");
const fetch = require("node-fetch");
AWS.config.update({ region: "us-west-2" });
const { env } = require("process");

const SECRET_NAME = env.SECRET_NAME;
const SECRET_REGION = env.SECRET_REGION;
const AUTO_SCALING_MAX = env.AUTO_SCALING_MAX;
const AUTO_SCALING_GROUP_NAME = env.AUTO_SCALING_GROUP_NAME;
const AUTO_SCALING_GROUP_REGION = env.AUTO_SCALING_GROUP_REGION;

exports.handler = async (event, context) => {
  return await getTasks().then(async (data) => {
    let numInstances = 0;
    if (data["unclaimed_task_count"] < AUTO_SCALING_MAX) {
      numInstances = data["unclaimed_task_count"];
    } else {
      numInstances = AUTO_SCALING_MAX;
    }

    await updateNumInstances(numInstances);
    return numInstances;
  });
};

async function updateNumInstances(numInstances) {
  const autoScaling = new AWS.AutoScaling({ region: AUTO_SCALING_GROUP_REGION });
  const params = {
    AutoScalingGroupName: AUTO_SCALING_GROUP_NAME,
    MinSize: 0,
    MaxSize: AUTO_SCALING_MAX,
    DesiredCapacity: numInstances,
  };
  await autoScaling.updateAutoScalingGroup(params).promise();
}

async function getTasks() {
  const secret = await getSecret();
  const url = `https://runner.circleci.com/api/v2/tasks?resource-class=${secret["resource_class"]}`;
  const headers = {
    "Circle-Token": secret["circle_token"],
  };

  const response = await fetch(url, {
    headers: headers,
  });
  const data = await response.json();
  return data;
}

async function getSecret() {
  const params = {
    SecretId: SECRET_NAME,
  };
  const data = await new AWS.SecretsManager({ region: SECRET_REGION })
    .getSecretValue(params)
    .promise();
  if ("SecretString" in data) {
    let secret = JSON.parse(data.SecretString);
    return secret;
  } else {
    let buff = new Buffer(data.SecretBinary, "base64");
    let decodedBinarySecret = buff.toString("ascii");
    return JSON.parse(decodedBinarySecret);
  }
} 

请注意:

  • Lambda 函数使用 AWS Secrets Manager 来检索认证任务 API 所需的 CircleCI 令牌。它接收机密名称作为环境变量。
  • 该函数还接收 AWS EC2 自动缩放组名称作为环境变量。它使用 NodeJS 的 AWS SDK 调用自动缩放服务 API 来更新实例计数。

定义 AWS EC2 启动脚本

一旦启动,您需要在 AWS EC2 实例上配置并运行 CircleCI 自托管 runner 服务。定义一个 shell 脚本,该脚本安装所需的软件包,创建 CircleCI runner 服务,并启动该服务以使其可被发现。在scripts/install_runner.sh处创建一个文件,并添加以下代码片段:

#!/bin/bash

#-------------------------------------------------------------------------------
# CircleCI Runner installation script
# Based on the documentation at https://circleci.com/docs/2.0/runner-installation/
#-------------------------------------------------------------------------------

# Prerequisites:
# Complete these:
# https://circleci.com/docs/2.0/runner-installation/#authentication
# This script must be run as root
# This script was tested on Ubuntu 22.04

platform="linux/amd64"                                  # Runner platform: linux/amd64 || linux/arm64 || platform=darwin/amd64
prefix="/opt/circleci"                                  # Runner install directory

CONFIG_PATH="/opt/circleci/launch-agent-config.yaml"    # Determines where Runner config will be stored
SERVICE_PATH="/opt/circleci/circleci.service"           # Determines where the Runner service definition will be stored
TIMESTAMP=$(date +"%g%m%d-%H%M%S-%3N")                  # Used to avoid Runner naming collisions

AUTH_TOKEN="<SELF_HOSTED_RUNNER_AUTH_TOKEN>"  # Auth token for CircleCI
RUNNER_NAME="<SELF_HOSTED_RUNNER_NAME>"           # A runner name - this is not the same as the Resource class - keep it short, and only with letters/numbers/dashes/underscores
UNIQUE_RUNNER_NAME="$RUNNER_NAME-$TIMESTAMP"            # Runners must have a unique name, so we'll append a timestamp
USERNAME="circleci"                                     # The user which the runner will execute as

#-------------------------------------------------------------------------------
# Update; install dependencies
#-------------------------------------------------------------------------------

apt update
apt install coreutils curl tar gzip -y

#-------------------------------------------------------------------------------
# Download, install, and verify the binary
#-------------------------------------------------------------------------------

sudo mkdir -p "$prefix/workdir"
base_url="https://circleci-binary-releases.s3.amazonaws.com/circleci-launch-agent"
echo "Determining latest version of CircleCI Launch Agent"
agent_version=$(curl "$base_url/release.txt")
echo "Using CircleCI Launch Agent version $agent_version"
echo "Downloading and verifying CircleCI Launch Agent Binary"
curl -sSL "$base_url/$agent_version/checksums.txt" -o checksums.txt
file="$(grep -F "$platform" checksums.txt | cut -d ' ' -f 2 | sed 's/^.//')"
sudo mkdir -p "$platform"
echo "Downloading CircleCI Launch Agent: $file"
curl --compressed -L "$base_url/$agent_version/$file" -o "$file"
echo "Verifying CircleCI Launch Agent download"
grep "$file" checksums.txt | sha256sum --check && chmod +x "$file"; sudo cp "$file" "$prefix/circleci-launch-agent" || echo "Invalid checksum for CircleCI Launch Agent, please try download again"

#-------------------------------------------------------------------------------
# Install the CircleCI runner configuration
# CircleCI Runner will be executing as the configured $USERNAME
# Note the short idle timeout - this script is designed for auto-scaling scenarios - if a runner is unclaimed, it will quit and the system will shut down as defined in the below service definition
#-------------------------------------------------------------------------------

sudo bash -c 'cat > /opt/circleci/launch-agent-config.yaml' << EOF
api:
  auth_token: $AUTH_TOKEN
runner:
  name: $UNIQUE_RUNNER_NAME
  command_prefix: ["sudo", "-niHu", "$USERNAME", "--"]
  working_directory: /opt/circleci/workdir/%s
  cleanup_working_directory: true
  idle_timeout: 5m
  max_run_time: 5h
  mode: single-task
EOF

# Set correct config file permissions and ownership
chown root: /opt/circleci/launch-agent-config.yaml
chmod 600 /opt/circleci/launch-agent-config.yaml

#-------------------------------------------------------------------------------
# Create the circleci user & give permissions to working directory
# This user should NOT already exist
#-------------------------------------------------------------------------------

adduser --disabled-password --gecos GECOS "$USERNAME"
chown -R "$USERNAME" "$prefix/workdir"

#-------------------------------------------------------------------------------
# Create the service
# The service will shut down the instance when it exits - that is, the runner has completed with a success or error
#-------------------------------------------------------------------------------

sudo bash -c 'cat > /opt/circleci/circleci.service' << EOF
[Unit]
Description=CircleCI Runner
After=network.target
[Service]
ExecStart=$prefix/circleci-launch-agent --config $CONFIG_PATH
ExecStopPost=shutdown now -h
Restart=no
User=root
NotifyAccess=exec
TimeoutStopSec=18300
[Install]
WantedBy = multi-user.target
EOF

#-------------------------------------------------------------------------------
# Configure your runner environment
# This script must be able to run unattended - without user input
#-------------------------------------------------------------------------------
sudo apt install -y nodejs npm

#-------------------------------------------------------------------------------
# Enable CircleCI Runner service and start it
# This MUST be done last, as it will immediately advertise to the CircleCI server that the runner is ready to use
#-------------------------------------------------------------------------------
sudo systemctl enable $prefix/circleci.service
sudo systemctl start circleci.service 

请注意,该脚本包含作为虚拟值的<SELF_HOSTED_RUNNER_AUTH_TOKEN><SELF_HOSTED_RUNNER_NAME>,它们将被环境变量中的值替换。在下一节中,CDK 堆栈中的 AWS EC2 自动缩放组将使用 shell 脚本。

为应用程序定义 CDK 结构

在本节中,您将为您的应用程序定义所有的 CDK 构造。AWS CDK 构造是云组件,封装了配置细节并粘合了使用一个或多个 AWS 服务的逻辑。CDK 为最常用的 AWS 服务提供了一个构造库。

当您使用app模板生成 CDK 项目时,会为您创建包含CircleciSelfHostedRunnerAutoscalingStack类的lib/circleci-self-hosted-runner-autoscaling-stack.ts文件。您将在该文件中定义 CDK 构造。

//  The snippet shows the original contents for reference. You do not need to replace the file contents.
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";

export class CircleciSelfHostedRunnerAutoscalingStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // we will add all the constructs here
  }
} 

首先,将StackProps扩展到CircleciSelfHostedRunnerAutoscalingStackProps并定义一些额外的属性。您正在扩展StackProps,以便堆栈可以从 CDK 应用程序接收一些自定义参数。

import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";

// extend StackProps
export interface CircleciSelfHostedRunnerAutoscalingStackProps extends StackProps {
  maxInstances: string;
  keypairName: string;
  runnerName: string;
}

export class CircleciSelfHostedRunnerAutoscalingStack extends Stack {
  // use CircleciSelfHostedRunnerAutoscalingStackProps instead of StackProps
  constructor(scope: Construct, id: string, props?: CircleciSelfHostedRunnerAutoscalingStackProps) {
    super(scope, id, props);
  }
} 

接下来,列出所有需要在堆栈中定义的自动缩放结构:

  • 一个 VPC ,一个安全组,以及一个 AMI 用于您的 AWS EC2 实例。
  • AWS EC2 的自动缩放组,在启动时执行自定义用户脚本。
  • 使用 AWS secret manager 存储 CircleCI 令牌和资源类。
  • AWS Lambda 函数,每分钟运行一次,以更新所需的实例计数。

定义 AWS EC2 的结构

在本节中,您将为创建 AWS EC2 自动缩放组时所需的内容定义 AWS CDK 结构。您需要为 EC2 实例定义一个 VPC、一个安全组和一个 AMI。

要定义一个 VPC,在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor内添加一个 CDK 构造。

import { Stack,
    StackProps,
    //update the existing import to add aws_ec2
    aws_ec2 as ec2,
 } from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props?: CircleciSelfHostedRunnerAutoscalingStackProps) {
    super(scope, id, props);

    // we will add all the constructs here
    // provide a unique name for your S3 bucket
    const circleCIVpc = new ec2.Vpc(this, "CircleCISelfHostedRunnerVPC", {
        maxAzs: 1,
        subnetConfiguration: [{
        name: 'public-subnet-1',
        subnetType: ec2.SubnetType.PUBLIC,
        cidrMask: 24,
        }]
    });
} 

要定义一个安全组,将这个代码片段添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中。

import { Stack,
    StackProps,
    //update the existing import to add aws_ec2
    aws_ec2 as ec2,
 } from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props?: CircleciSelfHostedRunnerAutoscalingStackProps) {
    super(scope, id, props);

    // add the security group below the existing constructs
    const circleCISecurityGroup = new ec2.SecurityGroup(this, 'CircleCISelfHostedRunnerSecurityGroup', {
        vpc: circleCIVpc,
    });

    circleCISecurityGroup.addIngressRule(
        ec2.Peer.anyIpv4(),
        ec2.Port.tcp(22),
        'allow SSH access from anywhere',
    );
} 

要为 EC2 实例定义一个 AMI,将这个代码片段添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中。一个文件是可用的 AWS SAM 参数用于获取所选 AWS 区域的 Ubuntu 实例的 AMI ID。

import { Stack,
    StackProps,
    //update the existing import to add aws_ec2
    aws_ec2 as ec2,
 } from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props?: CircleciSelfHostedRunnerAutoscalingStackProps) {
    super(scope, id, props);

    // add the AMI below the existing constructs
    const amiSamParameterName = '/aws/service/canonical/ubuntu/server/focal/stable/current/amd64/hvm/ebs-gp2/ami-id'

    const ami = ec2.MachineImage.fromSsmParameter(
      amiSamParameterName, {
      os: ec2.OperatingSystemType.LINUX
    });
} 

定义自动缩放组

接下来,定义一个 AWS EC2 自动缩放组来管理您的实例。将这段代码添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中。

import {
    StackProps,
    aws_ec2 as ec2,
    //update the existing import to add aws_autoscaling
    aws_autoscaling as autoscaling,
} from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    const instanceTypeName = "t3.micro"
    const instanceType = new ec2.InstanceType(instanceTypeName);

    const circleCiAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'CircleCiSelfHostedRunnerASG', {
        vpc: circleCIVpc,
        instanceType: instanceType,
        machineImage: ami,
        securityGroup: circleCISecurityGroup,
        keyName: props!!.keypairName,
        vpcSubnets: {
            subnetType: ec2.SubnetType.PUBLIC
        },
        minCapacity: 0,
        maxCapacity: Number(props!!.maxInstances),
    });
} 

如果您想要 SSH 到 EC2 实例,您需要使用 AWS 控制台手动创建 EC2 密钥对。否则,您可以在定义自动缩放构造时省略keyName属性。

自动缩放 CDK 构造允许您附加一个将在实例启动时执行的用户数据脚本。通过在堆栈文件中添加以下代码片段,将用户数据脚本附加到自动缩放组:

import { readFileSync } from 'fs';
const { env } = require("process");

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    let userDataScript = readFileSync('./scripts/install_runner.sh', 'utf8');

    userDataScript = userDataScript.replace('<SELF_HOSTED_RUNNER_AUTH_TOKEN>', env.SELF_HOSTED_RUNNER_AUTH_TOKEN);
    userDataScript = userDataScript.replace('<SELF_HOSTED_RUNNER_NAME>', props!!.runnerName);

    circleCiAutoScalingGroup.addUserData(userDataScript);
} 

请注意,用户数据脚本中的 auth token 占位符被替换为环境变量中的值,而 runner name 占位符被替换为 stack 属性值。

使用 AWS Secret Manager 存储凭据

CircleCI 令牌和资源类名应该安全地存储。您将使用 AWS Secret Manager 来存储凭证,而不是将它们存储为明文。将这个代码片段添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中,以创建一个新的秘密。

import {
    //update the existing import to add aws_secretsmanager
    aws_secretsmanager as secretsmanager,
    SecretValue,
  } from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    const circleCISecret = new secretsmanager.Secret(this, 'CircleCiSelfHostedRunnerSecret', {
        secretName: 'circleci-self-hosted-runner-secret',
        secretObjectValue: {
            "resource_class": SecretValue.unsafePlainText(env.SELF_HOSTED_RUNNER_RESOURCE_CLASS),
            "circle_token": SecretValue.unsafePlainText(env.CIRCLECI_TOKEN),
        }
    });
} 

请注意,您正在从 CircleCI 环境变量中获取凭证。AWS Lambda 函数将在调用 CircleCI tasks API 之前从 secrets manager 获取这些凭证。您还可以将凭证设置为 AWS Lambda 环境变量,但是任何有权访问 AWS 控制台的人都可以查看这些值。

创建 AWS Lambda 函数

首先,为 AWS Lambda 函数定义一个执行角色。执行角色应该有权限从秘密管理器获取凭证并更新自动缩放计数。将这段代码添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中。

import {
    //update the existing import to add aws_iam
    aws_iam as iam,
} from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    const lambdaPolicyDocument = new iam.PolicyDocument({
        statements: [
            new iam.PolicyStatement({
                resources: [circleCiAutoScalingGroup.autoScalingGroupArn],
                actions: ["autoscaling:UpdateAutoScalingGroup"],
            }),
            new iam.PolicyStatement({
                resources: [circleCISecret.secretArn],
                actions: ["secretsmanager:GetSecretValue"],
            })
        ],
    });

    const inferenceLambdaRole = new iam.Role(this, `CircleCIAutoScalingLambdaRole`, {
        assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
        description: "Role assumed by auto scaling lambda",
        inlinePolicies: {
            lambdaPolicy: lambdaPolicyDocument,
        },
        managedPolicies: [
            iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
        ]
    });
} 

要定义一个 AWS Lambda 函数,添加一个 CDK 构造,在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor内创建一个 AWS Lambda 函数。

import {
    //update the existing import to add aws_lambda and Duration
    aws_lambda as lambda,
    Duration,
} from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    const autoScalingLambda = new lambda.Function(this, 'CircleCiSelfHostedRunnerAutoScalingLambda', {
        functionName: 'CircleCiSelfHostedRunnerAutoScalingLambda',
        code: lambda.Code.fromAsset('./lambda/auto-scaling-lambda/'),
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: "index.handler",
        environment: {
            "SECRET_NAME": circleCISecret.secretName,
            "SECRET_REGION": props?.env?.region || 'us-west-2',
            "AUTO_SCALING_MAX": props!!.maxInstances,
            "AUTO_SCALING_GROUP_NAME": circleCiAutoScalingGroup.autoScalingGroupName,
            "AUTO_SCALING_GROUP_REGION": props?.env?.region || 'us-west-2'
        },
        timeout: Duration.minutes(1),
        role: inferenceLambdaRole
    });
} 

安排 Lambda 定期运行

最后,安排 AWS Lambda 函数每分钟触发一次。您可以根据预期的工作量选择不同的频率。将这段代码添加到在lib/circleci-self-hosted-runner-autoscaling-stack.ts文件中定义的constructor中。

import {
    //update the existing import to add aws_lambda and Duration
    aws_events as events,
    aws_events_targets as targets,
} from 'aws-cdk-lib';

constructor(scope: Construct, id: string, props ?: CircleciSelfHostedRunnerAutoscalingStackProps) {

    // add the following code snippet below the existing constructs
    const eventRule = new events.Rule(this, 'CircleCiLambdaSchedule', {
        schedule: events.Schedule.rate(Duration.minutes(1)),
    });

    eventRule.addTarget(new targets.LambdaFunction(autoScalingLambda))
} 

更新 CDK 应用程序

堆栈接受一些需要从 T2 CDK 应用程序传递过来的参数。用以下代码片段替换bin/circleci-self-hosted-runner-autoscaling.ts文件中的代码:

import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CircleciSelfHostedRunnerAutoscalingStack } from "../lib/circleci-self-hosted-runner-autoscaling-stack";
const { env } = require("process");

const app = new cdk.App();
new CircleciSelfHostedRunnerAutoscalingStack(app, "CircleciSelfHostedRunnerAutoscalingStack", {
  maxInstances: "4",
  keypairName: env.AWS_KEYPAIR_NAME,
  runnerName: "aws-runner",
}); 

请注意,您对最大实例数和流道名称的值进行了硬编码。另外,AWS_KEYPAIR_NAME是从一个环境变量中获取的。

部署 CDK 堆栈

现在,您可以继续将应用程序部署到 AWS 帐户。首先手动部署应用程序,以确保在自动化部署之前一切正常。

注意: 本地部署要求您在机器上设置 AWS CLI、AWS CDK CLI 和一些环境变量。如果您想直接使用 CircleCI 测试您的 CDK 堆栈,您可以跳过这一节。

在第一次部署之前,您需要使用cdk CLI 引导项目。应用程序的引导提供了 AWS CDK 部署应用程序可能需要的资源。从项目的根目录发出以下命令:

cdk bootstrap 

确保您在系统上配置了 AWS 凭据。请参考配置 AWS 凭据的先决条件部分中的链接。如果配置了凭据,CDK 将自动使用它们。

接下来,将应用程序部署到 AWS 帐户。

cdk deploy 

在部署堆栈之前,确保在本地设置了所需的环境变量。您需要设置AWS_KEYPAIR_NAMESELF_HOSTED_RUNNER_RESOURCE_CLASSSELF_HOSTED_RUNNER_AUTH_TOKENCIRCLECI_TOKEN来部署 app。

执行该命令后,可能会提示您确认应用于您的帐户的 IAM 角色/策略更改。如果应用程序设置正确,部署应该会成功。

使用 CircleCI 自动部署

干得好!您可以使用命令行手动部署 CDK 应用程序。现在,您可以自动化工作流,这样,每当您将代码推送到主分支时,就可以自动打包和部署基础结构的更改。要自动化部署,请执行以下操作:

  1. 更新。gitignore
  2. 更新 NPM 脚本
  3. 添加配置脚本
  4. 为应用程序创建一个 CircleCI 项目
  5. 设置环境变量

更新。gitignore

cdk init命令生成的代码包含一个默认忽略所有.js文件的.gitignore文件。用以下代码片段替换.gitignore的内容:

!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out 

更新 NPM 脚本

您的 CircleCI 部署配置使用 NPM 脚本来执行 deploy 和 diff 命令。将这些脚本添加到根级package.json文件:

// update the aws-cdk-lambda-circle-ci/package.json file with the following scripts
{
  ...
  "scripts": {
    ...
    // add the ci_diff and ci_deploy scripts
    "ci_diff": "cdk diff -c env=${ENV:-stg} 2>&1 | sed -r 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g' || true",
    "ci_deploy": "cdk deploy -c env=${ENV:-stg} --require-approval never"
  },
  ...
} 

添加配置脚本

在项目的根目录中添加一个.circleci/config.yaml脚本(包含 CI 管道的配置文件)。将以下代码片段添加到其中:

version: 2.1

orbs:
  aws-cli: circleci/aws-cli@2.0.6
executors:
  default:
    docker:
      - image: "cimg/node:14.18.2"
    environment:
      AWS_REGION: "us-west-2"
jobs:
  build:
    executor: "default"
    steps:
      - aws-cli/setup:
          aws-access-key-id: AWS_ACCESS_KEY
          aws-secret-access-key: AWS_ACCESS_SECRET
          aws-region: AWS_REGION_NAME
      - checkout
      - run:
          name: "install_lambda_packages"
          command: |
            cd lambda/auto-scaling-lambda && npm install
            cd ../
      - run:
          name: "build"
          command: |
            npm install
            npm run build
      - run:
          name: "cdk_diff"
          command: |
            if [ -n "$CIRCLE_PULL_REQUEST" ]; then
              export ENV=stg
              if [ "${CIRCLE_BRANCH}" == "develop" ]; then
                export ENV=prd
              fi 
              pr_number=${CIRCLE_PULL_REQUEST##*/}
              block='```'
              diff=$(echo -e "cdk diff (env=${ENV})\n${block}\n$(npm run --silent ci_diff)\n${block}")
              data=$(jq -n --arg body "$diff" '{ body: $body }') # escape
              curl -X POST -H 'Content-Type:application/json' \
                -H 'Accept: application/vnd.github.v3+json' \
                -H "Authorization: token ${GITHUB_TOKEN}" \
                -d "$data" \
                "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_number}/comments"
            fi
      - run:
          name: "cdk_deploy"
          command: |
            if [ "${CIRCLE_BRANCH}" == "main" ]; then
              ENV=prd npm run ci_deploy
            elif [ "${CIRCLE_BRANCH}" == "develop" ]; then
              ENV=stg npm run ci_deploy
            fi 

CI 脚本使用 CircleCI 的 aws-cli orb 来设置 aws 配置(访问密钥和密码)。build作业安装包,计算差异,并部署更改。cdk_diff步骤只在 pull 请求上执行,并在 PR 上添加一个总结基础设施变更的注释。

cdk_deploy命令检查分支并部署在prdstg环境中。注意,cdk_deploy命令执行在package.json文件中定义的ci_deploy脚本。

您的管道配置将负责构建、打包和部署 CDK 堆栈到指定的 AWS 帐户。提交更改并将其推送到 GitHub 存储库。

注意: 如果与您所在地区不同,请不要忘记更换上面指定的AWS_REGION

为应用程序创建一个 CircleCI 项目

使用 CircleCI 控制台将存储库设置为 CircleCI 项目。在 CircleCI 控制台上,点击项目,搜索 GitHub repo 名称,并为您的项目点击设置项目

Circle CI set up project

系统将提示您手动添加新的配置文件或使用现有的配置文件。因为您已经将所需的配置文件推送到代码库,所以选择最快的选项。输入承载配置文件的分支的名称。点击设置项目继续。

Circle CI project configuration

完成设置将触发管道。不出所料,管道将在第一次运行时失败,因为您还没有定义环境变量。

设置环境变量

从项目仪表盘中点击项目设置,进入环境变量选项卡。点击添加环境变量按钮,添加新的键值。

您需要添加这些环境变量:

  • AWS_ACCESS_KEY是创建 AWS 凭证时获得的访问密钥。
  • AWS_ACCESS_SECRET是创建 AWS 凭证时获得的密码。
  • AWS_REGION_NAME是您希望部署应用程序的区域。
  • AWS_KEYPAIR_NAME是使用 AWS 控制台创建的 EC2 密钥对的名称。
  • SELF_HOSTED_RUNNER_RESOURCE_CLASS是您在创建自托管运行程序资源类时设置的运行程序名称。
  • SELF_HOSTED_RUNNER_AUTH_TOKEN是在创建自托管 runner 资源类时获得的令牌。
  • CIRCLECI_TOKEN是 CircleCI 个人访问令牌。它与 CircleCI runner 令牌不同。

Circle CI set up environment variables

配置好环境变量后,重新运行管道。这一次它应该会成功构建。

Circle CI pipeline builds successfully

测试自动缩放设置

现在,您的自动缩放堆栈已经成功部署,您可以使用它来运行一些作业。为了测试设置,使用示例 NodeJS 应用程序,您可以使用这个链接来克隆它。

要使用自托管运行器运行作业,请确保作业配置使用您之前创建的自托管资源类。参考下面的代码片段作为设置resource_class的示例:

version: 2.1
workflows:
  testing:
    jobs:
      - runner-test

jobs:
  runner-test: # this can be any name you choose
    machine: true
    resource_class: tutorial-gwp/auto-scaling-st ack
    steps:
      - checkout # checkout source code
      - run:
          name:
          command: npm test 

按照上一节中的步骤为 NodeJS 应用程序创建一个 CircleCI 项目。在 CircleCI 项目页面,点击触发管道为您的应用程序触发一个作业。您可以多次单击它,让多个作业同时运行。

Trigger pipeline for the NodeJS application

转到 AWS EC2 控制台,注意新实例正在旋转。正在运行的实例数量将等于挂起作业的数量。

AWS instances spinning up

如果挂起作业的数量超过 AWS EC2 自动缩放组的maxCapacity,那么只有maxCapacity数量的实例将被加速。一旦其中一个作业完成执行,其他未决作业将被处理。

几分钟后,您会注意到所有的作业都已成功完成。

All NodeJS jobs completing successfully

结论

在本教程中,您学习了如何使用 AWS CDK 根据需求自动缩放自托管跑步者。借助 AWS CDK,您可以使用自己熟悉的语言为应用程序提供资源。AWS CDK 允许您在定义应用程序时使用逻辑语句和面向对象的技术。CircleCI 的自托管运行器满足独特的架构要求、特权访问和控制要求。通过自动扩展自托管跑步者资源,您可以优化成本并根据需求增加额外资源。

在 GitHub 上查看本教程中使用的完整源代码。如果您试图定义一个类似的堆栈,GitHub 项目也可以用作模板。

示例 NodeJS 应用程序的源代码可以在这里找到。


Vivek Kumar Maskara 是 JP 摩根的一名软件工程师。他喜欢写代码,开发应用程序,创建网站,并写关于他的经历的技术博客。他的简介和联系方式可以在maskaravivek.com找到。

阅读更多 Vivek Maskara 的帖子

posted @ 2024-10-31 16:49  绝不原创的飞龙  阅读(15)  评论(0编辑  收藏  举报