65.9K
CodeProject 正在变化。 阅读更多。
Home

Azure 上的高级 Pulumi

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2022 年 6 月 16 日

CPOL

5分钟阅读

viewsIcon

4498

在本文中,我们将探讨如何构建测试以确保我们构建的基础设施符合我们的要求。

在本系列的第二篇文章中,我们探讨了使用 Pulumi 和 TypeScript 在 Azure 上构建基础设施的便捷性。但基础设施代码不仅仅是编写和运行它。

与应用程序开发人员一样,我们也应该测试我们的代码,并且由于 Pulumi 使用通用的开发语言,我们可以使用特定于语言的测试框架。在本文中,我们将为我们的基础设施创建一些单元测试,以确保我们正确地构建它。我们还将使用CrossGuard构建一些属性测试,以确保我们的资源符合安全合规性。

为单元测试进行设置

对于我们的单元测试,我们将设置推荐的Mocha测试框架。如果您更熟悉 Jest、Puppeteer 或 Storybook,也可以使用它们。

在开始之前,请确保您拥有上一篇文章中的 Pulumi 示例应用程序,并导航到基础文件夹。

我们将通过运行以下命令来安装 Mocha 框架

npm install mocha @types/mocha ts-node --global

这会将 Mocha 框架全局安装,以便我们可以从任何地方访问它。我们可以通过运行 mocha --version 来测试这是否有效。

此外,我们需要将配置设置为环境变量,因为 Mocha 无法访问使用 Pulumi 命令行设置的常规配置变量。要在 Windows 计算机上执行此操作,您可以创建一个新的命令文件,并在顶部添加以下内容

set PULUMI_CONFIG={ "project:appInsightsKind" : "web", "project:appInsightsName" : "pulumi-insights", "project:appInsightsType" : "web", "project:appServiceKind" : "app", "project:appServiceName" : "pulumi-app", "project:appServiceSKUName" : "B1", "project:appServiceSKUTier" : "Basic", "project:configStorageKind" : "StorageV2", "project:configStorageSKU" : "Standard_LRS", "project:cosmosAccountName" : "pulumi-dev-cosmosdbaccount", "project:cosmosContainerName" : "pulumi-dev-cosmosdbcontainer", "project:cosmosDBName" : "pulumi-dev-cosmosdb", "project:frontEndName" : "pulumiFrontEnd", "project:location" : "EastUS", "project:resourceGroupName" : "gpDevPulumi", "project:storageKind" : "StorageV2", "project:storageName" : "pulumistorage", "project:storageSKU" : "Standard_LRS", "project:webAppName" : "pulumi-webapp" }

Linux 和 MacOS 的工作方式类似,但要为这些操作系统创建环境变量,请查看Digital Ocean 的教程。关键区别在于基本上删除 set 命令。

这会将您当前用于构建环境的配置设置为环境变量,以便 Mocha 可以正确运行 Pulumi。

为 Pulumi 设置模拟

让我们设置基本的测试结构和 Pulumi 的模拟。对于任何测试方案,您都需要将特定的状态和输入传递到测试框架。但是,Pulumi 在我们当前的开发环境中处理了大部分这些工作,这意味着我们可以为我们的测试使用更通用的模拟结构。

首先,创建一个名为 environmentTests.ts 的新文件,并添加以下代码块

import * as pulumi from "@pulumi/pulumi";
import "mocha";
 
pulumi.runtime.setMocks({
    newResource: function(args: pulumi.runtime.MockResourceArgs): {id: string, state: any} {
        return {
            id: args.inputs.name + "_id",
            state: args.inputs,
        };
    },
    call: function(args: pulumi.runtime.MockCallArgs) {
        return args.inputs;
    }, 
});

这添加了我们需要的基本模拟功能,但我们还需要更新我们的命令文件以运行 Mocha。打开命令文件,在环境变量设置行下,添加以下命令

npx mocha -r ts-node/register environmentTests.ts

保存此命令文件。从技术上讲,我们现在可以运行我们的测试套件,但除了模拟之外,没有任何东西可以测试。所以,让我们看看如何创建一些测试并测试我们的环境构建。

构建单元测试

通常,当我们构建单元测试时,我们会构建它们以使它们失败,然后编写代码使它们通过。由于我们已经构建了基础设施,让我们构建一个简单的测试来确保我们的位置设置为 EastUS。首先,我们需要通过导入我们的环境脚本来构建初始测试环境。为此,请在初始模拟下创建以下代码块

describe("Environment", function() {
    let environment: typeof import("./index");
 
    before(async function() {
        this.timeout(6000);
        environment = await import("./index");
    });
});

这会导入包含我们的环境构建的 index 脚本,并在运行我们的测试之前等待该导入完成。我们还将 before 语句的超时值设置为高于默认值的值。这是因为 Pulumi 需要时间来构建我们相当大的环境。

此代码建立我们的测试环境,但我们仍然没有实际的测试。让我们通过在 before 函数后添加以下代码块来创建它

describe("Resource Group", function() {
        it("must have a name set", function(done) {
            pulumi.all([environment.resourceGroup.name]).apply(func => {
                if(environment.resourceGroup.name == null){
                    done(new Error('Resource Group does not have a Name set'));
                } else {
                    done();
                }
            })
        })
    });

