1.6.10.1. newres

newres is a command-line tool designed to automatically generate Terraform configuration files. Simply put, given a specific cloud resource type (such as an AWS EC2 instance or an Azure Virtual Machine), newres creates the Terraform module code framework for that resource on your behalf. This includes two files: variables.tf (input variable definitions) and main.tf (resource definitions). With newres, there is no need to manually write tedious variable declarations and resource blocks; the tool automatically generates the necessary variables, attributes, and nested blocks based on the provided resource type. It even attaches descriptions from the official documentation, allowing you to start writing Terraform configurations much faster. This feature significantly reduces the time spent on manual configuration and minimizes the chance of errors.

newres supports mainstream Terraform providers, such as AWS, Azure, Google Cloud, and more. Whether it is a public cloud (like AWS) or a third-party provider (like Kubernetes or Helm), it can generate the corresponding Terraform module code based on the resource type. Currently, newres has built-in support for over a dozen official providers, including Alibaba Cloud, AWS, AzureRM, AzureAD, GCP, Kubernetes, Helm, etc. This means that regardless of which cloud platform a team uses, newres can adapt and generate the configuration framework for the corresponding resources, greatly enhancing the tool's universality.

1.6.10.1.1. Why We Need This Tool

Before newres, adding a new resource was often a tedious and error-prone process for Terraform module developers. Let's look at the typical problems encountered when manually creating resource directories and structures in actual projects:

  • Heavy Repetitive Labor: Every time a new resource needs to be added to a module, the developer must create or modify variables.tf to add corresponding variables and write the resource block in main.tf. For each input variable, one must manually write the type, default value, description, etc.; for the resource block, one must fill in all required parameters, handle default values for optional parameters, and even manage dynamic iterations for nested blocks. This kind of mechanical repetition is time-consuming and prone to oversight.
  • Prone to Omissions and Misconfiguration: Cloud resources typically have many parameters; some are required, while others are optional (with default values or nullable). When writing manually, it is easy to accidentally omit a necessary parameter or mistake a required parameter for an optional one, leading to deployment errors. Additionally, different developers have different views on which optional parameters should be explicitly configured. Without a unified standard, it is difficult to guarantee that key configurations are not missed during a manual process.
  • Inconsistent Naming and Structure: In team collaboration, different engineers have different habits, leading to varied variable naming conventions and internal resource block organizations. For instance, for the same type of resource, Engineer A might use short names for variables, while Engineer B uses long names with prefixes. Or for nested blocks, some might write them directly within the resource, while others prefer using dynamic blocks. These inconsistencies cause confusion during code reviews and later maintenance, requiring extra effort to align styles.
  • High Cost of Consulting Documentation: Correctly writing Terraform code relies heavily on consulting official documentation. Every time a resource is added, one must flip through provider documentation to find the meaning and default value of each parameter and determine which are required. This is time-consuming in itself. Furthermore, if developers are lazy and skip writing comments, module users will have to check the documentation again later to understand parameter meanings, preventing knowledge from being consolidated within the code.
  • Issues with Scale: In large Terraform projects, these problems are amplified. If a module needs to support dozens of resources over time, manually managing variable and resource files becomes unsustainable. The "copy-paste + modify" approach is not only inefficient but also risks breaking existing formatting or variable definitions during modifications, burying hidden hazards.

