Hi I am new to Golang and Pulumi so sorry for the ...
# general
l
Hi I am new to Golang and Pulumi so sorry for the basic question . I am following the below example to create the webserver. This example works perfect to create a single VM at a time . We are interested in creating multiple VM with single request. Lets say developer select 3 or 4 Webserver vms with name of web1,2,3 respectively , which should create vms accordingly.We are using automation api with golang. We are looking more dynamic way for creating vm rather than repeating the VM code for each vm we want to add.Any advise will be helpful. https://github.com/pulumi/automation-api-examples/tree/main/go/vm_manager_azure
b
l
šŸ‘Thanks
Hi @billowy-army-68599 Thanks for sharing that worked perfectly as expected . Now I would like to clarify one more qustion . After provisioning these Instances we would like to deploy Elasticsearch cluster or mongo db . What would be best way to deploy any apps using pulumi, any pointers will be appreciated .
b
do you mean inside the ec2 instance? if so, then use the new pulumi-command provider: https://www.pulumi.com/registry/packages/command/api-docs/
l
yes Inside ec2 machines when they ready
Thanks i will have look
One more thing to clarify please
Lets say If wanted to decommission one of the 5 web servers I added instead of destrying the whole stack . what would be the best way to remove individual ec2 machine from the stack. I am looking to do it using automation api.
b
you'd need to remove the instance from your code or do
pulumi destroy --target <urn>
l
Thanks, Just wondering how I can get the urn of the specific resource using code ?
b
pulumi stack -u
l
Thanks very much for sharing . How we can access this feature using autmation api , is there any example which I can look please
b
l
Hi @billowy-army-68599, I would really appreciate your advise .So I have Implemented the Ec2 instances . We are now looking to get the Public Ip address every Instance and other properties when they are fully provisioned . Following the auzre example I mentioned above . I am trying to implement similar function to the IP address . In my version I am trying to achieve the similar function using ec2.GetInstance function which does not except the pulumi.StringOutput and give me errros . ā€¢ First I would to confirm if I am using the write approach to GetInstance properties for Ec2 machine. More importantly when they are fully provisioned . ā€¢ I am using this example as it uses ApplyT(func(args []interface{}) (string, error) function which I think waits for the resource to be ready and gives use resource properties . ā€¢ Can i achieve the following some other way Azure Version
Copy code
func (ws *Webserver) GetIPAddress(ctx *pulumi.Context) pulumi.StringOutput {
   // The public IP address is not allocated until the VM is running, so wait for that resource to create, and then
   // lookup the IP address again to report its public IP.
   ready := pulumi.All(ws.VM.ID(), ws.PublicIP.Name, ws.PublicIP.ResourceGroupName)
   return ready.ApplyT(func(args []interface{}) (string, error) {
      name := args[1].(string)
      resourceGroupName := args[2].(string)
      ip, err := network.GetPublicIP(ctx, &network.GetPublicIPArgs{
         Name:              name,
         ResourceGroupName: resourceGroupName,
      })
      if err != nil {
         return "", err
      }
      return ip.IpAddress, nil
   }).(pulumi.StringOutput)
}
My Version for EC2
Copy code
func (v *virtualMachinesService) GetIPAddress(ctx *pulumi.Context) pulumi.StringOutput {
   vm := new(virtualmachines.VirtualMachines)
   // The public IP address is not allocated until the VM is running, so wait for that resource to create, and then
   // lookup the IP address again to report its public IP.
   ready := pulumi.All(vm.Instance.ID(), vm.Instance.PublicIp)
   return ready.ApplyT(func(args []interface{}) (string, error) {
      pubip := args[1].(string)

      ip, err := ec2.GetInstance(ctx, "web-server-www-",vm.Instance.ID(), &ec2.InstanceState{
         PublicIp: pubip,
      }

      })
      if err != nil {
         return "", err
      }
      return ip.p, nil
   }).(pulumi.StringOutput)
}
Copy code
type VirtualMachines struct {
   pulumi.ResourceState
   PublicIP         *ec2.Eip
   NetworkInterface *ec2.NetworkInterface
   VPC               *ec2.Vpc
   Instance  *ec2.Instance
   Internet_gateway *ec2.InternetGateway
   Route *ec2.Route
   GetAvailabilityZones *ec2.AvailabilityZoneGroup
   Subnet *ec2.Subnet
   SecurityGroup *ec2.SecurityGroup


}
b
You shouldn't need to use
GetInstance
for AWS EC2 instances, you should have an output with the IP address already which you can store in a slice
how did you provision your EC2 instances?
l
One way I tried
Copy code
// ctx.Export("ipAddress", srv.PublicIp)
// Export the resulting server's IP address and DNS name.
ctx.Export("publicIp", vm.Instance.PublicIp)
ctx.Export("publicHostName", vm.Instance.PublicDns)
Is that the output you think I can store in the slice ?
Here is code of the VM
Copy code
func NewVirtualMachine(ctx *pulumi.Context, name string, args *virtualmachines.VirtualMachinesArgs, opts ...pulumi.ResourceOption) (*virtualmachines.VirtualMachines, error){

   vm := &virtualmachines.VirtualMachines{}

   err := ctx.RegisterComponentResource("virtual_machine", name, vm, opts...)
   if err != nil {
      return nil, err
   }

   stackName := ctx.Stack()


   // Create a valid webserver security group
   vm.SecurityGroup, err = ec2.NewSecurityGroup(ctx, "web-secgrp", &ec2.SecurityGroupArgs{
      Ingress: ec2.SecurityGroupIngressArray{
         ec2.SecurityGroupIngressArgs{
            //Protocol:   pulumi.String("tcp"),
            Protocol:   args.Protocol,
            FromPort:   <http://pulumi.Int|pulumi.Int>(80),
            ToPort:     <http://pulumi.Int|pulumi.Int>(80),
            CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
         },
      },
   })
   if err != nil {
      return nil, err
   }

   userData := pulumi.Sprintf(`#!/bin/bash
      echo "Hello from stack %s" > index.html
      nohup python -m SimpleHTTPServer 80 &`, stackName)
fmt.Println(args.InstanceType)
   fmt.Println(args.NumberOfNodes)
   for i := 1; i <= args.NumberOfNodes; i++ {
      vm.Instance, err = ec2.NewInstance(ctx, fmt.Sprintf("web-server-www-%d", i), &ec2.InstanceArgs{
         Tags:                pulumi.StringMap{"Name": pulumi.String(fmt.Sprintf("web-server-www-%d", i))},
         //InstanceType:        pulumi.String("t3.large"),
         InstanceType:        args.InstanceType,
         VpcSecurityGroupIds: pulumi.StringArray{vm.SecurityGroup.ID()},
         Ami:                 pulumi.String("ami-0a24e25f7f758ff73"),
         UserData:            userData,
      })
      if err != nil {
         panic("error creating ec2 instance")
      }
   }

   // ctx.Export("ipAddress", srv.PublicIp)
   // Export the resulting server's IP address and DNS name.
   ctx.Export("publicIp", vm.Instance.PublicIp)
   ctx.Export("publicHostName", vm.Instance.PublicDns)
   return nil, nil
}
Then I read this in
Copy code
func(v *virtualMachinesService) GetDeployVMFunc(vm *virtualmachines.CreateVmReq)  pulumi.RunFunc {

   return func(ctx *pulumi.Context) error {

      fmt.Println(vm.Protocol)
      _ ,err := NewVirtualMachine(ctx,"ec2-vms", &virtualmachines.VirtualMachinesArgs{

         Protocol: pulumi.String(vm.Protocol),
         NumberOfNodes: vm.NumberOfNodes,
         InstanceType : pulumi.String(vm.InstanceType),
      })



      if err != nil {
         return err
      }
      fmt.Println(vm.NumberOfNodes)
   // ctx.Export("URN", vm.URN())
      return nil

}

}
then use the deploy function which deploys the machines
Copy code
// set out program for the deployment with the resulting network info
   w.SetProgram(VirtualMachinesService.GetDeployVMFunc(vm))

   fmt.Println("deploying vm webserver...")

   // wire up our update to stream progress to stdout
   stdoutStreamer := optup.ProgressStreams(os.Stdout)

   res, err := s.Up(ctx, stdoutStreamer)
   if err != nil {
      fmt.Printf("Failed to deploy vm stack: %v\n", err)
      os.Exit(1)
   }
   time.Sleep(60 * time.Second)

   for key, element := range res.Outputs {
      fmt.Println("key", key, "=>", element.Value)
   }
   fmt.Println("Update succeeded!")

   for key, element := range res.Outputs {
      fmt.Println("Key:", key, "=>", "Element:", element.Value)
   }
   fmt.Println("Update succeeded!")

      //fmt.Printf("deployed server running at public IP %s\n", res.Outputs["ip"].Value.(string))



   return nil
}
So I do see the Ip address on the console , but we want to make sure we get the Instance Information (publicip, PublicDNS, EIP and other info ) Just after EC2 machine is fully ready as we would like to store that info in DB for further usage
Thats why i am trying to implement GetIPaddress function as a starting point to safely get the required information and use the
return ready.ApplyT(func(args []interface{}) (pulumi.StringOutput, error)
which i think waits for the resource to be ready .
Below the Structs I am using
Copy code
type VirtualMachines struct {
   pulumi.ResourceState
   PublicIP         *ec2.Eip
   NetworkInterface *ec2.NetworkInterface
   VPC               *ec2.Vpc
   Instance  *ec2.Instance
   Internet_gateway *ec2.InternetGateway
   Route *ec2.Route
   GetAvailabilityZones *ec2.AvailabilityZoneGroup
   Subnet *ec2.Subnet
   SecurityGroup *ec2.SecurityGroup


}

type VirtualMachinesArgs struct {
   // A required username for the VM login.
   Username pulumi.StringInput

   // A required encrypted password for the VM password.
   Password pulumi.StringInput

   // An optional boot script that the VM will use.
   BootScript pulumi.StringInput

   // An optional VM size; if unspecified, Standard_A0 (micro) will be used.
   VMSize pulumi.StringInput

   // A required Resource Group in which to create the VM
   ResourceGroupName pulumi.StringInput

   // A required Subnet in which to deploy the VM
   SubnetID pulumi.StringInput

   // A required Subnet in which to deploy the VM
   CidrBlock pulumi.StringInput

   // This value for VPC to enable DNS
   EnableDnsHostnames pulumi.Bool

   // A map of tags to assign to the resource. If configured with a provider `defaultTags` configuration block present, tags with matching keys will overwrite those defined at the provider-level.
   Tags pulumi.StringMapInput
//
   DestinationCidrBlock pulumi.StringInput

   // Specify true to indicate
   // that instances launched into the subnet should be assigned
   // a public IP address. Default is `false`.
   MapPublicIpOnLaunch pulumi.Bool

   Protocol pulumi.StringInput

   // The instance type to use for the instance. Updates to this field will trigger a stop/start of the EC2 instance.
   InstanceType pulumi.StringInput
   // Number of Nodes
   NumberOfNodes int

   Workdir auto.LocalWorkspace
}
b
You don't need to use
GetInstance
unless there's something in that call that you need. You already get the IPs, so just push them into a slice
if you want to push them into a database, you'll need to iterate over the slice and use an
ApplyT
to get the string value
l
Thanks very much @billowy-army-68599, I will have look šŸ™
Hi @billowy-army-68599, Following the example you mention earlier . Noticed that example built on v2 pulumi. So not able to find the AppyIDArray method , Just wondering that other method can I use to send out the pulumi.IDArrayOutput in v4 aws pulumi.
Copy code
func idOutputArrayToIDArrayOutput(as []pulumi.IDOutput) pulumi.IDArrayOutput {
   var outputs []interface{}
   for _, a := range as {
      outputs = append(outputs, a)
   }
   return pulumi.All(outputs...).ApplyIDArray(func(vs []interface{}) []pulumi.ID {
      var results []pulumi.ID
      for _, v := range vs {
         results = append(results, v.(pulumi.ID))
      }
      return results
   })
}
I have rewrriten like this , is that the right implementation of idOutputArrayToIDArrayOutput
Copy code
func idOutputArrayToIDArrayOutput(as []pulumi.IDOutput) pulumi.IDArrayOutput {
   var outputs []interface{}
   for _, a := range as {
      outputs = append(outputs, a)
   }
   return pulumi.IDArrayOutput(pulumi.All(outputs...).ApplyT(func(vs []interface{}) []pulumi.ID {
      var results []pulumi.ID
      for _, v := range vs {
         results = append(results, v.(pulumi.ID))
      }
      return results
   }).(pulumi.AnyOutput))
}
Hi @billowy-army-68599, I get below error when i implement above changed code, When I comment the code its works fine . Any suggestion how I can write the idOutputArrayToIDArrayOutput function please . really appreciate you advise .
pulumi:pulumi:Stack vpc-vpc-pulumi  error: an unhandled error occurred: go inline source runtime error, an unhandled error occurred:: interface conversion: pulumi.Output is pulumi.IDArrayOutput, not pulumi.IDArrayArrayOutput
pulumi:pulumi:Stack vpc-vpc-pulumi **failed** 1 error
Diagnostics:
pulumi:pulumi:Stack (vpc-vpc-pulumi):
error: an unhandled error occurred: go inline source runtime error, an unhandled error occurred:: interface conversion: pulumi.Output is pulumi.IDArrayOutput, not pulumi.IDArrayArrayOutput
b
i cant help here I'm afraid
l
Sure Thanks