输入代码后,您可能会注意到我们的环境的 resourceGroup 属性出现错误。这是因为测试框架无法访问我们在构建环境时创建的任何内部变量。

最简单的解决方法是在我们想要测试的任何内容前面添加 export。这会将我们创建的资源暴露给外部库。例如,资源组行现在将变为

export var resourceGroup = new resources.ResourceGroup(configRG, { location: configLocation, resourceGroupName: configRG });

让我们对所有资源都这样做。现在,当我们运行测试套件时,我们会看到一个测试通过。

让我们再创建一个测试,该测试会查看我们的配置并确保所有内容都带有标签值。输入以下代码块以检查所有项目

describe("All Resources", function() {
        it("Resource Group must be tagged", function(done) {
            pulumi.all([environment.resourceGroup.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('Resource Group does not have a Tag')); } else { done(); }
            });
        })
        it("CosmosDB Account must be tagged", function(done) {
            pulumi.all([environment.cosmosdbAccount.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('CosmosDB Account does not have a Tag')); } else { done(); }
            });
        })
        it("CosmosDB Database must be tagged", function(done) {
            pulumi.all([environment.cosmosdbDatabase.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('Cosmos Database does not have a Tag')); } else { done(); }
            });
        })
        
        it("Storage Account must be tagged", function(done) {
            pulumi.all([environment.storageAccount.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('Storage Account does not have a Tag')); } else { done(); }
            });
        })
        it("App Insights must be tagged", function(done) {
            pulumi.all([environment.appInsights.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('App Insights does not have a Tag')); } else { done(); }
            });
        })
        it("Service Plan must be tagged", function(done) {
            pulumi.all([environment.appServicePlan.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('App Service Plan does not have a Tag')); } else { done(); }
            });
        })
        it("Web App must be tagged", function(done) {
            pulumi.all([environment.webApp.tags]).apply(([tags]) => {
                if(!tags){ done(new Error('Web App does not have a Tag')); } else { done(); }
            });
        })
    });

现在,当我们运行测试脚本时,我们会看到所有资源下的七个项目都失败了。

让我们通过在所有相关资源上添加标签属性来解决此问题,方法是使用行

tags: { "Project" : "PulmiExample" }

所以,例如,我们的资源组现在将看起来像这样

export var resourceGroup = new resources.ResourceGroup(configRG, { location: configLocation, resourceGroupName: configRG, tags: { "Project" : "PulmiExample" } });

现在,当我们运行测试时,它们都通过了。

Pulumi 策略包

我们可以测试基础设施的另一种方法是通过 CrossGuard 策略包使用属性测试。策略包与单元测试不同,因为在测试基础设施属性是否符合规定之前,会先构建预览或完整环境。

让我们构建一个简单的策略包,以强制我们的存储帐户仅位于 WestUS。首先,我们需要在一个新目录(示例使用 azure-policy)中创建一个策略包,命令为 pulumi policy new azure-typescript

此过程将创建一个新的 Pulumi 应用程序,其中包含自己的 index.ts 文件。示例策略创建了测试以确保 blob 存储不对公众开放,但让我们修改该代码一点,使其成为下面的代码块

import * as azure from "@pulumi/azure-native";
import { PolicyPack, validateResourceOfType } from "@pulumi/policy";
 
new PolicyPack("azure-typescript", {
    policies: [{
        name: "storage-container-location",
        description: "Prohibits Azure Storage Containers being located anywhere else but WestUS.",
        enforcementLevel: "mandatory",
        validateResource: validateResourceOfType(azure.storage.StorageAccount, (storage, args, reportViolation) => {
            if (storage.location != "WestUS") {
                reportViolation("Azure Storage Accounts must be in WestUS");
            }
        }),
    }],
});

此代码块同时使用了 Azure 的原生 SDK 和 Pulumi 策略 SDK。它创建了一个新的策略包,其中包含一个强制性策略。

然后,我们使用 validateResource 方法,该方法会检查任何 StorageAccount 资源以确保其位置设置为 WestUS。我们可以扩展此策略包并添加任意数量的针对不同资源的策略,但让我们运行它,看看当策略失败时会发生什么。

我们通过从包含我们基础设施的基础目录运行 pulumi preview --policy-pack azure-policy(将 azure-policy 替换为您的策略包文件夹的名称)来做到这一点。这将以预览模式运行我们的基础设施,并确保满足我们的策略。

如果我们现在硬编码我们的存储帐户将其位置设置为 WestUS,然后重新运行策略检查器,我们就不会收到任何错误。但是,我们会收到有关检查了策略包的确认。

摘要

在本文中,我们探讨了通过 Mocha 等特定语言的测试框架测试基础设施代码的方法。我们还使用 Pulumi 内置的 CrossGuard 库探索了属性测试。

Pulumi 提供了强大的 IaC 功能,这些功能内置于您已经熟悉的语言中。本系列文章仅触及了 Pulumi 功能的皮毛。您可以通过访问他们的网站了解更多关于如何构建一致的 Azure 基础设施的信息。

要了解有关使用 Azure Pulumi 进行云工程的更多信息,请查看资源使用 Azure Pulumi 进行云工程

© . All rights reserved.