The emergence of newres is precisely to solve the above problems, making Terraform module development more efficient. It removes the pain points of manual workflows through automation:

  • Automates Boilerplate Code: newres automatically generates a complete set of variable definitions and resource block configurations based on the resource type, saving the effort of manual rewriting. Developers no longer need to type out variable "" {} blocks one by one or worry about the basic structure of resource blocks—the tool prepares this boilerplate code for you. This greatly reduces the cognitive load when creating new resources, allowing you to focus more on the business logic of resource configuration rather than boilerplate details.
  • No Missing Key Parameters: Since newres generates code directly using the Terraform provider's Schema data and documentation, it ensures that all required parameters are generated as input variables without omission. Optional parameters are usually given reasonable default values (such as null) to maintain the integrity of the Terraform configuration. This means resource blocks created with newres are "out-of-the-box" ready; they are correct and complete in terms of syntax and basic required attributes, preventing Terraform execution errors caused by missing attributes. Of course, manual adjustments may still be needed for some advanced scenarios, but with the starting point produced by the tool, developers are unlikely to miss key configurations.
  • Unified Standards: newres has fixed patterns for variable naming, handling optional parameters with default values, and expanding nested blocks. For example, as mentioned earlier, it tends to add a resource prefix to variable names and separate them with underscores, naturally forming a consistent style across modules. Additionally, for nested blocks, newres uses the generic dynamic syntax to handle optional sub-configurations, uniformly generating them this way even if the nested block is actually required, to simplify the code structure. Once these patterns become a consensus, using newres is equivalent to following a single set of coding standards, reducing issues with inconsistent styles. In large teams, the tool acts as the standard; with the standard format automatically output by the tool, code reviews can focus on logic rather than debating style.
  • Built-in Documentation and Hints: Each variable generated by newres comes with a description string explaining its purpose, whether it is required, and the meaning of its default value. These descriptions come directly from official documentation or provider code comments and are accurate and detailed. This is equivalent to having built-in documentation in the code, helping developers understand it themselves and making it easier for others to quickly understand the role of variables during future maintenance. Manual writing often overlooks descriptions or skims over them, whereas the explanations automatically filled by newres greatly improve the module's self-descriptiveness. For beginners, seeing these explanations makes it easier to understand what each part of the Terraform configuration does, thereby learning standard writing practices.
  • Increases Development Speed, Reduces Errors: Overall, newres compresses a small "development task" that might take tens of minutes into a single command. Especially when dealing with large resources with hundreds of parameters, manually typing variables one by one is boring and error-prone, whereas using the tool can produce a first draft in seconds. Such efficiency gains are precious in projects with a fast iteration rhythm. In addition, since the configuration is organized by a machine, the syntax and basic configuration items will be correct, reducing the possibility of low-level errors. This is also beneficial for CI/CD processes—fewer typos or omissions mean smoother pipeline execution.

In summary, we need newres because it provides a comprehensive solution to the frequent and error-prone "create new resource" step in Terraform module development. It liberates developers from repetitive labor, ensures code compliance and completeness, and safeguards module governance. In large-scale Terraform projects, newres is not just a tool for improving efficiency but also a sharp weapon for ensuring standard enforcement.

(Note: Although newres is highly automated, we should still review the generated code. The documentation structure of some Terraform providers is not uniform enough, which may cause newres to be unable to parse all fields 100% correctly. Therefore, when using the generated results, please verify them against actual requirements and the latest documentation to ensure configuration accuracy.)

1.6.10.1.2. Usage

Having explained the principles and benefits, let's look at the actual usage of newres. Overall, it is very simple to use: install via Go tools, then run a command to generate the code skeleton.

Installation: Since newres is written in Go, you need a Go development environment on your system. Assuming Go is installed, you can get the latest version of newres directly via the following command:

go install github.com/lonegunmanb/newres/v3@latest

newres provides two generation modes:

  • MultipleVariables Mode (Default): Generates independent variable inputs for each parameter and sub-block of the resource. In this mode, multiple variable blocks will be listed in variables.tf, with each variable corresponding to an attribute or nested configuration of the Terraform resource. For complex resources, this may generate quite a few variables, but the advantage is that it is clear, intuitive, and values can be passed independently for each parameter.
  • UniVariable Mode: As the name suggests, it generates only a single variable, embedding all configurations of the entire resource into a complex type variable (including possible nested blocks as sub-attributes of the variable). This mode is more like encapsulating the resource configuration into an object passed through one variable, suitable for scenarios where a simplified interface is desired.

By choosing between these two modes, newres can adapt to different team Terraform module design styles: use the default multi-variable mode if you prefer fine-grained parameters, or use the -u parameter to enable UniVariable mode if you prefer a single object input.

