285ff6940e
After some more research and testing it turns out we can support the use case where you want to reuse an associated IP address that already has port 22 or 5985 forwarded, by using a random public port. The correct port to open in the firewall is different for the type of firewall used. The standard firewall requires the public port to be opened and the network ACL requires the private port to be opened. So by partially reverting this code and updating which ports to open in which cases, we can support all use cases again.
239 lines
6.9 KiB
Go
239 lines
6.9 KiB
Go
package cloudstack
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mitchellh/multistep"
|
|
"github.com/mitchellh/packer/packer"
|
|
"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
|
|
}
|