在运行 `terraform plan` 时如何显示警告/错误信息?

huangapple go评论79阅读模式
英文:

How to show a warning/error when running 'terraform plan'?

问题

我正在构建一个Terraform插件/提供程序,它将帮助用户管理他们在云平台上的云资源,例如云实例、Kubernetes集群等。

目前,该云平台不支持在创建后更改Kubernetes节点的大小。如果用户想要更改节点的大小,他们需要使用新的节点大小创建一个新的节点池。

因此,我正在我的插件代码中添加以下代码块,具体是在Kubernetes集群更新方法中:

if d.HasChange("target_nodes_size") {
    errMsg := []string{
        "[ERR] 无法在创建后更新 'target_nodes_size'。",
        "请使用新的节点大小创建一个新的节点池。",
    }
    return fmt.Errorf(strings.Join(errMsg, " "))
}

问题是,只有当我运行terraform apply命令时,才会出现错误。我希望在用户运行terraform plan命令时就能显示错误,这样他们就能提前知道无法在不创建新的节点池的情况下更改节点大小。

如何使target_nodes_size字段不可变,并在terraform plan输出中提前显示错误呢?

在运行 `terraform plan` 时如何显示警告/错误信息?

英文:

I'm building a Terraform plugin/provider (link) which will help users manage their cloud resources e.g. cloud instances, Kubernetes clusters & etc on a cloud platform.

The cloud platform at this moment does not support Kubernetes nodes size change after it gets created. If user wants to change the nodes size, they need to create a new node pool with the new nodes size.

So I'm adding this block in my plugin code, specifically in the Kubernetes cluster update method (link):

if d.HasChange("target_nodes_size") {
	errMsg := []string{
		"[ERR] Unable to update 'target_nodes_size' after creation.",
		"Please create a new node pool with the new node size.",
	}
	return fmt.Errorf(strings.Join(errMsg, " "))
}

The problem is, the error only appears when I run terraform apply command. What I want is, I want it to show when user runs terraform plan command so they know it early that it's not possible to change the nodes size without creating a new node pool.

How do I make that target_nodes_size field immutable and show the error early in terraform plan output?

在运行 `terraform plan` 时如何显示警告/错误信息?

答案1

得分: 3

在这里正确的做法是告诉Terraform资源的更改不能在原地进行,而是需要重新创建资源(通常是先销毁再创建,但你也可以通过lifecycle.create_before_destroy来反转这个过程)。

创建提供程序时,可以在模式属性的ForceNew参数上进行设置

aws_launch_configuration资源为例,该资源在AWS的API端被认为是不可变的,因此模式中的每个非计算属性都标有ForceNew: true

func resourceAwsLaunchConfiguration() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLaunchConfigurationCreate,
		Read:   resourceAwsLaunchConfigurationRead,
		Delete: resourceAwsLaunchConfigurationDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validation.StringLenBetween(1, 255),
			},
// ...

如果你尝试修改任何ForceNew: true字段,那么Terraform的计划将显示需要替换资源,并且在应用时,只要用户接受计划,它将自动执行替换操作。

对于一个更复杂的例子,aws_elasticsearch_domain资源允许原地进行版本更改,但只允许特定的版本升级路径(例如,你不能直接从5.4升级到7.8,而是必须经过5.4 -> 5.6 -> 6.8 -> 7.8)。这是通过在模式上使用CustomizeDiff属性来实现的,它允许你在计划时使用逻辑来给出与静态配置不同的结果。

aws_elasticsearch_domainelasticsearch_version属性的CustomizeDiff如下所示:

func resourceAwsElasticSearchDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsElasticSearchDomainCreate,
		Read:   resourceAwsElasticSearchDomainRead,
		Update: resourceAwsElasticSearchDomainUpdate,
		Delete: resourceAwsElasticSearchDomainDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsElasticSearchDomainImport,
		},

		Timeouts: &schema.ResourceTimeout{
			Update: schema.DefaultTimeout(60 * time.Minute),
		},

		CustomizeDiff: customdiff.Sequence(
			customdiff.ForceNewIf("elasticsearch_version", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool {
				newVersion := d.Get("elasticsearch_version").(string)
				domainName := d.Get("domain_name").(string)

				conn := meta.(*AWSClient).esconn
				resp, err := conn.GetCompatibleElasticsearchVersions(&elasticsearch.GetCompatibleElasticsearchVersionsInput{
					DomainName: aws.String(domainName),
				})
				if err != nil {
					log.Printf("[ERROR] Failed to get compatible ElasticSearch versions %s", domainName)
					return false
				}
				if len(resp.CompatibleElasticsearchVersions) != 1 {
					return true
				}
				for _, targetVersion := range resp.CompatibleElasticsearchVersions[0].TargetVersions {
					if aws.StringValue(targetVersion) == newVersion {
						return false
					}
				}
				return true
			}),
			SetTagsDiff,
		),

尝试在接受的升级路径上升级aws_elasticsearch_domainelasticsearch_version(例如,7.4 -> 7.8)将在计划中显示为原地升级,并在应用时执行。另一方面,如果你尝试通过不允许的路径进行升级(例如,5.4 -> 7.8),那么Terraform的计划将显示需要销毁现有的Elasticsearch域并创建一个新的域。

英文:

The correct thing to do here is to tell Terraform that changes to the resource cannot be done in place and instead requires the recreation of the resource (normally destroy followed by creation but you can reverse that with lifecycle.create_before_destroy).

When creating a provider you can do this with the ForceNew parameter on a schema's attribute.

As an example, the aws_launch_configuration resource is considered immutable from AWS' API side so every non computed attribute in the schema is marked with ForceNew: true.:

func resourceAwsLaunchConfiguration() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLaunchConfigurationCreate,
		Read:   resourceAwsLaunchConfigurationRead,
		Delete: resourceAwsLaunchConfigurationDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validation.StringLenBetween(1, 255),
			},
// ...

If you then attempt to modify any of the ForceNew: true fields then Terraform's plan will show that it needs to replace the resource and at apply time it will automatically do that as long as the user accepts the plan.

For a more complicated example, the aws_elasticsearch_domain resource allows in place version changes but only for specific version upgrade paths (so you can't eg go from 5.4 to 7.8 directly and instead have to go to 5.4 -> 5.6 -> 6.8 -> 7.8. This is done by using the CustomizeDiff attribute on the schema which allows you to use logic at plan time to give a different result than would normally be found from static configuration.

The CustomizeDiff for the aws_elasticsearch_domain elasticsearch_version attribute looks like this:

func resourceAwsElasticSearchDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsElasticSearchDomainCreate,
		Read:   resourceAwsElasticSearchDomainRead,
		Update: resourceAwsElasticSearchDomainUpdate,
		Delete: resourceAwsElasticSearchDomainDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsElasticSearchDomainImport,
		},

		Timeouts: &schema.ResourceTimeout{
			Update: schema.DefaultTimeout(60 * time.Minute),
		},

		CustomizeDiff: customdiff.Sequence(
			customdiff.ForceNewIf("elasticsearch_version", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool {
				newVersion := d.Get("elasticsearch_version").(string)
				domainName := d.Get("domain_name").(string)

				conn := meta.(*AWSClient).esconn
				resp, err := conn.GetCompatibleElasticsearchVersions(&elasticsearch.GetCompatibleElasticsearchVersionsInput{
					DomainName: aws.String(domainName),
				})
				if err != nil {
					log.Printf("[ERROR] Failed to get compatible ElasticSearch versions %s", domainName)
					return false
				}
				if len(resp.CompatibleElasticsearchVersions) != 1 {
					return true
				}
				for _, targetVersion := range resp.CompatibleElasticsearchVersions[0].TargetVersions {
					if aws.StringValue(targetVersion) == newVersion {
						return false
					}
				}
				return true
			}),
			SetTagsDiff,
		),

Attempting to upgrade an aws_elasticsearch_domain's elasticsearch_version on an accepted upgrade path (eg 7.4 -> 7.8) will show that it's an in place upgrade in the plan and apply that at apply time. On the other hand if you tried to upgrade via a path that isn't allowed directly (eg 5.4 -> 7.8 directly) then Terraform's plan will show that it needs to destroy the existing Elasticsearch domain and create a new one.

huangapple
  • 本文由 发表于 2021年8月9日 12:05:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/68706728.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定