Basic Usage: The command-line arguments for newres are very simple, with three main parameters to note:

  • -dir [Directory Path]: Required parameter. Specifies the directory path where the generated Terraform configuration files will be stored. For example, writing -dir ./myresource means files will be generated in the "myresource" subdirectory of the current directory. If the directory does not exist, you need to create it manually first. We often point the target directory to a specific Terraform module directory.
  • -r [Resource Type]: Required parameter. Specifies the resource type for which to generate the configuration. The resource type syntax is consistent with the resource block type in Terraform configurations, such as aws_instance, azurerm_resource_group, google_compute_instance, etc. Through this parameter, newres knows which specific resource template we want to generate.
  • -u: Optional parameter. Indicates whether to use UniVariable mode. Adding this parameter generates code in single-variable mode; omitting it generates code in the default multi-variable mode (i.e., each attribute gets its own variable).

In addition to these core parameters, newres has additional options for certain specific provider resources. For example, for Azure's AzAPI provider resources (azapi_resource), you need to use the additional --azapi-resource-type parameter to provide the specific resource type string (such as "Microsoft.Resources/resourceGroups@2021-04-01"). Most conventional resources do not need this option; it is only used when generating AzAPI generic resources.

Usage Example: Suppose we are writing an Azure Resource Group module and want to quickly generate the basic Terraform template for a resource group. After entering the module directory in the terminal, run the following command:

newres -dir . -r azurerm_resource_group

