コード例 #1
0
ファイル: autoscalinggroup.go プロジェクト: crohling/kops
func (_ *AutoscalingGroup) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *AutoscalingGroup) error {
	tf := &terraformAutoscalingGroup{
		Name:                    e.Name,
		MinSize:                 e.MinSize,
		MaxSize:                 e.MaxSize,
		LaunchConfigurationName: e.LaunchConfiguration.TerraformLink(),
	}

	for _, s := range e.Subnets {
		tf.VPCZoneIdentifier = append(tf.VPCZoneIdentifier, s.TerraformLink())
	}

	tags := e.buildTags(t.Cloud)
	// Make sure we output in a stable order
	var tagKeys []string
	for k := range tags {
		tagKeys = append(tagKeys, k)
	}
	sort.Strings(tagKeys)
	for _, k := range tagKeys {
		v := tags[k]
		tf.Tags = append(tf.Tags, &terraformASGTag{
			Key:               fi.String(k),
			Value:             fi.String(v),
			PropagateAtLaunch: fi.Bool(true),
		})
	}

	return t.RenderResource("aws_autoscaling_group", *e.Name, tf)
}
コード例 #2
0
ファイル: launchconfiguration.go プロジェクト: crohling/kops
func (_ *LaunchConfiguration) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *LaunchConfiguration) error {
	cloud := t.Cloud.(*awsup.AWSCloud)

	if e.ImageID == nil {
		return fi.RequiredField("ImageID")
	}
	image, err := cloud.ResolveImage(*e.ImageID)
	if err != nil {
		return err
	}

	tf := &terraformLaunchConfiguration{
		NamePrefix:   fi.String(*e.Name + "-"),
		ImageID:      image.ImageId,
		InstanceType: e.InstanceType,
	}

	if e.SSHKey != nil {
		tf.KeyName = e.SSHKey.TerraformLink()
	}

	for _, sg := range e.SecurityGroups {
		tf.SecurityGroups = append(tf.SecurityGroups, sg.TerraformLink())
	}
	tf.AssociatePublicIpAddress = e.AssociatePublicIP

	if e.BlockDeviceMappings != nil {
		tf.EphemeralBlockDevice = []*terraformBlockDevice{}
		for deviceName, bdm := range e.BlockDeviceMappings {
			tf.EphemeralBlockDevice = append(tf.EphemeralBlockDevice, &terraformBlockDevice{
				VirtualName: bdm.VirtualName,
				DeviceName:  fi.String(deviceName),
			})
		}
	}

	if e.UserData != nil {
		tf.UserData, err = t.AddFile("aws_launch_configuration", *e.Name, "user_data", e.UserData)
		if err != nil {
			return err
		}
	}
	if e.IAMInstanceProfile != nil {
		tf.IAMInstanceProfile = e.IAMInstanceProfile.TerraformLink()
	}

	// So that we can update configurations
	tf.Lifecycle = &terraformLifecycle{CreateBeforeDestroy: fi.Bool(true)}

	return t.RenderResource("aws_launch_configuration", *e.Name, tf)
}
コード例 #3
0
ファイル: persistentdisk.go プロジェクト: crohling/kops
func (e *PersistentDisk) Find(c *fi.Context) (*PersistentDisk, error) {
	cloud := c.Cloud.(*gce.GCECloud)

	r, err := cloud.Compute.Disks.Get(cloud.Project, *e.Zone, *e.Name).Do()
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing PersistentDisks: %v", err)
	}

	actual := &PersistentDisk{}
	actual.Name = &r.Name
	actual.VolumeType = fi.String(lastComponent(r.Type))
	actual.Zone = fi.String(lastComponent(r.Zone))
	actual.SizeGB = &r.SizeGb

	return actual, nil
}
コード例 #4
0
ファイル: managedinstancegroup.go プロジェクト: crohling/kops
func (e *ManagedInstanceGroup) Find(c *fi.Context) (*ManagedInstanceGroup, error) {
	cloud := c.Cloud.(*gce.GCECloud)

	r, err := cloud.Compute.InstanceGroupManagers.Get(cloud.Project, *e.Zone, *e.Name).Do()
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing ManagedInstanceGroups: %v", err)
	}

	actual := &ManagedInstanceGroup{}
	actual.Name = &r.Name
	actual.Zone = fi.String(lastComponent(r.Zone))
	actual.BaseInstanceName = &r.BaseInstanceName
	actual.TargetSize = &r.TargetSize
	actual.InstanceTemplate = &InstanceTemplate{Name: fi.String(lastComponent(r.InstanceTemplate))}

	return actual, nil
}
コード例 #5
0
ファイル: launchconfiguration.go プロジェクト: crohling/kops
func (_ *LaunchConfiguration) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *LaunchConfiguration) error {
	launchConfigurationName := *e.Name + "-" + fi.BuildTimestampString()
	glog.V(2).Infof("Creating AutoscalingLaunchConfiguration with Name:%q", launchConfigurationName)

	if e.ImageID == nil {
		return fi.RequiredField("ImageID")
	}
	image, err := t.Cloud.ResolveImage(*e.ImageID)
	if err != nil {
		return err
	}

	request := &autoscaling.CreateLaunchConfigurationInput{}
	request.LaunchConfigurationName = &launchConfigurationName
	request.ImageId = image.ImageId
	request.InstanceType = e.InstanceType
	if e.SSHKey != nil {
		request.KeyName = e.SSHKey.Name
	}
	securityGroupIDs := []*string{}
	for _, sg := range e.SecurityGroups {
		securityGroupIDs = append(securityGroupIDs, sg.ID)
	}
	request.SecurityGroups = securityGroupIDs
	request.AssociatePublicIpAddress = e.AssociatePublicIP
	if e.BlockDeviceMappings != nil {
		request.BlockDeviceMappings = []*autoscaling.BlockDeviceMapping{}
		for device, bdm := range e.BlockDeviceMappings {
			request.BlockDeviceMappings = append(request.BlockDeviceMappings, bdm.ToAutoscaling(device))
		}
	}

	if e.UserData != nil {
		d, err := e.UserData.AsBytes()
		if err != nil {
			return fmt.Errorf("error rendering AutoScalingLaunchConfiguration UserData: %v", err)
		}
		request.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
	}
	if e.IAMInstanceProfile != nil {
		request.IamInstanceProfile = e.IAMInstanceProfile.Name
	}

	_, err = t.Cloud.Autoscaling.CreateLaunchConfiguration(request)
	if err != nil {
		return fmt.Errorf("error creating AutoscalingLaunchConfiguration: %v", err)
	}

	e.ID = fi.String(launchConfigurationName)

	return nil // No tags on a launch configuration
}
コード例 #6
0
ファイル: package.go プロジェクト: crohling/kops
func (e *Package) Find(c *fi.Context) (*Package, error) {
	args := []string{"dpkg-query", "-f", "${db:Status-Abbrev}${Version}\\n", "-W", e.Name}
	human := strings.Join(args, " ")

	glog.V(2).Infof("Listing installed packages: %s", human)
	cmd := exec.Command(args[0], args[1:]...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if strings.Contains(string(output), "no packages found") {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing installed packages: %v: %s", err, string(output))
	}

	installed := false
	installedVersion := ""
	for _, line := range strings.Split(string(output), "\n") {
		if line == "" {
			continue
		}

		tokens := strings.Split(line, " ")
		if len(tokens) != 2 {
			return nil, fmt.Errorf("error parsing dpkg-query line %q", line)
		}
		state := tokens[0]
		version := tokens[1]

		switch state {
		case "ii":
			installed = true
			installedVersion = version
		case "rc":
			// removed
			installed = false
		case "un":
			// unknown
			installed = false
		default:
			return nil, fmt.Errorf("unknown package state %q in line %q", state, line)
		}
	}

	if !installed {
		return nil, nil
	}

	return &Package{
		Name:    e.Name,
		Version: fi.String(installedVersion),
	}, nil
}
コード例 #7
0
ファイル: securitygrouprule.go プロジェクト: crohling/kops
func (_ *SecurityGroupRule) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *SecurityGroupRule) error {
	tf := &terraformSecurityGroupIngress{
		Type:          fi.String("ingress"),
		SecurityGroup: e.SecurityGroup.TerraformLink(),
		FromPort:      e.FromPort,
		ToPort:        e.ToPort,
		Protocol:      e.Protocol,
	}
	if fi.BoolValue(e.Egress) {
		tf.Type = fi.String("egress")
	}

	if e.Protocol == nil {
		tf.Protocol = fi.String("-1")
		tf.FromPort = fi.Int64(0)
		tf.ToPort = fi.Int64(0)
	}

	if tf.FromPort == nil {
		// FromPort is required by tf
		tf.FromPort = fi.Int64(0)
	}
	if tf.ToPort == nil {
		// ToPort is required by tf
		tf.ToPort = fi.Int64(65535)
	}

	if e.SourceGroup != nil {
		tf.SourceGroup = e.SourceGroup.TerraformLink()
	}

	if e.CIDR != nil {
		tf.CIDRBlocks = append(tf.CIDRBlocks, *e.CIDR)
	}
	return t.RenderResource("aws_security_group_rule", *e.Name, tf)
}
コード例 #8
0
ファイル: service.go プロジェクト: crohling/kops
func (e *Service) Find(c *fi.Context) (*Service, error) {
	servicePath := path.Join(systemdSystemPath, e.Name)

	d, err := ioutil.ReadFile(servicePath)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, fmt.Errorf("Error reading systemd file %q: %v", servicePath, err)
		}

		// Not found
		return &Service{
			Name:       e.Name,
			Definition: nil,
			Running:    fi.Bool(false),
		}, nil
	}

	actual := &Service{
		Name:       e.Name,
		Definition: fi.String(string(d)),

		// Avoid spurious changes
		ManageState:  e.ManageState,
		SmartRestart: e.SmartRestart,
	}

	properties, err := getSystemdStatus(e.Name)
	if err != nil {
		return nil, err
	}
	activeState := properties["ActiveState"]
	switch activeState {
	case "active":
		actual.Running = fi.Bool(true)

	case "failed", "inactive":
		actual.Running = fi.Bool(false)
	default:
		glog.Warningf("Unknown ActiveState=%q; will treat as not running", activeState)
		actual.Running = fi.Bool(false)
	}

	return actual, nil
}
コード例 #9
0
ファイル: file.go プロジェクト: crohling/kops
func findFile(p string) (*File, error) {
	stat, err := os.Lstat(p)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
	}

	actual := &File{}
	actual.Path = p
	actual.Mode = fi.String(fi.FileModeToString(stat.Mode() & os.ModePerm))

	uid := int(stat.Sys().(*syscall.Stat_t).Uid)
	owner, err := fi.LookupUserById(uid)
	if err != nil {
		return nil, err
	}
	if owner != nil {
		actual.Owner = fi.String(owner.Name)
	} else {
		actual.Owner = fi.String(strconv.Itoa(uid))
	}

	gid := int(stat.Sys().(*syscall.Stat_t).Gid)
	group, err := fi.LookupGroupById(gid)
	if err != nil {
		return nil, err
	}
	if group != nil {
		actual.Group = fi.String(group.Name)
	} else {
		actual.Group = fi.String(strconv.Itoa(gid))
	}

	if (stat.Mode() & os.ModeSymlink) != 0 {
		target, err := os.Readlink(p)
		if err != nil {
			return nil, fmt.Errorf("error reading symlink target: %v", err)
		}

		actual.Type = FileType_Symlink
		actual.Symlink = fi.String(target)
	} else if (stat.Mode() & os.ModeDir) != 0 {
		actual.Type = FileType_Directory
	} else {
		actual.Type = FileType_File
		actual.Contents = fi.NewFileResource(p)
	}

	return actual, nil
}
コード例 #10
0
ファイル: launchconfiguration.go プロジェクト: crohling/kops
func addEphemeralDevices(instanceTypeName *string, blockDeviceMappings map[string]*BlockDeviceMapping) (map[string]*BlockDeviceMapping, error) {
	// TODO: Any reason not to always attach the ephemeral devices?
	if instanceTypeName == nil {
		return nil, fi.RequiredField("InstanceType")
	}
	instanceType, err := awsup.GetMachineTypeInfo(*instanceTypeName)
	if err != nil {
		return nil, err
	}
	if blockDeviceMappings == nil {
		blockDeviceMappings = make(map[string]*BlockDeviceMapping)
	}
	for _, ed := range instanceType.EphemeralDevices() {
		if _, found := blockDeviceMappings[ed.DeviceName]; found {
			glog.Warningf("not attach ephemeral device - found duplicate device mapping: %q", ed.DeviceName)
			continue
		}
		blockDeviceMappings[ed.DeviceName] = &BlockDeviceMapping{VirtualName: fi.String(ed.VirtualName)}
	}
	return blockDeviceMappings, nil
}
コード例 #11
0
ファイル: firewall_rule.go プロジェクト: crohling/kops
func (e *FirewallRule) Find(c *fi.Context) (*FirewallRule, error) {
	cloud := c.Cloud.(*gce.GCECloud)

	r, err := cloud.Compute.Firewalls.Get(cloud.Project, *e.Name).Do()
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing FirewallRules: %v", err)
	}

	actual := &FirewallRule{}
	actual.Name = &r.Name
	actual.Network = &Network{Name: fi.String(lastComponent(r.Network))}
	actual.TargetTags = r.TargetTags
	actual.SourceRanges = r.SourceRanges
	actual.SourceTags = r.SourceTags
	for _, a := range r.Allowed {
		actual.Allowed = append(actual.Allowed, serializeFirewallAllowed(a))
	}

	return actual, nil
}
コード例 #12
0
ファイル: service.go プロジェクト: crohling/kops
func NewService(name string, contents string, meta string) (fi.Task, error) {
	s := &Service{Name: name}
	s.Definition = fi.String(contents)

	if meta != "" {
		err := json.Unmarshal([]byte(meta), s)
		if err != nil {
			return nil, fmt.Errorf("error parsing json for service %q: %v", name, err)
		}
	}

	// Default some values to true: Running, SmartRestart, ManageState
	if s.Running == nil {
		s.Running = fi.Bool(true)
	}
	if s.SmartRestart == nil {
		s.SmartRestart = fi.Bool(true)
	}
	if s.ManageState == nil {
		s.ManageState = fi.Bool(true)
	}

	return s, nil
}
コード例 #13
0
ファイル: instancetemplate.go プロジェクト: crohling/kops
func (e *InstanceTemplate) Find(c *fi.Context) (*InstanceTemplate, error) {
	cloud := c.Cloud.(*gce.GCECloud)

	r, err := cloud.Compute.InstanceTemplates.Get(cloud.Project, *e.Name).Do()
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing InstanceTemplates: %v", err)
	}

	actual := &InstanceTemplate{}
	actual.Name = &r.Name

	p := r.Properties

	for _, tag := range p.Tags.Items {
		actual.Tags = append(actual.Tags, tag)
	}
	actual.MachineType = fi.String(lastComponent(p.MachineType))
	actual.CanIPForward = &p.CanIpForward

	bootDiskImage, err := ShortenImageURL(cloud.Project, p.Disks[0].InitializeParams.SourceImage)
	if err != nil {
		return nil, fmt.Errorf("error parsing source image URL: %v", err)
	}
	actual.BootDiskImage = fi.String(bootDiskImage)
	actual.BootDiskType = &p.Disks[0].InitializeParams.DiskType
	actual.BootDiskSizeGB = &p.Disks[0].InitializeParams.DiskSizeGb

	if p.Scheduling != nil {
		actual.Preemptible = &p.Scheduling.Preemptible
	}
	if len(p.NetworkInterfaces) != 0 {
		ni := p.NetworkInterfaces[0]
		actual.Network = &Network{Name: fi.String(lastComponent(ni.Network))}
	}

	for _, serviceAccount := range p.ServiceAccounts {
		for _, scope := range serviceAccount.Scopes {
			actual.Scopes = append(actual.Scopes, scopeToShortForm(scope))
		}
	}

	//for i, disk := range p.Disks {
	//	if i == 0 {
	//		source := disk.Source
	//
	//		// TODO: Parse source URL instead of assuming same project/zone?
	//		name := lastComponent(source)
	//		d, err := cloud.Compute.Disks.Get(cloud.Project, *e.Zone, name).Do()
	//		if err != nil {
	//			if gce.IsNotFound(err) {
	//				return nil, fmt.Errorf("disk not found %q: %v", source, err)
	//			}
	//			return nil, fmt.Errorf("error querying for disk %q: %v", source, err)
	//		} else {
	//			imageURL, err := gce.ParseGoogleCloudURL(d.SourceImage)
	//			if err != nil {
	//				return nil, fmt.Errorf("unable to parse image URL: %q", d.SourceImage)
	//			}
	//			actual.Image = fi.String(imageURL.Project + "/" + imageURL.Name)
	//		}
	//	}
	//}

	if p.Metadata != nil {
		actual.Metadata = make(map[string]fi.Resource)
		for _, meta := range p.Metadata.Items {
			actual.Metadata[meta.Key] = fi.NewStringResource(*meta.Value)
		}
	}

	return actual, nil
}
コード例 #14
0
ファイル: instancetemplate.go プロジェクト: crohling/kops
func (e *InstanceTemplate) mapToGCE(project string) (*compute.InstanceTemplate, error) {
	// TODO: This is similar to Instance...
	var scheduling *compute.Scheduling

	if fi.BoolValue(e.Preemptible) {
		scheduling = &compute.Scheduling{
			AutomaticRestart:  false,
			OnHostMaintenance: "TERMINATE",
			Preemptible:       true,
		}
	} else {
		scheduling = &compute.Scheduling{
			AutomaticRestart: true,
			// TODO: Migrate or terminate?
			OnHostMaintenance: "MIGRATE",
			Preemptible:       false,
		}
	}

	glog.Infof("We should be using NVME for GCE")

	var disks []*compute.AttachedDisk
	disks = append(disks, &compute.AttachedDisk{
		InitializeParams: &compute.AttachedDiskInitializeParams{
			SourceImage: BuildImageURL(project, *e.BootDiskImage),
			DiskSizeGb:  *e.BootDiskSizeGB,
			DiskType:    *e.BootDiskType,
		},
		Boot:       true,
		DeviceName: "persistent-disks-0",
		Index:      0,
		AutoDelete: true,
		Mode:       "READ_WRITE",
		Type:       "PERSISTENT",
	})

	var tags *compute.Tags
	if e.Tags != nil {
		tags = &compute.Tags{
			Items: e.Tags,
		}
	}

	var networkInterfaces []*compute.NetworkInterface
	ni := &compute.NetworkInterface{
		AccessConfigs: []*compute.AccessConfig{{
			//NatIP: *e.IPAddress.Address,
			Type: "ONE_TO_ONE_NAT",
		}},
		Network: e.Network.URL(project),
	}
	if e.Subnet != nil {
		ni.Subnetwork = *e.Subnet.Name
	}
	networkInterfaces = append(networkInterfaces, ni)

	var serviceAccounts []*compute.ServiceAccount
	if e.Scopes != nil {
		var scopes []string
		for _, s := range e.Scopes {
			s = expandScopeAlias(s)

			scopes = append(scopes, s)
		}
		serviceAccounts = append(serviceAccounts, &compute.ServiceAccount{
			Email:  "default",
			Scopes: scopes,
		})
	}

	var metadataItems []*compute.MetadataItems
	for key, r := range e.Metadata {
		v, err := fi.ResourceAsString(r)
		if err != nil {
			return nil, fmt.Errorf("error rendering InstanceTemplate metadata %q: %v", key, err)
		}
		metadataItems = append(metadataItems, &compute.MetadataItems{
			Key:   key,
			Value: fi.String(v),
		})
	}

	i := &compute.InstanceTemplate{
		Name: *e.Name,
		Properties: &compute.InstanceProperties{
			CanIpForward: *e.CanIPForward,

			Disks: disks,

			MachineType: *e.MachineType,

			Metadata: &compute.Metadata{
				Items: metadataItems,
			},

			NetworkInterfaces: networkInterfaces,

			Scheduling: scheduling,

			ServiceAccounts: serviceAccounts,

			Tags: tags,
		},
	}

	return i, nil
}
コード例 #15
0
ファイル: instance.go プロジェクト: crohling/kops
func (e *Instance) Find(c *fi.Context) (*Instance, error) {
	cloud := c.Cloud.(*gce.GCECloud)

	r, err := cloud.Compute.Instances.Get(cloud.Project, *e.Zone, *e.Name).Do()
	if err != nil {
		if gce.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error listing Instances: %v", err)
	}

	actual := &Instance{}
	actual.Name = &r.Name
	for _, tag := range r.Tags.Items {
		actual.Tags = append(actual.Tags, tag)
	}
	actual.Zone = fi.String(lastComponent(r.Zone))
	actual.MachineType = fi.String(lastComponent(r.MachineType))
	actual.CanIPForward = &r.CanIpForward

	if r.Scheduling != nil {
		actual.Preemptible = &r.Scheduling.Preemptible
	}
	if len(r.NetworkInterfaces) != 0 {
		ni := r.NetworkInterfaces[0]
		actual.Network = &Network{Name: fi.String(lastComponent(ni.Network))}
		if len(ni.AccessConfigs) != 0 {
			ac := ni.AccessConfigs[0]
			if ac.NatIP != "" {
				addr, err := cloud.Compute.Addresses.List(cloud.Project, cloud.Region).Filter("address eq " + ac.NatIP).Do()
				if err != nil {
					return nil, fmt.Errorf("error querying for address %q: %v", ac.NatIP, err)
				} else if len(addr.Items) != 0 {
					actual.IPAddress = &IPAddress{Name: &addr.Items[0].Name}
				} else {
					return nil, fmt.Errorf("address not found %q: %v", ac.NatIP, err)
				}
			}
		}
	}

	for _, serviceAccount := range r.ServiceAccounts {
		for _, scope := range serviceAccount.Scopes {
			actual.Scopes = append(actual.Scopes, scopeToShortForm(scope))
		}
	}

	actual.Disks = make(map[string]*PersistentDisk)
	for i, disk := range r.Disks {
		if i == 0 {
			source := disk.Source

			// TODO: Parse source URL instead of assuming same project/zone?
			name := lastComponent(source)
			d, err := cloud.Compute.Disks.Get(cloud.Project, *e.Zone, name).Do()
			if err != nil {
				if gce.IsNotFound(err) {
					return nil, fmt.Errorf("disk not found %q: %v", source, err)
				}
				return nil, fmt.Errorf("error querying for disk %q: %v", source, err)
			}

			image, err := ShortenImageURL(cloud.Project, d.SourceImage)
			if err != nil {
				return nil, fmt.Errorf("error parsing source image URL: %v", err)
			}
			actual.Image = fi.String(image)
		} else {
			url, err := gce.ParseGoogleCloudURL(disk.Source)
			if err != nil {
				return nil, fmt.Errorf("unable to parse disk source URL: %q", disk.Source)
			}

			actual.Disks[disk.DeviceName] = &PersistentDisk{Name: &url.Name}
		}
	}

	if r.Metadata != nil {
		actual.Metadata = make(map[string]fi.Resource)
		for _, i := range r.Metadata.Items {
			if i.Value == nil {
				glog.Warningf("ignoring GCE instance metadata entry with nil-value: %q", i.Key)
				continue
			}
			actual.Metadata[i.Key] = fi.NewStringResource(*i.Value)
		}
		actual.metadataFingerprint = r.Metadata.Fingerprint
	}

	return actual, nil
}
コード例 #16
0
ファイル: instance.go プロジェクト: crohling/kops
func (e *Instance) mapToGCE(project string, ipAddressResolver func(*IPAddress) (*string, error)) (*compute.Instance, error) {
	zone := *e.Zone

	var scheduling *compute.Scheduling
	if fi.BoolValue(e.Preemptible) {
		scheduling = &compute.Scheduling{
			OnHostMaintenance: "TERMINATE",
			Preemptible:       true,
		}
	} else {
		scheduling = &compute.Scheduling{
			AutomaticRestart: true,
			// TODO: Migrate or terminate?
			OnHostMaintenance: "MIGRATE",
			Preemptible:       false,
		}
	}

	var disks []*compute.AttachedDisk
	disks = append(disks, &compute.AttachedDisk{
		InitializeParams: &compute.AttachedDiskInitializeParams{
			SourceImage: BuildImageURL(project, *e.Image),
		},
		Boot:       true,
		DeviceName: "persistent-disks-0",
		Index:      0,
		AutoDelete: true,
		Mode:       "READ_WRITE",
		Type:       "PERSISTENT",
	})

	for name, disk := range e.Disks {
		disks = append(disks, &compute.AttachedDisk{
			Source:     disk.URL(project),
			AutoDelete: false,
			Mode:       "READ_WRITE",
			DeviceName: name,
		})
	}

	var tags *compute.Tags
	if e.Tags != nil {
		tags = &compute.Tags{
			Items: e.Tags,
		}
	}

	var networkInterfaces []*compute.NetworkInterface
	if e.IPAddress != nil {
		addr, err := ipAddressResolver(e.IPAddress)
		if err != nil {
			return nil, fmt.Errorf("unable to resolve IP for instance: %v", err)
		}
		if addr == nil {
			return nil, fmt.Errorf("instance IP address has not yet been created")
		}
		networkInterface := &compute.NetworkInterface{
			AccessConfigs: []*compute.AccessConfig{{
				NatIP: *addr,
				Type:  "ONE_TO_ONE_NAT",
			}},
			Network: e.Network.URL(project),
		}
		if e.Subnet != nil {
			networkInterface.Subnetwork = *e.Subnet.Name
		}
		networkInterfaces = append(networkInterfaces, networkInterface)
	}

	var serviceAccounts []*compute.ServiceAccount
	if e.Scopes != nil {
		var scopes []string
		for _, s := range e.Scopes {
			s = expandScopeAlias(s)

			scopes = append(scopes, s)
		}
		serviceAccounts = append(serviceAccounts, &compute.ServiceAccount{
			Email:  "default",
			Scopes: scopes,
		})
	}

	var metadataItems []*compute.MetadataItems
	for key, r := range e.Metadata {
		v, err := fi.ResourceAsString(r)
		if err != nil {
			return nil, fmt.Errorf("error rendering Instance metadata %q: %v", key, err)
		}
		metadataItems = append(metadataItems, &compute.MetadataItems{
			Key:   key,
			Value: fi.String(v),
		})
	}

	i := &compute.Instance{
		CanIpForward: *e.CanIPForward,

		Disks: disks,

		MachineType: BuildMachineTypeURL(project, zone, *e.MachineType),

		Metadata: &compute.Metadata{
			Items: metadataItems,
		},

		Name: *e.Name,

		NetworkInterfaces: networkInterfaces,

		Scheduling: scheduling,

		ServiceAccounts: serviceAccounts,

		Tags: tags,
	}

	return i, nil
}