diff --git a/builder/oracle/classic/config.go b/builder/oracle/classic/config.go index 233a3a0d1..7916478f0 100644 --- a/builder/oracle/classic/config.go +++ b/builder/oracle/classic/config.go @@ -99,10 +99,6 @@ func NewConfig(raws ...interface{}) (*Config, error) { if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { errs = packer.MultiErrorAppend(errs, es...) } - if c.Comm.Type == "winrm" { - err = fmt.Errorf("winRM is not supported with the oracle-classic builder yet.") - errs = packer.MultiErrorAppend(errs, err) - } if errs != nil && len(errs.Errors) > 0 { return nil, errs diff --git a/builder/oracle/classic/step_add_keys.go b/builder/oracle/classic/step_add_keys.go index 544c35bf2..b09310a1f 100644 --- a/builder/oracle/classic/step_add_keys.go +++ b/builder/oracle/classic/step_add_keys.go @@ -19,6 +19,11 @@ func (s *stepAddKeysToAPI) Run(_ context.Context, state multistep.StateBag) mult config := state.Get("config").(*Config) client := state.Get("client").(*compute.ComputeClient) + if config.Comm.Type != "ssh" { + ui.Say("Not using SSH communicator; skip generating SSH keys...") + return multistep.ActionContinue + } + // grab packer-generated key from statebag context. sshPublicKey := strings.TrimSpace(state.Get("publicKey").(string)) @@ -49,10 +54,14 @@ func (s *stepAddKeysToAPI) Run(_ context.Context, state multistep.StateBag) mult func (s *stepAddKeysToAPI) Cleanup(state multistep.StateBag) { // Delete the keys we created during this run - keyName := state.Get("key_name").(string) + keyName, ok := state.GetOk("key_name") + if !ok { + // No keys were generated; none need to be cleaned up. + return + } ui := state.Get("ui").(packer.Ui) ui.Say("Deleting SSH keys...") - deleteInput := compute.DeleteSSHKeyInput{Name: keyName} + deleteInput := compute.DeleteSSHKeyInput{Name: keyName.(string)} client := state.Get("client").(*compute.ComputeClient) deleteClient := client.SSHKeys() err := deleteClient.DeleteSSHKey(&deleteInput) diff --git a/builder/oracle/classic/step_create_instance.go b/builder/oracle/classic/step_create_instance.go index cda67122c..d9d26b3e3 100644 --- a/builder/oracle/classic/step_create_instance.go +++ b/builder/oracle/classic/step_create_instance.go @@ -18,7 +18,6 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu config := state.Get("config").(*Config) client := state.Get("client").(*compute.ComputeClient) - keyName := state.Get("key_name").(string) ipAddName := state.Get("ipres_name").(string) secListName := state.Get("security_list").(string) @@ -35,10 +34,13 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu Name: config.ImageName, Shape: config.Shape, ImageList: config.SourceImageList, - SSHKeys: []string{keyName}, Networking: map[string]compute.NetworkingInfo{"eth0": netInfo}, Attributes: config.attribs, } + if config.Comm.Type == "ssh" { + keyName := state.Get("key_name").(string) + input.SSHKeys = []string{keyName} + } instanceInfo, err := instanceClient.CreateInstance(input) if err != nil { @@ -48,6 +50,7 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu return multistep.ActionHalt } + state.Put("instance_info", instanceInfo) state.Put("instance_id", instanceInfo.ID) ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID)) return multistep.ActionContinue diff --git a/builder/oracle/classic/step_security.go b/builder/oracle/classic/step_security.go index 22d74de08..892695f83 100644 --- a/builder/oracle/classic/step_security.go +++ b/builder/oracle/classic/step_security.go @@ -3,10 +3,10 @@ package classic import ( "context" "fmt" - "log" "strings" "github.com/hashicorp/go-oracle-terraform/compute" + "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -15,53 +15,101 @@ type stepSecurity struct{} func (s *stepSecurity) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - - ui.Say("Configuring security lists and rules to enable SSH access...") - config := state.Get("config").(*Config) - client := state.Get("client").(*compute.ComputeClient) - secListName := fmt.Sprintf("/Compute-%s/%s/Packer_SSH_Allow_%s", - config.IdentityDomain, config.Username, config.ImageName) + commType := "" + if config.Comm.Type == "ssh" { + commType = "SSH" + } else if config.Comm.Type == "winrm" { + commType = "WINRM" + } + + ui.Say(fmt.Sprintf("Configuring security lists and rules to enable %s access...", commType)) + + client := state.Get("client").(*compute.ComputeClient) + runUUID := uuid.TimeOrderedUUID() + + namePrefix := fmt.Sprintf("/Compute-%s/%s/", config.IdentityDomain, config.Username) + secListName := fmt.Sprintf("Packer_%s_Allow_%s_%s", commType, config.ImageName, runUUID) secListClient := client.SecurityLists() secListInput := compute.CreateSecurityListInput{ - Description: "Packer-generated security list to give packer ssh access", - Name: secListName, + Description: fmt.Sprintf("Packer-generated security list to give packer %s access", commType), + Name: namePrefix + secListName, } _, err := secListClient.CreateSecurityList(&secListInput) if err != nil { if !strings.Contains(err.Error(), "already exists") { err = fmt.Errorf("Error creating security List to"+ - " allow Packer to connect to Oracle instance via SSH: %s", err) + " allow Packer to connect to Oracle instance via %s: %s", commType, err) ui.Error(err.Error()) state.Put("error", err) return multistep.ActionHalt } } // DOCS NOTE: user must have Compute_Operations role - // Create security rule that allows Packer to connect via SSH - secRulesClient := client.SecRules() - secRulesInput := compute.CreateSecRuleInput{ - Action: "PERMIT", - Application: "/oracle/public/ssh", - Description: "Packer-generated security rule to allow ssh", - DestinationList: fmt.Sprintf("seclist:%s", secListName), - Name: fmt.Sprintf("Packer-allow-SSH-Rule_%s", config.ImageName), - SourceList: config.SSHSourceList, - } - - secRuleName := fmt.Sprintf("/Compute-%s/%s/Packer-allow-SSH-Rule_%s", - config.IdentityDomain, config.Username, config.ImageName) - _, err = secRulesClient.CreateSecRule(&secRulesInput) - if err != nil { - log.Printf("Error creating security rule to allow SSH: %s", err.Error()) - if !strings.Contains(err.Error(), "already exists") { - err = fmt.Errorf("Error creating security rule to"+ - " allow Packer to connect to Oracle instance via SSH: %s", err) + // Create security rule that allows Packer to connect via SSH or winRM + var application string + if commType == "SSH" { + application = "/oracle/public/ssh" + } else if commType == "WINRM" { + // Check to see whether a winRM security protocol is already defined; + // don't need to do this for SSH becasue it is built into the Oracle API. + protocolClient := client.SecurityProtocols() + winrmProtocol := fmt.Sprintf("WINRM_%s", runUUID) + input := compute.CreateSecurityProtocolInput{ + Name: winrmProtocol, + Description: "packer-generated protocol to allow winRM communicator", + DstPortSet: []string{"5985", "5986", "443"}, // TODO make configurable + IPProtocol: "tcp", + } + _, err = protocolClient.CreateSecurityProtocol(&input) + if err != nil { + err = fmt.Errorf("Error creating security protocol to"+ + " allow Packer to connect to Oracle instance via %s: %s", commType, err) ui.Error(err.Error()) state.Put("error", err) return multistep.ActionHalt } + state.Put("winrm_protocol", winrmProtocol) + + // Check to see whether a winRM security application is already defined + applicationClient := client.SecurityApplications() + application = fmt.Sprintf("packer_winRM_%s", runUUID) + applicationInput := compute.CreateSecurityApplicationInput{ + Description: "Allows Packer to connect to instance via winRM", + DPort: "5985-5986", + Name: application, + Protocol: "TCP", + } + _, err = applicationClient.CreateSecurityApplication(&applicationInput) + if err != nil { + err = fmt.Errorf("Error creating security application to"+ + " allow Packer to connect to Oracle instance via %s: %s", commType, err) + ui.Error(err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + state.Put("winrm_application", application) + } + secRulesClient := client.SecRules() + secRuleName := fmt.Sprintf("Packer-allow-%s-Rule_%s_%s", commType, + config.ImageName, runUUID) + secRulesInput := compute.CreateSecRuleInput{ + Action: "PERMIT", + Application: application, + Description: "Packer-generated security rule to allow ssh/winrm", + DestinationList: "seclist:" + namePrefix + secListName, + Name: namePrefix + secRuleName, + SourceList: config.SSHSourceList, + } + + _, err = secRulesClient.CreateSecRule(&secRulesInput) + if err != nil { + err = fmt.Errorf("Error creating security rule to"+ + " allow Packer to connect to Oracle instance: %s", err) + ui.Error(err.Error()) + state.Put("error", err) + return multistep.ActionHalt } state.Put("security_rule_name", secRuleName) state.Put("security_list", secListName) @@ -71,12 +119,15 @@ func (s *stepSecurity) Run(_ context.Context, state multistep.StateBag) multiste func (s *stepSecurity) Cleanup(state multistep.StateBag) { client := state.Get("client").(*compute.ComputeClient) ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(*Config) + ui.Say("Deleting temporary rules and lists...") + namePrefix := fmt.Sprintf("/Compute-%s/%s/", config.IdentityDomain, config.Username) // delete security rules that Packer generated secRuleName := state.Get("security_rule_name").(string) secRulesClient := client.SecRules() - ruleInput := compute.DeleteSecRuleInput{Name: secRuleName} + ruleInput := compute.DeleteSecRuleInput{Name: namePrefix + secRuleName} err := secRulesClient.DeleteSecRule(&ruleInput) if err != nil { ui.Say(fmt.Sprintf("Error deleting the packer-generated security rule %s; "+ @@ -86,10 +137,38 @@ func (s *stepSecurity) Cleanup(state multistep.StateBag) { // delete security list that Packer generated secListName := state.Get("security_list").(string) secListClient := client.SecurityLists() - input := compute.DeleteSecurityListInput{Name: secListName} + input := compute.DeleteSecurityListInput{Name: namePrefix + secListName} err = secListClient.DeleteSecurityList(&input) if err != nil { ui.Say(fmt.Sprintf("Error deleting the packer-generated security list %s; "+ "please delete manually. (error : %s)", secListName, err.Error())) } + + // Some extra cleanup if we used the winRM communicator + if config.Comm.Type == "winrm" { + // Delete the packer-generated protocol + protocol := state.Get("winrm_protocol").(string) + protocolClient := client.SecurityProtocols() + deleteProtocolInput := compute.DeleteSecurityProtocolInput{ + Name: namePrefix + protocol, + } + err = protocolClient.DeleteSecurityProtocol(&deleteProtocolInput) + if err != nil { + ui.Say(fmt.Sprintf("Error deleting the packer-generated winrm security protocol %s; "+ + "please delete manually. (error : %s)", protocol, err.Error())) + } + + // Delete the packer-generated application + application := state.Get("winrm_application").(string) + applicationClient := client.SecurityApplications() + deleteApplicationInput := compute.DeleteSecurityApplicationInput{ + Name: namePrefix + application, + } + err = applicationClient.DeleteSecurityApplication(&deleteApplicationInput) + if err != nil { + ui.Say(fmt.Sprintf("Error deleting the packer-generated winrm security application %s; "+ + "please delete manually. (error : %s)", application, err.Error())) + } + } + } diff --git a/builder/oracle/classic/step_snapshot.go b/builder/oracle/classic/step_snapshot.go index b7cbbf008..68c059897 100644 --- a/builder/oracle/classic/step_snapshot.go +++ b/builder/oracle/classic/step_snapshot.go @@ -3,16 +3,20 @@ package classic import ( "context" "fmt" + "time" "github.com/hashicorp/go-oracle-terraform/compute" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) -type stepSnapshot struct{} +type stepSnapshot struct { + cleanupSnap bool +} func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { // get variables from state + s.cleanupSnap = false ui := state.Get("ui").(packer.Ui) ui.Say("Creating Snapshot...") config := state.Get("config").(*Config) @@ -26,6 +30,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste snapshotInput := &compute.CreateSnapshotInput{ Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID), MachineImage: config.ImageName, + Timeout: time.Minute * 20, } snap, err := snapshotClient.CreateSnapshot(snapshotInput) @@ -35,7 +40,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste state.Put("error", err) return multistep.ActionHalt } - + s.cleanupSnap = true state.Put("snapshot", snap) ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name)) return multistep.ActionContinue @@ -44,6 +49,9 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste func (s *stepSnapshot) Cleanup(state multistep.StateBag) { // Delete the snapshot ui := state.Get("ui").(packer.Ui) + if !s.cleanupSnap { + return + } ui.Say("Deleting Snapshot...") client := state.Get("client").(*compute.ComputeClient) snap := state.Get("snapshot").(*compute.Snapshot) diff --git a/website/source/docs/builders/oracle-classic.html.md b/website/source/docs/builders/oracle-classic.html.md index 98db5f2aa..208a0a088 100644 --- a/website/source/docs/builders/oracle-classic.html.md +++ b/website/source/docs/builders/oracle-classic.html.md @@ -114,3 +114,62 @@ obfuscated; you will need to add a working `username`, `password`, ] } ``` + +## Basic Example -- Windows + +Attributes file is optional for connecting via ssh, but required for winrm. + +The following file contains the bare minimum necessary to get winRM working; +you have to give it the password to give to the "Administrator" user, which +will be the one winrm connects to. You must also whitelist your computer +to connect via winRM -- the empty braces below whitelist any computer to access +winRM but you can make it more secure by only allowing your build machine +access. See the [docs](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsg/automating-instance-initialization-using-opc-init.html#GUID-A0A107D6-3B38-47F4-8FC8-96D50D99379B) +for more details on how to define a trusted host. + +Save this file as `windows_attributes.json`: + +```{.json} +{ + "userdata": { + "administrator_password": "password", + "winrm": {} + } +} +``` + +Following is a minimal but working Packer config that references this attributes +file: +```{.json} +{ + "variables": { + "opc_username": "{{ env `OPC_USERNAME`}}", + "opc_password": "{{ env `OPC_PASSWORD`}}", + "opc_identity_domain": "{{env `OPC_IDENTITY_DOMAIN`}}", + "opc_api_endpoint": "{{ env `OPC_ENDPOINT`}}" + }, + "builders": [ + { + "type": "oracle-classic", + "username": "{{ user `opc_username`}}", + "password": "{{ user `opc_password`}}", + "identity_domain": "{{ user `opc_identity_domain`}}", + "api_endpoint": "{{ user `opc_api_endpoint`}}", + "source_image_list": "/Compute-{{ user `opc_identity_domain` }}/{{ user opc_username`}}/Microsoft_Windows_Server_2012_R2-17.3.6-20170930-124649", + "attributes_file": "./windows_attributes.json", + "shape": "oc3", + "image_name": "Packer_Windows_Demo_{{timestamp}}", + "dest_image_list": "Packer_Windows_Demo", + "communicator": "winrm", + "winrm_username": "Administrator", + "winrm_password": "password" + } + ], + "provisioners": [ + { + "type": "powershell", + "inline": "Write-Output(\"HELLO WORLD\")" + } + ] +} +```