package cloudstack import ( "fmt" "math/rand" "strings" "time" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" "github.com/xanzy/go-cloudstack/cloudstack" ) type stepSetupNetworking struct { privatePort int publicPort int } func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*cloudstack.CloudStackClient) config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) ui.Say("Setup networking...") if config.UseLocalIPAddress { ui.Message("Using the local IP address...") ui.Message("Networking has been setup!") return multistep.ActionContinue } // Generate a random public port used to configure our port forward. rand.Seed(time.Now().UnixNano()) s.publicPort = 50000 + rand.Intn(10000) // Set the currently configured port to be the private port. s.privatePort = config.Comm.Port() // Set the SSH or WinRM port to be the randomly generated public port. switch config.Comm.Type { case "ssh": config.Comm.SSHPort = s.publicPort case "winrm": config.Comm.WinRMPort = s.publicPort } // Retrieve the instance ID from the previously saved state. instanceID, ok := state.Get("instance_id").(string) if !ok || instanceID == "" { ui.Error("Could not retrieve instance_id from state!") return multistep.ActionHalt } network, _, err := client.Network.GetNetworkByID( config.Network, cloudstack.WithProject(config.Project), ) if err != nil { ui.Error(fmt.Sprintf("Failed to retrieve the network object: %s", err)) return multistep.ActionHalt } if config.PublicIPAddress == "" { ui.Message("Associating public IP address...") p := client.Address.NewAssociateIpAddressParams() if config.Project != "" { p.SetProjectid(config.Project) } if network.Vpcid != "" { p.SetVpcid(network.Vpcid) } else { p.SetNetworkid(network.Id) } // Associate a new public IP address. ipAddr, err := client.Address.AssociateIpAddress(p) if err != nil { ui.Error(fmt.Sprintf("Failed to associate public IP address: %s", err)) return multistep.ActionHalt } // Set the IP address and it's ID. config.PublicIPAddress = ipAddr.Id config.hostAddress = ipAddr.Ipaddress // Store the IP address ID. state.Put("ip_address_id", ipAddr.Id) } ui.Message("Creating port forward...") p := client.Firewall.NewCreatePortForwardingRuleParams( config.PublicIPAddress, s.privatePort, "TCP", s.publicPort, instanceID, ) // Configure the port forward. p.SetNetworkid(network.Id) p.SetOpenfirewall(false) // Create the port forward. forward, err := client.Firewall.CreatePortForwardingRule(p) if err != nil { ui.Error(fmt.Sprintf("Failed to create port forward: %s", err)) } // Store the port forward ID. state.Put("port_forward_id", forward.Id) if network.Vpcid != "" { ui.Message("Creating network ACL rule...") if network.Aclid == "" { ui.Error("Failed to configure the firewall: no ACL connected to the VPC network") return multistep.ActionHalt } // Create a new parameter struct. p := client.NetworkACL.NewCreateNetworkACLParams("TCP") // Configure the network ACL rule. p.SetAclid(network.Aclid) p.SetAction("allow") p.SetCidrlist(config.CIDRList) p.SetStartport(s.privatePort) p.SetEndport(s.privatePort) p.SetTraffictype("ingress") // Create the network ACL rule. aclRule, err := client.NetworkACL.CreateNetworkACL(p) if err != nil { ui.Error(fmt.Sprintf("Failed to create network ACL rule: %s", err)) return multistep.ActionHalt } // Store the network ACL rule ID. state.Put("network_acl_rule_id", aclRule.Id) } else { ui.Message("Creating firewall rule...") // Create a new parameter struct. p := client.Firewall.NewCreateFirewallRuleParams(config.PublicIPAddress, "TCP") // Configure the firewall rule. p.SetCidrlist(config.CIDRList) p.SetStartport(s.publicPort) p.SetEndport(s.publicPort) fwRule, err := client.Firewall.CreateFirewallRule(p) if err != nil { ui.Error(fmt.Sprintf("Failed to create firewall rule: %s", err)) return multistep.ActionHalt } // Store the firewall rule ID. state.Put("firewall_rule_id", fwRule.Id) } ui.Message("Networking has been setup!") return multistep.ActionContinue } // Cleanup any resources that may have been created during the Run phase. func (s *stepSetupNetworking) Cleanup(state multistep.StateBag) { client := state.Get("client").(*cloudstack.CloudStackClient) ui := state.Get("ui").(packer.Ui) ui.Say("Cleanup networking...") if fwRuleID, ok := state.Get("firewall_rule_id").(string); ok && fwRuleID != "" { // Create a new parameter struct. p := client.Firewall.NewDeleteFirewallRuleParams(fwRuleID) ui.Message("Deleting firewal rule...") if _, err := client.Firewall.DeleteFirewallRule(p); err != nil { // This is a very poor way to be told the ID does no longer exist :( if !strings.Contains(err.Error(), fmt.Sprintf( "Invalid parameter id value=%s due to incorrect long value format, "+ "or entity does not exist", fwRuleID)) { ui.Error(fmt.Sprintf("Error deleting firewall rule: %s", err)) } } } if aclRuleID, ok := state.Get("network_acl_rule_id").(string); ok && aclRuleID != "" { // Create a new parameter struct. p := client.NetworkACL.NewDeleteNetworkACLParams(aclRuleID) ui.Message("Deleting network ACL rule...") if _, err := client.NetworkACL.DeleteNetworkACL(p); err != nil { // This is a very poor way to be told the ID does no longer exist :( if !strings.Contains(err.Error(), fmt.Sprintf( "Invalid parameter id value=%s due to incorrect long value format, "+ "or entity does not exist", aclRuleID)) { ui.Error(fmt.Sprintf("Error deleting network ACL rule: %s", err)) } } } if forwardID, ok := state.Get("port_forward_id").(string); ok && forwardID != "" { // Create a new parameter struct. p := client.Firewall.NewDeletePortForwardingRuleParams(forwardID) ui.Message("Deleting port forward...") if _, err := client.Firewall.DeletePortForwardingRule(p); err != nil { // This is a very poor way to be told the ID does no longer exist :( if !strings.Contains(err.Error(), fmt.Sprintf( "Invalid parameter id value=%s due to incorrect long value format, "+ "or entity does not exist", forwardID)) { ui.Error(fmt.Sprintf("Error deleting port forward: %s", err)) } } } if ipAddrID, ok := state.Get("ip_address_id").(string); ok && ipAddrID != "" { // Create a new parameter struct. p := client.Address.NewDisassociateIpAddressParams(ipAddrID) ui.Message("Releasing public IP address...") if _, err := client.Address.DisassociateIpAddress(p); err != nil { // This is a very poor way to be told the ID does no longer exist :( if !strings.Contains(err.Error(), fmt.Sprintf( "Invalid parameter id value=%s due to incorrect long value format, "+ "or entity does not exist", ipAddrID)) { ui.Error(fmt.Sprintf("Error releasing public IP address: %s", err)) } } } ui.Message("Networking has been cleaned!") return }