{"record":{"id":"0aa5067b7112cef6","repo":"kubernetes/kops","slug":"unable-to-tag-subnet-v","errorCode":null,"errorMessage":"Unable to tag subnet %v","messagePattern":"Unable to tag subnet (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/awstasks/elastic_ip.go","lineNumber":265,"sourceCode":"\t} else {\n\t\tpublicIp = a.PublicIP\n\t\teipId = a.ID\n\t\tif err := t.AddAWSTags(*e.ID, e.Tags); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Tag the associated subnet\n\tif e.TagOnSubnet != nil {\n\t\tif e.TagOnSubnet.ID == nil {\n\t\t\treturn fmt.Errorf(\"Subnet ID not set\")\n\t\t}\n\t\ttags := make(map[string]string)\n\t\ttags[\"AssociatedElasticIp\"] = *publicIp\n\t\ttags[\"AssociatedElasticIpAllocationId\"] = *eipId // Leaving this in for reference, even though we don't use it\n\t\terr := t.AddAWSTags(*e.TagOnSubnet.ID, tags)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Unable to tag subnet %v\", err)\n\t\t}\n\t} else {\n\t\t// TODO: Figure out what we can do.  We're sort of stuck between wanting to have one code-path with\n\t\t// terraform, and having a bigger \"window of loss\" here before we create the NATGateway\n\t\tklog.V(2).Infof(\"ElasticIP %q not tagged on subnet; risk of leaking\", fi.ValueOf(publicIp))\n\t}\n\n\treturn nil\n}\n\ntype terraformElasticIP struct {\n\tDomain *string           `cty:\"domain\"`\n\tTags   map[string]string `cty:\"tags\"`\n}\n\nfunc (_ *ElasticIP) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *ElasticIP) error {\n\tif fi.ValueOf(e.Shared) {\n\t\tif e.ID == nil {","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/awstasks/elastic_ip.go#L247-L283","documentation":"When tagging the associated subnet via t.AddAWSTags(*e.TagOnSubnet.ID, tags) fails, RenderAWS wraps the error as 'Unable to tag subnet %v'. The EIP itself was allocated successfully, but writing the AssociatedElasticIp tags onto the subnet failed, leaving reconciliation incomplete (and, per code comments, risking a leaked EIP).","triggerScenarios":"ec2.CreateTags on the subnet fails: IAM policy missing ec2:CreateTags, subnet deleted concurrently, invalid subnet ID, throttling, or tag-value validation failure for AssociatedElasticIp/AssociatedElasticIpAllocationId.","commonSituations":"Least-privilege IAM policies missing ec2:CreateTags on subnet resources; race with concurrent cluster deletion; very long tag values rejected by AWS.","solutions":["Grant ec2:CreateTags in the kops IAM policy and re-run kops update cluster","Verify the subnet still exists: aws ec2 describe-subnets --subnet-ids <id>","Retry after throttling; add jitter/backoff in CI","Consider dropping TagOnSubnet (deprecated) and relying on NAT gateway association discovery"],"exampleFix":"// before: denied\n{\"Effect\":\"Deny\"} // no ec2:CreateTags\n// after\n{\"Effect\":\"Allow\",\"Action\":[\"ec2:CreateTags\"],\"Resource\":\"*\"}","handlingStrategy":"try-catch","validationCode":"// pre-check subnet exists and credentials can tag\n_, err := ec2Client.DescribeSubnets(ctx, &ec2.DescribeSubnetsInput{SubnetIds: []string{subnetID}})\nif err != nil { return fmt.Errorf(\"subnet %s unavailable: %w\", subnetID, err) }","typeGuard":null,"tryCatchPattern":"err := runKopsUpdate(ctx)\nif err != nil && strings.Contains(err.Error(), \"Unable to tag subnet\") {\n    // check ec2:CreateTags IAM permission and subnet existence before retry\n}","preventionTips":["Grant ec2:CreateTags for subnet resources in the kops IAM policy","Avoid deleting subnets concurrently with updates","Keep tag values short enough for AWS limits","Migrate away from deprecated TagOnSubnet"],"tags":["aws","ec2","tags","subnet","iam"],"backgroundTag":"aws-api-call-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}