Add the winRM communicator to Oracle Classic builder.

update oracle classic docs with a minimal working windows example
This commit is contained in:
Megan Marsh 2018-02-08 14:55:04 -08:00
parent 36179c5a1d
commit df45e0916d
6 changed files with 195 additions and 41 deletions

View File

@ -99,10 +99,6 @@ func NewConfig(raws ...interface{}) (*Config, error) {
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...) 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 { if errs != nil && len(errs.Errors) > 0 {
return nil, errs return nil, errs

View File

@ -19,6 +19,11 @@ func (s *stepAddKeysToAPI) Run(_ context.Context, state multistep.StateBag) mult
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient) 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. // grab packer-generated key from statebag context.
sshPublicKey := strings.TrimSpace(state.Get("publicKey").(string)) 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) { func (s *stepAddKeysToAPI) Cleanup(state multistep.StateBag) {
// Delete the keys we created during this run // 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 := state.Get("ui").(packer.Ui)
ui.Say("Deleting SSH keys...") ui.Say("Deleting SSH keys...")
deleteInput := compute.DeleteSSHKeyInput{Name: keyName} deleteInput := compute.DeleteSSHKeyInput{Name: keyName.(string)}
client := state.Get("client").(*compute.ComputeClient) client := state.Get("client").(*compute.ComputeClient)
deleteClient := client.SSHKeys() deleteClient := client.SSHKeys()
err := deleteClient.DeleteSSHKey(&deleteInput) err := deleteClient.DeleteSSHKey(&deleteInput)

View File

@ -18,7 +18,6 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient) client := state.Get("client").(*compute.ComputeClient)
keyName := state.Get("key_name").(string)
ipAddName := state.Get("ipres_name").(string) ipAddName := state.Get("ipres_name").(string)
secListName := state.Get("security_list").(string) secListName := state.Get("security_list").(string)
@ -35,10 +34,13 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu
Name: config.ImageName, Name: config.ImageName,
Shape: config.Shape, Shape: config.Shape,
ImageList: config.SourceImageList, ImageList: config.SourceImageList,
SSHKeys: []string{keyName},
Networking: map[string]compute.NetworkingInfo{"eth0": netInfo}, Networking: map[string]compute.NetworkingInfo{"eth0": netInfo},
Attributes: config.attribs, Attributes: config.attribs,
} }
if config.Comm.Type == "ssh" {
keyName := state.Get("key_name").(string)
input.SSHKeys = []string{keyName}
}
instanceInfo, err := instanceClient.CreateInstance(input) instanceInfo, err := instanceClient.CreateInstance(input)
if err != nil { if err != nil {
@ -48,6 +50,7 @@ func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) mu
return multistep.ActionHalt return multistep.ActionHalt
} }
state.Put("instance_info", instanceInfo)
state.Put("instance_id", instanceInfo.ID) state.Put("instance_id", instanceInfo.ID)
ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID)) ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID))
return multistep.ActionContinue return multistep.ActionContinue

View File