Here we specify the current directory (./) as the output directory and the resource type as azurerm_resource_group (Azure Resource Manager provider's resource group). After execution, newres will automatically generate (or update) two files in the current directory: variables.tf and main.tf. variables.tf contains the input variable definitions required for the Azure resource group, such as resource_group_location, resource_group_name, resource_group_tags, etc., each with a type, default value (if applicable), and description. main.tf contains the corresponding resource block:

resource "azurerm_resource_group" "this" {
  location   = var.resource_group_location
  name       = var.resource_group_name
  managed_by = var.resource_group_managed_by
  tags       = var.resource_group_tags

  dynamic "timeouts" {
    for_each = var.resource_group_timeouts == null ? [] : [var.resource_group_timeouts]

    content {
      create = timeouts.value.create
      delete = timeouts.value.delete
      read   = timeouts.value.read
      update = timeouts.value.update
    }
  }
}

From the results, we can see that basic attributes like location, name, and tags are already bound to the corresponding variables, and the associations and syntax are ready.

Meanwhile, in the variables.tf file:

variable "resource_group_location" {
  type        = string
  description = "(Required) The Azure Region where the Resource Group should exist. Changing this forces a new Resource Group to be created."
  nullable    = false
}

variable "resource_group_name" {
  type        = string
  description = "(Required) The Name which should be used for this Resource Group. Changing this forces a new Resource Group to be created."
  nullable    = false
}

variable "resource_group_managed_by" {
  type        = string
  default     = null
  description = "(Optional) The ID of the resource or application that manages this Resource Group."
}

variable "resource_group_tags" {
  type        = map(string)
  default     = null
  description = "(Optional) A mapping of tags which should be assigned to the Resource Group."
}

variable "resource_group_timeouts" {
  type = object({
    create = optional(string)
    delete = optional(string)
    read   = optional(string)
    update = optional(string)
  })
  default     = null
  description = <<-EOT
 - `create` - (Defaults to 90 minutes) Used when creating the Resource Group.
 - `delete` - (Defaults to 90 minutes) Used when deleting the Resource Group.
 - `read` - (Defaults to 5 minutes) Used when retrieving the Resource Group.
 - `update` - (Defaults to 90 minutes) Used when updating the Resource Group.
EOT
}

Developers now only need to check if variable default values or descriptions need adjustment and supplement any module-specific logic (such as adding output.tf to export some values) according to requirements. The whole process eliminates the need to manually check documentation and write boilerplate.

It is worth emphasizing that if we run the newres command multiple times to generate different resource types in the same target directory, newres will append the newly generated configuration to the existing files instead of overwriting them. That is, the first run might generate content related to a resource group, and the second run, if specifying another resource (such as azurerm_storage_account), will add the variables needed for the storage account to the original variables.tf and append the storage account resource block to the original main.tf. In this way, we can accumulate a complete configuration for a module supporting multiple resources step by step through multiple calls without manual merging, which is very suitable for scenarios where module functions are expanded gradually. Of course, after appending, it is recommended to read through the merged files to ensure there are no conflicts in variable naming for different resources and that the order aligns with team habits.

Other Examples: Besides the Azure example, let's look at another scenario. If you want to create an AWS EC2 instance module, you can execute:

newres -dir ./aws_ec2_module -r aws_instance

This will generate the EC2 instance Terraform resource block in the specified directory:

resource "aws_instance" "this" {
  ami                                  = var.instance_ami
  associate_public_ip_address          = var.instance_associate_public_ip_address
  availability_zone                    = var.instance_availability_zone
  cpu_core_count                       = var.instance_cpu_core_count
  cpu_threads_per_core                 = var.instance_cpu_threads_per_core
  disable_api_stop                     = var.instance_disable_api_stop
  disable_api_termination              = var.instance_disable_api_termination
  ebs_optimized                        = var.instance_ebs_optimized
  enable_primary_ipv6                  = var.instance_enable_primary_ipv6
  get_password_data                    = var.instance_get_password_data
  hibernation                          = var.instance_hibernation
  host_id                              = var.instance_host_id
  host_resource_group_arn              = var.instance_host_resource_group_arn
  iam_instance_profile                 = var.instance_iam_instance_profile
  instance_initiated_shutdown_behavior = var.instance_instance_initiated_shutdown_behavior
  instance_type                        = var.instance_instance_type
  ipv6_address_count                   = var.instance_ipv6_address_count
  ipv6_addresses                       = var.instance_ipv6_addresses
  key_name                             = var.instance_key_name
  monitoring                           = var.instance_monitoring
  placement_group                      = var.instance_placement_group
  placement_partition_number           = var.instance_placement_partition_number
  private_ip                           = var.instance_private_ip
  secondary_private_ips                = var.instance_secondary_private_ips
  security_groups                      = var.instance_security_groups
  source_dest_check                    = var.instance_source_dest_check
  subnet_id                            = var.instance_subnet_id
  tags                                 = var.instance_tags
  tags_all                             = var.instance_tags_all
  tenancy                              = var.instance_tenancy
  user_data                            = var.instance_user_data
  user_data_base64                     = var.instance_user_data_base64
  user_data_replace_on_change          = var.instance_user_data_replace_on_change
  volume_tags                          = var.instance_volume_tags
  vpc_security_group_ids               = var.instance_vpc_security_group_ids

  dynamic "capacity_reservation_specification" {
    for_each = var.instance_capacity_reservation_specification == null ? [] : [var.instance_capacity_reservation_specification]

    content {
      capacity_reservation_preference = capacity_reservation_specification.value.capacity_reservation_preference

      dynamic "capacity_reservation_target" {
        for_each = capacity_reservation_specification.value.capacity_reservation_target == null ? [] : [capacity_reservation_specification.value.capacity_reservation_target]

        content {
          capacity_reservation_id                 = capacity_reservation_target.value.capacity_reservation_id
          capacity_reservation_resource_group_arn = capacity_reservation_target.value.capacity_reservation_resource_group_arn
        }
      }
    }
  }
  dynamic "cpu_options" {
    for_each = var.instance_cpu_options == null ? [] : [var.instance_cpu_options]

    content {
      amd_sev_snp      = cpu_options.value.amd_sev_snp
      core_count       = cpu_options.value.core_count
      threads_per_core = cpu_options.value.threads_per_core
    }
  }
  dynamic "credit_specification" {
    for_each = var.instance_credit_specification == null ? [] : [var.instance_credit_specification]

    content {
      cpu_credits = credit_specification.value.cpu_credits
    }
  }
  dynamic "ebs_block_device" {
    for_each = var.instance_ebs_block_device == null ? [] : var.instance_ebs_block_device

    content {
      device_name           = ebs_block_device.value.device_name
      delete_on_termination = ebs_block_device.value.delete_on_termination
      encrypted             = ebs_block_device.value.encrypted
      iops                  = ebs_block_device.value.iops
      kms_key_id            = ebs_block_device.value.kms_key_id
      snapshot_id           = ebs_block_device.value.snapshot_id
      tags                  = ebs_block_device.value.tags
      tags_all              = ebs_block_device.value.tags_all
      throughput            = ebs_block_device.value.throughput
      volume_size           = ebs_block_device.value.volume_size
      volume_type           = ebs_block_device.value.volume_type
    }
  }
  dynamic "enclave_options" {
    for_each = var.instance_enclave_options == null ? [] : [var.instance_enclave_options]

    content {
      enabled = enclave_options.value.enabled
    }
  }
  dynamic "ephemeral_block_device" {
    for_each = var.instance_ephemeral_block_device == null ? [] : var.instance_ephemeral_block_device

    content {
      device_name  = ephemeral_block_device.value.device_name
      no_device    = ephemeral_block_device.value.no_device
      virtual_name = ephemeral_block_device.value.virtual_name
    }
  }
  dynamic "instance_market_options" {
    for_each = var.instance_instance_market_options == null ? [] : [var.instance_instance_market_options]

    content {
      market_type = instance_market_options.value.market_type

      dynamic "spot_options" {
        for_each = instance_market_options.value.spot_options == null ? [] : [instance_market_options.value.spot_options]

        content {
          instance_interruption_behavior = spot_options.value.instance_interruption_behavior
          max_price                      = spot_options.value.max_price
          spot_instance_type             = spot_options.value.spot_instance_type
          valid_until                    = spot_options.value.valid_until
        }
      }
    }
  }
  dynamic "launch_template" {
    for_each = var.instance_launch_template == null ? [] : [var.instance_launch_template]

    content {
      id      = launch_template.value.id
      name    = launch_template.value.name
      version = launch_template.value.version
    }
  }
  dynamic "maintenance_options" {
    for_each = var.instance_maintenance_options == null ? [] : [var.instance_maintenance_options]

    content {
      auto_recovery = maintenance_options.value.auto_recovery
    }
  }
  dynamic "metadata_options" {
    for_each = var.instance_metadata_options == null ? [] : [var.instance_metadata_options]

    content {
      http_endpoint               = metadata_options.value.http_endpoint
      http_protocol_ipv6          = metadata_options.value.http_protocol_ipv6
      http_put_response_hop_limit = metadata_options.value.http_put_response_hop_limit
      http_tokens                 = metadata_options.value.http_tokens
      instance_metadata_tags      = metadata_options.value.instance_metadata_tags
    }
  }
  dynamic "network_interface" {
    for_each = var.instance_network_interface == null ? [] : var.instance_network_interface

    content {
      device_index          = network_interface.value.device_index
      network_interface_id  = network_interface.value.network_interface_id
      delete_on_termination = network_interface.value.delete_on_termination
      network_card_index    = network_interface.value.network_card_index
    }
  }
  dynamic "private_dns_name_options" {
    for_each = var.instance_private_dns_name_options == null ? [] : [var.instance_private_dns_name_options]

    content {
      enable_resource_name_dns_a_record    = private_dns_name_options.value.enable_resource_name_dns_a_record
      enable_resource_name_dns_aaaa_record = private_dns_name_options.value.enable_resource_name_dns_aaaa_record
      hostname_type                        = private_dns_name_options.value.hostname_type
    }
  }
  dynamic "root_block_device" {
    for_each = var.instance_root_block_device == null ? [] : [var.instance_root_block_device]

    content {
      delete_on_termination = root_block_device.value.delete_on_termination
      encrypted             = root_block_device.value.encrypted
      iops                  = root_block_device.value.iops
      kms_key_id            = root_block_device.value.kms_key_id
      tags                  = root_block_device.value.tags
      tags_all              = root_block_device.value.tags_all
      throughput            = root_block_device.value.throughput
      volume_size           = root_block_device.value.volume_size
      volume_type           = root_block_device.value.volume_type
    }
  }
  dynamic "timeouts" {
    for_each = var.instance_timeouts == null ? [] : [var.instance_timeouts]

    content {
      create = timeouts.value.create
      delete = timeouts.value.delete
      read   = timeouts.value.read
      update = timeouts.value.update
    }
  }
}

Similarly, variables such as instance_ami (AMI ID), instance_type (instance type), instance_tags, etc., will appear in variables.tf, and main.tf will have an aws_instance resource block where attributes are mapped to the aforementioned variables. Through these variable names, we can also appreciate newres's naming style: <resource>_<attribute>, making variable meanings clear and avoiding conflicts. If you wish to package all inputs into a single variable object, you can also add the -u parameter to the command to test the effect.

Special Handling for AzAPI:

AzAPI is a new Terraform Provider developed and maintained by Microsoft in recent years for invoking the Azure Resource Management API. Unlike the AzureRM Provider, AzAPI mainly encapsulates calls to the Azure API. It is more similar to Bicep and is much more dynamic, as it does not require waiting for the Provider development team to create corresponding Resource logic—you can construct resource blocks yourself by reading the API definition. newres handles AzAPI specially. For example, running the following command:

newres -r azapi_resource --azapi-resource-type="Microsoft.CognitiveServices/accounts@2024-10-01" -dir .

The resulting Terraform code is:

resource "azapi_resource" "this" {
  location  = var.resource_location
  name      = var.resource_name
  parent_id = var.resource_parent_id
  type      = "Microsoft.CognitiveServices/accounts@2024-10-01"
  body      = var.resource_body
  tags      = var.resource_tags

  dynamic "identity" {
    for_each = var.resource_identity == null ? [] : [var.resource_identity]

    content {
      type = identity.value.type

      dynamic "userAssignedIdentities" {
        for_each = identity.value.userAssignedIdentities == null ? {} : identity.value.userAssignedIdentities

        content {}
      }
    }
  }
}

The key highlight is the generated var.resource_body:

variable "resource_body" {
  type = object({
    kind = optional(string)
    properties = optional(object({
      allowedFqdnList               = optional(list(string))
      customSubDomainName           = optional(string)
      disableLocalAuth              = optional(bool)
      dynamicThrottlingEnabled      = optional(bool)
      migrationToken                = optional(string)
      publicNetworkAccess           = optional(string)
      restore                       = optional(bool)
      restrictOutboundNetworkAccess = optional(bool)
      amlWorkspace = optional(object({
        identityClientId = optional(string)
        resourceId       = optional(string)
      }))
      apiProperties = optional(object({
        aadClientId                    = optional(string)
        aadTenantId                    = optional(string)
        eventHubConnectionString       = optional(string)
        qnaAzureSearchEndpointId       = optional(string)
        qnaAzureSearchEndpointKey      = optional(string)
        qnaRuntimeEndpoint             = optional(string)
        statisticsEnabled              = optional(bool)
        storageAccountConnectionString = optional(string)
        superUser                      = optional(string)
        websiteName                    = optional(string)
      }))
      encryption = optional(object({
        keySource = optional(string)
        keyVaultProperties = optional(object({
          identityClientId = optional(string)
          keyName          = optional(string)
          keyVaultUri      = optional(string)
          keyVersion       = optional(string)
        }))
      }))
      locations = optional(object({
        routingMethod = optional(string)
        regions = optional(list(object({
          customsubdomain = string
          name            = string
          value           = number
        })))
      }))
      networkAcls = optional(object({
        bypass        = optional(string)
        defaultAction = optional(string)
        ipRules = optional(list(object({
          value = string
        })))
        virtualNetworkRules = optional(list(object({
          id                               = string
          ignoreMissingVnetServiceEndpoint = bool
          state                            = string
        })))
      }))
      raiMonitorConfig = optional(object({
        adxStorageResourceId = optional(string)
        identityClientId     = optional(string)
      }))
      userOwnedStorage = optional(list(object({
        identityClientId = string
        resourceId       = string
      })))
    }))
    sku = optional(object({
      capacity = optional(number)
      family   = optional(string)
      name     = string
      size     = optional(string)
      tier     = optional(string)
    }))
  })
  nullable    = false
}

newres maps the API request structure of the resource into an object type input variable block, greatly accelerating the process for users to write correct azapi_resource blocks.

1.6.10.1.3. Summary

Conclusion: Using newres is very intuitive: Install -> Run Command -> View Results. For Terraform beginners, this tool helps quickly understand the elements required for a complete resource configuration; for experienced engineers, it saves a lot of mechanical work, allowing them to focus on architectural design and parameter decisions. In the practice of large-scale module governance, newres has demonstrated its value—acting as both a standard setter (since the tool's output is the standard) and an efficient assistant. Therefore, if you are developing Terraform modules, especially in scenarios involving many resources and variables, you might want to incorporate newres into your workflow. It will greatly simplify your module development process.

results matching ""

    No results matching ""