pulumi / tf2pulumi

A tool to convert Terraform projects to Pulumi
https://www.pulumi.com
Apache License 2.0
286 stars 33 forks source link

Error importing Terraform state into Pulumi: "Only version '3' tfstate files currently supported: 4" #241

Closed fhperuchi closed 1 year ago

fhperuchi commented 3 years ago

Error trying to import Terraform state using import.ts (https://github.com/pulumi/tf2pulumi/blob/master/misc/import/import.ts) script provided by Pulumi:

Steps to reproduce

$ pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/fhperuchi/s3/dev/previews/f98f7448-35ed-47bc-8b19-b9cdb328060d

     Type                 Name    Plan     Info
     pulumi:pulumi:Stack  s3-dev           1 error

Diagnostics:
  pulumi:pulumi:Stack (s3-dev):
    error: Running program '/home/fhperuchi/pathable/pathable-terraform/s3' failed with an unhandled exception:
    Error: Only version '3' tfstate files currently supported: 4
        at Object.<anonymous> (/home/fhperuchi/pathable/pathable-terraform/s3/import.ts:81:35)
        at Module._compile (node:internal/modules/cjs/loader:1109:14)
        at Module.m._compile (/home/fhperuchi/pathable/pathable-terraform/s3/node_modules/ts-node/src/index.ts:439:23)
        at Module._extensions..js (node:internal/modules/cjs/loader:1138:10)
        at Object.require.extensions.<computed> [as .ts] (/home/fhperuchi/pathable/pathable-terraform/s3/node_modules/ts-node/src/index.ts:442:12)
        at Module.load (node:internal/modules/cjs/loader:989:32)
        at Function.Module._load (node:internal/modules/cjs/loader:829:14)
        at Module.require (node:internal/modules/cjs/loader:1013:19)
        at require (node:internal/modules/cjs/helpers:93:18)
        at Object.<anonymous> (/home/fhperuchi/pathable/pathable-terraform/s3/index.ts:3:1)
DavidHooper commented 2 years ago

If anyone has issues, here's the import.ts I tweaked that worked for my project.

import * as pulumi from '@pulumi/pulumi';
import * as fs from 'fs';
import { snakeCase } from 'lodash';

const config = new pulumi.Config();
const importFromStatefile = config.get('importFromStatefile');

// Note: If when using this script to import existing resources you get warnings about properties
// being different which will cause the import to fail, it is most likely the case that the value
// you are providing is somehow not the normalzied value stored by the cloud provider.  There are
// two options for addressing this:
// 1. Add the `ignoreChanges: ["name"]` resource option for any property names that are triggering
//    this to your program source code.
// 2. Modify your program to pass the normalized version of the value.

// This is currently a manual mapping of TypeScript type names to Terraform type names.  If using
// this in your own project you will get errors for any types missing from this mapping list, and
// should add the appropriate mapping lines for the resource types you need.  In most cases, you
// will only use 10-20 resource types even in large projects.  We expect to be able to auto-generate
// this mapping in the future.
const typeMapping: Record<string, string | null> = {
  'aws:elb/appCookieStickinessPolicy:AppCookieStickinessPolicy':
    'aws_app_cookie_stickiness_policy',
  'aws:cloudfront/distribution:Distribution': 'aws_cloudfront_distribution',
  'aws:cloudwatch/metricAlarm:MetricAlarm': 'aws_cloudwatch_metric_alarm',
  'aws:rds/instance:Instance': 'aws_db_instance',
  'aws:rds/parameterGroup:ParameterGroup': 'aws_db_parameter_group',
  'aws:rds/subnetGroup:SubnetGroup': 'aws_db_subnet_group',
  'aws:elasticache/cluster:Cluster': 'aws_elasticache_cluster',
  'aws:elasticache/parameterGroup:ParameterGroup':
    'aws_elasticache_parameter_group',
  'aws:elasticache/replicationGroup:ReplicationGroup':
    'aws_elasticache_replication_group',
  'aws:elasticache/subnetGroup:SubnetGroup': 'aws_elasticache_subnet_group',
  'aws:elb/loadBalancer:LoadBalancer': 'aws_elb',
  'aws:iam/instanceProfile:InstanceProfile': 'aws_iam_instance_profile',
  'aws:iam/policy:Policy': 'aws_iam_policy',
  'aws:iam/policyAttachment:PolicyAttachment': null,
  'aws:iam/role:Role': 'aws_iam_role',
  'aws:iam/rolePolicy:RolePolicy': 'aws_iam_role_policy',
  'aws:iam/rolePolicyAttachment:RolePolicyAttachment': null,
  'aws:ec2/instance:Instance': 'aws_instance',
  'aws:kms/alias:Alias': 'aws_kms_alias',
  'aws:kms/key:Key': 'aws_kms_key',
  'aws:elb/sslNegotiationPolicy:SslNegotiationPolicy':
    'aws_lb_ssl_negotiation_policy',
  'aws:elb/listenerPolicy:ListenerPolicy': 'aws_load_balancer_listener_policy',
  'aws:route53/record:Record': 'aws_route53_record',
  'aws:ec2/securityGroup:SecurityGroup': 'aws_security_group',
  'aws:shield/protection:Protection': 'aws_shield_protection',
  'aws:ec2/vpc:Vpc': 'aws_vpc',
  'aws:ec2/subnet:Subnet': 'aws_subnet',
  'aws:ec2/internetGateway:InternetGateway': 'aws_internet_gateway',
  'aws:ec2/routeTable:RouteTable': 'aws_route_table',
  'aws:ec2/routeTableAssociation:RouteTableAssociation':
    'aws_route_table_association',
  'aws:ec2/keyPair:KeyPair': 'aws_key_pair',
  'aws:ecs/cluster:Cluster': 'aws_ecs_cluster',
  'aws:cloudwatch/logGroup:LogGroup': 'aws_cloudwatch_log_group',
  'aws:ec2/launchConfiguration:LaunchConfiguration': 'aws_launch_configuration',
  'aws:autoscaling/group:Group': 'aws_autoscaling_group',
  'aws:ecs/taskDefinition:TaskDefinition': 'aws_ecs_task_definition',
  'aws:alb/targetGroup:TargetGroup': 'aws_alb_target_group',
  'aws:alb/loadBalancer:LoadBalancer': 'aws_alb',
  'aws:alb/listener:Listener': 'aws_alb_listener',
  'aws:ecs/service:Service': 'aws_ecs_service',
  'aws:secretsmanager/secret:Secret': 'aws_secretsmanager_secret',
  'aws:ec2/eip:Eip': 'aws_eip',
  'aws:ec2/natGateway:NatGateway': 'aws_nat_gateway',
  'aws:alb/targetGroupAttachment:TargetGroupAttachment':
    'aws_alb_target_group_attachment'
};

// Most resources can be imported by passing their `id`, but a few need to be imported using some
// other property of the resource.  This table includes any of these exceptions.  If you get errors
// or warnings about resources not being able to be found or the format of resource ids being
// incorrect, add a mapping here that constructs the correct id format based on the property values
// in the Terraform state file.
const idMapping: Record<string, (attrs: any) => string> = {
  aws_ecs_cluster: attrs => attrs.name,
  aws_ecs_service: attrs => `${attrs.cluster.split('/').pop()}/${attrs.name}`,
  aws_ecs_task_definition: attrs => attrs.arn,
  aws_route_table_association: attrs =>
    `${attrs['subnet_id']}/${attrs['route_table_id']}`,
  aws_iam_role: attrs => attrs.name
};

// If the `importFromStatefile` config variable has been provideed, add `import` attributes to all
// resources based on the mapping in the corresponding statefile.
if (importFromStatefile) {
  // Read the `.tfstate` file specified and extract out the logical TF names and cloud resource
  // ids defined in it.
  const statefileString = fs.readFileSync(importFromStatefile).toString();
  const data = JSON.parse(statefileString);

  const mapping: Record<string, string> = {};
  for (const resource of data.resources) {
    const attributes = resource.instances[0].attributes;
    const propMapper = idMapping[resource.type];
    mapping[`${resource.type}.${resource.name}`] = propMapper
      ? propMapper(attributes)
      : attributes.id
      ? attributes.id
      : attributes.arn;
  }

  // Add an `import` mapping to every resource defined in this stack.
  pulumi.runtime.registerStackTransformation(({ type, name, props, opts }) => {
    return {
      props,
      opts: pulumi.mergeOptions(opts, {
        import: lookupId(type, name)
      })
    };
  });

  // Looks up a physical ID to import for the given type and name
  function lookupId(t: string, name: string): string | undefined {
    const tfType = typeMapping[t];
    if (tfType === undefined) throw new Error('Unknown TF type: ' + t);
    if (tfType === null) return undefined;

    let result;
    let key = `${tfType}.${snakeCase(name)}`;
    result = mapping[key];

    if (!result) {
      const potentialNameToReplace = snakeCase(t.split(':').pop());
      key = `${tfType}.${snakeCase(name.replace('aws_', '')).replace(
        `_${potentialNameToReplace}`,
        ''
      )}`;
      result = mapping[key];
    }

    return result;
  }
}
Frassle commented 1 year ago

tf2pulumi is being archived including the state importer code included in here. We are looking at writing a new terraform importer integrated into pulumi convert (https://github.com/pulumi/pulumi/issues/8043)