@ -3,10 +3,10 @@ package classic
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"strings" "strings"
"github.com/hashicorp/go-oracle-terraform/compute" "github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
) )
@ -15,53 +15,101 @@ type stepSecurity struct{}
func (s *stepSecurity) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { func (s *stepSecurity) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Configuring security lists and rules to enable SSH access...")
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient)
secListName := fmt.Sprintf("/Compute-%s/%s/Packer_SSH_Allow_%s", commType := ""
config.IdentityDomain, config.Username, config.ImageName) 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() secListClient := client.SecurityLists()
secListInput := compute.CreateSecurityListInput{ secListInput := compute.CreateSecurityListInput{
Description: "Packer-generated security list to give packer ssh access", Description: fmt.Sprintf("Packer-generated security list to give packer %s access", commType),
Name: secListName, Name: namePrefix + secListName,
} }
_, err := secListClient.CreateSecurityList(&secListInput) _, err := secListClient.CreateSecurityList(&secListInput)
if err != nil { if err != nil {
if !strings.Contains(err.Error(), "already exists") { if !strings.Contains(err.Error(), "already exists") {
err = fmt.Errorf("Error creating security List to"+ 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()) ui.Error(err.Error())
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
} }
// DOCS NOTE: user must have Compute_Operations role // DOCS NOTE: user must have Compute_Operations role
// Create security rule that allows Packer to connect via SSH // Create security rule that allows Packer to connect via SSH or winRM
secRulesClient := client.SecRules() var application string
secRulesInput := compute.CreateSecRuleInput{ if commType == "SSH" {
Action: "PERMIT", application = "/oracle/public/ssh"
Application: "/oracle/public/ssh", } else if commType == "WINRM" {
Description: "Packer-generated security rule to allow ssh", // Check to see whether a winRM security protocol is already defined;
DestinationList: fmt.Sprintf("seclist:%s", secListName), // don't need to do this for SSH becasue it is built into the Oracle API.
Name: fmt.Sprintf("Packer-allow-SSH-Rule_%s", config.ImageName), protocolClient := client.SecurityProtocols()
SourceList: config.SSHSourceList, 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)
secRuleName := fmt.Sprintf("/Compute-%s/%s/Packer-allow-SSH-Rule_%s",
config.IdentityDomain, config.Username, config.ImageName)
_, err = secRulesClient.CreateSecRule(&secRulesInput)
if err != nil { if err != nil {
log.Printf("Error creating security rule to allow SSH: %s", err.Error()) err = fmt.Errorf("Error creating security protocol to"+
if !strings.Contains(err.Error(), "already exists") { " allow Packer to connect to Oracle instance via %s: %s", commType, err)
err = fmt.Errorf("Error creating security rule to"+
" allow Packer to connect to Oracle instance via SSH: %s", err)
ui.Error(err.Error()) ui.Error(err.Error())
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt 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_rule_name", secRuleName)
state.Put("security_list", secListName) 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) { func (s *stepSecurity) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*compute.ComputeClient) client := state.Get("client").(*compute.ComputeClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
ui.Say("Deleting temporary rules and lists...") ui.Say("Deleting temporary rules and lists...")
namePrefix := fmt.Sprintf("/Compute-%s/%s/", config.IdentityDomain, config.Username)
// delete security rules that Packer generated // delete security rules that Packer generated
secRuleName := state.Get("security_rule_name").(string) secRuleName := state.Get("security_rule_name").(string)
secRulesClient := client.SecRules() secRulesClient := client.SecRules()
ruleInput := compute.DeleteSecRuleInput{Name: secRuleName} ruleInput := compute.DeleteSecRuleInput{Name: namePrefix + secRuleName}
err := secRulesClient.DeleteSecRule(&ruleInput) err := secRulesClient.DeleteSecRule(&ruleInput)
if err != nil { if err != nil {
ui.Say(fmt.Sprintf("Error deleting the packer-generated security rule %s; "+ 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 // delete security list that Packer generated
secListName := state.Get("security_list").(string) secListName := state.Get("security_list").(string)
secListClient := client.SecurityLists() secListClient := client.SecurityLists()
input := compute.DeleteSecurityListInput{Name: secListName} input := compute.DeleteSecurityListInput{Name: namePrefix + secListName}
err = secListClient.DeleteSecurityList(&input) err = secListClient.DeleteSecurityList(&input)
if err != nil { if err != nil {
ui.Say(fmt.Sprintf("Error deleting the packer-generated security list %s; "+ ui.Say(fmt.Sprintf("Error deleting the packer-generated security list %s; "+
"please delete manually. (error : %s)", secListName, err.Error())) "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()))
}
}
} }

View File

@ -3,16 +3,20 @@ package classic
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/hashicorp/go-oracle-terraform/compute" "github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
) )
type stepSnapshot struct{} type stepSnapshot struct {
cleanupSnap bool
}
func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// get variables from state // get variables from state
s.cleanupSnap = false
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Creating Snapshot...") ui.Say("Creating Snapshot...")
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
@ -26,6 +30,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
snapshotInput := &compute.CreateSnapshotInput{ snapshotInput := &compute.CreateSnapshotInput{
Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID), Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID),
MachineImage: config.ImageName, MachineImage: config.ImageName,
Timeout: time.Minute * 20,
} }
snap, err := snapshotClient.CreateSnapshot(snapshotInput) snap, err := snapshotClient.CreateSnapshot(snapshotInput)
@ -35,7 +40,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
s.cleanupSnap = true
state.Put("snapshot", snap) state.Put("snapshot", snap)
ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name)) ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name))
return multistep.ActionContinue return multistep.ActionContinue
@ -44,6 +49,9 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
func (s *stepSnapshot) Cleanup(state multistep.StateBag) { func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
// Delete the snapshot // Delete the snapshot
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
if !s.cleanupSnap {
return
}
ui.Say("Deleting Snapshot...") ui.Say("Deleting Snapshot...")
client := state.Get("client").(*compute.ComputeClient) client := state.Get("client").(*compute.ComputeClient)
snap := state.Get("snapshot").(*compute.Snapshot) snap := state.Get("snapshot").(*compute.Snapshot)

View File

@ -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\")"
}
]
}
```