Merge pull request #5819 from hashicorp/oracle-oci-builder

Oracle oci builder
This commit is contained in:
SwampDragons 2018-02-02 11:56:18 -08:00 committed by GitHub
commit caea770af1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 8192 additions and 72 deletions

View File

@ -71,11 +71,16 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
b := &Builder{}
config := testConfig()
config["chroot_mounts"] = [][]string{
{"bad"},
}
warnings, err = b.Prepare(config)
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
@ -135,9 +140,14 @@ func TestBuilderPrepare_CopyFiles(t *testing.T) {
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
t.Errorf("Was expecting default value for copy_files.")
}
}
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
b := &Builder{}
config := testConfig()
config["copy_files"] = []string{}
warnings, err = b.Prepare(config)
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}

View File

@ -0,0 +1,48 @@
package classic
import (
"fmt"
"github.com/hashicorp/go-oracle-terraform/compute"
)
// Artifact is an artifact implementation that contains Image List
// and Machine Image info.
type Artifact struct {
MachineImageName string
MachineImageFile string
ImageListVersion int
driver *compute.ComputeClient
}
// BuilderId uniquely identifies the builder.
func (a *Artifact) BuilderId() string {
return BuilderId
}
// Files lists the files associated with an artifact. We don't have any files
// as the custom image is stored server side.
func (a *Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return a.MachineImageName
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image list entry was created: \n"+
"Name: %s\n"+
"File: %s\n"+
"Version: %d",
a.MachineImageName, a.MachineImageFile, a.ImageListVersion)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
// Destroy deletes the custom image associated with the artifact.
func (a *Artifact) Destroy() error {
return nil
}

View File

@ -0,0 +1,117 @@
package classic
import (
"fmt"
"log"
"os"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/go-oracle-terraform/opc"
ocommon "github.com/hashicorp/packer/builder/oracle/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// BuilderId uniquely identifies the builder
const BuilderId = "packer.oracle.classic"
// Builder is a builder implementation that creates Oracle OCI custom images.
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) {
config, err := NewConfig(rawConfig...)
if err != nil {
return nil, err
}
b.config = config
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
loggingEnabled := os.Getenv("PACKER_OCI_CLASSIC_LOGGING") != ""
httpClient := cleanhttp.DefaultClient()
config := &opc.Config{
Username: opc.String(b.config.Username),
Password: opc.String(b.config.Password),
IdentityDomain: opc.String(b.config.IdentityDomain),
APIEndpoint: b.config.apiEndpointURL,
LogLevel: opc.LogDebug,
Logger: &Logger{loggingEnabled},
// Logger: # Leave blank to use the default logger, or provide your own
HTTPClient: httpClient,
}
// Create the Compute Client
client, err := compute.NewComputeClient(config)
if err != nil {
return nil, fmt.Errorf("Error creating OPC Compute Client: %s", err)
}
// Populate the state bag
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("client", client)
// Build the steps
steps := []multistep.Step{
&ocommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("oci_classic_%s.pem", b.config.PackerBuildName),
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
},
&stepCreateIPReservation{},
&stepAddKeysToAPI{},
&stepSecurity{},
&stepCreateInstance{},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: ocommon.CommHost,
SSHConfig: ocommon.SSHConfig(
b.config.Comm.SSHUsername,
b.config.Comm.SSHPassword),
},
&common.StepProvision{},
&stepSnapshot{},
&stepListImages{},
}
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there is no snapshot, then just return
if _, ok := state.GetOk("snapshot"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
ImageListVersion: state.Get("image_list_version").(int),
MachineImageName: state.Get("machine_image_name").(string),
MachineImageFile: state.Get("machine_image_file").(string),
driver: client,
}
return artifact, nil
}
// Cancel terminates a running build.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}

View File

@ -0,0 +1,93 @@
package classic
import (
"fmt"
"net/url"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
// Access config overrides
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
IdentityDomain string `mapstructure:"identity_domain"`
APIEndpoint string `mapstructure:"api_endpoint"`
apiEndpointURL *url.URL
// Image
ImageName string `mapstructure:"image_name"`
Shape string `mapstructure:"shape"`
SourceImageList string `mapstructure:"source_image_list"`
DestImageList string `mapstructure:"dest_image_list"`
// Optional; if you don't enter anything, the image list description
// will read "Packer-built image list"
DestImageListDescription string `mapstructure:"image_description"`
// Optional. Describes what computers are allowed to reach your instance
// via SSH. This whitelist must contain the computer you're running Packer
// from. It defaults to public-internet, meaning that you can SSH into your
// instance from anywhere as long as you have the right keys
SSHSourceList string `mapstructure:"ssh_source_list"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, error) {
c := &Config{}
// Decode from template
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err)
}
c.apiEndpointURL, err = url.Parse(c.APIEndpoint)
if err != nil {
return nil, fmt.Errorf("Error parsing API Endpoint: %s", err)
}
// Set default source list
if c.SSHSourceList == "" {
c.SSHSourceList = "seciplist:/oracle/public/public-internet"
}
// Use default oracle username with sudo privileges
if c.Comm.SSHUsername == "" {
c.Comm.SSHUsername = "opc"
}
// Validate that all required fields are present
var errs *packer.MultiError
required := map[string]string{
"username": c.Username,
"password": c.Password,
"api_endpoint": c.APIEndpoint,
"identity_domain": c.IdentityDomain,
"source_image_list": c.SourceImageList,
"dest_image_list": c.DestImageList,
"shape": c.Shape,
}
for k, v := range required {
if v == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You must specify a %s.", k))
}
}
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
return c, nil
}

View File

@ -0,0 +1,61 @@
package classic
import (
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"identity_domain": "abc12345",
"username": "test@hashicorp.com",
"password": "testpassword123",
"api_endpoint": "https://api-test.compute.test.oraclecloud.com/",
"dest_image_list": "/Config-thing/myuser/myimage",
"source_image_list": "/oracle/public/whatever",
"shape": "oc3",
"image_name": "TestImageName",
"ssh_username": "opc",
}
}
func TestConfigAutoFillsSourceList(t *testing.T) {
tc := testConfig()
conf, err := NewConfig(tc)
if err != nil {
t.Fatalf("Should not have error: %s", err.Error())
}
if conf.SSHSourceList != "seciplist:/oracle/public/public-internet" {
t.Fatalf("conf.SSHSourceList should have been "+
"\"seciplist:/oracle/public/public-internet\" but is \"%s\"",
conf.SSHSourceList)
}
}
func TestConfigValidationCatchesMissing(t *testing.T) {
required := []string{
"username",
"password",
"api_endpoint",
"identity_domain",
"dest_image_list",
"source_image_list",
"shape",
}
for _, key := range required {
tc := testConfig()
delete(tc, key)
_, err := NewConfig(tc)
if err == nil {
t.Fatalf("Test should have failed when config lacked %s!", key)
}
}
}
func TestValidationsIgnoresOptional(t *testing.T) {
tc := testConfig()
delete(tc, "ssh_username")
_, err := NewConfig(tc)
if err != nil {
t.Fatalf("Shouldn't care if ssh_username is missing: err: %#v", err.Error())
}
}

View File

@ -0,0 +1,14 @@
package classic
import "log"
type Logger struct {
Enabled bool
}
func (l *Logger) Log(input ...interface{}) {
if !l.Enabled {
return
}
log.Println(input...)
}

View File

@ -0,0 +1,63 @@
package classic
import (
"context"
"fmt"
"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"
)
type stepAddKeysToAPI struct{}
func (s *stepAddKeysToAPI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// get variables from state
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient)
// grab packer-generated key from statebag context.
sshPublicKey := strings.TrimSpace(state.Get("publicKey").(string))
// form API call to add key to compute cloud
sshKeyName := fmt.Sprintf("/Compute-%s/%s/packer_generated_key_%s",
config.IdentityDomain, config.Username, uuid.TimeOrderedUUID())
ui.Say(fmt.Sprintf("Creating temporary key: %s", sshKeyName))
sshKeysClient := client.SSHKeys()
sshKeysInput := compute.CreateSSHKeyInput{
Name: sshKeyName,
Key: sshPublicKey,
Enabled: true,
}
// Load the packer-generated SSH key into the Oracle Compute cloud.
keyInfo, err := sshKeysClient.CreateSSHKey(&sshKeysInput)
if err != nil {
err = fmt.Errorf("Problem adding Public SSH key through Oracle's API: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("key_name", keyInfo.Name)
return multistep.ActionContinue
}
func (s *stepAddKeysToAPI) Cleanup(state multistep.StateBag) {
// Delete the keys we created during this run
keyName := state.Get("key_name").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting SSH keys...")
deleteInput := compute.DeleteSSHKeyInput{Name: keyName}
client := state.Get("client").(*compute.ComputeClient)
deleteClient := client.SSHKeys()
err := deleteClient.DeleteSSHKey(&deleteInput)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting SSH keys: %s", err.Error()))
}
return
}

View File

@ -0,0 +1,79 @@
package classic
import (
"context"
"fmt"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateInstance struct{}
func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// get variables from state
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating Instance...")
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)
netInfo := compute.NetworkingInfo{
Nat: []string{ipAddName},
SecLists: []string{secListName},
}
// get instances client
instanceClient := client.Instances()
// Instances Input
input := &compute.CreateInstanceInput{
Name: config.ImageName,
Shape: config.Shape,
ImageList: config.SourceImageList,
SSHKeys: []string{keyName},
Networking: map[string]compute.NetworkingInfo{"eth0": netInfo},
}
instanceInfo, err := instanceClient.CreateInstance(input)
if err != nil {
err = fmt.Errorf("Problem creating instance: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("instance_id", instanceInfo.ID)
ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID))
return multistep.ActionContinue
}
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
// terminate instance
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*compute.ComputeClient)
config := state.Get("config").(*Config)
imID := state.Get("instance_id").(string)
ui.Say("Terminating source instance...")
instanceClient := client.Instances()
input := &compute.DeleteInstanceInput{
Name: config.ImageName,
ID: imID,
}
err := instanceClient.DeleteInstance(input)
if err != nil {
err = fmt.Errorf("Problem destroying instance: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return
}
// TODO wait for instance state to change to deleted?
ui.Say("Terminated instance.")
}

View File

@ -0,0 +1,57 @@
package classic
import (
"context"
"fmt"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateIPReservation struct{}
func (s *stepCreateIPReservation) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient)
iprClient := client.IPReservations()
// TODO: add optional Name and Tags
ipresName := fmt.Sprintf("ipres_%s_%s", config.ImageName, uuid.TimeOrderedUUID())
ui.Message(fmt.Sprintf("Creating temporary IP reservation: %s", ipresName))
IPInput := &compute.CreateIPReservationInput{
ParentPool: compute.PublicReservationPool,
Permanent: true,
Name: ipresName,
}
ipRes, err := iprClient.CreateIPReservation(IPInput)
if err != nil {
err := fmt.Errorf("Error creating IP Reservation: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("instance_ip", ipRes.IP)
state.Put("ipres_name", ipresName)
return multistep.ActionContinue
}
func (s *stepCreateIPReservation) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
ui.Say("Cleaning up IP reservations...")
client := state.Get("client").(*compute.ComputeClient)
ipResName := state.Get("ipres_name").(string)
input := compute.DeleteIPReservationInput{Name: ipResName}
ipClient := client.IPReservations()
err := ipClient.DeleteIPReservation(&input)
if err != nil {
fmt.Printf("error deleting IP reservation: %s", err.Error())
}
}

View File

@ -0,0 +1,104 @@
package classic
import (
"context"
"fmt"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepListImages struct{}
func (s *stepListImages) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// get variables from state
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient)
ui.Say("Adding image to image list...")
imageListClient := client.ImageList()
getInput := compute.GetImageListInput{
Name: config.DestImageList,
}
imList, err := imageListClient.GetImageList(&getInput)
if err != nil {
// If the list didn't exist, create it.
ui.Say(fmt.Sprintf(err.Error()))
ui.Say(fmt.Sprintf("Destination image list %s does not exist; Creating it...",
config.DestImageList))
ilInput := compute.CreateImageListInput{
Name: config.DestImageList,
Description: "Packer-built image list",
}
imList, err = imageListClient.CreateImageList(&ilInput)
if err != nil {
err = fmt.Errorf("Problem creating image list: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Image list %s created!", imList.URI))
}
// Now create and image list entry for the image into that list.
snap := state.Get("snapshot").(*compute.Snapshot)
version := len(imList.Entries) + 1
entriesClient := client.ImageListEntries()
entriesInput := compute.CreateImageListEntryInput{
Name: config.DestImageList,
MachineImages: []string{fmt.Sprintf("/Compute-%s/%s/%s",
config.IdentityDomain, config.Username, snap.MachineImage)},
Version: version,
}
entryInfo, err := entriesClient.CreateImageListEntry(&entriesInput)
if err != nil {
err = fmt.Errorf("Problem creating an image list entry: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("image_list_entry", entryInfo)
ui.Message(fmt.Sprintf("created image list entry %s", entryInfo.Name))
machineImagesClient := client.MachineImages()
getImagesInput := compute.GetMachineImageInput{
Name: config.ImageName,
}
// Update image list default to use latest version
updateInput := compute.UpdateImageListInput{
Default: version,
Description: config.DestImageListDescription,
Name: config.DestImageList,
}
_, err = imageListClient.UpdateImageList(&updateInput)
if err != nil {
err = fmt.Errorf("Problem updating default image list version: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
// Grab info about the machine image to return with the artifact
imInfo, err := machineImagesClient.GetMachineImage(&getImagesInput)
if err != nil {
err = fmt.Errorf("Problem getting machine image info: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("machine_image_file", imInfo.File)
state.Put("machine_image_name", imInfo.Name)
state.Put("image_list_version", version)
return multistep.ActionContinue
}
func (s *stepListImages) Cleanup(state multistep.StateBag) {
// Nothing to do
return
}

View File

@ -0,0 +1,95 @@
package classic
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
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)
secListClient := client.SecurityLists()
secListInput := compute.CreateSecurityListInput{
Description: "Packer-generated security list to give packer ssh access",
Name: 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)
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)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
}
state.Put("security_rule_name", secRuleName)
state.Put("security_list", secListName)
return multistep.ActionContinue
}
func (s *stepSecurity) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*compute.ComputeClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary rules and lists...")
// delete security rules that Packer generated
secRuleName := state.Get("security_rule_name").(string)
secRulesClient := client.SecRules()
ruleInput := compute.DeleteSecRuleInput{Name: secRuleName}
err := secRulesClient.DeleteSecRule(&ruleInput)
if err != nil {
ui.Say(fmt.Sprintf("Error deleting the packer-generated security rule %s; "+
"please delete manually. (error: %s)", secRuleName, err.Error()))
}
// delete security list that Packer generated
secListName := state.Get("security_list").(string)
secListClient := client.SecurityLists()
input := compute.DeleteSecurityListInput{Name: 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()))
}
}

View File

@ -0,0 +1,63 @@
package classic
import (
"context"
"fmt"
"github.com/hashicorp/go-oracle-terraform/compute"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepSnapshot struct{}
func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// get variables from state
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating Snapshot...")
config := state.Get("config").(*Config)
client := state.Get("client").(*compute.ComputeClient)
instanceID := state.Get("instance_id").(string)
// get instances client
snapshotClient := client.Snapshots()
// Instances Input
snapshotInput := &compute.CreateSnapshotInput{
Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID),
MachineImage: config.ImageName,
}
snap, err := snapshotClient.CreateSnapshot(snapshotInput)
if err != nil {
err = fmt.Errorf("Problem creating snapshot: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("snapshot", snap)
ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name))
return multistep.ActionContinue
}
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
// Delete the snapshot
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting Snapshot...")
client := state.Get("client").(*compute.ComputeClient)
snap := state.Get("snapshot").(*compute.Snapshot)
snapClient := client.Snapshots()
snapInput := compute.DeleteSnapshotInput{
Snapshot: snap.Name,
MachineImage: snap.MachineImage,
}
err := snapClient.DeleteSnapshotResourceOnly(&snapInput)
if err != nil {
err = fmt.Errorf("Problem deleting snapshot: %s", err)
ui.Error(err.Error())
state.Put("error", err)
}
return
}

View File

@ -1,4 +1,4 @@
package oci
package common
import (
"fmt"
@ -8,7 +8,7 @@ import (
"golang.org/x/crypto/ssh"
)
func commHost(state multistep.StateBag) (string, error) {
func CommHost(state multistep.StateBag) (string, error) {
ipAddress := state.Get("instance_ip").(string)
return ipAddress, nil
}

View File

@ -1,4 +1,4 @@
package oci
package common
import (
"context"
@ -16,13 +16,13 @@ import (
"golang.org/x/crypto/ssh"
)
type stepKeyPair struct {
type StepKeyPair struct {
Debug bool
DebugKeyPath string
PrivateKeyFile string
}
func (s *stepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.PrivateKeyFile != "" {
@ -112,6 +112,6 @@ func (s *stepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
return multistep.ActionContinue
}
func (s *stepKeyPair) Cleanup(state multistep.StateBag) {
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// Nothing to do
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"log"
ocommon "github.com/hashicorp/packer/builder/oracle/common"
client "github.com/hashicorp/packer/builder/oracle/oci/client"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
@ -50,7 +51,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Build the steps
steps := []multistep.Step{
&stepKeyPair{
&ocommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("oci_%s.pem", b.config.PackerBuildName),
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
@ -59,8 +60,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&stepInstanceInfo{},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: commHost,
SSHConfig: SSHConfig(
Host: ocommon.CommHost,
SSHConfig: ocommon.SSHConfig(
b.config.Comm.SSHUsername,
b.config.Comm.SSHPassword),
},

View File

@ -32,6 +32,7 @@ import (
nullbuilder "github.com/hashicorp/packer/builder/null"
oneandonebuilder "github.com/hashicorp/packer/builder/oneandone"
openstackbuilder "github.com/hashicorp/packer/builder/openstack"
oracleclassicbuilder "github.com/hashicorp/packer/builder/oracle/classic"
oracleocibuilder "github.com/hashicorp/packer/builder/oracle/oci"
parallelsisobuilder "github.com/hashicorp/packer/builder/parallels/iso"
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
@ -99,6 +100,7 @@ var Builders = map[string]packer.Builder{
"null": new(nullbuilder.Builder),
"oneandone": new(oneandonebuilder.Builder),
"openstack": new(openstackbuilder.Builder),
"oracle-classic": new(oracleclassicbuilder.Builder),
"oracle-oci": new(oracleocibuilder.Builder),
"parallels-iso": new(parallelsisobuilder.Builder),
"parallels-pvm": new(parallelspvmbuilder.Builder),

View File

@ -0,0 +1,134 @@
## 0.6.6 (January 11, 2018)
* compute: Create and delete machine images [GH-101]
## 0.6.5 (January 8, 2018)
* compute: Orchestration failures should explicitly tell the user why it failed [GH-100]
## 0.6.4 (Decemeber 20, 2017)
* compute: Added suspend functionality to orchestrated instances [GH-99]
## 0.6.3 (December 13, 2017)
* storage: Added remove header option to storage objects and containers [GH-96]
## 0.6.2 (November 28, 2017)
* client: Added a UserAgent to the Client [GH-98]
## 0.6.1 (Novemeber 26, 2017)
* compute: Added is_default_gateway to network attributes for instances [GH-97]
## 0.6.0 (November 10, 2017)
* compute: Added is_default_gateway to network attributes for instances [GH-90]
* compute: Added the orchestration resource, specifically for instance creation [GH-91]
## 0.5.1 (October 5, 2017)
* java: Fixed subscription_type field
## 0.5.0 (October 5, 2017)
* java: Added more fields to java service instance [GH-89]
## 0.4.0 (September 14, 2017)
* database: Add utility resources [GH-87]
* compute: Increase storage volume snapshot create timeout [GH-88]
## 0.3.4 (August 16, 2017)
* storage_volumes: Actually capture errors during a storage volume create ([#86](https://github.com/hashicorp/go-oracle-terraform/issues/86))
## 0.3.3 (August 10, 2017)
* Add `ExposedHeaders` to storage containers ([#85](https://github.com/hashicorp/go-oracle-terraform/issues/85))
* Fixed `AllowedOrigins` in storage containers ([#85](https://github.com/hashicorp/go-oracle-terraform/issues/85))
## 0.3.2 (August 7, 2017)
* Add `id` for storage objects ([#84](https://github.com/hashicorp/go-oracle-terraform/issues/84))
## 0.3.1 (August 7, 2017)
* Update tests for Database parameter changes ([#83](https://github.com/hashicorp/go-oracle-terraform/issues/83))
## 0.3.0 (August 7, 2017)
* Add JaaS Service Instances ([#82](https://github.com/hashicorp/go-oracle-terraform/issues/82))
* Add storage objects ([#81](https://github.com/hashicorp/go-oracle-terraform/issues/81))
## 0.2.0 (July 27, 2017)
* service_instance: Switches yes/no strings to bool in input struct and then converts back to strings for ease of use on user end ([#80](https://github.com/hashicorp/go-oracle-terraform/issues/80))
## 0.1.9 (July 20, 2017)
* service_instance: Update delete retry count ([#79](https://github.com/hashicorp/go-oracle-terraform/issues/79))
* service_instance: Add additional fields ([#79](https://github.com/hashicorp/go-oracle-terraform/issues/79))
## 0.1.8 (July 19, 2017)
* storage_volumes: Add SSD support ([#78](https://github.com/hashicorp/go-oracle-terraform/issues/78))
## 0.1.7 (July 19, 2017)
* database: Adds the Oracle Database Cloud to the available sdks. ([#77](https://github.com/hashicorp/go-oracle-terraform/issues/77))
* database: Adds Service Instances to the database sdk ([#77](https://github.com/hashicorp/go-oracle-terraform/issues/77))
## 0.1.6 (July 18, 2017)
* opc: Add timeouts to instance and storage inputs ([#75](https://github.com/hashicorp/go-oracle-terraform/issues/75))
## 0.1.5 (July 5, 2017)
* storage: User must pass in Storage URL to CRUD resources ([#74](https://github.com/hashicorp/go-oracle-terraform/issues/74))
## 0.1.4 (June 30, 2017)
* opc: Fix infinite loop around auth token exceeding it's 25 minute duration. ([#73](https://github.com/hashicorp/go-oracle-terraform/issues/73))
## 0.1.3 (June 30, 2017)
* opc: Add additional logs instance logs ([#72](https://github.com/hashicorp/go-oracle-terraform/issues/72))
* opc: Increase instance creation and deletion timeout ([#72](https://github.com/hashicorp/go-oracle-terraform/issues/72))
## 0.1.2 (June 30, 2017)
FEATURES:
* opc: Add image snapshots ([#67](https://github.com/hashicorp/go-oracle-terraform/issues/67))
* storage: Storage containers have been added ([#70](https://github.com/hashicorp/go-oracle-terraform/issues/70))
IMPROVEMENTS:
* opc: Refactored client to be generic for multiple Oracle api endpoints ([#68](https://github.com/hashicorp/go-oracle-terraform/issues/68))
* opc: Instance creation retries when an instance enters a deleted state ([#71](https://github.com/hashicorp/go-oracle-terraform/issues/71))
## 0.1.1 (May 31, 2017)
IMPROVEMENTS:
* opc: Add max_retries capabilities ([#66](https://github.com/hashicorp/go-oracle-terraform/issues/66))
## 0.1.0 (May 25, 2017)
BACKWARDS INCOMPATIBILITIES / NOTES:
* Initial Release of OPC SDK

View File

@ -0,0 +1,41 @@
TEST?=$$(go list ./... |grep -v 'vendor')
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
test: fmtcheck errcheck
go test -i $(TEST) || exit 1
echo $(TEST) | \
xargs -t -n4 go test $(TESTARGS) -timeout=60m -parallel=4
testacc: fmtcheck
ORACLE_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout 120m
testrace: fmtcheck
ORACLE_ACC= go test -race $(TEST) $(TESTARGS)
cover:
@go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \
go get -u golang.org/x/tools/cmd/cover; \
fi
go test $(TEST) -coverprofile=coverage.out
go tool cover -html=coverage.out
rm coverage.out
vet:
@echo "go vet ."
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for review."; \
exit 1; \
fi
fmt:
gofmt -w $(GOFMT_FILES)
fmtcheck:
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
errcheck:
@sh -c "'$(CURDIR)/scripts/errcheck.sh'"
.PHONY: tools build test testacc testrace cover vet fmt fmtcheck errcheck

373
vendor/github.com/hashicorp/go-oracle-terraform/LICENSE generated vendored Normal file
View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -0,0 +1,108 @@
Oracle SDK for Terraform
===========================================
**Note:** This SDK is _not_ meant to be a comprehensive SDK for Oracle Cloud. This is meant to be used solely with Terraform.
OPC Config
----------
To create the Oracle clients, a populated configuration struct is required.
The config struct holds the following fields:
* `Username` - (`*string`) The Username used to authenticate to Oracle Public Cloud.
* `Password` - (`*string`) The Password used to authenticate to Oracle Public Cloud.
* `IdentityDomain` - (`*string`) The identity domain for Oracle Public Cloud.
* `APIEndpoint` - (`*url.URL`) The API Endpoint provided by Oracle Public Cloud.
* `LogLevel` - (`LogLevelType`) Defaults to `opc.LogOff`, can be either `opc.LogOff` or `opc.LogDebug`.
* `Logger` - (`Logger`) Must satisfy the generic `Logger` interface. Defaults to `ioutil.Discard` for the `LogOff` loglevel, and `os.Stderr` for the `LogDebug` loglevel.
* `HTTPClient` - (`*http.Client`) Defaults to generic HTTP Client if unspecified.
Oracle Compute Client
----------------------
The Oracle Compute Client requires an OPC Config object to be populated in order to create the client.
Full example to create an OPC Compute instance:
```go
package main
import (
"fmt"
"net/url"
"github.com/hashicorp/go-oracle-terraform/opc"
"github.com/hashicorp/go-oracle-terraform/compute"
)
func main() {
apiEndpoint, err := url.Parse("myAPIEndpoint")
if err != nil {
fmt.Errorf("Error parsing API Endpoint: %s", err)
}
config := &opc.Config{
Username: opc.String("myusername"),
Password: opc.String("mypassword"),
IdentityDomain: opc.String("myidentitydomain"),
APIEndpoint: apiEndpoint,
LogLevel: opc.LogDebug,
// Logger: # Leave blank to use the default logger, or provide your own
// HTTPClient: # Leave blank to use default HTTP Client, or provider your own
}
// Create the Compute Client
client, err := compute.NewComputeClient(config)
if err != nil {
fmt.Errorf("Error creating OPC Compute Client: %s", err)
}
// Create instances client
instanceClient := client.Instances()
// Instances Input
input := &compute.CreateInstanceInput{
Name: "test-instance",
Label: "test",
Shape: "oc3",
ImageList: "/oracle/public/oel_6.7_apaas_16.4.5_1610211300",
Storage: nil,
BootOrder: nil,
SSHKeys: []string{},
Attributes: map[string]interface{}{},
}
// Create the instance
instance, err := instanceClient.CreateInstance(input)
if err != nil {
fmt.Errorf("Error creating instance: %s", err)
}
fmt.Printf("Instance Created: %#v", instance)
}
```
Please refer to inline documentation for each resource that the compute client provides.
Running the SDK Integration Tests
-----------------------------
To authenticate with the Oracle Compute Cloud the following credentails must be set in the following environment variables:
- `OPC_ENDPOINT` - Endpoint provided by Oracle Public Cloud (e.g. https://api-z13.compute.em2.oraclecloud.com/\)
- `OPC_USERNAME` - Username for Oracle Public Cloud
- `OPC_PASSWORD` - Password for Oracle Public Cloud
- `OPC_IDENTITY_DOMAIN` - Identity domain for Oracle Public Cloud
The Integration tests can be ran with the following command:
```sh
$ make testacc
```
Isolating a single SDK package can be done via the `TEST` environment variable
```sh
$ make testacc TEST=./compute
```
Isolating a single test within a package can be done via the `TESTARGS` environment variable
```sh
$ make testacc TEST=./compute TESTARGS='-run=TestAccIPAssociationLifeCycle'
```
Tests are ran with logs being sent to `ioutil.Discard` by default.
Display debug logs inside of tests by setting the `ORACLE_LOG` environment variable to any value.

View File

@ -0,0 +1,245 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"runtime"
"time"
"github.com/hashicorp/go-oracle-terraform/opc"
)
const DEFAULT_MAX_RETRIES = 1
const USER_AGENT_HEADER = "User-Agent"
var (
// defaultUserAgent builds a string containing the Go version, system archityecture and OS,
// and the go-autorest version.
defaultUserAgent = fmt.Sprintf("Go/%s (%s-%s) go-oracle-terraform/%s",
runtime.Version(),
runtime.GOARCH,
runtime.GOOS,
Version(),
)
)
// Client represents an authenticated compute client, with compute credentials and an api client.
type Client struct {
IdentityDomain *string
UserName *string
Password *string
APIEndpoint *url.URL
httpClient *http.Client
MaxRetries *int
UserAgent *string
logger opc.Logger
loglevel opc.LogLevelType
}
func NewClient(c *opc.Config) (*Client, error) {
// First create a client
client := &Client{
IdentityDomain: c.IdentityDomain,
UserName: c.Username,
Password: c.Password,
APIEndpoint: c.APIEndpoint,
UserAgent: &defaultUserAgent,
httpClient: c.HTTPClient,
MaxRetries: c.MaxRetries,
loglevel: c.LogLevel,
}
if c.UserAgent != nil {
client.UserAgent = c.UserAgent
}
// Setup logger; defaults to stdout
if c.Logger == nil {
client.logger = opc.NewDefaultLogger()
} else {
client.logger = c.Logger
}
// If LogLevel was not set to something different,
// double check for env var
if c.LogLevel == 0 {
client.loglevel = opc.LogLevel()
}
// Default max retries if unset
if c.MaxRetries == nil {
client.MaxRetries = opc.Int(DEFAULT_MAX_RETRIES)
}
// Protect against any nil http client
if c.HTTPClient == nil {
return nil, fmt.Errorf("No HTTP client specified in config")
}
return client, nil
}
// Marshalls the request body and returns the resulting byte slice
// This is split out of the BuildRequestBody method so as to allow
// the developer to print a debug string of the request body if they
// should so choose.
func (c *Client) MarshallRequestBody(body interface{}) ([]byte, error) {
// Verify interface isnt' nil
if body == nil {
return nil, nil
}
return json.Marshal(body)
}
// Builds an HTTP Request that accepts a pre-marshaled body parameter as a raw byte array
// Returns the raw HTTP Request and any error occured
func (c *Client) BuildRequestBody(method, path string, body []byte) (*http.Request, error) {
// Parse URL Path
urlPath, err := url.Parse(path)
if err != nil {
return nil, err
}
var requestBody io.ReadSeeker
if len(body) != 0 {
requestBody = bytes.NewReader(body)
}
// Create Request
req, err := http.NewRequest(method, c.formatURL(urlPath), requestBody)
if err != nil {
return nil, err
}
// Adding UserAgent Header
req.Header.Add(USER_AGENT_HEADER, *c.UserAgent)
return req, nil
}
// Build a new HTTP request that doesn't marshall the request body
func (c *Client) BuildNonJSONRequest(method, path string, body io.ReadSeeker) (*http.Request, error) {
// Parse URL Path
urlPath, err := url.Parse(path)
if err != nil {
return nil, err
}
// Create request
req, err := http.NewRequest(method, c.formatURL(urlPath), body)
if err != nil {
return nil, err
}
// Adding UserAgentHeader
req.Header.Add(USER_AGENT_HEADER, *c.UserAgent)
return req, nil
}
// This method executes the http.Request from the BuildRequest method.
// It is split up to add additional authentication that is Oracle API dependent.
func (c *Client) ExecuteRequest(req *http.Request) (*http.Response, error) {
// Execute request with supplied client
resp, err := c.retryRequest(req)
if err != nil {
return resp, err
}
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
return resp, nil
}
oracleErr := &opc.OracleError{
StatusCode: resp.StatusCode,
}
// Even though the returned body will be in json form, it's undocumented what
// fields are actually returned. Once we get documentation of the actual
// error fields that are possible to be returned we can have stricter error types.
if resp.Body != nil {
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
oracleErr.Message = buf.String()
}
// Should return the response object regardless of error,
// some resources need to verify and check status code on errors to
// determine if an error actually occurs or not.
return resp, oracleErr
}
// Allow retrying the request until it either returns no error,
// or we exceed the number of max retries
func (c *Client) retryRequest(req *http.Request) (*http.Response, error) {
// Double check maxRetries is not nil
var retries int
if c.MaxRetries == nil {
retries = DEFAULT_MAX_RETRIES
} else {
retries = *c.MaxRetries
}
var statusCode int
var errMessage string
for i := 0; i < retries; i++ {
resp, err := c.httpClient.Do(req)
if err != nil {
return resp, err
}
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
return resp, nil
}
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
errMessage = buf.String()
statusCode = resp.StatusCode
c.DebugLogString(fmt.Sprintf("Encountered HTTP (%d) Error: %s", statusCode, errMessage))
c.DebugLogString(fmt.Sprintf("%d/%d retries left", i+1, retries))
}
oracleErr := &opc.OracleError{
StatusCode: statusCode,
Message: errMessage,
}
// We ran out of retries to make, return the error and response
return nil, oracleErr
}
func (c *Client) formatURL(path *url.URL) string {
return c.APIEndpoint.ResolveReference(path).String()
}
// Retry function
func (c *Client) WaitFor(description string, timeout time.Duration, test func() (bool, error)) error {
tick := time.Tick(1 * time.Second)
timeoutSeconds := int(timeout.Seconds())
for i := 0; i < timeoutSeconds; i++ {
select {
case <-tick:
completed, err := test()
c.DebugLogString(fmt.Sprintf("Waiting for %s (%d/%ds)", description, i, timeoutSeconds))
if err != nil || completed {
return err
}
}
}
return fmt.Errorf("Timeout waiting for %s", description)
}
// Used to determine if the checked resource was found or not.
func WasNotFoundError(e error) bool {
err, ok := e.(*opc.OracleError)
if ok {
return err.StatusCode == 404
}
return false
}

View File

@ -0,0 +1,28 @@
package client
import (
"bytes"
"fmt"
"net/http"
"github.com/hashicorp/go-oracle-terraform/opc"
)
// Log a string if debug logs are on
func (c *Client) DebugLogString(str string) {
if c.loglevel != opc.LogDebug {
return
}
c.logger.Log(str)
}
func (c *Client) DebugLogReq(req *http.Request) {
// Don't need to log this if not debugging
if c.loglevel != opc.LogDebug {
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(req.Body)
c.logger.Log(fmt.Sprintf("DEBUG: HTTP %s Req %s: %s",
req.Method, req.URL.String(), buf.String()))
}

View File

@ -0,0 +1,35 @@
package client
import (
"bytes"
"fmt"
"strings"
"sync"
)
const (
major = 0
minor = 6
patch = 2
tag = ""
)
var once sync.Once
var version string
// Version returns the semantic version (see http://semver.org).
func Version() string {
once.Do(func() {
semver := fmt.Sprintf("%d.%d.%d", major, minor, patch)
verBuilder := bytes.NewBufferString(semver)
if tag != "" && tag != "-" {
updated := strings.TrimPrefix(tag, "-")
_, err := verBuilder.WriteString("-" + updated)
if err == nil {
verBuilder = bytes.NewBufferString(semver)
}
}
version = verBuilder.String()
})
return version
}

View File

@ -0,0 +1,138 @@
package compute
// ACLsClient is a client for the ACLs functions of the Compute API.
type ACLsClient struct {
ResourceClient
}
const (
ACLDescription = "acl"
ACLContainerPath = "/network/v1/acl/"
ACLResourcePath = "/network/v1/acl"
)
// ACLs obtains a ACLsClient which can be used to access to the
// ACLs functions of the Compute API
func (c *ComputeClient) ACLs() *ACLsClient {
return &ACLsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: ACLDescription,
ContainerPath: ACLContainerPath,
ResourceRootPath: ACLResourcePath,
}}
}
// ACLInfo describes an existing ACL.
type ACLInfo struct {
// Description of the ACL
Description string `json:"description"`
// Indicates whether the ACL is enabled
Enabled bool `json:"enabledFlag"`
// The name of the ACL
Name string `json:"name"`
// Tags associated with the ACL
Tags []string `json:"tags"`
// Uniform Resource Identifier for the ACL
URI string `json:"uri"`
}
// CreateACLInput defines a ACL to be created.
type CreateACLInput struct {
// Description of the ACL
// Optional
Description string `json:"description"`
// Enables or disables the ACL. Set to true by default.
//Set this to false to disable the ACL.
// Optional
Enabled bool `json:"enabledFlag"`
// The name of the ACL to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Strings that you can use to tag the ACL.
// Optional
Tags []string `json:"tags"`
}
// CreateACL creates a new ACL.
func (c *ACLsClient) CreateACL(createInput *CreateACLInput) (*ACLInfo, error) {
createInput.Name = c.getQualifiedName(createInput.Name)
var aclInfo ACLInfo
if err := c.createResource(createInput, &aclInfo); err != nil {
return nil, err
}
return c.success(&aclInfo)
}
// GetACLInput describes the ACL to get
type GetACLInput struct {
// The name of the ACL to query for
// Required
Name string `json:"name"`
}
// GetACL retrieves the ACL with the given name.
func (c *ACLsClient) GetACL(getInput *GetACLInput) (*ACLInfo, error) {
var aclInfo ACLInfo
if err := c.getResource(getInput.Name, &aclInfo); err != nil {
return nil, err
}
return c.success(&aclInfo)
}
// UpdateACLInput describes a secruity rule to update
type UpdateACLInput struct {
// Description of the ACL
// Optional
Description string `json:"description"`
// Enables or disables the ACL. Set to true by default.
//Set this to false to disable the ACL.
// Optional
Enabled bool `json:"enabledFlag"`
// The name of the ACL to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Strings that you can use to tag the ACL.
// Optional
Tags []string `json:"tags"`
}
// UpdateACL modifies the properties of the ACL with the given name.
func (c *ACLsClient) UpdateACL(updateInput *UpdateACLInput) (*ACLInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
var aclInfo ACLInfo
if err := c.updateResource(updateInput.Name, updateInput, &aclInfo); err != nil {
return nil, err
}
return c.success(&aclInfo)
}
// DeleteACLInput describes the ACL to delete
type DeleteACLInput struct {
// The name of the ACL to delete.
// Required
Name string `json:"name"`
}
// DeleteACL deletes the ACL with the given name.
func (c *ACLsClient) DeleteACL(deleteInput *DeleteACLInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *ACLsClient) success(aclInfo *ACLInfo) (*ACLInfo, error) {
aclInfo.Name = c.getUnqualifiedName(aclInfo.Name)
return aclInfo, nil
}

View File

@ -0,0 +1,34 @@
package compute
import (
"fmt"
"time"
)
// AuthenticationReq represents the body of an authentication request.
type AuthenticationReq struct {
User string `json:"user"`
Password string `json:"password"`
}
// Get a new auth cookie for the compute client
func (c *ComputeClient) getAuthenticationCookie() error {
req := AuthenticationReq{
User: c.getUserName(),
Password: *c.client.Password,
}
rsp, err := c.executeRequest("POST", "/authenticate/", req)
if err != nil {
return err
}
if len(rsp.Cookies()) == 0 {
return fmt.Errorf("No authentication cookie found in response %#v", rsp)
}
c.client.DebugLogString("Successfully authenticated to OPC")
c.authCookie = rsp.Cookies()[0]
c.cookieIssued = time.Now()
return nil
}

View File

@ -0,0 +1,167 @@
package compute
import (
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/hashicorp/go-oracle-terraform/client"
"github.com/hashicorp/go-oracle-terraform/opc"
)
const CMP_ACME = "/Compute-%s"
const CMP_USERNAME = "/Compute-%s/%s"
const CMP_QUALIFIED_NAME = "%s/%s"
// Client represents an authenticated compute client, with compute credentials and an api client.
type ComputeClient struct {
client *client.Client
authCookie *http.Cookie
cookieIssued time.Time
}
func NewComputeClient(c *opc.Config) (*ComputeClient, error) {
computeClient := &ComputeClient{}
client, err := client.NewClient(c)
if err != nil {
return nil, err
}
computeClient.client = client
if err := computeClient.getAuthenticationCookie(); err != nil {
return nil, err
}
return computeClient, nil
}
func (c *ComputeClient) executeRequest(method, path string, body interface{}) (*http.Response, error) {
reqBody, err := c.client.MarshallRequestBody(body)
if err != nil {
return nil, err
}
req, err := c.client.BuildRequestBody(method, path, reqBody)
if err != nil {
return nil, err
}
debugReqString := fmt.Sprintf("HTTP %s Req (%s)", method, path)
if body != nil {
req.Header.Set("Content-Type", "application/oracle-compute-v3+json")
// Don't leak credentials in STDERR
if path != "/authenticate/" {
debugReqString = fmt.Sprintf("%s:\n %+v", debugReqString, string(reqBody))
}
}
// Log the request before the authentication cookie, so as not to leak credentials
c.client.DebugLogString(debugReqString)
// If we have an authentication cookie, let's authenticate, refreshing cookie if need be
if c.authCookie != nil {
if time.Since(c.cookieIssued).Minutes() > 25 {
c.authCookie = nil
if err := c.getAuthenticationCookie(); err != nil {
return nil, err
}
}
req.AddCookie(c.authCookie)
}
resp, err := c.client.ExecuteRequest(req)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *ComputeClient) getACME() string {
return fmt.Sprintf(CMP_ACME, *c.client.IdentityDomain)
}
func (c *ComputeClient) getUserName() string {
return fmt.Sprintf(CMP_USERNAME, *c.client.IdentityDomain, *c.client.UserName)
}
func (c *ComputeClient) getQualifiedACMEName(name string) string {
if name == "" {
return ""
}
if strings.HasPrefix(name, "/Compute-") && len(strings.Split(name, "/")) == 1 {
return name
}
return fmt.Sprintf(CMP_QUALIFIED_NAME, c.getACME(), name)
}
// From compute_client
// GetObjectName returns the fully-qualified name of an OPC object, e.g. /identity-domain/user@email/{name}
func (c *ComputeClient) getQualifiedName(name string) string {
if name == "" {
return ""
}
if strings.HasPrefix(name, "/oracle") || strings.HasPrefix(name, "/Compute-") {
return name
}
return fmt.Sprintf(CMP_QUALIFIED_NAME, c.getUserName(), name)
}
func (c *ComputeClient) getObjectPath(root, name string) string {
return fmt.Sprintf("%s%s", root, c.getQualifiedName(name))
}
// GetUnqualifiedName returns the unqualified name of an OPC object, e.g. the {name} part of /identity-domain/user@email/{name}
func (c *ComputeClient) getUnqualifiedName(name string) string {
if name == "" {
return name
}
if strings.HasPrefix(name, "/oracle") {
return name
}
if !strings.Contains(name, "/") {
return name
}
nameParts := strings.Split(name, "/")
return strings.Join(nameParts[3:], "/")
}
func (c *ComputeClient) unqualify(names ...*string) {
for _, name := range names {
*name = c.getUnqualifiedName(*name)
}
}
func (c *ComputeClient) unqualifyUrl(url *string) {
var validID = regexp.MustCompile(`(\/(Compute[^\/\s]+))(\/[^\/\s]+)(\/[^\/\s]+)`)
name := validID.FindString(*url)
*url = c.getUnqualifiedName(name)
}
func (c *ComputeClient) getQualifiedList(list []string) []string {
for i, name := range list {
list[i] = c.getQualifiedName(name)
}
return list
}
func (c *ComputeClient) getUnqualifiedList(list []string) []string {
for i, name := range list {
list[i] = c.getUnqualifiedName(name)
}
return list
}
func (c *ComputeClient) getQualifiedListName(name string) string {
nameParts := strings.Split(name, ":")
listType := nameParts[0]
listName := nameParts[1]
return fmt.Sprintf("%s:%s", listType, c.getQualifiedName(listName))
}
func (c *ComputeClient) unqualifyListName(qualifiedName string) string {
nameParts := strings.Split(qualifiedName, ":")
listType := nameParts[0]
listName := nameParts[1]
return fmt.Sprintf("%s:%s", listType, c.getUnqualifiedName(listName))
}

View File

@ -0,0 +1,113 @@
package compute
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/mitchellh/mapstructure"
)
// ResourceClient is an AuthenticatedClient with some additional information about the resources to be addressed.
type ResourceClient struct {
*ComputeClient
ResourceDescription string
ContainerPath string
ResourceRootPath string
}
func (c *ResourceClient) createResource(requestBody interface{}, responseBody interface{}) error {
resp, err := c.executeRequest("POST", c.ContainerPath, requestBody)
if err != nil {
return err
}
return c.unmarshalResponseBody(resp, responseBody)
}
func (c *ResourceClient) updateResource(name string, requestBody interface{}, responseBody interface{}) error {
resp, err := c.executeRequest("PUT", c.getObjectPath(c.ResourceRootPath, name), requestBody)
if err != nil {
return err
}
return c.unmarshalResponseBody(resp, responseBody)
}
func (c *ResourceClient) getResource(name string, responseBody interface{}) error {
var objectPath string
if name != "" {
objectPath = c.getObjectPath(c.ResourceRootPath, name)
} else {
objectPath = c.ResourceRootPath
}
resp, err := c.executeRequest("GET", objectPath, nil)
if err != nil {
return err
}
return c.unmarshalResponseBody(resp, responseBody)
}
func (c *ResourceClient) deleteResource(name string) error {
var objectPath string
if name != "" {
objectPath = c.getObjectPath(c.ResourceRootPath, name)
} else {
objectPath = c.ResourceRootPath
}
_, err := c.executeRequest("DELETE", objectPath, nil)
if err != nil {
return err
}
// No errors and no response body to write
return nil
}
func (c *ResourceClient) deleteOrchestration(name string) error {
var objectPath string
if name != "" {
objectPath = c.getObjectPath(c.ResourceRootPath, name)
} else {
objectPath = c.ResourceRootPath
}
// Set terminate to true as we always want to delete an orchestration
objectPath = fmt.Sprintf("%s?terminate=True", objectPath)
_, err := c.executeRequest("DELETE", objectPath, nil)
if err != nil {
return err
}
// No errors and no response body to write
return nil
}
func (c *ResourceClient) unmarshalResponseBody(resp *http.Response, iface interface{}) error {
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
c.client.DebugLogString(fmt.Sprintf("HTTP Resp (%d): %s", resp.StatusCode, buf.String()))
// JSON decode response into interface
var tmp interface{}
dcd := json.NewDecoder(buf)
if err := dcd.Decode(&tmp); err != nil {
return err
}
// Use mapstructure to weakly decode into the resulting interface
msdcd, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: iface,
TagName: "json",
})
if err != nil {
return err
}
if err := msdcd.Decode(tmp); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,154 @@
package compute
const (
ImageListDescription = "Image List"
ImageListContainerPath = "/imagelist/"
ImageListResourcePath = "/imagelist"
)
// ImageListClient is a client for the Image List functions of the Compute API.
type ImageListClient struct {
ResourceClient
}
// ImageList obtains an ImageListClient which can be used to access to the
// Image List functions of the Compute API
func (c *ComputeClient) ImageList() *ImageListClient {
return &ImageListClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: ImageListDescription,
ContainerPath: ImageListContainerPath,
ResourceRootPath: ImageListResourcePath,
}}
}
type ImageListEntry struct {
// User-defined parameters, in JSON format, that can be passed to an instance of this machine image when it is launched.
Attributes map[string]interface{} `json:"attributes"`
// Name of the Image List.
ImageList string `json:"imagelist"`
// A list of machine images.
MachineImages []string `json:"machineimages"`
// Uniform Resource Identifier.
URI string `json:"uri"`
// Version number of these Machine Images in the Image List.
Version int `json:"version"`
}
// ImageList describes an existing Image List.
type ImageList struct {
// The image list entry to be used, by default, when launching instances using this image list
Default int `json:"default"`
// A description of this image list.
Description string `json:"description"`
// Each machine image in an image list is identified by an image list entry.
Entries []ImageListEntry `json:"entries"`
// The name of the Image List
Name string `json:"name"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateImageListInput defines an Image List to be created.
type CreateImageListInput struct {
// The image list entry to be used, by default, when launching instances using this image list.
// If you don't specify this value, it is set to 1.
// Optional
Default int `json:"default"`
// A description of this image list.
// Required
Description string `json:"description"`
// The name of the Image List
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
}
// CreateImageList creates a new Image List with the given name, key and enabled flag.
func (c *ImageListClient) CreateImageList(createInput *CreateImageListInput) (*ImageList, error) {
var imageList ImageList
createInput.Name = c.getQualifiedName(createInput.Name)
if err := c.createResource(&createInput, &imageList); err != nil {
return nil, err
}
return c.success(&imageList)
}
// DeleteKeyInput describes the image list to delete
type DeleteImageListInput struct {
// The name of the Image List
Name string `json:name`
}
// DeleteImageList deletes the Image List with the given name.
func (c *ImageListClient) DeleteImageList(deleteInput *DeleteImageListInput) error {
deleteInput.Name = c.getQualifiedName(deleteInput.Name)
return c.deleteResource(deleteInput.Name)
}
// GetImageListInput describes the image list to get
type GetImageListInput struct {
// The name of the Image List
Name string `json:name`
}
// GetImageList retrieves the Image List with the given name.
func (c *ImageListClient) GetImageList(getInput *GetImageListInput) (*ImageList, error) {
getInput.Name = c.getQualifiedName(getInput.Name)
var imageList ImageList
if err := c.getResource(getInput.Name, &imageList); err != nil {
return nil, err
}
return c.success(&imageList)
}
// UpdateImageListInput defines an Image List to be updated
type UpdateImageListInput struct {
// The image list entry to be used, by default, when launching instances using this image list.
// If you don't specify this value, it is set to 1.
// Optional
Default int `json:"default"`
// A description of this image list.
// Required
Description string `json:"description"`
// The name of the Image List
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
}
// UpdateImageList updates the key and enabled flag of the Image List with the given name.
func (c *ImageListClient) UpdateImageList(updateInput *UpdateImageListInput) (*ImageList, error) {
var imageList ImageList
updateInput.Name = c.getQualifiedName(updateInput.Name)
if err := c.updateResource(updateInput.Name, updateInput, &imageList); err != nil {
return nil, err
}
return c.success(&imageList)
}
func (c *ImageListClient) success(imageList *ImageList) (*ImageList, error) {
c.unqualify(&imageList.Name)
for _, v := range imageList.Entries {
v.MachineImages = c.getUnqualifiedList(v.MachineImages)
}
return imageList, nil
}

View File

@ -0,0 +1,122 @@
package compute
import "fmt"
const (
ImageListEntryDescription = "image list entry"
ImageListEntryContainerPath = "/imagelist"
ImageListEntryResourcePath = "/imagelist"
)
type ImageListEntriesClient struct {
ResourceClient
}
// ImageListEntries() returns an ImageListEntriesClient that can be used to access the
// necessary CRUD functions for Image List Entry's.
func (c *ComputeClient) ImageListEntries() *ImageListEntriesClient {
return &ImageListEntriesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: ImageListEntryDescription,
ContainerPath: ImageListEntryContainerPath,
ResourceRootPath: ImageListEntryResourcePath,
},
}
}
// ImageListEntryInfo contains the exported fields necessary to hold all the information about an
// Image List Entry
type ImageListEntryInfo struct {
// User-defined parameters, in JSON format, that can be passed to an instance of this machine
// image when it is launched. This field can be used, for example, to specify the location of
// a database server and login details. Instance metadata, including user-defined data is available
// at http://192.0.0.192/ within an instance. See Retrieving User-Defined Instance Attributes in Using
// Oracle Compute Cloud Service (IaaS).
Attributes map[string]interface{} `json:"attributes"`
// Name of the imagelist.
Name string `json:"imagelist"`
// A list of machine images.
MachineImages []string `json:"machineimages"`
// Uniform Resource Identifier for the Image List Entry
Uri string `json:"uri"`
// Version number of these machineImages in the imagelist.
Version int `json:"version"`
}
type CreateImageListEntryInput struct {
// The name of the Image List
Name string
// User-defined parameters, in JSON format, that can be passed to an instance of this machine
// image when it is launched. This field can be used, for example, to specify the location of
// a database server and login details. Instance metadata, including user-defined data is
//available at http://192.0.0.192/ within an instance. See Retrieving User-Defined Instance
//Attributes in Using Oracle Compute Cloud Service (IaaS).
// Optional
Attributes map[string]interface{} `json:"attributes"`
// A list of machine images.
// Required
MachineImages []string `json:"machineimages"`
// The unique version of the entry in the image list.
// Required
Version int `json:"version"`
}
// Create a new Image List Entry from an ImageListEntriesClient and an input struct.
// Returns a populated Info struct for the Image List Entry, and any errors
func (c *ImageListEntriesClient) CreateImageListEntry(input *CreateImageListEntryInput) (*ImageListEntryInfo, error) {
c.updateClientPaths(input.Name, -1)
var imageListEntryInfo ImageListEntryInfo
if err := c.createResource(&input, &imageListEntryInfo); err != nil {
return nil, err
}
return c.success(&imageListEntryInfo)
}
type GetImageListEntryInput struct {
// The name of the Image List
Name string
// Version number of these machineImages in the imagelist.
Version int
}
// Returns a populated ImageListEntryInfo struct from an input struct
func (c *ImageListEntriesClient) GetImageListEntry(input *GetImageListEntryInput) (*ImageListEntryInfo, error) {
c.updateClientPaths(input.Name, input.Version)
var imageListEntryInfo ImageListEntryInfo
if err := c.getResource("", &imageListEntryInfo); err != nil {
return nil, err
}
return c.success(&imageListEntryInfo)
}
type DeleteImageListEntryInput struct {
// The name of the Image List
Name string
// Version number of these machineImages in the imagelist.
Version int
}
func (c *ImageListEntriesClient) DeleteImageListEntry(input *DeleteImageListEntryInput) error {
c.updateClientPaths(input.Name, input.Version)
return c.deleteResource("")
}
func (c *ImageListEntriesClient) updateClientPaths(name string, version int) {
var containerPath, resourcePath string
name = c.getQualifiedName(name)
containerPath = ImageListEntryContainerPath + name + "/entry/"
resourcePath = ImageListEntryContainerPath + name + "/entry"
if version != -1 {
containerPath = fmt.Sprintf("%s%d", containerPath, version)
resourcePath = fmt.Sprintf("%s/%d", resourcePath, version)
}
c.ContainerPath = containerPath
c.ResourceRootPath = resourcePath
}
// Unqualifies any qualified fields in the IPNetworkInfo struct
func (c *ImageListEntriesClient) success(info *ImageListEntryInfo) (*ImageListEntryInfo, error) {
c.unqualifyUrl(&info.Uri)
return info, nil
}

View File

@ -0,0 +1,788 @@
package compute
import (
"errors"
"fmt"
"strings"
"time"
"github.com/hashicorp/go-oracle-terraform/client"
)
const WaitForInstanceReadyTimeout = time.Duration(3600 * time.Second)
const WaitForInstanceDeleteTimeout = time.Duration(3600 * time.Second)
// InstancesClient is a client for the Instance functions of the Compute API.
type InstancesClient struct {
ResourceClient
}
// Instances obtains an InstancesClient which can be used to access to the
// Instance functions of the Compute API
func (c *ComputeClient) Instances() *InstancesClient {
return &InstancesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "instance",
ContainerPath: "/launchplan/",
ResourceRootPath: "/instance",
}}
}
type InstanceState string
const (
InstanceRunning InstanceState = "running"
InstanceInitializing InstanceState = "initializing"
InstancePreparing InstanceState = "preparing"
InstanceStarting InstanceState = "starting"
InstanceStopping InstanceState = "stopping"
InstanceShutdown InstanceState = "shutdown"
InstanceQueued InstanceState = "queued"
InstanceError InstanceState = "error"
)
type InstanceDesiredState string
const (
InstanceDesiredRunning InstanceDesiredState = "running"
InstanceDesiredShutdown InstanceDesiredState = "shutdown"
)
// InstanceInfo represents the Compute API's view of the state of an instance.
type InstanceInfo struct {
// The ID for the instance. Set by the SDK based on the request - not the API.
ID string
// A dictionary of attributes to be made available to the instance.
// A value with the key "userdata" will be made available in an EC2-compatible manner.
Attributes map[string]interface{} `json:"attributes"`
// The availability domain for the instance
AvailabilityDomain string `json:"availability_domain"`
// Boot order list.
BootOrder []int `json:"boot_order"`
// The default domain to use for the hostname and DNS lookups
Domain string `json:"domain"`
// The desired state of an instance
DesiredState InstanceDesiredState `json:"desired_state"`
// Optional ImageListEntry number. Default will be used if not specified
Entry int `json:"entry"`
// The reason for the instance going to error state, if available.
ErrorReason string `json:"error_reason"`
// SSH Server Fingerprint presented by the instance
Fingerprint string `json:"fingerprint"`
// The hostname for the instance
Hostname string `json:"hostname"`
// The format of the image
ImageFormat string `json:"image_format"`
// Name of imagelist to be launched.
ImageList string `json:"imagelist"`
// IP address of the instance.
IPAddress string `json:"ip"`
// A label assigned by the user, specifically for defining inter-instance relationships.
Label string `json:"label"`
// Name of this instance, generated by the server.
Name string `json:"name"`
// Mapping of to network specifiers for virtual NICs to be attached to this instance.
Networking map[string]NetworkingInfo `json:"networking"`
// A list of strings specifying arbitrary tags on nodes to be matched on placement.
PlacementRequirements []string `json:"placement_requirements"`
// The OS platform for the instance.
Platform string `json:"platform"`
// The priority at which this instance will be run
Priority string `json:"priority"`
// Reference to the QuotaReservation, to be destroyed with the instance
QuotaReservation string `json:"quota_reservation"`
// Array of relationship specifications to be satisfied on this instance's placement
Relationships []string `json:"relationships"`
// Resolvers to use instead of the default resolvers
Resolvers []string `json:"resolvers"`
// Add PTR records for the hostname
ReverseDNS bool `json:"reverse_dns"`
// Type of instance, as defined on site configuration.
Shape string `json:"shape"`
// Site to run on
Site string `json:"site"`
// ID's of SSH keys that will be exposed to the instance.
SSHKeys []string `json:"sshkeys"`
// The start time of the instance
StartTime string `json:"start_time"`
// State of the instance.
State InstanceState `json:"state"`
// The Storage Attachment information.
Storage []StorageAttachment `json:"storage_attachments"`
// Array of tags associated with the instance.
Tags []string `json:"tags"`
// vCable for this instance.
VCableID string `json:"vcable_id"`
// Specify if the devices created for the instance are virtio devices. If not specified, the default
// will come from the cluster configuration file
Virtio bool `json:"virtio,omitempty"`
// IP Address and port of the VNC console for the instance
VNC string `json:"vnc"`
}
type StorageAttachment struct {
// The index number for the volume.
Index int `json:"index"`
// The three-part name (/Compute-identity_domain/user/object) of the storage attachment.
Name string `json:"name"`
// The three-part name (/Compute-identity_domain/user/object) of the storage volume attached to the instance.
StorageVolumeName string `json:"storage_volume_name"`
}
func (i *InstanceInfo) getInstanceName() string {
return fmt.Sprintf(CMP_QUALIFIED_NAME, i.Name, i.ID)
}
type CreateInstanceInput struct {
// A dictionary of user-defined attributes to be made available to the instance.
// Optional
Attributes map[string]interface{} `json:"attributes"`
// Boot order list
// Optional
BootOrder []int `json:"boot_order,omitempty"`
// The desired state of the opc instance. Can only be `running` or `shutdown`
// Omits if empty.
// Optional
DesiredState InstanceDesiredState `json:"desired_state,omitempty"`
// The host name assigned to the instance. On an Oracle Linux instance,
// this host name is displayed in response to the hostname command.
// Only relative DNS is supported. The domain name is suffixed to the host name
// that you specify. The host name must not end with a period. If you don't specify a
// host name, then a name is generated automatically.
// Optional
Hostname string `json:"hostname"`
// Name of imagelist to be launched.
// Optional
ImageList string `json:"imagelist"`
// A label assigned by the user, specifically for defining inter-instance relationships.
// Optional
Label string `json:"label"`
// Name of this instance, generated by the server.
// Optional
Name string `json:"name"`
// Networking information.
// Optional
Networking map[string]NetworkingInfo `json:"networking"`
// If set to true (default), then reverse DNS records are created.
// If set to false, no reverse DNS records are created.
// Optional
ReverseDNS bool `json:"reverse_dns,omitempty"`
// Type of instance, as defined on site configuration.
// Required
Shape string `json:"shape"`
// A list of the Storage Attachments you want to associate with the instance.
// Optional
Storage []StorageAttachmentInput `json:"storage_attachments,omitempty"`
// A list of the SSH public keys that you want to associate with the instance.
// Optional
SSHKeys []string `json:"sshkeys"`
// A list of tags to be supplied to the instance
// Optional
Tags []string `json:"tags"`
// Time to wait for an instance to be ready
Timeout time.Duration `json:"-"`
}
type StorageAttachmentInput struct {
// The index number for the volume. The allowed range is 1 to 10.
// If you want to use a storage volume as the boot disk for an instance, you must specify the index number for that volume as 1.
// The index determines the device name by which the volume is exposed to the instance.
Index int `json:"index"`
// The three-part name (/Compute-identity_domain/user/object) of the storage volume that you want to attach to the instance.
// Note that volumes attached to an instance at launch time can't be detached.
Volume string `json:"volume"`
}
const ReservationPrefix = "ipreservation"
const ReservationIPPrefix = "network/v1/ipreservation"
type NICModel string
const (
NICDefaultModel NICModel = "e1000"
)
// Struct of Networking info from a populated instance, or to be used as input to create an instance
type NetworkingInfo struct {
// The DNS name for the Shared network (Required)
// DNS A Record for an IP Network (Optional)
DNS []string `json:"dns,omitempty"`
// IP Network only.
// If you want to associate a static private IP Address,
// specify that here within the range of the supplied IPNetwork attribute.
// Optional
IPAddress string `json:"ip,omitempty"`
// IP Network only.
// The name of the IP Network you want to add the instance to.
// Required
IPNetwork string `json:"ipnetwork,omitempty"`
// IP Network only.
// Set interface as default gateway for all traffic
// Optional
IsDefaultGateway bool `json:"is_default_gateway,omitempty"`
// IP Network only.
// The hexadecimal MAC Address of the interface
// Optional
MACAddress string `json:"address,omitempty"`
// Shared Network only.
// The type of NIC used. Must be set to 'e1000'
// Required
Model NICModel `json:"model,omitempty"`
// IP Network and Shared Network
// The name servers that are sent through DHCP as option 6.
// You can specify a maximum of eight name server IP addresses per interface.
// Optional
NameServers []string `json:"name_servers,omitempty"`
// The names of an IP Reservation to associate in an IP Network (Optional)
// Indicates whether a temporary or permanent public IP Address should be assigned
// in a Shared Network (Required)
Nat []string `json:"nat,omitempty"`
// IP Network and Shared Network
// The search domains that should be sent through DHCP as option 119.
// You can enter a maximum of eight search domain zones per interface.
// Optional
SearchDomains []string `json:"search_domains,omitempty"`
// Shared Network only.
// The security lists that you want to add the instance to
// Required
SecLists []string `json:"seclists,omitempty"`
// IP Network Only
// The name of the vNIC
// Optional
Vnic string `json:"vnic,omitempty"`
// IP Network only.
// The names of the vNICSets you want to add the interface to.
// Optional
VnicSets []string `json:"vnicsets,omitempty"`
}
// LaunchPlan defines a launch plan, used to launch instances with the supplied InstanceSpec(s)
type LaunchPlanInput struct {
// Describes an array of instances which should be launched
Instances []CreateInstanceInput `json:"instances"`
// Time to wait for instance boot
Timeout time.Duration `json:"-"`
}
type LaunchPlanResponse struct {
// An array of instances which have been launched
Instances []InstanceInfo `json:"instances"`
}
// LaunchInstance creates and submits a LaunchPlan to launch a new instance.
func (c *InstancesClient) CreateInstance(input *CreateInstanceInput) (*InstanceInfo, error) {
qualifiedSSHKeys := []string{}
for _, key := range input.SSHKeys {
qualifiedSSHKeys = append(qualifiedSSHKeys, c.getQualifiedName(key))
}
input.SSHKeys = qualifiedSSHKeys
qualifiedStorageAttachments := []StorageAttachmentInput{}
for _, attachment := range input.Storage {
qualifiedStorageAttachments = append(qualifiedStorageAttachments, StorageAttachmentInput{
Index: attachment.Index,
Volume: c.getQualifiedName(attachment.Volume),
})
}
input.Storage = qualifiedStorageAttachments
input.Networking = c.qualifyNetworking(input.Networking)
input.Name = fmt.Sprintf(CMP_QUALIFIED_NAME, c.getUserName(), input.Name)
plan := LaunchPlanInput{
Instances: []CreateInstanceInput{*input},
Timeout: input.Timeout,
}
var (
instanceInfo *InstanceInfo
instanceError error
)
for i := 0; i < *c.ComputeClient.client.MaxRetries; i++ {
c.client.DebugLogString(fmt.Sprintf("(Iteration: %d of %d) Creating instance with name %s\n Plan: %+v", i, *c.ComputeClient.client.MaxRetries, input.Name, plan))
instanceInfo, instanceError = c.startInstance(input.Name, plan)
if instanceError == nil {
c.client.DebugLogString(fmt.Sprintf("(Iteration: %d of %d) Finished creating instance with name %s\n Info: %+v", i, *c.ComputeClient.client.MaxRetries, input.Name, instanceInfo))
return instanceInfo, nil
}
}
return nil, instanceError
}
func (c *InstancesClient) startInstance(name string, plan LaunchPlanInput) (*InstanceInfo, error) {
var responseBody LaunchPlanResponse
if err := c.createResource(&plan, &responseBody); err != nil {
return nil, err
}
if len(responseBody.Instances) == 0 {
return nil, fmt.Errorf("No instance information returned: %#v", responseBody)
}
// Call wait for instance ready now, as creating the instance is an eventually consistent operation
getInput := &GetInstanceInput{
Name: name,
ID: responseBody.Instances[0].ID,
}
//timeout := WaitForInstanceReadyTimeout
if plan.Timeout == 0 {
plan.Timeout = WaitForInstanceReadyTimeout
}
// Wait for instance to be ready and return the result
// Don't have to unqualify any objects, as the GetInstance method will handle that
instanceInfo, instanceError := c.WaitForInstanceRunning(getInput, plan.Timeout)
// If the instance enters an error state we need to delete the instance and retry
if instanceError != nil {
deleteInput := &DeleteInstanceInput{
Name: name,
ID: responseBody.Instances[0].ID,
}
err := c.DeleteInstance(deleteInput)
if err != nil {
return nil, fmt.Errorf("Error deleting instance %s: %s", name, err)
}
return nil, instanceError
}
return instanceInfo, nil
}
// Both of these fields are required. If they're not provided, things go wrong in
// incredibly amazing ways.
type GetInstanceInput struct {
// The Unqualified Name of this Instance
Name string
// The Unqualified ID of this Instance
ID string
}
func (g *GetInstanceInput) String() string {
return fmt.Sprintf(CMP_QUALIFIED_NAME, g.Name, g.ID)
}
// GetInstance retrieves information about an instance.
func (c *InstancesClient) GetInstance(input *GetInstanceInput) (*InstanceInfo, error) {
if input.ID == "" || input.Name == "" {
return nil, errors.New("Both instance name and ID need to be specified")
}
var responseBody InstanceInfo
if err := c.getResource(input.String(), &responseBody); err != nil {
return nil, err
}
if responseBody.Name == "" {
return nil, fmt.Errorf("Empty response body when requesting instance %s", input.Name)
}
// The returned 'Name' attribute is the fully qualified instance name + "/" + ID
// Split these out to accurately populate the fields
nID := strings.Split(c.getUnqualifiedName(responseBody.Name), "/")
responseBody.Name = nID[0]
responseBody.ID = nID[1]
c.unqualify(&responseBody.VCableID)
// Unqualify SSH Key names
sshKeyNames := []string{}
for _, sshKeyRef := range responseBody.SSHKeys {
sshKeyNames = append(sshKeyNames, c.getUnqualifiedName(sshKeyRef))
}
responseBody.SSHKeys = sshKeyNames
var networkingErr error
responseBody.Networking, networkingErr = c.unqualifyNetworking(responseBody.Networking)
if networkingErr != nil {
return nil, networkingErr
}
responseBody.Storage = c.unqualifyStorage(responseBody.Storage)
return &responseBody, nil
}
type InstancesInfo struct {
Instances []InstanceInfo `json:"result"`
}
type GetInstanceIdInput struct {
// Name of the instance you want to get
Name string
}
// GetInstanceFromName loops through all the instances and finds the instance for the given name
// This is needed for orchestration since it doesn't return the id for the instance it creates.
func (c *InstancesClient) GetInstanceFromName(input *GetInstanceIdInput) (*InstanceInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var instancesInfo InstancesInfo
if err := c.getResource(fmt.Sprintf("%s/", c.getUserName()), &instancesInfo); err != nil {
return nil, err
}
for _, i := range instancesInfo.Instances {
if strings.Contains(i.Name, input.Name) {
if i.Name == "" {
return nil, fmt.Errorf("Empty response body when requesting instance %s", input.Name)
}
// The returned 'Name' attribute is the fully qualified instance name + "/" + ID
// Split these out to accurately populate the fields
nID := strings.Split(c.getUnqualifiedName(i.Name), "/")
i.Name = nID[0]
i.ID = nID[1]
c.unqualify(&i.VCableID)
// Unqualify SSH Key names
sshKeyNames := []string{}
for _, sshKeyRef := range i.SSHKeys {
sshKeyNames = append(sshKeyNames, c.getUnqualifiedName(sshKeyRef))
}
i.SSHKeys = sshKeyNames
var networkingErr error
i.Networking, networkingErr = c.unqualifyNetworking(i.Networking)
if networkingErr != nil {
return nil, networkingErr
}
i.Storage = c.unqualifyStorage(i.Storage)
return &i, nil
}
}
return nil, fmt.Errorf("Unable to find instance: %q", input.Name)
}
type UpdateInstanceInput struct {
// Name of this instance, generated by the server.
// Required
Name string `json:"name"`
// The desired state of the opc instance. Can only be `running` or `shutdown`
// Omits if empty.
// Optional
DesiredState InstanceDesiredState `json:"desired_state,omitempty"`
// The ID of the instance
// Required
ID string `json:"-"`
// A list of tags to be supplied to the instance
// Optional
Tags []string `json:"tags,omitempty"`
// Time to wait for instance to be ready, or shutdown depending on desired state
Timeout time.Duration `json:"-"`
}
func (g *UpdateInstanceInput) String() string {
return fmt.Sprintf(CMP_QUALIFIED_NAME, g.Name, g.ID)
}
func (c *InstancesClient) UpdateInstance(input *UpdateInstanceInput) (*InstanceInfo, error) {
if input.Name == "" || input.ID == "" {
return nil, errors.New("Both instance name and ID need to be specified")
}
input.Name = fmt.Sprintf(CMP_QUALIFIED_NAME, c.getUserName(), input.Name)
var responseBody InstanceInfo
if err := c.updateResource(input.String(), input, &responseBody); err != nil {
return nil, err
}
getInput := &GetInstanceInput{
Name: input.Name,
ID: input.ID,
}
if input.Timeout == 0 {
input.Timeout = WaitForInstanceReadyTimeout
}
// Wait for the correct instance action depending on the current desired state.
// If the instance is already running, and the desired state is to be "running", the
// wait loop will only execute a single time to verify the instance state. Otherwise
// we wait until the correct action has finalized, either a shutdown or restart, catching
// any intermittent errors during the process.
if responseBody.DesiredState == InstanceDesiredRunning {
return c.WaitForInstanceRunning(getInput, input.Timeout)
} else {
return c.WaitForInstanceShutdown(getInput, input.Timeout)
}
}
type DeleteInstanceInput struct {
// The Unqualified Name of this Instance
Name string
// The Unqualified ID of this Instance
ID string
// Time to wait for instance to be deleted
Timeout time.Duration
}
func (d *DeleteInstanceInput) String() string {
return fmt.Sprintf(CMP_QUALIFIED_NAME, d.Name, d.ID)
}
// DeleteInstance deletes an instance.
func (c *InstancesClient) DeleteInstance(input *DeleteInstanceInput) error {
// Call to delete the instance
if err := c.deleteResource(input.String()); err != nil {
return err
}
if input.Timeout == 0 {
input.Timeout = WaitForInstanceDeleteTimeout
}
// Wait for instance to be deleted
return c.WaitForInstanceDeleted(input, input.Timeout)
}
// WaitForInstanceRunning waits for an instance to be completely initialized and available.
func (c *InstancesClient) WaitForInstanceRunning(input *GetInstanceInput, timeout time.Duration) (*InstanceInfo, error) {
var info *InstanceInfo
var getErr error
err := c.client.WaitFor("instance to be ready", timeout, func() (bool, error) {
info, getErr = c.GetInstance(input)
if getErr != nil {
return false, getErr
}
c.client.DebugLogString(fmt.Sprintf("Instance name is %v, Instance info is %+v", info.Name, info))
switch s := info.State; s {
case InstanceError:
return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason)
case InstanceRunning: // Target State
c.client.DebugLogString("Instance Running")
return true, nil
case InstanceQueued:
c.client.DebugLogString("Instance Queuing")
return false, nil
case InstanceInitializing:
c.client.DebugLogString("Instance Initializing")
return false, nil
case InstancePreparing:
c.client.DebugLogString("Instance Preparing")
return false, nil
case InstanceStarting:
c.client.DebugLogString("Instance Starting")
return false, nil
default:
c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
return false, nil
}
})
return info, err
}
// WaitForInstanceShutdown waits for an instance to be shutdown
func (c *InstancesClient) WaitForInstanceShutdown(input *GetInstanceInput, timeout time.Duration) (*InstanceInfo, error) {
var info *InstanceInfo
var getErr error
err := c.client.WaitFor("instance to be shutdown", timeout, func() (bool, error) {
info, getErr = c.GetInstance(input)
if getErr != nil {
return false, getErr
}
switch s := info.State; s {
case InstanceError:
return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason)
case InstanceRunning:
c.client.DebugLogString("Instance Running")
return false, nil
case InstanceQueued:
c.client.DebugLogString("Instance Queuing")
return false, nil
case InstanceInitializing:
c.client.DebugLogString("Instance Initializing")
return false, nil
case InstancePreparing:
c.client.DebugLogString("Instance Preparing")
return false, nil
case InstanceStarting:
c.client.DebugLogString("Instance Starting")
return false, nil
case InstanceShutdown: // Target State
c.client.DebugLogString("Instance Shutdown")
return true, nil
default:
c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
return false, nil
}
})
return info, err
}
// WaitForInstanceDeleted waits for an instance to be fully deleted.
func (c *InstancesClient) WaitForInstanceDeleted(input *DeleteInstanceInput, timeout time.Duration) error {
return c.client.WaitFor("instance to be deleted", timeout, func() (bool, error) {
var info InstanceInfo
if err := c.getResource(input.String(), &info); err != nil {
if client.WasNotFoundError(err) {
// Instance could not be found, thus deleted
return true, nil
}
// Some other error occurred trying to get instance, exit
return false, err
}
switch s := info.State; s {
case InstanceError:
return false, fmt.Errorf("Error stopping instance: %s", info.ErrorReason)
case InstanceStopping:
c.client.DebugLogString("Instance stopping")
return false, nil
default:
c.client.DebugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s))
return false, nil
}
})
}
func (c *InstancesClient) qualifyNetworking(info map[string]NetworkingInfo) map[string]NetworkingInfo {
qualifiedNetworks := map[string]NetworkingInfo{}
for k, v := range info {
qfd := v
sharedNetwork := false
if v.IPNetwork != "" {
// Network interface is for an IP Network
qfd.IPNetwork = c.getQualifiedName(v.IPNetwork)
sharedNetwork = true
}
if v.Vnic != "" {
qfd.Vnic = c.getQualifiedName(v.Vnic)
}
if v.Nat != nil {
qfd.Nat = c.qualifyNat(v.Nat, sharedNetwork)
}
if v.VnicSets != nil {
qfd.VnicSets = c.getQualifiedList(v.VnicSets)
}
if v.SecLists != nil {
// Network interface is for the shared network
secLists := []string{}
for _, v := range v.SecLists {
secLists = append(secLists, c.getQualifiedName(v))
}
qfd.SecLists = secLists
}
qualifiedNetworks[k] = qfd
}
return qualifiedNetworks
}
func (c *InstancesClient) unqualifyNetworking(info map[string]NetworkingInfo) (map[string]NetworkingInfo, error) {
// Unqualify ip network
var err error
unqualifiedNetworks := map[string]NetworkingInfo{}
for k, v := range info {
unq := v
if v.IPNetwork != "" {
unq.IPNetwork = c.getUnqualifiedName(v.IPNetwork)
}
if v.Vnic != "" {
unq.Vnic = c.getUnqualifiedName(v.Vnic)
}
if v.Nat != nil {
unq.Nat, err = c.unqualifyNat(v.Nat)
if err != nil {
return nil, err
}
}
if v.VnicSets != nil {
unq.VnicSets = c.getUnqualifiedList(v.VnicSets)
}
if v.SecLists != nil {
secLists := []string{}
for _, v := range v.SecLists {
secLists = append(secLists, c.getUnqualifiedName(v))
}
v.SecLists = secLists
}
unqualifiedNetworks[k] = unq
}
return unqualifiedNetworks, nil
}
func (c *InstancesClient) qualifyNat(nat []string, shared bool) []string {
qualifiedNats := []string{}
for _, v := range nat {
if strings.HasPrefix(v, "ippool:/oracle") {
qualifiedNats = append(qualifiedNats, v)
continue
}
prefix := ReservationPrefix
if shared {
prefix = ReservationIPPrefix
}
qualifiedNats = append(qualifiedNats, fmt.Sprintf("%s:%s", prefix, c.getQualifiedName(v)))
}
return qualifiedNats
}
func (c *InstancesClient) unqualifyNat(nat []string) ([]string, error) {
unQualifiedNats := []string{}
for _, v := range nat {
if strings.HasPrefix(v, "ippool:/oracle") {
unQualifiedNats = append(unQualifiedNats, v)
continue
}
n := strings.Split(v, ":")
if len(n) < 1 {
return nil, fmt.Errorf("Error unqualifying NAT: %s", v)
}
u := n[1]
unQualifiedNats = append(unQualifiedNats, c.getUnqualifiedName(u))
}
return unQualifiedNats, nil
}
func (c *InstancesClient) unqualifyStorage(attachments []StorageAttachment) []StorageAttachment {
unqAttachments := []StorageAttachment{}
for _, v := range attachments {
if v.StorageVolumeName != "" {
v.StorageVolumeName = c.getUnqualifiedName(v.StorageVolumeName)
}
unqAttachments = append(unqAttachments, v)
}
return unqAttachments
}

View File

@ -0,0 +1,152 @@
package compute
const (
IPAddressAssociationDescription = "ip address association"
IPAddressAssociationContainerPath = "/network/v1/ipassociation/"
IPAddressAssociationResourcePath = "/network/v1/ipassociation"
)
type IPAddressAssociationsClient struct {
ResourceClient
}
// IPAddressAssociations() returns an IPAddressAssociationsClient that can be used to access the
// necessary CRUD functions for IP Address Associations.
func (c *ComputeClient) IPAddressAssociations() *IPAddressAssociationsClient {
return &IPAddressAssociationsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: IPAddressAssociationDescription,
ContainerPath: IPAddressAssociationContainerPath,
ResourceRootPath: IPAddressAssociationResourcePath,
},
}
}
// IPAddressAssociationInfo contains the exported fields necessary to hold all the information about an
// IP Address Association
type IPAddressAssociationInfo struct {
// The name of the NAT IP address reservation.
IPAddressReservation string `json:"ipAddressReservation"`
// Name of the virtual NIC associated with this NAT IP reservation.
Vnic string `json:"vnic"`
// The name of the IP Address Association
Name string `json:"name"`
// Description of the IP Address Association
Description string `json:"description"`
// Slice of tags associated with the IP Address Association
Tags []string `json:"tags"`
// Uniform Resource Identifier for the IP Address Association
Uri string `json:"uri"`
}
type CreateIPAddressAssociationInput struct {
// The name of the IP Address Association to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// The name of the NAT IP address reservation.
// Optional
IPAddressReservation string `json:"ipAddressReservation,omitempty"`
// Name of the virtual NIC associated with this NAT IP reservation.
// Optional
Vnic string `json:"vnic,omitempty"`
// Description of the IPAddressAssociation
// Optional
Description string `json:"description"`
// String slice of tags to apply to the IP Address Association object
// Optional
Tags []string `json:"tags"`
}
// Create a new IP Address Association from an IPAddressAssociationsClient and an input struct.
// Returns a populated Info struct for the IP Address Association, and any errors
func (c *IPAddressAssociationsClient) CreateIPAddressAssociation(input *CreateIPAddressAssociationInput) (*IPAddressAssociationInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.IPAddressReservation = c.getQualifiedName(input.IPAddressReservation)
input.Vnic = c.getQualifiedName(input.Vnic)
var ipInfo IPAddressAssociationInfo
if err := c.createResource(&input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type GetIPAddressAssociationInput struct {
// The name of the IP Address Association to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated IPAddressAssociationInfo struct from an input struct
func (c *IPAddressAssociationsClient) GetIPAddressAssociation(input *GetIPAddressAssociationInput) (*IPAddressAssociationInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPAddressAssociationInfo
if err := c.getResource(input.Name, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
// UpdateIPAddressAssociationInput defines what to update in a ip address association
type UpdateIPAddressAssociationInput struct {
// The name of the IP Address Association to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// The name of the NAT IP address reservation.
// Optional
IPAddressReservation string `json:"ipAddressReservation,omitempty"`
// Name of the virtual NIC associated with this NAT IP reservation.
// Optional
Vnic string `json:"vnic,omitempty"`
// Description of the IPAddressAssociation
// Optional
Description string `json:"description"`
// String slice of tags to apply to the IP Address Association object
// Optional
Tags []string `json:"tags"`
}
// UpdateIPAddressAssociation update the ip address association
func (c *IPAddressAssociationsClient) UpdateIPAddressAssociation(updateInput *UpdateIPAddressAssociationInput) (*IPAddressAssociationInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
updateInput.IPAddressReservation = c.getQualifiedName(updateInput.IPAddressReservation)
updateInput.Vnic = c.getQualifiedName(updateInput.Vnic)
var ipInfo IPAddressAssociationInfo
if err := c.updateResource(updateInput.Name, updateInput, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type DeleteIPAddressAssociationInput struct {
// The name of the IP Address Association to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *IPAddressAssociationsClient) DeleteIPAddressAssociation(input *DeleteIPAddressAssociationInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the IPAddressAssociationInfo struct
func (c *IPAddressAssociationsClient) success(info *IPAddressAssociationInfo) (*IPAddressAssociationInfo, error) {
c.unqualify(&info.Name)
c.unqualify(&info.Vnic)
c.unqualify(&info.IPAddressReservation)
return info, nil
}

View File

@ -0,0 +1,135 @@
package compute
const (
IPAddressPrefixSetDescription = "ip address prefix set"
IPAddressPrefixSetContainerPath = "/network/v1/ipaddressprefixset/"
IPAddressPrefixSetResourcePath = "/network/v1/ipaddressprefixset"
)
type IPAddressPrefixSetsClient struct {
ResourceClient
}
// IPAddressPrefixSets() returns an IPAddressPrefixSetsClient that can be used to access the
// necessary CRUD functions for IP Address Prefix Sets.
func (c *ComputeClient) IPAddressPrefixSets() *IPAddressPrefixSetsClient {
return &IPAddressPrefixSetsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: IPAddressPrefixSetDescription,
ContainerPath: IPAddressPrefixSetContainerPath,
ResourceRootPath: IPAddressPrefixSetResourcePath,
},
}
}
// IPAddressPrefixSetInfo contains the exported fields necessary to hold all the information about an
// IP Address Prefix Set
type IPAddressPrefixSetInfo struct {
// The name of the IP Address Prefix Set
Name string `json:"name"`
// Description of the IP Address Prefix Set
Description string `json:"description"`
// List of CIDR IPv4 prefixes assigned in the virtual network.
IPAddressPrefixes []string `json:"ipAddressPrefixes"`
// Slice of tags associated with the IP Address Prefix Set
Tags []string `json:"tags"`
// Uniform Resource Identifier for the IP Address Prefix Set
Uri string `json:"uri"`
}
type CreateIPAddressPrefixSetInput struct {
// The name of the IP Address Prefix Set to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Description of the IPAddressPrefixSet
// Optional
Description string `json:"description"`
// List of CIDR IPv4 prefixes assigned in the virtual network.
// Optional
IPAddressPrefixes []string `json:"ipAddressPrefixes"`
// String slice of tags to apply to the IP Address Prefix Set object
// Optional
Tags []string `json:"tags"`
}
// Create a new IP Address Prefix Set from an IPAddressPrefixSetsClient and an input struct.
// Returns a populated Info struct for the IP Address Prefix Set, and any errors
func (c *IPAddressPrefixSetsClient) CreateIPAddressPrefixSet(input *CreateIPAddressPrefixSetInput) (*IPAddressPrefixSetInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPAddressPrefixSetInfo
if err := c.createResource(&input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type GetIPAddressPrefixSetInput struct {
// The name of the IP Address Prefix Set to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated IPAddressPrefixSetInfo struct from an input struct
func (c *IPAddressPrefixSetsClient) GetIPAddressPrefixSet(input *GetIPAddressPrefixSetInput) (*IPAddressPrefixSetInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPAddressPrefixSetInfo
if err := c.getResource(input.Name, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
// UpdateIPAddressPrefixSetInput defines what to update in a ip address prefix set
type UpdateIPAddressPrefixSetInput struct {
// The name of the IP Address Prefix Set to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Description of the IPAddressPrefixSet
// Optional
Description string `json:"description"`
// List of CIDR IPv4 prefixes assigned in the virtual network.
IPAddressPrefixes []string `json:"ipAddressPrefixes"`
// String slice of tags to apply to the IP Address Prefix Set object
// Optional
Tags []string `json:"tags"`
}
// UpdateIPAddressPrefixSet update the ip address prefix set
func (c *IPAddressPrefixSetsClient) UpdateIPAddressPrefixSet(updateInput *UpdateIPAddressPrefixSetInput) (*IPAddressPrefixSetInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
var ipInfo IPAddressPrefixSetInfo
if err := c.updateResource(updateInput.Name, updateInput, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type DeleteIPAddressPrefixSetInput struct {
// The name of the IP Address Prefix Set to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *IPAddressPrefixSetsClient) DeleteIPAddressPrefixSet(input *DeleteIPAddressPrefixSetInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the IPAddressPrefixSetInfo struct
func (c *IPAddressPrefixSetsClient) success(info *IPAddressPrefixSetInfo) (*IPAddressPrefixSetInfo, error) {
c.unqualify(&info.Name)
return info, nil
}

View File

@ -0,0 +1,190 @@
package compute
import (
"fmt"
"path/filepath"
)
// IPAddressReservationsClient is a client to manage ip address reservation resources
type IPAddressReservationsClient struct {
*ResourceClient
}
const (
IPAddressReservationDescription = "IP Address Reservation"
IPAddressReservationContainerPath = "/network/v1/ipreservation/"
IPAddressReservationResourcePath = "/network/v1/ipreservation"
IPAddressReservationQualifier = "/oracle/public"
)
// IPAddressReservations returns an IPAddressReservationsClient to manage IP address reservation
// resources
func (c *ComputeClient) IPAddressReservations() *IPAddressReservationsClient {
return &IPAddressReservationsClient{
ResourceClient: &ResourceClient{
ComputeClient: c,
ResourceDescription: IPAddressReservationDescription,
ContainerPath: IPAddressReservationContainerPath,
ResourceRootPath: IPAddressReservationResourcePath,
},
}
}
// IPAddressReservation describes an IP Address reservation
type IPAddressReservation struct {
// Description of the IP Address Reservation
Description string `json:"description"`
// Reserved NAT IPv4 address from the IP Address Pool
IPAddress string `json:"ipAddress"`
// Name of the IP Address pool to reserve the NAT IP from
IPAddressPool string `json:"ipAddressPool"`
// Name of the reservation
Name string `json:"name"`
// Tags associated with the object
Tags []string `json:"tags"`
// Uniform Resource Identified for the reservation
Uri string `json:"uri"`
}
const (
PublicIPAddressPool = "public-ippool"
PrivateIPAddressPool = "cloud-ippool"
)
// CreateIPAddressReservationInput defines input parameters to create an ip address reservation
type CreateIPAddressReservationInput struct {
// Description of the IP Address Reservation
// Optional
Description string `json:"description"`
// IP Address pool from which to reserve an IP Address.
// Can be one of the following:
//
// 'public-ippool' - When you attach an IP Address from this pool to an instance, you enable
// access between the public Internet and the instance
// 'cloud-ippool' - When you attach an IP Address from this pool to an instance, the instance
// can communicate privately with other Oracle Cloud Services
// Optional
IPAddressPool string `json:"ipAddressPool"`
// The name of the reservation to create
// Required
Name string `json:"name"`
// Tags to associate with the IP Reservation
// Optional
Tags []string `json:"tags"`
}
// Takes an input struct, creates an IP Address reservation, and returns the info struct and any errors
func (c *IPAddressReservationsClient) CreateIPAddressReservation(input *CreateIPAddressReservationInput) (*IPAddressReservation, error) {
var ipAddrRes IPAddressReservation
// Qualify supplied name
input.Name = c.getQualifiedName(input.Name)
// Qualify supplied address pool if not nil
if input.IPAddressPool != "" {
input.IPAddressPool = c.qualifyIPAddressPool(input.IPAddressPool)
}
if err := c.createResource(input, &ipAddrRes); err != nil {
return nil, err
}
return c.success(&ipAddrRes)
}
// Parameters to retrieve information on an ip address reservation
type GetIPAddressReservationInput struct {
// Name of the IP Reservation
// Required
Name string `json:"name"`
}
// Returns an IP Address Reservation and any errors
func (c *IPAddressReservationsClient) GetIPAddressReservation(input *GetIPAddressReservationInput) (*IPAddressReservation, error) {
var ipAddrRes IPAddressReservation
input.Name = c.getQualifiedName(input.Name)
if err := c.getResource(input.Name, &ipAddrRes); err != nil {
return nil, err
}
return c.success(&ipAddrRes)
}
// Parameters to update an IP Address reservation
type UpdateIPAddressReservationInput struct {
// Description of the IP Address Reservation
// Optional
Description string `json:"description"`
// IP Address pool from which to reserve an IP Address.
// Can be one of the following:
//
// 'public-ippool' - When you attach an IP Address from this pool to an instance, you enable
// access between the public Internet and the instance
// 'cloud-ippool' - When you attach an IP Address from this pool to an instance, the instance
// can communicate privately with other Oracle Cloud Services
// Optional
IPAddressPool string `json:"ipAddressPool"`
// The name of the reservation to create
// Required
Name string `json:"name"`
// Tags to associate with the IP Reservation
// Optional
Tags []string `json:"tags"`
}
func (c *IPAddressReservationsClient) UpdateIPAddressReservation(input *UpdateIPAddressReservationInput) (*IPAddressReservation, error) {
var ipAddrRes IPAddressReservation
// Qualify supplied name
input.Name = c.getQualifiedName(input.Name)
// Qualify supplied address pool if not nil
if input.IPAddressPool != "" {
input.IPAddressPool = c.qualifyIPAddressPool(input.IPAddressPool)
}
if err := c.updateResource(input.Name, input, &ipAddrRes); err != nil {
return nil, err
}
return c.success(&ipAddrRes)
}
// Parameters to delete an IP Address Reservation
type DeleteIPAddressReservationInput struct {
// The name of the reservation to delete
Name string `json:"name"`
}
func (c *IPAddressReservationsClient) DeleteIPAddressReservation(input *DeleteIPAddressReservationInput) error {
input.Name = c.getQualifiedName(input.Name)
return c.deleteResource(input.Name)
}
func (c *IPAddressReservationsClient) success(result *IPAddressReservation) (*IPAddressReservation, error) {
c.unqualify(&result.Name)
if result.IPAddressPool != "" {
result.IPAddressPool = c.unqualifyIPAddressPool(result.IPAddressPool)
}
return result, nil
}
func (c *IPAddressReservationsClient) qualifyIPAddressPool(input string) string {
// Add '/oracle/public/'
return fmt.Sprintf("%s/%s", IPAddressReservationQualifier, input)
}
func (c *IPAddressReservationsClient) unqualifyIPAddressPool(input string) string {
// Remove '/oracle/public/'
return filepath.Base(input)
}

View File

@ -0,0 +1,118 @@
package compute
import (
"fmt"
"strings"
)
// IPAssociationsClient is a client for the IP Association functions of the Compute API.
type IPAssociationsClient struct {
*ResourceClient
}
// IPAssociations obtains a IPAssociationsClient which can be used to access to the
// IP Association functions of the Compute API
func (c *ComputeClient) IPAssociations() *IPAssociationsClient {
return &IPAssociationsClient{
ResourceClient: &ResourceClient{
ComputeClient: c,
ResourceDescription: "ip association",
ContainerPath: "/ip/association/",
ResourceRootPath: "/ip/association",
}}
}
// IPAssociationInfo describes an existing IP association.
type IPAssociationInfo struct {
// TODO: it'd probably make sense to expose the `ip` field here too?
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The three-part name of the IP reservation object in the format (/Compute-identity_domain/user/object).
// An IP reservation is a public IP address which is attached to an Oracle Compute Cloud Service instance that requires access to or from the Internet.
Reservation string `json:"reservation"`
// The type of IP Address to associate with this instance
// for a Dynamic IP address specify `ippool:/oracle/public/ippool`.
// for a Static IP address specify the three part name of the existing IP reservation
ParentPool string `json:"parentpool"`
// Uniform Resource Identifier for the IP Association
URI string `json:"uri"`
// The three-part name of a vcable ID of an instance that is associated with the IP reservation.
VCable string `json:"vcable"`
}
type CreateIPAssociationInput struct {
// The type of IP Address to associate with this instance
// for a Dynamic IP address specify `ippool:/oracle/public/ippool`.
// for a Static IP address specify the three part name of the existing IP reservation
// Required
ParentPool string `json:"parentpool"`
// The three-part name of the vcable ID of the instance that you want to associate with an IP address. The three-part name is in the format: /Compute-identity_domain/user/object.
// Required
VCable string `json:"vcable"`
}
// CreateIPAssociation creates a new IP association with the supplied vcable and parentpool.
func (c *IPAssociationsClient) CreateIPAssociation(input *CreateIPAssociationInput) (*IPAssociationInfo, error) {
input.VCable = c.getQualifiedName(input.VCable)
input.ParentPool = c.getQualifiedParentPoolName(input.ParentPool)
var assocInfo IPAssociationInfo
if err := c.createResource(input, &assocInfo); err != nil {
return nil, err
}
return c.success(&assocInfo)
}
type GetIPAssociationInput struct {
// The three-part name of the IP Association
// Required.
Name string `json:"name"`
}
// GetIPAssociation retrieves the IP association with the given name.
func (c *IPAssociationsClient) GetIPAssociation(input *GetIPAssociationInput) (*IPAssociationInfo, error) {
var assocInfo IPAssociationInfo
if err := c.getResource(input.Name, &assocInfo); err != nil {
return nil, err
}
return c.success(&assocInfo)
}
type DeleteIPAssociationInput struct {
// The three-part name of the IP Association
// Required.
Name string `json:"name"`
}
// DeleteIPAssociation deletes the IP association with the given name.
func (c *IPAssociationsClient) DeleteIPAssociation(input *DeleteIPAssociationInput) error {
return c.deleteResource(input.Name)
}
func (c *IPAssociationsClient) getQualifiedParentPoolName(parentpool string) string {
parts := strings.Split(parentpool, ":")
pooltype := parts[0]
name := parts[1]
return fmt.Sprintf("%s:%s", pooltype, c.getQualifiedName(name))
}
func (c *IPAssociationsClient) unqualifyParentPoolName(parentpool *string) {
parts := strings.Split(*parentpool, ":")
pooltype := parts[0]
name := parts[1]
*parentpool = fmt.Sprintf("%s:%s", pooltype, c.getUnqualifiedName(name))
}
// Unqualifies identifiers
func (c *IPAssociationsClient) success(assocInfo *IPAssociationInfo) (*IPAssociationInfo, error) {
c.unqualify(&assocInfo.Name, &assocInfo.VCable)
c.unqualifyParentPoolName(&assocInfo.ParentPool)
return assocInfo, nil
}

View File

@ -0,0 +1,99 @@
package compute
const (
IPNetworkExchangeDescription = "ip network exchange"
IPNetworkExchangeContainerPath = "/network/v1/ipnetworkexchange/"
IPNetworkExchangeResourcePath = "/network/v1/ipnetworkexchange"
)
type IPNetworkExchangesClient struct {
ResourceClient
}
// IPNetworkExchanges() returns an IPNetworkExchangesClient that can be used to access the
// necessary CRUD functions for IP Network Exchanges.
func (c *ComputeClient) IPNetworkExchanges() *IPNetworkExchangesClient {
return &IPNetworkExchangesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: IPNetworkExchangeDescription,
ContainerPath: IPNetworkExchangeContainerPath,
ResourceRootPath: IPNetworkExchangeResourcePath,
},
}
}
// IPNetworkExchangeInfo contains the exported fields necessary to hold all the information about an
// IP Network Exchange
type IPNetworkExchangeInfo struct {
// The name of the IP Network Exchange
Name string `json:"name"`
// Description of the IP Network Exchange
Description string `json:"description"`
// Slice of tags associated with the IP Network Exchange
Tags []string `json:"tags"`
// Uniform Resource Identifier for the IP Network Exchange
Uri string `json:"uri"`
}
type CreateIPNetworkExchangeInput struct {
// The name of the IP Network Exchange to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Description of the IPNetworkExchange
// Optional
Description string `json:"description"`
// String slice of tags to apply to the IP Network Exchange object
// Optional
Tags []string `json:"tags"`
}
// Create a new IP Network Exchange from an IPNetworkExchangesClient and an input struct.
// Returns a populated Info struct for the IP Network Exchange, and any errors
func (c *IPNetworkExchangesClient) CreateIPNetworkExchange(input *CreateIPNetworkExchangeInput) (*IPNetworkExchangeInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPNetworkExchangeInfo
if err := c.createResource(&input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type GetIPNetworkExchangeInput struct {
// The name of the IP Network Exchange to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated IPNetworkExchangeInfo struct from an input struct
func (c *IPNetworkExchangesClient) GetIPNetworkExchange(input *GetIPNetworkExchangeInput) (*IPNetworkExchangeInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPNetworkExchangeInfo
if err := c.getResource(input.Name, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type DeleteIPNetworkExchangeInput struct {
// The name of the IP Network Exchange to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *IPNetworkExchangesClient) DeleteIPNetworkExchange(input *DeleteIPNetworkExchangeInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the IPNetworkExchangeInfo struct
func (c *IPNetworkExchangesClient) success(info *IPNetworkExchangeInfo) (*IPNetworkExchangeInfo, error) {
c.unqualify(&info.Name)
return info, nil
}

View File

@ -0,0 +1,186 @@
package compute
const (
IPNetworkDescription = "ip network"
IPNetworkContainerPath = "/network/v1/ipnetwork/"
IPNetworkResourcePath = "/network/v1/ipnetwork"
)
type IPNetworksClient struct {
ResourceClient
}
// IPNetworks() returns an IPNetworksClient that can be used to access the
// necessary CRUD functions for IP Networks.
func (c *ComputeClient) IPNetworks() *IPNetworksClient {
return &IPNetworksClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: IPNetworkDescription,
ContainerPath: IPNetworkContainerPath,
ResourceRootPath: IPNetworkResourcePath,
},
}
}
// IPNetworkInfo contains the exported fields necessary to hold all the information about an
// IP Network
type IPNetworkInfo struct {
// The name of the IP Network
Name string `json:"name"`
// The CIDR IPv4 prefix associated with the IP Network
IPAddressPrefix string `json:"ipAddressPrefix"`
// Name of the IP Network Exchange associated with the IP Network
IPNetworkExchange string `json:"ipNetworkExchange,omitempty"`
// Description of the IP Network
Description string `json:"description"`
// Whether public internet access was enabled using NAPT for VNICs without any public IP reservation
PublicNaptEnabled bool `json:"publicNaptEnabledFlag"`
// Slice of tags associated with the IP Network
Tags []string `json:"tags"`
// Uniform Resource Identifier for the IP Network
Uri string `json:"uri"`
}
type CreateIPNetworkInput struct {
// The name of the IP Network to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Specify the size of the IP Subnet. It is a range of IPv4 addresses assigned in the virtual
// network, in CIDR address prefix format.
// While specifying the IP address prefix take care of the following points:
//
//* These IP addresses aren't part of the common pool of Oracle-provided IP addresses used by the shared network.
//
//* There's no conflict with the range of IP addresses used in another IP network, the IP addresses used your on-premises network, or with the range of private IP addresses used in the shared network. If IP networks with overlapping IP subnets are linked to an IP exchange, packets going to and from those IP networks are dropped.
//
//* The upper limit of the CIDR block size for an IP network is /16.
//
//Note: The first IP address of any IP network is reserved for the default gateway, the DHCP server, and the DNS server of that IP network.
// Required
IPAddressPrefix string `json:"ipAddressPrefix"`
//Specify the IP network exchange to which the IP network belongs.
//You can add an IP network to only one IP network exchange, but an IP network exchange
//can include multiple IP networks. An IP network exchange enables access between IP networks
//that have non-overlapping addresses, so that instances on these networks can exchange packets
//with each other without NAT.
// Optional
IPNetworkExchange string `json:"ipNetworkExchange,omitempty"`
// Description of the IPNetwork
// Optional
Description string `json:"description"`
// Enable public internet access using NAPT for VNICs without any public IP reservation
// Optional
PublicNaptEnabled bool `json:"publicNaptEnabledFlag"`
// String slice of tags to apply to the IP Network object
// Optional
Tags []string `json:"tags"`
}
// Create a new IP Network from an IPNetworksClient and an input struct.
// Returns a populated Info struct for the IP Network, and any errors
func (c *IPNetworksClient) CreateIPNetwork(input *CreateIPNetworkInput) (*IPNetworkInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.IPNetworkExchange = c.getQualifiedName(input.IPNetworkExchange)
var ipInfo IPNetworkInfo
if err := c.createResource(&input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type GetIPNetworkInput struct {
// The name of the IP Network to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated IPNetworkInfo struct from an input struct
func (c *IPNetworksClient) GetIPNetwork(input *GetIPNetworkInput) (*IPNetworkInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo IPNetworkInfo
if err := c.getResource(input.Name, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type UpdateIPNetworkInput struct {
// The name of the IP Network to update. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Specify the size of the IP Subnet. It is a range of IPv4 addresses assigned in the virtual
// network, in CIDR address prefix format.
// While specifying the IP address prefix take care of the following points:
//
//* These IP addresses aren't part of the common pool of Oracle-provided IP addresses used by the shared network.
//
//* There's no conflict with the range of IP addresses used in another IP network, the IP addresses used your on-premises network, or with the range of private IP addresses used in the shared network. If IP networks with overlapping IP subnets are linked to an IP exchange, packets going to and from those IP networks are dropped.
//
//* The upper limit of the CIDR block size for an IP network is /16.
//
//Note: The first IP address of any IP network is reserved for the default gateway, the DHCP server, and the DNS server of that IP network.
// Required
IPAddressPrefix string `json:"ipAddressPrefix"`
//Specify the IP network exchange to which the IP network belongs.
//You can add an IP network to only one IP network exchange, but an IP network exchange
//can include multiple IP networks. An IP network exchange enables access between IP networks
//that have non-overlapping addresses, so that instances on these networks can exchange packets
//with each other without NAT.
// Optional
IPNetworkExchange string `json:"ipNetworkExchange,omitempty"`
// Description of the IPNetwork
// Optional
Description string `json:"description"`
// Enable public internet access using NAPT for VNICs without any public IP reservation
// Optional
PublicNaptEnabled bool `json:"publicNaptEnabledFlag"`
// String slice of tags to apply to the IP Network object
// Optional
Tags []string `json:"tags"`
}
func (c *IPNetworksClient) UpdateIPNetwork(input *UpdateIPNetworkInput) (*IPNetworkInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.IPNetworkExchange = c.getQualifiedName(input.IPNetworkExchange)
var ipInfo IPNetworkInfo
if err := c.updateResource(input.Name, &input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type DeleteIPNetworkInput struct {
// The name of the IP Network to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *IPNetworksClient) DeleteIPNetwork(input *DeleteIPNetworkInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the IPNetworkInfo struct
func (c *IPNetworksClient) success(info *IPNetworkInfo) (*IPNetworkInfo, error) {
c.unqualify(&info.Name)
c.unqualify(&info.IPNetworkExchange)
return info, nil
}

View File

@ -0,0 +1,147 @@
package compute
// IPReservationsClient is a client for the IP Reservations functions of the Compute API.
type IPReservationsClient struct {
*ResourceClient
}
const (
IPReservationDesc = "ip reservation"
IPReservationContainerPath = "/ip/reservation/"
IPReservataionResourcePath = "/ip/reservation"
)
// IPReservations obtains an IPReservationsClient which can be used to access to the
// IP Reservations functions of the Compute API
func (c *ComputeClient) IPReservations() *IPReservationsClient {
return &IPReservationsClient{
ResourceClient: &ResourceClient{
ComputeClient: c,
ResourceDescription: IPReservationDesc,
ContainerPath: IPReservationContainerPath,
ResourceRootPath: IPReservataionResourcePath,
}}
}
type IPReservationPool string
const (
PublicReservationPool IPReservationPool = "/oracle/public/ippool"
)
// IPReservationInput describes an existing IP reservation.
type IPReservation struct {
// Shows the default account for your identity domain.
Account string `json:"account"`
// Public IP address.
IP string `json:"ip"`
// The three-part name of the IP Reservation (/Compute-identity_domain/user/object).
Name string `json:"name"`
// Pool of public IP addresses
ParentPool IPReservationPool `json:"parentpool"`
// Is the IP Reservation Persistent (i.e. static) or not (i.e. Dynamic)?
Permanent bool `json:"permanent"`
// A comma-separated list of strings which helps you to identify IP reservation.
Tags []string `json:"tags"`
// Uniform Resource Identifier
Uri string `json:"uri"`
// Is the IP reservation associated with an instance?
Used bool `json:"used"`
}
// CreateIPReservationInput defines an IP reservation to be created.
type CreateIPReservationInput struct {
// The name of the object
// If you don't specify a name for this object, then the name is generated automatically.
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods.
// Object names are case-sensitive.
// Optional
Name string `json:"name"`
// Pool of public IP addresses. This must be set to `ippool`
// Required
ParentPool IPReservationPool `json:"parentpool"`
// Is the IP Reservation Persistent (i.e. static) or not (i.e. Dynamic)?
// Required
Permanent bool `json:"permanent"`
// A comma-separated list of strings which helps you to identify IP reservations.
// Optional
Tags []string `json:"tags"`
}
// CreateIPReservation creates a new IP reservation with the given parentpool, tags and permanent flag.
func (c *IPReservationsClient) CreateIPReservation(input *CreateIPReservationInput) (*IPReservation, error) {
var ipInput IPReservation
input.Name = c.getQualifiedName(input.Name)
if err := c.createResource(input, &ipInput); err != nil {
return nil, err
}
return c.success(&ipInput)
}
// GetIPReservationInput defines an IP Reservation to get
type GetIPReservationInput struct {
// The name of the IP Reservation
// Required
Name string
}
// GetIPReservation retrieves the IP reservation with the given name.
func (c *IPReservationsClient) GetIPReservation(input *GetIPReservationInput) (*IPReservation, error) {
var ipInput IPReservation
input.Name = c.getQualifiedName(input.Name)
if err := c.getResource(input.Name, &ipInput); err != nil {
return nil, err
}
return c.success(&ipInput)
}
// UpdateIPReservationInput defines an IP Reservation to be updated
type UpdateIPReservationInput struct {
// The name of the object
// If you don't specify a name for this object, then the name is generated automatically.
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods.
// Object names are case-sensitive.
// Required
Name string `json:"name"`
// Pool of public IP addresses.
// Required
ParentPool IPReservationPool `json:"parentpool"`
// Is the IP Reservation Persistent (i.e. static) or not (i.e. Dynamic)?
// Required
Permanent bool `json:"permanent"`
// A comma-separated list of strings which helps you to identify IP reservations.
// Optional
Tags []string `json:"tags"`
}
// UpdateIPReservation updates the IP reservation.
func (c *IPReservationsClient) UpdateIPReservation(input *UpdateIPReservationInput) (*IPReservation, error) {
var updateOutput IPReservation
input.Name = c.getQualifiedName(input.Name)
if err := c.updateResource(input.Name, input, &updateOutput); err != nil {
return nil, err
}
return c.success(&updateOutput)
}
// DeleteIPReservationInput defines an IP Reservation to delete
type DeleteIPReservationInput struct {
// The name of the IP Reservation
// Required
Name string
}
// DeleteIPReservation deletes the IP reservation with the given name.
func (c *IPReservationsClient) DeleteIPReservation(input *DeleteIPReservationInput) error {
input.Name = c.getQualifiedName(input.Name)
return c.deleteResource(input.Name)
}
func (c *IPReservationsClient) success(result *IPReservation) (*IPReservation, error) {
c.unqualify(&result.Name)
return result, nil
}

View File

@ -0,0 +1,143 @@
package compute
// MachineImagesClient is a client for the MachineImage functions of the Compute API.
type MachineImagesClient struct {
ResourceClient
}
// MachineImages obtains an MachineImagesClient which can be used to access to the
// MachineImage functions of the Compute API
func (c *ComputeClient) MachineImages() *MachineImagesClient {
return &MachineImagesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "MachineImage",
ContainerPath: "/machineimage/",
ResourceRootPath: "/machineimage",
}}
}
// MahineImage describes an existing Machine Image.
type MachineImage struct {
// account of the associated Object Storage Classic instance
Account string `json:"account"`
// Dictionary of attributes to be made available to the instance
Attributes map[string]interface{} `json:"attributes"`
// Last time when this image was audited
Audited string `json:"audited"`
// Describing the image
Description string `json:"description"`
// Description of the state of the machine image if there is an error
ErrorReason string `json:"error_reason"`
// dictionary of hypervisor-specific attributes
Hypervisor map[string]interface{} `json:"hypervisor"`
// The format of the image
ImageFormat string `json:"image_format"`
// name of the machine image file uploaded to Object Storage Classic
File string `json:"file"`
// name of the machine image
Name string `json:"name"`
// Indicates that the image file is available in Object Storage Classic
NoUpload bool `json:"no_upload"`
// The OS platform of the image
Platform string `json:"platform"`
// Size values of the image file
Sizes map[string]interface{} `json:"sizes"`
// The state of the uploaded machine image
State string `json:"state"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateMachineImageInput defines an Image List to be created.
type CreateMachineImageInput struct {
// account of the associated Object Storage Classic instance
Account string `json:"account"`
// Dictionary of attributes to be made available to the instance
Attributes map[string]interface{} `json:"attributes,omitempty"`
// Describing the image
Description string `json:"description,omitempty"`
// name of the machine image file uploaded to Object Storage Classic
File string `json:"file,omitempty"`
// name of the machine image
Name string `json:"name"`
// Indicates that the image file is available in Object Storage Classic
NoUpload bool `json:"no_upload"`
// Size values of the image file
Sizes map[string]interface{} `json:"sizes"`
}
// CreateMachineImage creates a new Machine Image with the given parameters.
func (c *MachineImagesClient) CreateMachineImage(createInput *CreateMachineImageInput) (*MachineImage, error) {
var machineImage MachineImage
// If `sizes` is not set then is mst be defaulted to {"total": 0}
if createInput.Sizes == nil {
createInput.Sizes = map[string]interface{}{"total": 0}
}
// `no_upload` must always be true
createInput.NoUpload = true
createInput.Name = c.getQualifiedName(createInput.Name)
if err := c.createResource(createInput, &machineImage); err != nil {
return nil, err
}
return c.success(&machineImage)
}
// DeleteMachineImageInput describes the MachineImage to delete
type DeleteMachineImageInput struct {
// The name of the MachineImage
Name string `json:name`
}
// DeleteMachineImage deletes the MachineImage with the given name.
func (c *MachineImagesClient) DeleteMachineImage(deleteInput *DeleteMachineImageInput) error {
return c.deleteResource(deleteInput.Name)
}
// GetMachineList describes the MachineImage to get
type GetMachineImageInput struct {
// account of the associated Object Storage Classic instance
Account string `json:"account"`
// The name of the Machine Image
Name string `json:name`
}
// GetMachineImage retrieves the MachineImage with the given name.
func (c *MachineImagesClient) GetMachineImage(getInput *GetMachineImageInput) (*MachineImage, error) {
getInput.Name = c.getQualifiedName(getInput.Name)
var machineImage MachineImage
if err := c.getResource(getInput.Name, &machineImage); err != nil {
return nil, err
}
return c.success(&machineImage)
}
func (c *MachineImagesClient) success(result *MachineImage) (*MachineImage, error) {
c.unqualify(&result.Name)
return result, nil
}

View File

@ -0,0 +1,451 @@
package compute
import (
"fmt"
"time"
"github.com/hashicorp/go-oracle-terraform/client"
)
const WaitForOrchestrationActiveTimeout = time.Duration(3600 * time.Second)
const WaitForOrchestrationDeleteTimeout = time.Duration(3600 * time.Second)
// OrchestrationsClient is a client for the Orchestration functions of the Compute API.
type OrchestrationsClient struct {
ResourceClient
}
// Orchestrations obtains an OrchestrationsClient which can be used to access to the
// Orchestration functions of the Compute API
func (c *ComputeClient) Orchestrations() *OrchestrationsClient {
return &OrchestrationsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "Orchestration",
ContainerPath: "/platform/v1/orchestration/",
ResourceRootPath: "/platform/v1/orchestration",
}}
}
type OrchestrationDesiredState string
const (
// * active: Creates all the orchestration objects defined in the orchestration.
OrchestrationDesiredStateActive OrchestrationDesiredState = "active"
// * inactive: Adds the orchestration to Oracle Compute Cloud Service, but does not create any of the orchestration
OrchestrationDesiredStateInactive OrchestrationDesiredState = "inactive"
// * suspended: Suspends all orchestration objects defined in the orchestration
OrchestrationDesiredStateSuspend OrchestrationDesiredState = "suspend"
)
type OrchestrationStatus string
const (
OrchestrationStatusActive OrchestrationStatus = "active"
OrchestrationStatusInactive OrchestrationStatus = "inactive"
OrchestrationStatusSuspend OrchestrationStatus = "suspend"
OrchestrationStatusActivating OrchestrationStatus = "activating"
OrchestrationStatusDeleting OrchestrationStatus = "deleting"
OrchestrationStatusError OrchestrationStatus = "terminal_error"
OrchestrationStatusStopping OrchestrationStatus = "stopping"
OrchestrationStatusSuspending OrchestrationStatus = "suspending"
OrchestrationStatusStarting OrchestrationStatus = "starting"
OrchestrationStatusDeactivating OrchestrationStatus = "deactivating"
OrchestrationStatusSuspended OrchestrationStatus = "suspended"
)
type OrchestrationType string
const (
OrchestrationTypeInstance OrchestrationType = "Instance"
)
// OrchestrationInfo describes an existing Orchestration.
type Orchestration struct {
// The default Oracle Compute Cloud Service account, such as /Compute-acme/default.
Account string `json:"account"`
// Description of this orchestration
Description string `json:"description"`
// The desired_state specified in the orchestration JSON file. A unique identifier for this orchestration.
DesiredState OrchestrationDesiredState `json:"desired_state"`
// Unique identifier of this orchestration
ID string `json:"id"`
// The three-part name of the Orchestration (/Compute-identity_domain/user/object).
Name string `json:"name"`
// List of orchestration objects
Objects []Object `json:"objects"`
// Current status of this orchestration
Status OrchestrationStatus `json:"status"`
// Strings that describe the orchestration and help you identify it.
Tags []string `json:"tags"`
// Time the orchestration was last audited
TimeAudited string `json:"time_audited"`
// The time when the orchestration was added to Oracle Compute Cloud Service.
TimeCreated string `json:"time_created"`
// The time when the orchestration was last updated in Oracle Compute Cloud Service.
TimeUpdated string `json:"time_updated"`
// Unique Resource Identifier
URI string `json:"uri"`
// Name of the user who added this orchestration or made the most recent update to this orchestration.
User string `json:"user"`
// Version of this orchestration. It is automatically generated by the server.
Version int `json:"version"`
}
// CreateOrchestrationInput defines an Orchestration to be created.
type CreateOrchestrationInput struct {
// The default Oracle Compute Cloud Service account, such as /Compute-acme/default.
// Optional
Account string `json:"account,omitempty"`
// Description of this orchestration
// Optional
Description string `json:"description,omitempty"`
// Specify the desired state of this orchestration: active, inactive, or suspend.
// You can manage the state of the orchestration objects by changing the desired state of the orchestration.
// * active: Creates all the orchestration objects defined in the orchestration.
// * inactive: Adds the orchestration to Oracle Compute Cloud Service, but does not create any of the orchestration
// objects defined in the orchestration.
// Required
DesiredState OrchestrationDesiredState `json:"desired_state"`
// The three-part name of the Orchestration (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// The list of objects in the orchestration. An object is the primary building block of an orchestration.
// An orchestration can contain up to 100 objects.
// Required
Objects []Object `json:"objects"`
// Strings that describe the orchestration and help you identify it.
Tags []string `json:"tags,omitempty"`
// Version of this orchestration. It is automatically generated by the server.
Version int `json:"version,omitempty"`
// Time to wait for an orchestration to be ready
Timeout time.Duration `json:"-"`
}
type Object struct {
// The default Oracle Compute Cloud Service account, such as /Compute-acme/default.
// Optional
Account string `json:"account,omitempty"`
// Description of this orchestration
// Optional
Description string `json:"description,omitempty"`
// The desired state of the object
// Optional
DesiredState OrchestrationDesiredState `json:"desired_state,omitempty"`
// Dictionary containing the current state of the object
Health Health `json:"health,omitempty"`
// A text string describing the object. Labels can't include spaces. In an orchestration, the label for
// each object must be unique. Maximum length is 256 characters.
// Required
Label string `json:"label"`
// The four-part name of the object (/Compute-identity_domain/user/orchestration/object). If you don't specify a name
// for this object, the name is generated automatically. Object names can contain only alphanumeric characters, hyphens,
// underscores, and periods. Object names are case-sensitive. When you specify the object name, ensure that an object of
// the same type and with the same name doesn't already exist. If such a object already exists, then another
// object of the same type and with the same name won't be created and the existing object won't be updated.
// Optional
Name string `json:"name,omitempty"`
// The three-part name (/Compute-identity_domain/user/object) of the orchestration to which the object belongs.
// Required
Orchestration string `json:"orchestration"`
// Specifies whether the object should persist when the orchestration is suspended. Specify one of the following:
// * true: The object persists when the orchestration is suspended.
// * false: The object is deleted when the orchestration is suspended.
// By default, persistent is set to false. It is recommended that you specify true for storage
// volumes and other critical objects. Persistence applies only when you're suspending an orchestration.
// When you terminate an orchestration, all the objects defined in it are deleted.
// Optional
Persistent bool `json:"persistent,omitempty"`
// The relationship between the objects that are created by this orchestration. The
// only supported relationship is depends, indicating that the specified target objects must be created first.
// Note that when recovering from a failure, the orchestration doesn't consider object relationships.
// Orchestrations v2 use object references to recover interdependent objects to a healthy state. SeeObject
// References and Relationships in Using Oracle Compute Cloud Service (IaaS).
Relationship []Object `json:"relationships,omitempty"`
// The template attribute defines the properties or characteristics of the Oracle Compute Cloud Service object
// that you want to create, as specified by the type attribute.
// The fields in the template section vary depending on the specified type. See Orchestration v2 Attributes
// Specific to Each Object Type in Using Oracle Compute Cloud Service (IaaS) to determine the parameters that are
// specific to each object type that you want to create.
// For example, if you want to create a storage volume, the type would be StorageVolume, and the template would include
// size and bootable. If you want to create an instance, the type would be Instance, and the template would include
// instance-specific attributes, such as imagelist and shape.
// Required
Template interface{} `json:"template"`
// Specify one of the following object types that you want to create.
// The only allowed type is Instance
// Required
Type OrchestrationType `json:"type"`
// Version of this object, generated by the server
// Optional
Version int `json:"version,omitempty"`
}
type Health struct {
// The status of the object
Status OrchestrationStatus `json:"status,omitempty"`
// What caused the status of the object
Cause string `json:"cause,omitempty"`
// The specific details for what happened to the object
Detail string `json:"detail,omitempty"`
// Any errors associated with creation of the object
Error string `json:"error,omitempty"`
}
// CreateOrchestration creates a new Orchestration with the given name, key and enabled flag.
func (c *OrchestrationsClient) CreateOrchestration(input *CreateOrchestrationInput) (*Orchestration, error) {
var createdOrchestration Orchestration
input.Name = c.getQualifiedName(input.Name)
for _, i := range input.Objects {
i.Orchestration = c.getQualifiedName(i.Orchestration)
if i.Type == OrchestrationTypeInstance {
instanceClient := c.ComputeClient.Instances()
instanceInput := i.Template.(*CreateInstanceInput)
instanceInput.Name = c.getQualifiedName(instanceInput.Name)
qualifiedSSHKeys := []string{}
for _, key := range instanceInput.SSHKeys {
qualifiedSSHKeys = append(qualifiedSSHKeys, c.getQualifiedName(key))
}
instanceInput.SSHKeys = qualifiedSSHKeys
qualifiedStorageAttachments := []StorageAttachmentInput{}
for _, attachment := range instanceInput.Storage {
qualifiedStorageAttachments = append(qualifiedStorageAttachments, StorageAttachmentInput{
Index: attachment.Index,
Volume: c.getQualifiedName(attachment.Volume),
})
}
instanceInput.Storage = qualifiedStorageAttachments
instanceInput.Networking = instanceClient.qualifyNetworking(instanceInput.Networking)
}
}
if err := c.createResource(&input, &createdOrchestration); err != nil {
return nil, err
}
// Call wait for orchestration ready now, as creating the orchestration is an eventually consistent operation
getInput := &GetOrchestrationInput{
Name: createdOrchestration.Name,
}
if input.Timeout == 0 {
input.Timeout = WaitForOrchestrationActiveTimeout
}
// Wait for orchestration to be ready and return the result
// Don't have to unqualify any objects, as the GetOrchestration method will handle that
orchestrationInfo, orchestrationError := c.WaitForOrchestrationState(getInput, input.Timeout)
if orchestrationError != nil {
deleteInput := &DeleteOrchestrationInput{
Name: createdOrchestration.Name,
}
err := c.DeleteOrchestration(deleteInput)
if err != nil {
return nil, fmt.Errorf("Error deleting orchestration %s: %s", getInput.Name, err)
}
return nil, fmt.Errorf("Error creating orchestration %s: %s", getInput.Name, orchestrationError)
}
return &orchestrationInfo, nil
}
// GetOrchestrationInput describes the Orchestration to get
type GetOrchestrationInput struct {
// The three-part name of the Orchestration (/Compute-identity_domain/user/object).
Name string `json:name`
}
// GetOrchestration retrieves the Orchestration with the given name.
func (c *OrchestrationsClient) GetOrchestration(input *GetOrchestrationInput) (*Orchestration, error) {
var orchestrationInfo Orchestration
if err := c.getResource(input.Name, &orchestrationInfo); err != nil {
return nil, err
}
return c.success(&orchestrationInfo)
}
// UpdateOrchestrationInput defines an Orchestration to be updated
type UpdateOrchestrationInput struct {
// The default Oracle Compute Cloud Service account, such as /Compute-acme/default.
// Optional
Account string `json:"account,omitempty"`
// Description of this orchestration
// Optional
Description string `json:"description,omitempty"`
// Specify the desired state of this orchestration: active, inactive, or suspend.
// You can manage the state of the orchestration objects by changing the desired state of the orchestration.
// * active: Creates all the orchestration objects defined in the orchestration.
// * inactive: Adds the orchestration to Oracle Compute Cloud Service, but does not create any of the orchestration
// objects defined in the orchestration.
// Required
DesiredState OrchestrationDesiredState `json:"desired_state"`
// The three-part name of the Orchestration (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// The list of objects in the orchestration. An object is the primary building block of an orchestration.
// An orchestration can contain up to 100 objects.
// Required
Objects []Object `json:"objects"`
// Strings that describe the orchestration and help you identify it.
Tags []string `json:"tags,omitempty"`
// Version of this orchestration. It is automatically generated by the server.
Version int `json:"version,omitempty"`
// Time to wait for an orchestration to be ready
Timeout time.Duration `json:"-"`
}
// UpdateOrchestration updates the orchestration.
func (c *OrchestrationsClient) UpdateOrchestration(input *UpdateOrchestrationInput) (*Orchestration, error) {
var updatedOrchestration Orchestration
input.Name = c.getQualifiedName(input.Name)
for _, i := range input.Objects {
i.Orchestration = c.getQualifiedName(i.Orchestration)
if i.Type == OrchestrationTypeInstance {
instanceInput := i.Template.(map[string]interface{})
instanceInput["name"] = c.getQualifiedName(instanceInput["name"].(string))
}
}
if err := c.updateResource(input.Name, input, &updatedOrchestration); err != nil {
return nil, err
}
// Call wait for orchestration ready now, as creating the orchestration is an eventually consistent operation
getInput := &GetOrchestrationInput{
Name: updatedOrchestration.Name,
}
if input.Timeout == 0 {
input.Timeout = WaitForOrchestrationActiveTimeout
}
// Wait for orchestration to be ready and return the result
// Don't have to unqualify any objects, as the GetOrchestration method will handle that
orchestrationInfo, orchestrationError := c.WaitForOrchestrationState(getInput, input.Timeout)
if orchestrationError != nil {
return nil, orchestrationError
}
return &orchestrationInfo, nil
}
// DeleteOrchestrationInput describes the Orchestration to delete
type DeleteOrchestrationInput struct {
// The three-part name of the Orchestration (/Compute-identity_domain/user/object).
// Required
Name string `json:name`
// Timeout for delete request
Timeout time.Duration `json:"-"`
}
// DeleteOrchestration deletes the Orchestration with the given name.
func (c *OrchestrationsClient) DeleteOrchestration(input *DeleteOrchestrationInput) error {
if err := c.deleteOrchestration(input.Name); err != nil {
return err
}
if input.Timeout == 0 {
input.Timeout = WaitForOrchestrationDeleteTimeout
}
return c.WaitForOrchestrationDeleted(input, input.Timeout)
}
func (c *OrchestrationsClient) success(info *Orchestration) (*Orchestration, error) {
c.unqualify(&info.Name)
for _, i := range info.Objects {
c.unqualify(&i.Orchestration)
if OrchestrationType(i.Type) == OrchestrationTypeInstance {
instanceInput := i.Template.(map[string]interface{})
instanceInput["name"] = c.getUnqualifiedName(instanceInput["name"].(string))
}
}
return info, nil
}
// WaitForOrchestrationActive waits for an orchestration to be completely initialized and available.
func (c *OrchestrationsClient) WaitForOrchestrationState(input *GetOrchestrationInput, timeout time.Duration) (Orchestration, error) {
var info *Orchestration
var getErr error
err := c.client.WaitFor("orchestration to be ready", timeout, func() (bool, error) {
info, getErr = c.GetOrchestration(input)
if getErr != nil {
return false, getErr
}
c.client.DebugLogString(fmt.Sprintf("Orchestration name is %v, Orchestration info is %+v", info.Name, info))
switch s := info.Status; s {
case OrchestrationStatusError:
// We need to check and see if an object the orchestration is trying to create is giving us an error instead of just the orchestration as a whole.
for _, object := range info.Objects {
if object.Health.Status == OrchestrationStatusError {
return false, fmt.Errorf("Error creating instance %s: %+v", object.Name, object.Health)
}
}
return false, fmt.Errorf("Error initializing orchestration: %+v", info)
case OrchestrationStatus(info.DesiredState):
c.client.DebugLogString(fmt.Sprintf("Orchestration %s", info.DesiredState))
return true, nil
case OrchestrationStatusActivating:
c.client.DebugLogString("Orchestration activating")
return false, nil
case OrchestrationStatusStopping:
c.client.DebugLogString("Orchestration stopping")
return false, nil
case OrchestrationStatusSuspending:
c.client.DebugLogString("Orchestration suspending")
return false, nil
case OrchestrationStatusDeactivating:
c.client.DebugLogString("Orchestration deactivating")
return false, nil
case OrchestrationStatusSuspended:
c.client.DebugLogString("Orchestration suspended")
if info.DesiredState == OrchestrationDesiredStateSuspend {
return true, nil
} else {
return false, nil
}
default:
return false, fmt.Errorf("Unknown orchestration state: %s, erroring", s)
}
})
return *info, err
}
// WaitForOrchestrationDeleted waits for an orchestration to be fully deleted.
func (c *OrchestrationsClient) WaitForOrchestrationDeleted(input *DeleteOrchestrationInput, timeout time.Duration) error {
return c.client.WaitFor("orchestration to be deleted", timeout, func() (bool, error) {
var info Orchestration
if err := c.getResource(input.Name, &info); err != nil {
if client.WasNotFoundError(err) {
// Orchestration could not be found, thus deleted
return true, nil
}
// Some other error occurred trying to get Orchestration, exit
return false, err
}
switch s := info.Status; s {
case OrchestrationStatusError:
return false, fmt.Errorf("Error stopping orchestration: %+v", info)
case OrchestrationStatusStopping:
c.client.DebugLogString("Orchestration stopping")
return false, nil
case OrchestrationStatusDeleting:
c.client.DebugLogString("Orchestration deleting")
return false, nil
case OrchestrationStatusActive:
c.client.DebugLogString("Orchestration active")
return false, nil
default:
return false, fmt.Errorf("Unknown orchestration state: %s, erroring", s)
}
})
}

View File

@ -0,0 +1,153 @@
package compute
const (
RoutesDescription = "IP Network Route"
RoutesContainerPath = "/network/v1/route/"
RoutesResourcePath = "/network/v1/route"
)
type RoutesClient struct {
ResourceClient
}
func (c *ComputeClient) Routes() *RoutesClient {
return &RoutesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: RoutesDescription,
ContainerPath: RoutesContainerPath,
ResourceRootPath: RoutesResourcePath,
},
}
}
type RouteInfo struct {
// Admin distance associated with this route
AdminDistance int `json:"adminDistance"`
// Description of the route
Description string `json:"description"`
// CIDR IPv4 Prefix associated with this route
IPAddressPrefix string `json:"ipAddressPrefix"`
// Name of the route
Name string `json:"name"`
// Name of the VNIC set associated with the route
NextHopVnicSet string `json:"nextHopVnicSet"`
// Slice of Tags associated with the route
Tags []string `json:"tags,omitempty"`
// Uniform resource identifier associated with the route
Uri string `json:"uri"`
}
type CreateRouteInput struct {
// Specify 0,1, or 2 as the route's administrative distance.
// If you do not specify a value, the default value is 0.
// The same prefix can be used in multiple routes. In this case, packets are routed over all the matching
// routes with the lowest administrative distance.
// In the case multiple routes with the same lowest administrative distance match,
// routing occurs over all these routes using ECMP.
// Optional
AdminDistance int `json:"adminDistance"`
// Description of the route
// Optional
Description string `json:"description"`
// The IPv4 address prefix in CIDR format, of the external network (external to the vNIC set)
// from which you want to route traffic
// Required
IPAddressPrefix string `json:"ipAddressPrefix"`
// Name of the route.
// Names can only contain alphanumeric, underscore, dash, and period characters. Case-sensitive
// Required
Name string `json:"name"`
// Name of the virtual NIC set to route matching packets to.
// Routed flows are load-balanced among all the virtual NICs in the virtual NIC set
// Required
NextHopVnicSet string `json:"nextHopVnicSet"`
// Slice of tags to be associated with the route
// Optional
Tags []string `json:"tags,omitempty"`
}
func (c *RoutesClient) CreateRoute(input *CreateRouteInput) (*RouteInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.NextHopVnicSet = c.getQualifiedName(input.NextHopVnicSet)
var routeInfo RouteInfo
if err := c.createResource(&input, &routeInfo); err != nil {
return nil, err
}
return c.success(&routeInfo)
}
type GetRouteInput struct {
// Name of the Route to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *RoutesClient) GetRoute(input *GetRouteInput) (*RouteInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var routeInfo RouteInfo
if err := c.getResource(input.Name, &routeInfo); err != nil {
return nil, err
}
return c.success(&routeInfo)
}
type UpdateRouteInput struct {
// Specify 0,1, or 2 as the route's administrative distance.
// If you do not specify a value, the default value is 0.
// The same prefix can be used in multiple routes. In this case, packets are routed over all the matching
// routes with the lowest administrative distance.
// In the case multiple routes with the same lowest administrative distance match,
// routing occurs over all these routes using ECMP.
// Optional
AdminDistance int `json:"adminDistance"`
// Description of the route
// Optional
Description string `json:"description"`
// The IPv4 address prefix in CIDR format, of the external network (external to the vNIC set)
// from which you want to route traffic
// Required
IPAddressPrefix string `json:"ipAddressPrefix"`
// Name of the route.
// Names can only contain alphanumeric, underscore, dash, and period characters. Case-sensitive
// Required
Name string `json:"name"`
// Name of the virtual NIC set to route matching packets to.
// Routed flows are load-balanced among all the virtual NICs in the virtual NIC set
// Required
NextHopVnicSet string `json:"nextHopVnicSet"`
// Slice of tags to be associated with the route
// Optional
Tags []string `json:"tags"`
}
func (c *RoutesClient) UpdateRoute(input *UpdateRouteInput) (*RouteInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.NextHopVnicSet = c.getQualifiedName(input.NextHopVnicSet)
var routeInfo RouteInfo
if err := c.updateResource(input.Name, &input, &routeInfo); err != nil {
return nil, err
}
return c.success(&routeInfo)
}
type DeleteRouteInput struct {
// Name of the Route to delete. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *RoutesClient) DeleteRoute(input *DeleteRouteInput) error {
return c.deleteResource(input.Name)
}
func (c *RoutesClient) success(info *RouteInfo) (*RouteInfo, error) {
c.unqualify(&info.Name)
c.unqualify(&info.NextHopVnicSet)
return info, nil
}

View File

@ -0,0 +1,193 @@
package compute
// SecRulesClient is a client for the Sec Rules functions of the Compute API.
type SecRulesClient struct {
ResourceClient
}
// SecRules obtains a SecRulesClient which can be used to access to the
// Sec Rules functions of the Compute API
func (c *ComputeClient) SecRules() *SecRulesClient {
return &SecRulesClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "security ip list",
ContainerPath: "/secrule/",
ResourceRootPath: "/secrule",
}}
}
// SecRuleInfo describes an existing sec rule.
type SecRuleInfo struct {
// Set this parameter to PERMIT.
Action string `json:"action"`
// The name of the security application
Application string `json:"application"`
// A description of the sec rule
Description string `json:"description"`
// Indicates whether the security rule is enabled
Disabled bool `json:"disabled"`
// The name of the destination security list or security IP list.
DestinationList string `json:"dst_list"`
// The name of the sec rule
Name string `json:"name"`
// The name of the source security list or security IP list.
SourceList string `json:"src_list"`
// Uniform Resource Identifier for the sec rule
URI string `json:"uri"`
}
// CreateSecRuleInput defines a sec rule to be created.
type CreateSecRuleInput struct {
// Set this parameter to PERMIT.
// Required
Action string `json:"action"`
// The name of the security application for user-defined or predefined security applications.
// Required
Application string `json:"application"`
// Description of the IP Network
// Optional
Description string `json:"description"`
// Indicates whether the sec rule is enabled (set to false) or disabled (true).
// The default setting is false.
// Optional
Disabled bool `json:"disabled"`
// The name of the destination security list or security IP list.
//
// You must use the prefix seclist: or seciplist: to identify the list type.
//
// You can specify a security IP list as the destination in a secrule,
// provided src_list is a security list that has DENY as its outbound policy.
//
// You cannot specify any of the security IP lists in the /oracle/public container
// as a destination in a secrule.
// Required
DestinationList string `json:"dst_list"`
// The name of the Sec Rule to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// The name of the source security list or security IP list.
//
// You must use the prefix seclist: or seciplist: to identify the list type.
//
// Required
SourceList string `json:"src_list"`
}
// CreateSecRule creates a new sec rule.
func (c *SecRulesClient) CreateSecRule(createInput *CreateSecRuleInput) (*SecRuleInfo, error) {
createInput.Name = c.getQualifiedName(createInput.Name)
createInput.SourceList = c.getQualifiedListName(createInput.SourceList)
createInput.DestinationList = c.getQualifiedListName(createInput.DestinationList)
createInput.Application = c.getQualifiedName(createInput.Application)
var ruleInfo SecRuleInfo
if err := c.createResource(createInput, &ruleInfo); err != nil {
return nil, err
}
return c.success(&ruleInfo)
}
// GetSecRuleInput describes the Sec Rule to get
type GetSecRuleInput struct {
// The name of the Sec Rule to query for
// Required
Name string `json:"name"`
}
// GetSecRule retrieves the sec rule with the given name.
func (c *SecRulesClient) GetSecRule(getInput *GetSecRuleInput) (*SecRuleInfo, error) {
var ruleInfo SecRuleInfo
if err := c.getResource(getInput.Name, &ruleInfo); err != nil {
return nil, err
}
return c.success(&ruleInfo)
}
// UpdateSecRuleInput describes a secruity rule to update
type UpdateSecRuleInput struct {
// Set this parameter to PERMIT.
// Required
Action string `json:"action"`
// The name of the security application for user-defined or predefined security applications.
// Required
Application string `json:"application"`
// Description of the IP Network
// Optional
Description string `json:"description"`
// Indicates whether the sec rule is enabled (set to false) or disabled (true).
// The default setting is false.
// Optional
Disabled bool `json:"disabled"`
// The name of the destination security list or security IP list.
//
// You must use the prefix seclist: or seciplist: to identify the list type.
//
// You can specify a security IP list as the destination in a secrule,
// provided src_list is a security list that has DENY as its outbound policy.
//
// You cannot specify any of the security IP lists in the /oracle/public container
// as a destination in a secrule.
// Required
DestinationList string `json:"dst_list"`
// The name of the Sec Rule to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// The name of the source security list or security IP list.
//
// You must use the prefix seclist: or seciplist: to identify the list type.
//
// Required
SourceList string `json:"src_list"`
}
// UpdateSecRule modifies the properties of the sec rule with the given name.
func (c *SecRulesClient) UpdateSecRule(updateInput *UpdateSecRuleInput) (*SecRuleInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
updateInput.SourceList = c.getQualifiedListName(updateInput.SourceList)
updateInput.DestinationList = c.getQualifiedListName(updateInput.DestinationList)
updateInput.Application = c.getQualifiedName(updateInput.Application)
var ruleInfo SecRuleInfo
if err := c.updateResource(updateInput.Name, updateInput, &ruleInfo); err != nil {
return nil, err
}
return c.success(&ruleInfo)
}
// DeleteSecRuleInput describes the sec rule to delete
type DeleteSecRuleInput struct {
// The name of the Sec Rule to delete.
// Required
Name string `json:"name"`
}
// DeleteSecRule deletes the sec rule with the given name.
func (c *SecRulesClient) DeleteSecRule(deleteInput *DeleteSecRuleInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *SecRulesClient) success(ruleInfo *SecRuleInfo) (*SecRuleInfo, error) {
ruleInfo.Name = c.getUnqualifiedName(ruleInfo.Name)
ruleInfo.SourceList = c.unqualifyListName(ruleInfo.SourceList)
ruleInfo.DestinationList = c.unqualifyListName(ruleInfo.DestinationList)
ruleInfo.Application = c.getUnqualifiedName(ruleInfo.Application)
return ruleInfo, nil
}

View File

@ -0,0 +1,150 @@
package compute
// SecurityApplicationsClient is a client for the Security Application functions of the Compute API.
type SecurityApplicationsClient struct {
ResourceClient
}
// SecurityApplications obtains a SecurityApplicationsClient which can be used to access to the
// Security Application functions of the Compute API
func (c *ComputeClient) SecurityApplications() *SecurityApplicationsClient {
return &SecurityApplicationsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "security application",
ContainerPath: "/secapplication/",
ResourceRootPath: "/secapplication",
}}
}
// SecurityApplicationInfo describes an existing security application.
type SecurityApplicationInfo struct {
// A description of the security application.
Description string `json:"description"`
// The TCP or UDP destination port number. This can be a port range, such as 5900-5999 for TCP.
DPort string `json:"dport"`
// The ICMP code.
ICMPCode SecurityApplicationICMPCode `json:"icmpcode"`
// The ICMP type.
ICMPType SecurityApplicationICMPType `json:"icmptype"`
// The three-part name of the Security Application (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The protocol to use.
Protocol SecurityApplicationProtocol `json:"protocol"`
// The Uniform Resource Identifier
URI string `json:"uri"`
}
type SecurityApplicationProtocol string
const (
All SecurityApplicationProtocol = "all"
AH SecurityApplicationProtocol = "ah"
ESP SecurityApplicationProtocol = "esp"
ICMP SecurityApplicationProtocol = "icmp"
ICMPV6 SecurityApplicationProtocol = "icmpv6"
IGMP SecurityApplicationProtocol = "igmp"
IPIP SecurityApplicationProtocol = "ipip"
GRE SecurityApplicationProtocol = "gre"
MPLSIP SecurityApplicationProtocol = "mplsip"
OSPF SecurityApplicationProtocol = "ospf"
PIM SecurityApplicationProtocol = "pim"
RDP SecurityApplicationProtocol = "rdp"
SCTP SecurityApplicationProtocol = "sctp"
TCP SecurityApplicationProtocol = "tcp"
UDP SecurityApplicationProtocol = "udp"
)
type SecurityApplicationICMPCode string
const (
Admin SecurityApplicationICMPCode = "admin"
Df SecurityApplicationICMPCode = "df"
Host SecurityApplicationICMPCode = "host"
Network SecurityApplicationICMPCode = "network"
Port SecurityApplicationICMPCode = "port"
Protocol SecurityApplicationICMPCode = "protocol"
)
type SecurityApplicationICMPType string
const (
Echo SecurityApplicationICMPType = "echo"
Reply SecurityApplicationICMPType = "reply"
TTL SecurityApplicationICMPType = "ttl"
TraceRoute SecurityApplicationICMPType = "traceroute"
Unreachable SecurityApplicationICMPType = "unreachable"
)
func (c *SecurityApplicationsClient) success(result *SecurityApplicationInfo) (*SecurityApplicationInfo, error) {
c.unqualify(&result.Name)
return result, nil
}
// CreateSecurityApplicationInput describes the Security Application to create
type CreateSecurityApplicationInput struct {
// A description of the security application.
// Optional
Description string `json:"description"`
// The TCP or UDP destination port number.
// You can also specify a port range, such as 5900-5999 for TCP.
// This parameter isn't relevant to the icmp protocol.
// Required if the Protocol is TCP or UDP
DPort string `json:"dport"`
// The ICMP code. This parameter is relevant only if you specify ICMP as the protocol.
// If you specify icmp as the protocol and don't specify icmptype or icmpcode, then all ICMP packets are matched.
// Optional
ICMPCode SecurityApplicationICMPCode `json:"icmpcode,omitempty"`
// This parameter is relevant only if you specify ICMP as the protocol.
// If you specify icmp as the protocol and don't specify icmptype or icmpcode, then all ICMP packets are matched.
// Optional
ICMPType SecurityApplicationICMPType `json:"icmptype,omitempty"`
// The three-part name of the Security Application (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// The protocol to use.
// Required
Protocol SecurityApplicationProtocol `json:"protocol"`
}
// CreateSecurityApplication creates a new security application.
func (c *SecurityApplicationsClient) CreateSecurityApplication(input *CreateSecurityApplicationInput) (*SecurityApplicationInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var appInfo SecurityApplicationInfo
if err := c.createResource(&input, &appInfo); err != nil {
return nil, err
}
return c.success(&appInfo)
}
// GetSecurityApplicationInput describes the Security Application to obtain
type GetSecurityApplicationInput struct {
// The three-part name of the Security Application (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// GetSecurityApplication retrieves the security application with the given name.
func (c *SecurityApplicationsClient) GetSecurityApplication(input *GetSecurityApplicationInput) (*SecurityApplicationInfo, error) {
var appInfo SecurityApplicationInfo
if err := c.getResource(input.Name, &appInfo); err != nil {
return nil, err
}
return c.success(&appInfo)
}
// DeleteSecurityApplicationInput describes the Security Application to delete
type DeleteSecurityApplicationInput struct {
// The three-part name of the Security Application (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// DeleteSecurityApplication deletes the security application with the given name.
func (c *SecurityApplicationsClient) DeleteSecurityApplication(input *DeleteSecurityApplicationInput) error {
return c.deleteResource(input.Name)
}

View File

@ -0,0 +1,95 @@
package compute
// SecurityAssociationsClient is a client for the Security Association functions of the Compute API.
type SecurityAssociationsClient struct {
ResourceClient
}
// SecurityAssociations obtains a SecurityAssociationsClient which can be used to access to the
// Security Association functions of the Compute API
func (c *ComputeClient) SecurityAssociations() *SecurityAssociationsClient {
return &SecurityAssociationsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "security association",
ContainerPath: "/secassociation/",
ResourceRootPath: "/secassociation",
}}
}
// SecurityAssociationInfo describes an existing security association.
type SecurityAssociationInfo struct {
// The three-part name of the Security Association (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The name of the Security List that you want to associate with the instance.
SecList string `json:"seclist"`
// vCable of the instance that you want to associate with the security list.
VCable string `json:"vcable"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateSecurityAssociationInput defines a security association to be created.
type CreateSecurityAssociationInput struct {
// The three-part name of the Security Association (/Compute-identity_domain/user/object).
// If you don't specify a name for this object, then the name is generated automatically.
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Optional
Name string `json:"name"`
// The name of the Security list that you want to associate with the instance.
// Required
SecList string `json:"seclist"`
// The name of the vCable of the instance that you want to associate with the security list.
// Required
VCable string `json:"vcable"`
}
// CreateSecurityAssociation creates a security association between the given VCable and security list.
func (c *SecurityAssociationsClient) CreateSecurityAssociation(createInput *CreateSecurityAssociationInput) (*SecurityAssociationInfo, error) {
if createInput.Name != "" {
createInput.Name = c.getQualifiedName(createInput.Name)
}
createInput.VCable = c.getQualifiedName(createInput.VCable)
createInput.SecList = c.getQualifiedName(createInput.SecList)
var assocInfo SecurityAssociationInfo
if err := c.createResource(&createInput, &assocInfo); err != nil {
return nil, err
}
return c.success(&assocInfo)
}
// GetSecurityAssociationInput describes the security association to get
type GetSecurityAssociationInput struct {
// The three-part name of the Security Association (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// GetSecurityAssociation retrieves the security association with the given name.
func (c *SecurityAssociationsClient) GetSecurityAssociation(getInput *GetSecurityAssociationInput) (*SecurityAssociationInfo, error) {
var assocInfo SecurityAssociationInfo
if err := c.getResource(getInput.Name, &assocInfo); err != nil {
return nil, err
}
return c.success(&assocInfo)
}
// DeleteSecurityAssociationInput describes the security association to delete
type DeleteSecurityAssociationInput struct {
// The three-part name of the Security Association (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// DeleteSecurityAssociation deletes the security association with the given name.
func (c *SecurityAssociationsClient) DeleteSecurityAssociation(deleteInput *DeleteSecurityAssociationInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *SecurityAssociationsClient) success(assocInfo *SecurityAssociationInfo) (*SecurityAssociationInfo, error) {
c.unqualify(&assocInfo.Name, &assocInfo.SecList, &assocInfo.VCable)
return assocInfo, nil
}

View File

@ -0,0 +1,113 @@
package compute
// SecurityIPListsClient is a client for the Security IP List functions of the Compute API.
type SecurityIPListsClient struct {
ResourceClient
}
// SecurityIPLists obtains a SecurityIPListsClient which can be used to access to the
// Security IP List functions of the Compute API
func (c *ComputeClient) SecurityIPLists() *SecurityIPListsClient {
return &SecurityIPListsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "security ip list",
ContainerPath: "/seciplist/",
ResourceRootPath: "/seciplist",
}}
}
// SecurityIPListInfo describes an existing security IP list.
type SecurityIPListInfo struct {
// A description of the security IP list.
Description string `json:"description"`
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// A comma-separated list of the subnets (in CIDR format) or IPv4 addresses for which you want to create this security IP list.
SecIPEntries []string `json:"secipentries"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateSecurityIPListInput defines a security IP list to be created.
type CreateSecurityIPListInput struct {
// A description of the security IP list.
// Optional
Description string `json:"description"`
// The three-part name of the object (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// A comma-separated list of the subnets (in CIDR format) or IPv4 addresses for which you want to create this security IP list.
// Required
SecIPEntries []string `json:"secipentries"`
}
// CreateSecurityIPList creates a security IP list with the given name and entries.
func (c *SecurityIPListsClient) CreateSecurityIPList(createInput *CreateSecurityIPListInput) (*SecurityIPListInfo, error) {
createInput.Name = c.getQualifiedName(createInput.Name)
var listInfo SecurityIPListInfo
if err := c.createResource(createInput, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// GetSecurityIPListInput describes the Security IP List to obtain
type GetSecurityIPListInput struct {
// The three-part name of the object (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// GetSecurityIPList gets the security IP list with the given name.
func (c *SecurityIPListsClient) GetSecurityIPList(getInput *GetSecurityIPListInput) (*SecurityIPListInfo, error) {
var listInfo SecurityIPListInfo
if err := c.getResource(getInput.Name, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// UpdateSecurityIPListInput describes the security ip list to update
type UpdateSecurityIPListInput struct {
// A description of the security IP list.
// Optional
Description string `json:"description"`
// The three-part name of the object (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
// A comma-separated list of the subnets (in CIDR format) or IPv4 addresses for which you want to create this security IP list.
// Required
SecIPEntries []string `json:"secipentries"`
}
// UpdateSecurityIPList modifies the entries in the security IP list with the given name.
func (c *SecurityIPListsClient) UpdateSecurityIPList(updateInput *UpdateSecurityIPListInput) (*SecurityIPListInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
var listInfo SecurityIPListInfo
if err := c.updateResource(updateInput.Name, updateInput, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// DeleteSecurityIPListInput describes the security ip list to delete.
type DeleteSecurityIPListInput struct {
// The three-part name of the object (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// DeleteSecurityIPList deletes the security IP list with the given name.
func (c *SecurityIPListsClient) DeleteSecurityIPList(deleteInput *DeleteSecurityIPListInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *SecurityIPListsClient) success(listInfo *SecurityIPListInfo) (*SecurityIPListInfo, error) {
c.unqualify(&listInfo.Name)
return listInfo, nil
}

View File

@ -0,0 +1,131 @@
package compute
// SecurityListsClient is a client for the Security List functions of the Compute API.
type SecurityListsClient struct {
ResourceClient
}
// SecurityLists obtains a SecurityListsClient which can be used to access to the
// Security List functions of the Compute API
func (c *ComputeClient) SecurityLists() *SecurityListsClient {
return &SecurityListsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "security list",
ContainerPath: "/seclist/",
ResourceRootPath: "/seclist",
}}
}
type SecurityListPolicy string
const (
SecurityListPolicyDeny SecurityListPolicy = "deny"
SecurityListPolicyReject SecurityListPolicy = "reject"
SecurityListPolicyPermit SecurityListPolicy = "permit"
)
// SecurityListInfo describes an existing security list.
type SecurityListInfo struct {
// Shows the default account for your identity domain.
Account string `json:"account"`
// A description of the security list.
Description string `json:description`
// The three-part name of the security list (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The policy for outbound traffic from the security list.
OutboundCIDRPolicy SecurityListPolicy `json:"outbound_cidr_policy"`
// The policy for inbound traffic to the security list
Policy SecurityListPolicy `json:"policy"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateSecurityListInput defines a security list to be created.
type CreateSecurityListInput struct {
// A description of the security list.
// Optional
Description string `json:"description"`
// The three-part name of the Security List (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// The policy for outbound traffic from the security list.
// Optional (defaults to `permit`)
OutboundCIDRPolicy SecurityListPolicy `json:"outbound_cidr_policy"`
// The policy for inbound traffic to the security list.
// Optional (defaults to `deny`)
Policy SecurityListPolicy `json:"policy"`
}
// CreateSecurityList creates a new security list with the given name, policy and outbound CIDR policy.
func (c *SecurityListsClient) CreateSecurityList(createInput *CreateSecurityListInput) (*SecurityListInfo, error) {
createInput.Name = c.getQualifiedName(createInput.Name)
var listInfo SecurityListInfo
if err := c.createResource(createInput, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// GetSecurityListInput describes the security list you want to get
type GetSecurityListInput struct {
// The three-part name of the Security List (/Compute-identity_domain/user/object).
// Required
Name string `json:name`
}
// GetSecurityList retrieves the security list with the given name.
func (c *SecurityListsClient) GetSecurityList(getInput *GetSecurityListInput) (*SecurityListInfo, error) {
var listInfo SecurityListInfo
if err := c.getResource(getInput.Name, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// UpdateSecurityListInput defines what to update in a security list
type UpdateSecurityListInput struct {
// A description of the security list.
// Optional
Description string `json:description`
// The three-part name of the Security List (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
// The policy for outbound traffic from the security list.
// Optional (defaults to `permit`)
OutboundCIDRPolicy SecurityListPolicy `json:"outbound_cidr_policy"`
// The policy for inbound traffic to the security list.
// Optional (defaults to `deny`)
Policy SecurityListPolicy `json:"policy"`
}
// UpdateSecurityList updates the policy and outbound CIDR pol
func (c *SecurityListsClient) UpdateSecurityList(updateInput *UpdateSecurityListInput) (*SecurityListInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
var listInfo SecurityListInfo
if err := c.updateResource(updateInput.Name, updateInput, &listInfo); err != nil {
return nil, err
}
return c.success(&listInfo)
}
// DeleteSecurityListInput describes the security list to destroy
type DeleteSecurityListInput struct {
// The three-part name of the Security List (/Compute-identity_domain/user/object).
// Required
Name string `json:name`
}
// DeleteSecurityList deletes the security list with the given name.
func (c *SecurityListsClient) DeleteSecurityList(deleteInput *DeleteSecurityListInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *SecurityListsClient) success(listInfo *SecurityListInfo) (*SecurityListInfo, error) {
c.unqualify(&listInfo.Name)
return listInfo, nil
}

View File

@ -0,0 +1,187 @@
package compute
const (
SecurityProtocolDescription = "security protocol"
SecurityProtocolContainerPath = "/network/v1/secprotocol/"
SecurityProtocolResourcePath = "/network/v1/secprotocol"
)
type SecurityProtocolsClient struct {
ResourceClient
}
// SecurityProtocols() returns an SecurityProtocolsClient that can be used to access the
// necessary CRUD functions for Security Protocols.
func (c *ComputeClient) SecurityProtocols() *SecurityProtocolsClient {
return &SecurityProtocolsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: SecurityProtocolDescription,
ContainerPath: SecurityProtocolContainerPath,
ResourceRootPath: SecurityProtocolResourcePath,
},
}
}
// SecurityProtocolInfo contains the exported fields necessary to hold all the information about an
// Security Protocol
type SecurityProtocolInfo struct {
// List of port numbers or port range strings to match the packet's destination port.
DstPortSet []string `json:"dstPortSet"`
// Protocol used in the data portion of the IP datagram.
IPProtocol string `json:"ipProtocol"`
// List of port numbers or port range strings to match the packet's source port.
SrcPortSet []string `json:"srcPortSet"`
// The name of the Security Protocol
Name string `json:"name"`
// Description of the Security Protocol
Description string `json:"description"`
// Slice of tags associated with the Security Protocol
Tags []string `json:"tags"`
// Uniform Resource Identifier for the Security Protocol
Uri string `json:"uri"`
}
type CreateSecurityProtocolInput struct {
// The name of the Security Protocol to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Description of the SecurityProtocol
// Optional
Description string `json:"description"`
// Enter a list of port numbers or port range strings.
//Traffic is enabled by a security rule when a packet's destination port matches the
// ports specified here.
// For TCP, SCTP, and UDP, each port is a destination transport port, between 0 and 65535,
// inclusive. For ICMP, each port is an ICMP type, between 0 and 255, inclusive.
// If no destination ports are specified, all destination ports or ICMP types are allowed.
// Optional
DstPortSet []string `json:"dstPortSet"`
// The protocol used in the data portion of the IP datagram.
// Specify one of the permitted values or enter a number in the range 0254 to
// represent the protocol that you want to specify. See Assigned Internet Protocol Numbers.
// Permitted values are: tcp, udp, icmp, igmp, ipip, rdp, esp, ah, gre, icmpv6, ospf, pim, sctp,
// mplsip, all.
// Traffic is enabled by a security rule when the protocol in the packet matches the
// protocol specified here. If no protocol is specified, all protocols are allowed.
// Optional
IPProtocol string `json:"ipProtocol"`
// Enter a list of port numbers or port range strings.
// Traffic is enabled by a security rule when a packet's source port matches the
// ports specified here.
// For TCP, SCTP, and UDP, each port is a source transport port,
// between 0 and 65535, inclusive.
// For ICMP, each port is an ICMP type, between 0 and 255, inclusive.
// If no source ports are specified, all source ports or ICMP types are allowed.
// Optional
SrcPortSet []string `json:"srcPortSet"`
// String slice of tags to apply to the Security Protocol object
// Optional
Tags []string `json:"tags"`
}
// Create a new Security Protocol from an SecurityProtocolsClient and an input struct.
// Returns a populated Info struct for the Security Protocol, and any errors
func (c *SecurityProtocolsClient) CreateSecurityProtocol(input *CreateSecurityProtocolInput) (*SecurityProtocolInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo SecurityProtocolInfo
if err := c.createResource(&input, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type GetSecurityProtocolInput struct {
// The name of the Security Protocol to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated SecurityProtocolInfo struct from an input struct
func (c *SecurityProtocolsClient) GetSecurityProtocol(input *GetSecurityProtocolInput) (*SecurityProtocolInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var ipInfo SecurityProtocolInfo
if err := c.getResource(input.Name, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
// UpdateSecurityProtocolInput defines what to update in a security protocol
type UpdateSecurityProtocolInput struct {
// The name of the Security Protocol to create. Object names can only contain alphanumeric,
// underscore, dash, and period characters. Names are case-sensitive.
// Required
Name string `json:"name"`
// Description of the SecurityProtocol
// Optional
Description string `json:"description"`
// Enter a list of port numbers or port range strings.
//Traffic is enabled by a security rule when a packet's destination port matches the
// ports specified here.
// For TCP, SCTP, and UDP, each port is a destination transport port, between 0 and 65535,
// inclusive. For ICMP, each port is an ICMP type, between 0 and 255, inclusive.
// If no destination ports are specified, all destination ports or ICMP types are allowed.
DstPortSet []string `json:"dstPortSet"`
// The protocol used in the data portion of the IP datagram.
// Specify one of the permitted values or enter a number in the range 0254 to
// represent the protocol that you want to specify. See Assigned Internet Protocol Numbers.
// Permitted values are: tcp, udp, icmp, igmp, ipip, rdp, esp, ah, gre, icmpv6, ospf, pim, sctp,
// mplsip, all.
// Traffic is enabled by a security rule when the protocol in the packet matches the
// protocol specified here. If no protocol is specified, all protocols are allowed.
IPProtocol string `json:"ipProtocol"`
// Enter a list of port numbers or port range strings.
// Traffic is enabled by a security rule when a packet's source port matches the
// ports specified here.
// For TCP, SCTP, and UDP, each port is a source transport port,
// between 0 and 65535, inclusive.
// For ICMP, each port is an ICMP type, between 0 and 255, inclusive.
// If no source ports are specified, all source ports or ICMP types are allowed.
SrcPortSet []string `json:"srcPortSet"`
// String slice of tags to apply to the Security Protocol object
// Optional
Tags []string `json:"tags"`
}
// UpdateSecurityProtocol update the security protocol
func (c *SecurityProtocolsClient) UpdateSecurityProtocol(updateInput *UpdateSecurityProtocolInput) (*SecurityProtocolInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
var ipInfo SecurityProtocolInfo
if err := c.updateResource(updateInput.Name, updateInput, &ipInfo); err != nil {
return nil, err
}
return c.success(&ipInfo)
}
type DeleteSecurityProtocolInput struct {
// The name of the Security Protocol to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *SecurityProtocolsClient) DeleteSecurityProtocol(input *DeleteSecurityProtocolInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the SecurityProtocolInfo struct
func (c *SecurityProtocolsClient) success(info *SecurityProtocolInfo) (*SecurityProtocolInfo, error) {
c.unqualify(&info.Name)
return info, nil
}

View File

@ -0,0 +1,266 @@
package compute
const (
SecurityRuleDescription = "security rules"
SecurityRuleContainerPath = "/network/v1/secrule/"
SecurityRuleResourcePath = "/network/v1/secrule"
)
type SecurityRuleClient struct {
ResourceClient
}
// SecurityRules() returns an SecurityRulesClient that can be used to access the
// necessary CRUD functions for Security Rules.
func (c *ComputeClient) SecurityRules() *SecurityRuleClient {
return &SecurityRuleClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: SecurityRuleDescription,
ContainerPath: SecurityRuleContainerPath,
ResourceRootPath: SecurityRuleResourcePath,
},
}
}
// SecurityRuleInfo contains the exported fields necessary to hold all the information about a
// Security Rule
type SecurityRuleInfo struct {
// Name of the ACL that contains this rule.
ACL string `json:"acl"`
// Description of the Security Rule
Description string `json:"description"`
// List of IP address prefix set names to match the packet's destination IP address.
DstIpAddressPrefixSets []string `json:"dstIpAddressPrefixSets"`
// Name of virtual NIC set containing the packet's destination virtual NIC.
DstVnicSet string `json:"dstVnicSet"`
// Allows the security rule to be disabled.
Enabled bool `json:"enabledFlag"`
// Direction of the flow; Can be "egress" or "ingress".
FlowDirection string `json:"FlowDirection"`
// The name of the Security Rule
Name string `json:"name"`
// List of security protocol names to match the packet's protocol and port.
SecProtocols []string `json:"secProtocols"`
// List of multipart names of IP address prefix set to match the packet's source IP address.
SrcIpAddressPrefixSets []string `json:"srcIpAddressPrefixSets"`
// Name of virtual NIC set containing the packet's source virtual NIC.
SrcVnicSet string `json:"srcVnicSet"`
// Slice of tags associated with the Security Rule
Tags []string `json:"tags"`
// Uniform Resource Identifier for the Security Rule
Uri string `json:"uri"`
}
type CreateSecurityRuleInput struct {
//Select the name of the access control list (ACL) that you want to add this
// security rule to. Security rules are applied to vNIC sets by using ACLs.
// Optional
ACL string `json:"acl,omitempty"`
// Description of the Security Rule
// Optional
Description string `json:"description"`
// A list of IP address prefix sets to which you want to permit traffic.
// Only packets to IP addresses in the specified IP address prefix sets are permitted.
// When no destination IP address prefix sets are specified, traffic to any
// IP address is permitted.
// Optional
DstIpAddressPrefixSets []string `json:"dstIpAddressPrefixSets"`
// The vNICset to which you want to permit traffic. Only packets to vNICs in the
// specified vNICset are permitted. When no destination vNICset is specified, traffic
// to any vNIC is permitted.
// Optional
DstVnicSet string `json:"dstVnicSet,omitempty"`
// Allows the security rule to be enabled or disabled. This parameter is set to
// true by default. Specify false to disable the security rule.
// Optional
Enabled bool `json:"enabledFlag"`
// Specify the direction of flow of traffic, which is relative to the instances,
// for this security rule. Allowed values are ingress or egress.
// An ingress packet is a packet received by a virtual NIC, for example from
// another virtual NIC or from the public Internet.
// An egress packet is a packet sent by a virtual NIC, for example to another
// virtual NIC or to the public Internet.
// Required
FlowDirection string `json:"flowDirection"`
// The name of the Security Rule
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods.
// Object names are case-sensitive. When you specify the object name, ensure that an object
// of the same type and with the same name doesn't already exist.
// If such an object already exists, another object of the same type and with the same name won't
// be created and the existing object won't be updated.
// Required
Name string `json:"name"`
// A list of security protocols for which you want to permit traffic. Only packets that
// match the specified protocols and ports are permitted. When no security protocols are
// specified, traffic using any protocol over any port is permitted.
// Optional
SecProtocols []string `json:"secProtocols"`
// A list of IP address prefix sets from which you want to permit traffic. Only packets
// from IP addresses in the specified IP address prefix sets are permitted. When no source
// IP address prefix sets are specified, traffic from any IP address is permitted.
// Optional
SrcIpAddressPrefixSets []string `json:"srcIpAddressPrefixSets"`
// The vNICset from which you want to permit traffic. Only packets from vNICs in the
// specified vNICset are permitted. When no source vNICset is specified, traffic from any
// vNIC is permitted.
// Optional
SrcVnicSet string `json:"srcVnicSet,omitempty"`
// Strings that you can use to tag the security rule.
// Optional
Tags []string `json:"tags"`
}
// Create a new Security Rule from an SecurityRuleClient and an input struct.
// Returns a populated Info struct for the Security Rule, and any errors
func (c *SecurityRuleClient) CreateSecurityRule(input *CreateSecurityRuleInput) (*SecurityRuleInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.ACL = c.getQualifiedName(input.ACL)
input.SrcVnicSet = c.getQualifiedName(input.SrcVnicSet)
input.DstVnicSet = c.getQualifiedName(input.DstVnicSet)
input.SrcIpAddressPrefixSets = c.getQualifiedList(input.SrcIpAddressPrefixSets)
input.DstIpAddressPrefixSets = c.getQualifiedList(input.DstIpAddressPrefixSets)
input.SecProtocols = c.getQualifiedList(input.SecProtocols)
var securityRuleInfo SecurityRuleInfo
if err := c.createResource(&input, &securityRuleInfo); err != nil {
return nil, err
}
return c.success(&securityRuleInfo)
}
type GetSecurityRuleInput struct {
// The name of the Security Rule to query for. Case-sensitive
// Required
Name string `json:"name"`
}
// Returns a populated SecurityRuleInfo struct from an input struct
func (c *SecurityRuleClient) GetSecurityRule(input *GetSecurityRuleInput) (*SecurityRuleInfo, error) {
input.Name = c.getQualifiedName(input.Name)
var securityRuleInfo SecurityRuleInfo
if err := c.getResource(input.Name, &securityRuleInfo); err != nil {
return nil, err
}
return c.success(&securityRuleInfo)
}
// UpdateSecurityRuleInput describes a secruity rule to update
type UpdateSecurityRuleInput struct {
//Select the name of the access control list (ACL) that you want to add this
// security rule to. Security rules are applied to vNIC sets by using ACLs.
// Optional
ACL string `json:"acl,omitempty"`
// Description of the Security Rule
// Optional
Description string `json:"description"`
// A list of IP address prefix sets to which you want to permit traffic.
// Only packets to IP addresses in the specified IP address prefix sets are permitted.
// When no destination IP address prefix sets are specified, traffic to any
// IP address is permitted.
// Optional
DstIpAddressPrefixSets []string `json:"dstIpAddressPrefixSets"`
// The vNICset to which you want to permit traffic. Only packets to vNICs in the
// specified vNICset are permitted. When no destination vNICset is specified, traffic
// to any vNIC is permitted.
// Optional
DstVnicSet string `json:"dstVnicSet,omitempty"`
// Allows the security rule to be enabled or disabled. This parameter is set to
// true by default. Specify false to disable the security rule.
// Optional
Enabled bool `json:"enabledFlag"`
// Specify the direction of flow of traffic, which is relative to the instances,
// for this security rule. Allowed values are ingress or egress.
// An ingress packet is a packet received by a virtual NIC, for example from
// another virtual NIC or from the public Internet.
// An egress packet is a packet sent by a virtual NIC, for example to another
// virtual NIC or to the public Internet.
// Required
FlowDirection string `json:"flowDirection"`
// The name of the Security Rule
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods.
// Object names are case-sensitive. When you specify the object name, ensure that an object
// of the same type and with the same name doesn't already exist.
// If such an object already exists, another object of the same type and with the same name won't
// be created and the existing object won't be updated.
// Required
Name string `json:"name"`
// A list of security protocols for which you want to permit traffic. Only packets that
// match the specified protocols and ports are permitted. When no security protocols are
// specified, traffic using any protocol over any port is permitted.
// Optional
SecProtocols []string `json:"secProtocols"`
// A list of IP address prefix sets from which you want to permit traffic. Only packets
// from IP addresses in the specified IP address prefix sets are permitted. When no source
// IP address prefix sets are specified, traffic from any IP address is permitted.
// Optional
SrcIpAddressPrefixSets []string `json:"srcIpAddressPrefixSets"`
// The vNICset from which you want to permit traffic. Only packets from vNICs in the
// specified vNICset are permitted. When no source vNICset is specified, traffic from any
// vNIC is permitted.
// Optional
SrcVnicSet string `json:"srcVnicSet,omitempty"`
// Strings that you can use to tag the security rule.
// Optional
Tags []string `json:"tags"`
}
// UpdateSecRule modifies the properties of the sec rule with the given name.
func (c *SecurityRuleClient) UpdateSecurityRule(updateInput *UpdateSecurityRuleInput) (*SecurityRuleInfo, error) {
updateInput.Name = c.getQualifiedName(updateInput.Name)
updateInput.ACL = c.getQualifiedName(updateInput.ACL)
updateInput.SrcVnicSet = c.getQualifiedName(updateInput.SrcVnicSet)
updateInput.DstVnicSet = c.getQualifiedName(updateInput.DstVnicSet)
updateInput.SrcIpAddressPrefixSets = c.getQualifiedList(updateInput.SrcIpAddressPrefixSets)
updateInput.DstIpAddressPrefixSets = c.getQualifiedList(updateInput.DstIpAddressPrefixSets)
updateInput.SecProtocols = c.getQualifiedList(updateInput.SecProtocols)
var securityRuleInfo SecurityRuleInfo
if err := c.updateResource(updateInput.Name, updateInput, &securityRuleInfo); err != nil {
return nil, err
}
return c.success(&securityRuleInfo)
}
type DeleteSecurityRuleInput struct {
// The name of the Security Rule to query for. Case-sensitive
// Required
Name string `json:"name"`
}
func (c *SecurityRuleClient) DeleteSecurityRule(input *DeleteSecurityRuleInput) error {
return c.deleteResource(input.Name)
}
// Unqualifies any qualified fields in the IPNetworkExchangeInfo struct
func (c *SecurityRuleClient) success(info *SecurityRuleInfo) (*SecurityRuleInfo, error) {
c.unqualify(&info.Name, &info.ACL, &info.SrcVnicSet, &info.DstVnicSet)
info.SrcIpAddressPrefixSets = c.getUnqualifiedList(info.SrcIpAddressPrefixSets)
info.DstIpAddressPrefixSets = c.getUnqualifiedList(info.DstIpAddressPrefixSets)
info.SecProtocols = c.getUnqualifiedList(info.SecProtocols)
return info, nil
}

View File

@ -0,0 +1,242 @@
package compute
import (
"fmt"
"time"
)
const WaitForSnapshotCompleteTimeout = time.Duration(600 * time.Second)
// SnapshotsClient is a client for the Snapshot functions of the Compute API.
type SnapshotsClient struct {
ResourceClient
}
// Snapshots obtains an SnapshotsClient which can be used to access to the
// Snapshot functions of the Compute API
func (c *ComputeClient) Snapshots() *SnapshotsClient {
return &SnapshotsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "Snapshot",
ContainerPath: "/snapshot/",
ResourceRootPath: "/snapshot",
}}
}
type SnapshotState string
const (
SnapshotActive SnapshotState = "active"
SnapshotComplete SnapshotState = "complete"
SnapshotQueued SnapshotState = "queued"
SnapshotError SnapshotState = "error"
)
type SnapshotDelay string
const (
SnapshotDelayShutdown SnapshotDelay = "shutdown"
)
// SnapshotInfo describes an existing Snapshot.
type Snapshot struct {
// Shows the default account for your identity domain.
Account string `json:"account"`
// Timestamp when this request was created.
CreationTime string `json:"creation_time"`
// Snapshot of the instance is not taken immediately.
Delay SnapshotDelay `json:"delay"`
// A description of the reason this request entered "error" state.
ErrorReason string `json:"error_reason"`
// Name of the instance
Instance string `json:"instance"`
// Name of the machine image generated from the instance snapshot request.
MachineImage string `json:"machineimage"`
// Name of the instance snapshot request.
Name string `json:"name"`
// Not used
Quota string `json:"quota"`
// The state of the request.
State SnapshotState `json:"state"`
// Uniform Resource Identifier
URI string `json:"uri"`
}
// CreateSnapshotInput defines an Snapshot to be created.
type CreateSnapshotInput struct {
// The name of the account that contains the credentials and access details of
// Oracle Storage Cloud Service. The machine image file is uploaded to the Oracle
// Storage Cloud Service account that you specify.
// Optional
Account string `json:"account,omitempty"`
// Use this option when you want to preserve the custom changes you have made
// to an instance before deleting the instance. The only permitted value is shutdown.
// Snapshot of the instance is not taken immediately. It creates a machine image which
// preserves the changes you have made to the instance, and then the instance is deleted.
// Note: This option has no effect if you shutdown the instance from inside it. Any pending
// snapshot request on that instance goes into error state. You must delete the instance
// (DELETE /instance/{name}).
// Optional
Delay SnapshotDelay `json:"delay,omitempty"`
// Name of the instance that you want to clone.
// Required
Instance string `json:"instance"`
// Specify the name of the machine image created by the snapshot request.
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods.
// Object names are case-sensitive.
// If you don't specify a name for this object, then the name is generated automatically.
// Optional
MachineImage string `json:"machineimage,omitempty"`
// Time to wait for snapshot to be completed
Timeout time.Duration `json:"-"`
}
// CreateSnapshot creates a new Snapshot
func (c *SnapshotsClient) CreateSnapshot(input *CreateSnapshotInput) (*Snapshot, error) {
input.Account = c.getQualifiedACMEName(input.Account)
input.Instance = c.getQualifiedName(input.Instance)
input.MachineImage = c.getQualifiedName(input.MachineImage)
var snapshotInfo Snapshot
if err := c.createResource(&input, &snapshotInfo); err != nil {
return nil, err
}
// Call wait for snapshot complete now, as creating the snashot is an eventually consistent operation
getInput := &GetSnapshotInput{
Name: snapshotInfo.Name,
}
if input.Timeout == 0 {
input.Timeout = WaitForSnapshotCompleteTimeout
}
// Wait for snapshot to be complete and return the result
return c.WaitForSnapshotComplete(getInput, input.Timeout)
}
// GetSnapshotInput describes the snapshot to get
type GetSnapshotInput struct {
// The name of the Snapshot
// Required
Name string `json:name`
}
// GetSnapshot retrieves the Snapshot with the given name.
func (c *SnapshotsClient) GetSnapshot(getInput *GetSnapshotInput) (*Snapshot, error) {
getInput.Name = c.getQualifiedName(getInput.Name)
var snapshotInfo Snapshot
if err := c.getResource(getInput.Name, &snapshotInfo); err != nil {
return nil, err
}
return c.success(&snapshotInfo)
}
// DeleteSnapshotInput describes the snapshot to delete
type DeleteSnapshotInput struct {
// The name of the Snapshot
// Required
Snapshot string
// The name of the machine image
// Required
MachineImage string
// Time to wait for snapshot to be deleted
Timeout time.Duration
}
// DeleteSnapshot deletes the Snapshot with the given name.
// A machine image gets created with the associated snapshot and needs to be deleted as well.
func (c *SnapshotsClient) DeleteSnapshot(machineImagesClient *MachineImagesClient, input *DeleteSnapshotInput) error {
// Wait for snapshot complete in case delay is active and the corresponding instance needs to be deleted first
getInput := &GetSnapshotInput{
Name: input.Snapshot,
}
if input.Timeout == 0 {
input.Timeout = WaitForSnapshotCompleteTimeout
}
if _, err := c.WaitForSnapshotComplete(getInput, input.Timeout); err != nil {
return fmt.Errorf("Could not delete snapshot: %s", err)
}
if err := c.deleteResource(input.Snapshot); err != nil {
return fmt.Errorf("Could not delete snapshot: %s", err)
}
deleteMachineImageRequest := &DeleteMachineImageInput{
Name: input.MachineImage,
}
if err := machineImagesClient.DeleteMachineImage(deleteMachineImageRequest); err != nil {
return fmt.Errorf("Could not delete machine image associated with snapshot: %s", err)
}
return nil
}
// DeleteSnapshot deletes the Snapshot with the given name.
// A machine image gets created with the associated snapshot is not deleted
// by this method.
func (c *SnapshotsClient) DeleteSnapshotResourceOnly(input *DeleteSnapshotInput) error {
// Wait for snapshot complete in case delay is active and the corresponding
// instance needs to be deleted first
getInput := &GetSnapshotInput{
Name: input.Snapshot,
}
if input.Timeout == 0 {
input.Timeout = WaitForSnapshotCompleteTimeout
}
if _, err := c.WaitForSnapshotComplete(getInput, input.Timeout); err != nil {
return fmt.Errorf("Could not delete snapshot: %s", err)
}
if err := c.deleteResource(input.Snapshot); err != nil {
return fmt.Errorf("Could not delete snapshot: %s", err)
}
return nil
}
// WaitForSnapshotComplete waits for an snapshot to be completely initialized and available.
func (c *SnapshotsClient) WaitForSnapshotComplete(input *GetSnapshotInput, timeout time.Duration) (*Snapshot, error) {
var info *Snapshot
var getErr error
err := c.client.WaitFor("snapshot to be complete", timeout, func() (bool, error) {
info, getErr = c.GetSnapshot(input)
if getErr != nil {
return false, getErr
}
switch s := info.State; s {
case SnapshotError:
return false, fmt.Errorf("Error initializing snapshot: %s", info.ErrorReason)
case SnapshotComplete:
c.client.DebugLogString("Snapshot Complete")
return true, nil
case SnapshotQueued:
c.client.DebugLogString("Snapshot Queuing")
return false, nil
case SnapshotActive:
c.client.DebugLogString("Snapshot Active")
if info.Delay == SnapshotDelayShutdown {
return true, nil
}
return false, nil
default:
c.client.DebugLogString(fmt.Sprintf("Unknown snapshot state: %s, waiting", s))
return false, nil
}
})
return info, err
}
func (c *SnapshotsClient) success(snapshotInfo *Snapshot) (*Snapshot, error) {
c.unqualify(&snapshotInfo.Account)
c.unqualify(&snapshotInfo.Instance)
c.unqualify(&snapshotInfo.MachineImage)
c.unqualify(&snapshotInfo.Name)
return snapshotInfo, nil
}

View File

@ -0,0 +1,124 @@
package compute
// SSHKeysClient is a client for the SSH key functions of the Compute API.
type SSHKeysClient struct {
ResourceClient
}
// SSHKeys obtains an SSHKeysClient which can be used to access to the
// SSH key functions of the Compute API
func (c *ComputeClient) SSHKeys() *SSHKeysClient {
return &SSHKeysClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "SSH key",
ContainerPath: "/sshkey/",
ResourceRootPath: "/sshkey",
}}
}
// SSHKeyInfo describes an existing SSH key.
type SSHKey struct {
// Indicates whether the key is enabled (true) or disabled.
Enabled bool `json:"enabled"`
// The SSH public key value.
Key string `json:"key"`
// The three-part name of the SSH Key (/Compute-identity_domain/user/object).
Name string `json:"name"`
// Unique Resource Identifier
URI string `json:"uri"`
}
// CreateSSHKeyInput defines an SSH key to be created.
type CreateSSHKeyInput struct {
// The three-part name of the SSH Key (/Compute-identity_domain/user/object).
// Object names can contain only alphanumeric characters, hyphens, underscores, and periods. Object names are case-sensitive.
// Required
Name string `json:"name"`
// The SSH public key value.
// Required
Key string `json:"key"`
// Indicates whether the key must be enabled (default) or disabled. Note that disabled keys cannot be associated with instances.
// To explicitly enable the key, specify true. To disable the key, specify false.
// Optional
Enabled bool `json:"enabled"`
}
// CreateSSHKey creates a new SSH key with the given name, key and enabled flag.
func (c *SSHKeysClient) CreateSSHKey(createInput *CreateSSHKeyInput) (*SSHKey, error) {
var keyInfo SSHKey
// We have to update after create to get the full ssh key into opc
updateSSHKeyInput := UpdateSSHKeyInput{
Name: createInput.Name,
Key: createInput.Key,
Enabled: createInput.Enabled,
}
createInput.Name = c.getQualifiedName(createInput.Name)
if err := c.createResource(&createInput, &keyInfo); err != nil {
return nil, err
}
_, err := c.UpdateSSHKey(&updateSSHKeyInput)
if err != nil {
return nil, err
}
return c.success(&keyInfo)
}
// GetSSHKeyInput describes the ssh key to get
type GetSSHKeyInput struct {
// The three-part name of the SSH Key (/Compute-identity_domain/user/object).
Name string `json:name`
}
// GetSSHKey retrieves the SSH key with the given name.
func (c *SSHKeysClient) GetSSHKey(getInput *GetSSHKeyInput) (*SSHKey, error) {
var keyInfo SSHKey
if err := c.getResource(getInput.Name, &keyInfo); err != nil {
return nil, err
}
return c.success(&keyInfo)
}
// UpdateSSHKeyInput defines an SSH key to be updated
type UpdateSSHKeyInput struct {
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The SSH public key value.
// Required
Key string `json:"key"`
// Indicates whether the key must be enabled (default) or disabled. Note that disabled keys cannot be associated with instances.
// To explicitly enable the key, specify true. To disable the key, specify false.
// Optional
// TODO/NOTE: isn't this required?
Enabled bool `json:"enabled"`
}
// UpdateSSHKey updates the key and enabled flag of the SSH key with the given name.
func (c *SSHKeysClient) UpdateSSHKey(updateInput *UpdateSSHKeyInput) (*SSHKey, error) {
var keyInfo SSHKey
updateInput.Name = c.getQualifiedName(updateInput.Name)
if err := c.updateResource(updateInput.Name, updateInput, &keyInfo); err != nil {
return nil, err
}
return c.success(&keyInfo)
}
// DeleteKeyInput describes the ssh key to delete
type DeleteSSHKeyInput struct {
// The three-part name of the SSH Key (/Compute-identity_domain/user/object).
Name string `json:name`
}
// DeleteSSHKey deletes the SSH key with the given name.
func (c *SSHKeysClient) DeleteSSHKey(deleteInput *DeleteSSHKeyInput) error {
return c.deleteResource(deleteInput.Name)
}
func (c *SSHKeysClient) success(keyInfo *SSHKey) (*SSHKey, error) {
c.unqualify(&keyInfo.Name)
return keyInfo, nil
}

View File

@ -0,0 +1,178 @@
package compute
import (
"time"
"github.com/hashicorp/go-oracle-terraform/client"
)
const WaitForVolumeAttachmentDeleteTimeout = time.Duration(30 * time.Second)
const WaitForVolumeAttachmentReadyTimeout = time.Duration(30 * time.Second)
// StorageAttachmentsClient is a client for the Storage Attachment functions of the Compute API.
type StorageAttachmentsClient struct {
ResourceClient
}
// StorageAttachments obtains a StorageAttachmentsClient which can be used to access to the
// Storage Attachment functions of the Compute API
func (c *ComputeClient) StorageAttachments() *StorageAttachmentsClient {
return &StorageAttachmentsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "storage volume attachment",
ContainerPath: "/storage/attachment/",
ResourceRootPath: "/storage/attachment",
}}
}
type StorageAttachmentState string
const (
Attaching StorageAttachmentState = "attaching"
Attached StorageAttachmentState = "attached"
Detaching StorageAttachmentState = "detaching"
Unavailable StorageAttachmentState = "unavailable"
Unknown StorageAttachmentState = "unknown"
)
// StorageAttachmentInfo describes an existing storage attachment.
type StorageAttachmentInfo struct {
// Name of this attachment, generated by the server.
Name string `json:"name"`
// Index number for the volume. The allowed range is 1-10
// An attachment with index 1 is exposed to the instance as /dev/xvdb, an attachment with index 2 is exposed as /dev/xvdc, and so on.
Index int `json:"index"`
// Multipart name of the instance attached to the storage volume.
InstanceName string `json:"instance_name"`
// Multipart name of the volume attached to the instance.
StorageVolumeName string `json:"storage_volume_name"`
// The State of the Storage Attachment
State StorageAttachmentState `json:"state"`
}
func (c *StorageAttachmentsClient) success(attachmentInfo *StorageAttachmentInfo) (*StorageAttachmentInfo, error) {
c.unqualify(&attachmentInfo.Name, &attachmentInfo.InstanceName, &attachmentInfo.StorageVolumeName)
return attachmentInfo, nil
}
type CreateStorageAttachmentInput struct {
// Index number for the volume. The allowed range is 1-10
// An attachment with index 1 is exposed to the instance as /dev/xvdb, an attachment with index 2 is exposed as /dev/xvdc, and so on.
// Required.
Index int `json:"index"`
// Multipart name of the instance to which you want to attach the volume.
// Required.
InstanceName string `json:"instance_name"`
// Multipart name of the volume that you want to attach.
// Required.
StorageVolumeName string `json:"storage_volume_name"`
// Time to wait for storage volume attachment
Timeout time.Duration `json:"-"`
}
// CreateStorageAttachment creates a storage attachment attaching the given volume to the given instance at the given index.
func (c *StorageAttachmentsClient) CreateStorageAttachment(input *CreateStorageAttachmentInput) (*StorageAttachmentInfo, error) {
input.InstanceName = c.getQualifiedName(input.InstanceName)
var attachmentInfo *StorageAttachmentInfo
if err := c.createResource(&input, &attachmentInfo); err != nil {
return nil, err
}
if input.Timeout == 0 {
input.Timeout = WaitForVolumeAttachmentReadyTimeout
}
return c.waitForStorageAttachmentToFullyAttach(attachmentInfo.Name, input.Timeout)
}
// DeleteStorageAttachmentInput represents the body of an API request to delete a Storage Attachment.
type DeleteStorageAttachmentInput struct {
// The three-part name of the Storage Attachment (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
// Time to wait for storage volume snapshot
Timeout time.Duration `json:"-"`
}
// DeleteStorageAttachment deletes the storage attachment with the given name.
func (c *StorageAttachmentsClient) DeleteStorageAttachment(input *DeleteStorageAttachmentInput) error {
if err := c.deleteResource(input.Name); err != nil {
return err
}
if input.Timeout == 0 {
input.Timeout = WaitForVolumeAttachmentDeleteTimeout
}
return c.waitForStorageAttachmentToBeDeleted(input.Name, input.Timeout)
}
// GetStorageAttachmentInput represents the body of an API request to obtain a Storage Attachment.
type GetStorageAttachmentInput struct {
// The three-part name of the Storage Attachment (/Compute-identity_domain/user/object).
// Required
Name string `json:"name"`
}
// GetStorageAttachment retrieves the storage attachment with the given name.
func (c *StorageAttachmentsClient) GetStorageAttachment(input *GetStorageAttachmentInput) (*StorageAttachmentInfo, error) {
var attachmentInfo *StorageAttachmentInfo
if err := c.getResource(input.Name, &attachmentInfo); err != nil {
return nil, err
}
return c.success(attachmentInfo)
}
// waitForStorageAttachmentToFullyAttach waits for the storage attachment with the given name to be fully attached, or times out.
func (c *StorageAttachmentsClient) waitForStorageAttachmentToFullyAttach(name string, timeout time.Duration) (*StorageAttachmentInfo, error) {
var waitResult *StorageAttachmentInfo
err := c.client.WaitFor("storage attachment to be attached", timeout, func() (bool, error) {
input := &GetStorageAttachmentInput{
Name: name,
}
info, err := c.GetStorageAttachment(input)
if err != nil {
return false, err
}
if info != nil {
if info.State == Attached {
waitResult = info
return true, nil
}
}
return false, nil
})
return waitResult, err
}
// waitForStorageAttachmentToBeDeleted waits for the storage attachment with the given name to be fully deleted, or times out.
func (c *StorageAttachmentsClient) waitForStorageAttachmentToBeDeleted(name string, timeout time.Duration) error {
return c.client.WaitFor("storage attachment to be deleted", timeout, func() (bool, error) {
input := &GetStorageAttachmentInput{
Name: name,
}
_, err := c.GetStorageAttachment(input)
if err != nil {
if client.WasNotFoundError(err) {
return true, nil
}
return false, err
}
return false, nil
})
}

View File

@ -0,0 +1,251 @@
package compute
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/go-oracle-terraform/client"
)
const (
StorageVolumeSnapshotDescription = "storage volume snapshot"
StorageVolumeSnapshotContainerPath = "/storage/snapshot/"
StorageVolumeSnapshotResourcePath = "/storage/snapshot"
WaitForSnapshotCreateTimeout = time.Duration(2400 * time.Second)
WaitForSnapshotDeleteTimeout = time.Duration(1500 * time.Second)
// Collocated Snapshot Property
SnapshotPropertyCollocated = "/oracle/private/storage/snapshot/collocated"
)
// StorageVolumeSnapshotClient is a client for the Storage Volume Snapshot functions of the Compute API.
type StorageVolumeSnapshotClient struct {
ResourceClient
}
func (c *ComputeClient) StorageVolumeSnapshots() *StorageVolumeSnapshotClient {
return &StorageVolumeSnapshotClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: StorageVolumeSnapshotDescription,
ContainerPath: StorageVolumeSnapshotContainerPath,
ResourceRootPath: StorageVolumeSnapshotResourcePath,
},
}
}
// StorageVolumeSnapshotInfo represents the information retrieved from the service about a storage volume snapshot
type StorageVolumeSnapshotInfo struct {
// Account to use for snapshots
Account string `json:"account"`
// Description of the snapshot
Description string `json:"description"`
// The name of the machine image that's used in the boot volume from which this snapshot is taken
MachineImageName string `json:"machineimage_name"`
// Name of the snapshot
Name string `json:"name"`
// String indicating whether the parent volume is bootable or not
ParentVolumeBootable string `json:"parent_volume_bootable"`
// Platform the snapshot is compatible with
Platform string `json:"platform"`
// String determining whether the snapshot is remote or collocated
Property string `json:"property"`
// The size of the snapshot in GB
Size string `json:"size"`
// The ID of the snapshot. Generated by the server
SnapshotID string `json:"snapshot_id"`
// The timestamp of the storage snapshot
SnapshotTimestamp string `json:"snapshot_timestamp"`
// Timestamp for when the operation started
StartTimestamp string `json:"start_timestamp"`
// Status of the snapshot
Status string `json:"status"`
// Status Detail of the storage snapshot
StatusDetail string `json:"status_detail"`
// Indicates the time that the current view of the storage volume snapshot was generated.
StatusTimestamp string `json:"status_timestamp"`
// Array of tags for the snapshot
Tags []string `json:"tags,omitempty"`
// Uniform Resource Identifier
URI string `json:"uri"`
// Name of the parent storage volume for the snapshot
Volume string `json:"volume"`
}
// CreateStorageVolumeSnapshotInput represents the body of an API request to create a new storage volume snapshot
type CreateStorageVolumeSnapshotInput struct {
// Description of the snapshot
// Optional
Description string `json:"description,omitempty"`
// Name of the snapshot
// Optional, will be generated if not specified
Name string `json:"name,omitempty"`
// Whether or not the parent volume is bootable
// Optional
ParentVolumeBootable string `json:"parent_volume_bootable,omitempty"`
// Whether collocated or remote
// Optional, will be remote if unspecified
Property string `json:"property,omitempty"`
// Array of tags for the snapshot
// Optional
Tags []string `json:"tags,omitempty"`
// Name of the volume to create the snapshot from
// Required
Volume string `json:"volume"`
// Timeout to wait for snapshot to be completed. Will use default if unspecified
Timeout time.Duration `json:"-"`
}
// CreateStorageVolumeSnapshot creates a snapshot based on the supplied information struct
func (c *StorageVolumeSnapshotClient) CreateStorageVolumeSnapshot(input *CreateStorageVolumeSnapshotInput) (*StorageVolumeSnapshotInfo, error) {
if input.Name != "" {
input.Name = c.getQualifiedName(input.Name)
}
input.Volume = c.getQualifiedName(input.Volume)
var storageSnapshotInfo StorageVolumeSnapshotInfo
if err := c.createResource(&input, &storageSnapshotInfo); err != nil {
return nil, err
}
if input.Timeout == 0 {
input.Timeout = WaitForSnapshotCreateTimeout
}
// The name of the snapshot could have been generated. Use the response name as input
return c.waitForStorageSnapshotAvailable(storageSnapshotInfo.Name, input.Timeout)
}
// GetStorageVolumeSnapshotInput represents the body of an API request to get information on a storage volume snapshot
type GetStorageVolumeSnapshotInput struct {
// Name of the snapshot
Name string `json:"name"`
}
// GetStorageVolumeSnapshot makes an API request to populate information on a storage volume snapshot
func (c *StorageVolumeSnapshotClient) GetStorageVolumeSnapshot(input *GetStorageVolumeSnapshotInput) (*StorageVolumeSnapshotInfo, error) {
var storageSnapshot StorageVolumeSnapshotInfo
input.Name = c.getQualifiedName(input.Name)
if err := c.getResource(input.Name, &storageSnapshot); err != nil {
if client.WasNotFoundError(err) {
return nil, nil
}
return nil, err
}
return c.success(&storageSnapshot)
}
// DeleteStorageVolumeSnapshotInput represents the body of an API request to delete a storage volume snapshot
type DeleteStorageVolumeSnapshotInput struct {
// Name of the snapshot to delete
Name string `json:"name"`
// Timeout to wait for deletion, will use default if unspecified
Timeout time.Duration `json:"-"`
}
// DeleteStoragevolumeSnapshot makes an API request to delete a storage volume snapshot
func (c *StorageVolumeSnapshotClient) DeleteStorageVolumeSnapshot(input *DeleteStorageVolumeSnapshotInput) error {
input.Name = c.getQualifiedName(input.Name)
if err := c.deleteResource(input.Name); err != nil {
return err
}
if input.Timeout == 0 {
input.Timeout = WaitForSnapshotDeleteTimeout
}
return c.waitForStorageSnapshotDeleted(input.Name, input.Timeout)
}
func (c *StorageVolumeSnapshotClient) success(result *StorageVolumeSnapshotInfo) (*StorageVolumeSnapshotInfo, error) {
c.unqualify(&result.Name)
c.unqualify(&result.Volume)
sizeInGigaBytes, err := sizeInGigaBytes(result.Size)
if err != nil {
return nil, err
}
result.Size = sizeInGigaBytes
return result, nil
}
// Waits for a storage snapshot to become available
func (c *StorageVolumeSnapshotClient) waitForStorageSnapshotAvailable(name string, timeout time.Duration) (*StorageVolumeSnapshotInfo, error) {
var result *StorageVolumeSnapshotInfo
err := c.client.WaitFor(
fmt.Sprintf("storage volume snapshot %s to become available", c.getQualifiedName(name)),
timeout,
func() (bool, error) {
req := &GetStorageVolumeSnapshotInput{
Name: name,
}
res, err := c.GetStorageVolumeSnapshot(req)
if err != nil {
return false, err
}
if res != nil {
result = res
if strings.ToLower(result.Status) == "completed" {
return true, nil
} else if strings.ToLower(result.Status) == "error" {
return false, fmt.Errorf("Snapshot '%s' failed to create successfully. Status: %s Status Detail: %s", result.Name, result.Status, result.StatusDetail)
}
}
return false, nil
})
return result, err
}
// Waits for a storage snapshot to be deleted
func (c *StorageVolumeSnapshotClient) waitForStorageSnapshotDeleted(name string, timeout time.Duration) error {
return c.client.WaitFor(
fmt.Sprintf("storage volume snapshot %s to be deleted", c.getQualifiedName(name)),
timeout,
func() (bool, error) {
req := &GetStorageVolumeSnapshotInput{
Name: name,
}
res, err := c.GetStorageVolumeSnapshot(req)
if res == nil {
return true, nil
}
if err != nil {
return false, err
}
return res == nil, nil
})
}

View File

@ -0,0 +1,389 @@
package compute
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/hashicorp/go-oracle-terraform/client"
)
const WaitForVolumeReadyTimeout = time.Duration(600 * time.Second)
const WaitForVolumeDeleteTimeout = time.Duration(600 * time.Second)
// StorageVolumeClient is a client for the Storage Volume functions of the Compute API.
type StorageVolumeClient struct {
ResourceClient
}
// StorageVolumes obtains a StorageVolumeClient which can be used to access to the
// Storage Volume functions of the Compute API
func (c *ComputeClient) StorageVolumes() *StorageVolumeClient {
return &StorageVolumeClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "storage volume",
ContainerPath: "/storage/volume/",
ResourceRootPath: "/storage/volume",
}}
}
type StorageVolumeKind string
const (
StorageVolumeKindDefault StorageVolumeKind = "/oracle/public/storage/default"
StorageVolumeKindLatency StorageVolumeKind = "/oracle/public/storage/latency"
StorageVolumeKindSSD StorageVolumeKind = "/oracle/public/storage/ssd/gpl"
)
// StorageVolumeInfo represents information retrieved from the service about a Storage Volume.
type StorageVolumeInfo struct {
// Shows the default account for your identity domain.
Account string `json:"account,omitempty"`
// true indicates that the storage volume can also be used as a boot disk for an instance.
// If you set the value to true, then you must specify values for the `ImageList` and `ImageListEntry` fields.
Bootable bool `json:"bootable,omitempty"`
// The description of the storage volume.
Description string `json:"description,omitempty"`
// The hypervisor that this volume is compatible with.
Hypervisor string `json:"hypervisor,omitempty"`
// Name of machine image to extract onto this volume when created. This information is provided only for bootable storage volumes.
ImageList string `json:"imagelist,omitempty"`
// Specific imagelist entry version to extract.
ImageListEntry int `json:"imagelist_entry,omitempty"`
// Three-part name of the machine image. This information is available if the volume is a bootable storage volume.
MachineImage string `json:"machineimage_name,omitempty"`
// All volumes are managed volumes. Default value is true.
Managed bool `json:"managed,omitempty"`
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The OS platform this volume is compatible with.
Platform string `json:"platform,omitempty"`
// The storage-pool property: /oracle/public/storage/latency or /oracle/public/storage/default.
Properties []string `json:"properties,omitempty"`
// Boolean field indicating whether this volume can be attached as readonly. If set to False the volume will be attached as read-write.
ReadOnly bool `json:"readonly,omitempty"`
// The size of this storage volume in GB.
Size string `json:"size"`
// Name of the parent snapshot from which the storage volume is restored or cloned.
Snapshot string `json:"snapshot,omitempty"`
// Account of the parent snapshot from which the storage volume is restored.
SnapshotAccount string `json:"snapshot_account,omitempty"`
// Id of the parent snapshot from which the storage volume is restored or cloned.
SnapshotID string `json:"snapshot_id,omitempty"`
// TODO: this should become a Constant, if/when we have the values
// The current state of the storage volume.
Status string `json:"status,omitempty"`
// Details about the latest state of the storage volume.
StatusDetail string `json:"status_detail,omitempty"`
// It indicates the time that the current view of the storage volume was generated.
StatusTimestamp string `json:"status_timestamp,omitempty"`
// The storage pool from which this volume is allocated.
StoragePool string `json:"storage_pool,omitempty"`
// Comma-separated strings that tag the storage volume.
Tags []string `json:"tags,omitempty"`
// Uniform Resource Identifier
URI string `json:"uri,omitempty"`
}
func (c *StorageVolumeClient) getStorageVolumePath(name string) string {
return c.getObjectPath("/storage/volume", name)
}
// CreateStorageVolumeInput represents the body of an API request to create a new Storage Volume.
type CreateStorageVolumeInput struct {
// true indicates that the storage volume can also be used as a boot disk for an instance.
// If you set the value to true, then you must specify values for the `ImageList` and `ImageListEntry` fields.
Bootable bool `json:"bootable,omitempty"`
// The description of the storage volume.
Description string `json:"description,omitempty"`
// Name of machine image to extract onto this volume when created. This information is provided only for bootable storage volumes.
ImageList string `json:"imagelist,omitempty"`
// Specific imagelist entry version to extract.
ImageListEntry int `json:"imagelist_entry,omitempty"`
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The storage-pool property: /oracle/public/storage/latency or /oracle/public/storage/default.
Properties []string `json:"properties,omitempty"`
// The size of this storage volume in GB.
Size string `json:"size"`
// Name of the parent snapshot from which the storage volume is restored or cloned.
Snapshot string `json:"snapshot,omitempty"`
// Account of the parent snapshot from which the storage volume is restored.
SnapshotAccount string `json:"snapshot_account,omitempty"`
// Id of the parent snapshot from which the storage volume is restored or cloned.
SnapshotID string `json:"snapshot_id,omitempty"`
// Comma-separated strings that tag the storage volume.
Tags []string `json:"tags,omitempty"`
// Timeout to wait for storage volume creation.
Timeout time.Duration `json:"-"`
}
// CreateStorageVolume uses the given CreateStorageVolumeInput to create a new Storage Volume.
func (c *StorageVolumeClient) CreateStorageVolume(input *CreateStorageVolumeInput) (*StorageVolumeInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.ImageList = c.getQualifiedName(input.ImageList)
sizeInBytes, err := sizeInBytes(input.Size)
if err != nil {
return nil, err
}
input.Size = sizeInBytes
var storageInfo StorageVolumeInfo
if err := c.createResource(&input, &storageInfo); err != nil {
return nil, err
}
// Should never be nil, as we set this in the provider; but protect against it anyways.
if input.Timeout == 0 {
input.Timeout = WaitForVolumeReadyTimeout
}
volume, err := c.waitForStorageVolumeToBecomeAvailable(input.Name, input.Timeout)
if err != nil {
if volume != nil {
deleteInput := &DeleteStorageVolumeInput{
Name: volume.Name,
}
if err := c.DeleteStorageVolume(deleteInput); err != nil {
return nil, err
}
}
}
return volume, err
}
// DeleteStorageVolumeInput represents the body of an API request to delete a Storage Volume.
type DeleteStorageVolumeInput struct {
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// Timeout to wait for storage volume deletion
Timeout time.Duration `json:"-"`
}
// DeleteStorageVolume deletes the specified storage volume.
func (c *StorageVolumeClient) DeleteStorageVolume(input *DeleteStorageVolumeInput) error {
if err := c.deleteResource(input.Name); err != nil {
return err
}
// Should never be nil, but protect against it anyways
if input.Timeout == 0 {
input.Timeout = WaitForVolumeDeleteTimeout
}
return c.waitForStorageVolumeToBeDeleted(input.Name, input.Timeout)
}
// GetStorageVolumeInput represents the body of an API request to obtain a Storage Volume.
type GetStorageVolumeInput struct {
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
}
func (c *StorageVolumeClient) success(result *StorageVolumeInfo) (*StorageVolumeInfo, error) {
c.unqualify(&result.ImageList)
c.unqualify(&result.Name)
c.unqualify(&result.Snapshot)
sizeInMegaBytes, err := sizeInGigaBytes(result.Size)
if err != nil {
return nil, err
}
result.Size = sizeInMegaBytes
return result, nil
}
// GetStorageVolume gets Storage Volume information for the specified storage volume.
func (c *StorageVolumeClient) GetStorageVolume(input *GetStorageVolumeInput) (*StorageVolumeInfo, error) {
var storageVolume StorageVolumeInfo
if err := c.getResource(input.Name, &storageVolume); err != nil {
if client.WasNotFoundError(err) {
return nil, nil
}
return nil, err
}
return c.success(&storageVolume)
}
// UpdateStorageVolumeInput represents the body of an API request to update a Storage Volume.
type UpdateStorageVolumeInput struct {
// The description of the storage volume.
Description string `json:"description,omitempty"`
// Name of machine image to extract onto this volume when created. This information is provided only for bootable storage volumes.
ImageList string `json:"imagelist,omitempty"`
// Specific imagelist entry version to extract.
ImageListEntry int `json:"imagelist_entry,omitempty"`
// The three-part name of the object (/Compute-identity_domain/user/object).
Name string `json:"name"`
// The storage-pool property: /oracle/public/storage/latency or /oracle/public/storage/default.
Properties []string `json:"properties,omitempty"`
// The size of this storage volume in GB.
Size string `json:"size"`
// Name of the parent snapshot from which the storage volume is restored or cloned.
Snapshot string `json:"snapshot,omitempty"`
// Account of the parent snapshot from which the storage volume is restored.
SnapshotAccount string `json:"snapshot_account,omitempty"`
// Id of the parent snapshot from which the storage volume is restored or cloned.
SnapshotID string `json:"snapshot_id,omitempty"`
// Comma-separated strings that tag the storage volume.
Tags []string `json:"tags,omitempty"`
// Time to wait for storage volume ready
Timeout time.Duration `json:"-"`
}
// UpdateStorageVolume updates the specified storage volume, optionally modifying size, description and tags.
func (c *StorageVolumeClient) UpdateStorageVolume(input *UpdateStorageVolumeInput) (*StorageVolumeInfo, error) {
input.Name = c.getQualifiedName(input.Name)
input.ImageList = c.getQualifiedName(input.ImageList)
sizeInBytes, err := sizeInBytes(input.Size)
if err != nil {
return nil, err
}
input.Size = sizeInBytes
path := c.getStorageVolumePath(input.Name)
_, err = c.executeRequest("PUT", path, input)
if err != nil {
return nil, err
}
if input.Timeout == 0 {
input.Timeout = WaitForVolumeReadyTimeout
}
volumeInfo, err := c.waitForStorageVolumeToBecomeAvailable(input.Name, input.Timeout)
if err != nil {
return nil, err
}
return volumeInfo, nil
}
// waitForStorageVolumeToBecomeAvailable waits until a new Storage Volume is available (i.e. has finished initialising or updating).
func (c *StorageVolumeClient) waitForStorageVolumeToBecomeAvailable(name string, timeout time.Duration) (*StorageVolumeInfo, error) {
var waitResult *StorageVolumeInfo
err := c.client.WaitFor(
fmt.Sprintf("storage volume %s to become available", c.getQualifiedName(name)),
timeout,
func() (bool, error) {
getRequest := &GetStorageVolumeInput{
Name: name,
}
result, err := c.GetStorageVolume(getRequest)
if err != nil {
return false, err
}
if result != nil {
waitResult = result
if strings.ToLower(waitResult.Status) == "online" {
return true, nil
}
if strings.ToLower(waitResult.Status) == "error" {
return false, fmt.Errorf("Error Creating Storage Volume: %s", waitResult.StatusDetail)
}
}
return false, nil
})
return waitResult, err
}
// waitForStorageVolumeToBeDeleted waits until the specified storage volume has been deleted.
func (c *StorageVolumeClient) waitForStorageVolumeToBeDeleted(name string, timeout time.Duration) error {
return c.client.WaitFor(
fmt.Sprintf("storage volume %s to be deleted", c.getQualifiedName(name)),
timeout,
func() (bool, error) {
getRequest := &GetStorageVolumeInput{
Name: name,
}
result, err := c.GetStorageVolume(getRequest)
if result == nil {
return true, nil
}
if err != nil {
return false, err
}
return result == nil, nil
})
}
func sizeInGigaBytes(input string) (string, error) {
sizeInBytes, err := strconv.Atoi(input)
if err != nil {
return "", err
}
sizeInKB := sizeInBytes / 1024
sizeInMB := sizeInKB / 1024
sizeInGb := sizeInMB / 1024
return strconv.Itoa(sizeInGb), nil
}
func sizeInBytes(input string) (string, error) {
sizeInGB, err := strconv.Atoi(input)
if err != nil {
return "", err
}
sizeInMB := sizeInGB * 1024
sizeInKB := sizeInMB * 1024
sizeInBytes := sizeInKB * 1024
return strconv.Itoa(sizeInBytes), nil
}

View File

@ -0,0 +1,119 @@
package compute
import (
"bytes"
"encoding/json"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/hashicorp/go-oracle-terraform/opc"
)
const (
_ClientTestUser = "test-user"
_ClientTestDomain = "test-domain"
)
func newAuthenticatingServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if os.Getenv("ORACLE_LOG") != "" {
log.Printf("[DEBUG] Received request: %s, %s\n", r.Method, r.URL)
}
if r.URL.Path == "/authenticate/" {
http.SetCookie(w, &http.Cookie{Name: "testAuthCookie", Value: "cookie value"})
// w.WriteHeader(200)
} else {
handler(w, r)
}
}))
}
func getTestClient(c *opc.Config) (*ComputeClient, error) {
// Build up config with default values if omitted
if c.APIEndpoint == nil {
if os.Getenv("OPC_ENDPOINT") == "" {
panic("OPC_ENDPOINT not set in environment")
}
endpoint, err := url.Parse(os.Getenv("OPC_ENDPOINT"))
if err != nil {
return nil, err
}
c.APIEndpoint = endpoint
}
if c.IdentityDomain == nil {
domain := os.Getenv("OPC_IDENTITY_DOMAIN")
c.IdentityDomain = &domain
}
if c.Username == nil {
username := os.Getenv("OPC_USERNAME")
c.Username = &username
}
if c.Password == nil {
password := os.Getenv("OPC_PASSWORD")
c.Password = &password
}
if c.HTTPClient == nil {
c.HTTPClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 120 * time.Second},
}
}
return NewComputeClient(c)
}
func getBlankTestClient() (*ComputeClient, *httptest.Server, error) {
server := newAuthenticatingServer(func(w http.ResponseWriter, r *http.Request) {
})
endpoint, err := url.Parse(server.URL)
if err != nil {
server.Close()
return nil, nil, err
}
client, err := getTestClient(&opc.Config{
IdentityDomain: opc.String(_ClientTestDomain),
Username: opc.String(_ClientTestUser),
APIEndpoint: endpoint,
})
if err != nil {
server.Close()
return nil, nil, err
}
return client, server, nil
}
// Returns a stub client with default values, and a custom API Endpoint
func getStubClient(endpoint *url.URL) (*ComputeClient, error) {
domain := "test"
username := "test"
password := "test"
config := &opc.Config{
IdentityDomain: &domain,
Username: &username,
Password: &password,
APIEndpoint: endpoint,
}
return getTestClient(config)
}
func unmarshalRequestBody(t *testing.T, r *http.Request, target interface{}) {
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
err := json.Unmarshal(buf.Bytes(), target)
if err != nil {
t.Fatalf("Error marshalling request: %s", err)
}
}

View File

@ -0,0 +1,52 @@
package compute
type VirtNICsClient struct {
ResourceClient
}
func (c *ComputeClient) VirtNICs() *VirtNICsClient {
return &VirtNICsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "Virtual NIC",
ContainerPath: "/network/v1/vnic/",
ResourceRootPath: "/network/v1/vnic",
},
}
}
type VirtualNIC struct {
// Description of the object.
Description string `json:"description"`
// MAC address of this VNIC.
MACAddress string `json:"macAddress"`
// The three-part name (/Compute-identity_domain/user/object) of the Virtual NIC.
Name string `json:"name"`
// Tags associated with the object.
Tags []string `json:"tags"`
// True if the VNIC is of type "transit".
TransitFlag bool `json:"transitFlag"`
// Uniform Resource Identifier
Uri string `json:"uri"`
}
// Can only GET a virtual NIC, not update, create, or delete
type GetVirtualNICInput struct {
// The three-part name (/Compute-identity_domain/user/object) of the Virtual NIC.
// Required
Name string `json:"name"`
}
func (c *VirtNICsClient) GetVirtualNIC(input *GetVirtualNICInput) (*VirtualNIC, error) {
var virtNIC VirtualNIC
input.Name = c.getQualifiedName(input.Name)
if err := c.getResource(input.Name, &virtNIC); err != nil {
return nil, err
}
return c.success(&virtNIC)
}
func (c *VirtNICsClient) success(info *VirtualNIC) (*VirtualNIC, error) {
c.unqualify(&info.Name)
return info, nil
}

View File

@ -0,0 +1,154 @@
package compute
type VirtNICSetsClient struct {
ResourceClient
}
func (c *ComputeClient) VirtNICSets() *VirtNICSetsClient {
return &VirtNICSetsClient{
ResourceClient: ResourceClient{
ComputeClient: c,
ResourceDescription: "Virtual NIC Set",
ContainerPath: "/network/v1/vnicset/",
ResourceRootPath: "/network/v1/vnicset",
},
}
}
// Describes an existing virtual nic set
type VirtualNICSet struct {
// List of ACLs applied to the VNICs in the set.
AppliedACLs []string `json:"appliedAcls"`
// Description of the VNIC Set.
Description string `json:"description"`
// Name of the VNIC set.
Name string `json:"name"`
// The three-part name (/Compute-identity_domain/user/object) of the virtual NIC set.
Tags []string `json:"tags"`
// Uniform Resource Identifier
Uri string `json:"uri"`
// List of VNICs associated with this VNIC set.
VirtualNICs []string `json:"vnics"`
}
type CreateVirtualNICSetInput struct {
// List of ACLs applied to the VNICs in the set.
// Optional
AppliedACLs []string `json:"appliedAcls"`
// Description of the object.
// Optional
Description string `json:"description"`
// The three-part name (/Compute-identity_domain/user/object) of the virtual NIC set.
// Object names can contain only alphanumeric, underscore (_), dash (-), and period (.) characters. Object names are case-sensitive.
// Required
Name string `json:"name"`
// Tags associated with this VNIC set.
// Optional
Tags []string `json:"tags"`
// List of VNICs associated with this VNIC set.
// Optional
VirtualNICs []string `json:"vnics"`
}
func (c *VirtNICSetsClient) CreateVirtualNICSet(input *CreateVirtualNICSetInput) (*VirtualNICSet, error) {
input.Name = c.getQualifiedName(input.Name)
input.AppliedACLs = c.getQualifiedAcls(input.AppliedACLs)
qualifiedNics := c.getQualifiedList(input.VirtualNICs)
if len(qualifiedNics) != 0 {
input.VirtualNICs = qualifiedNics
}
var virtNicSet VirtualNICSet
if err := c.createResource(input, &virtNicSet); err != nil {
return nil, err
}
return c.success(&virtNicSet)
}
type GetVirtualNICSetInput struct {
// The three-part name (/Compute-identity_domain/user/object) of the virtual NIC set.
// Required
Name string `json:"name"`
}
func (c *VirtNICSetsClient) GetVirtualNICSet(input *GetVirtualNICSetInput) (*VirtualNICSet, error) {
var virtNicSet VirtualNICSet
// Qualify Name
input.Name = c.getQualifiedName(input.Name)
if err := c.getResource(input.Name, &virtNicSet); err != nil {
return nil, err
}
return c.success(&virtNicSet)
}
type UpdateVirtualNICSetInput struct {
// List of ACLs applied to the VNICs in the set.
// Optional
AppliedACLs []string `json:"appliedAcls"`
// Description of the object.
// Optional
Description string `json:"description"`
// The three-part name (/Compute-identity_domain/user/object) of the virtual NIC set.
// Object names can contain only alphanumeric, underscore (_), dash (-), and period (.) characters. Object names are case-sensitive.
// Required
Name string `json:"name"`
// Tags associated with this VNIC set.
// Optional
Tags []string `json:"tags"`
// List of VNICs associated with this VNIC set.
// Optional
VirtualNICs []string `json:"vnics"`
}
func (c *VirtNICSetsClient) UpdateVirtualNICSet(input *UpdateVirtualNICSetInput) (*VirtualNICSet, error) {
input.Name = c.getQualifiedName(input.Name)
input.AppliedACLs = c.getQualifiedAcls(input.AppliedACLs)
// Qualify VirtualNICs
qualifiedVNICs := c.getQualifiedList(input.VirtualNICs)
if len(qualifiedVNICs) != 0 {
input.VirtualNICs = qualifiedVNICs
}
var virtNICSet VirtualNICSet
if err := c.updateResource(input.Name, input, &virtNICSet); err != nil {
return nil, err
}
return c.success(&virtNICSet)
}
type DeleteVirtualNICSetInput struct {
// The name of the virtual NIC set.
// Required
Name string `json:"name"`
}
func (c *VirtNICSetsClient) DeleteVirtualNICSet(input *DeleteVirtualNICSetInput) error {
input.Name = c.getQualifiedName(input.Name)
return c.deleteResource(input.Name)
}
func (c *VirtNICSetsClient) getQualifiedAcls(acls []string) []string {
qualifiedAcls := []string{}
for _, acl := range acls {
qualifiedAcls = append(qualifiedAcls, c.getQualifiedName(acl))
}
return qualifiedAcls
}
func (c *VirtNICSetsClient) unqualifyAcls(acls []string) []string {
unqualifiedAcls := []string{}
for _, acl := range acls {
unqualifiedAcls = append(unqualifiedAcls, c.getUnqualifiedName(acl))
}
return unqualifiedAcls
}
func (c *VirtNICSetsClient) success(info *VirtualNICSet) (*VirtualNICSet, error) {
c.unqualify(&info.Name)
info.AppliedACLs = c.unqualifyAcls(info.AppliedACLs)
info.VirtualNICs = c.getUnqualifiedList(info.VirtualNICs)
return info, nil
}

View File

@ -0,0 +1,22 @@
package opc
import (
"net/http"
"net/url"
)
type Config struct {
Username *string
Password *string
IdentityDomain *string
APIEndpoint *url.URL
MaxRetries *int
LogLevel LogLevelType
Logger Logger
HTTPClient *http.Client
UserAgent *string
}
func NewConfig() *Config {
return &Config{}
}

View File

@ -0,0 +1,9 @@
package opc
func String(v string) *string {
return &v
}
func Int(v int) *int {
return &v
}

View File

@ -0,0 +1,12 @@
package opc
import "fmt"
type OracleError struct {
StatusCode int
Message string
}
func (e OracleError) Error() string {
return fmt.Sprintf("%d: %s", e.StatusCode, e.Message)
}

View File

@ -0,0 +1,70 @@
package opc
import (
"io"
"io/ioutil"
"log"
"os"
)
const (
LogOff LogLevelType = 0
LogDebug LogLevelType = 1
)
type LogLevelType uint
// Logger interface. Should be satisfied by Terraform's logger as well as the Default logger
type Logger interface {
Log(...interface{})
}
type LoggerFunc func(...interface{})
func (f LoggerFunc) Log(args ...interface{}) {
f(args...)
}
// Returns a default logger if one isn't specified during configuration
func NewDefaultLogger() Logger {
logWriter, err := LogOutput()
if err != nil {
log.Fatalf("Error setting up log writer: %s", err)
}
return &defaultLogger{
logger: log.New(logWriter, "", log.LstdFlags),
}
}
// Default logger to satisfy the logger interface
type defaultLogger struct {
logger *log.Logger
}
func (l defaultLogger) Log(args ...interface{}) {
l.logger.Println(args...)
}
func LogOutput() (logOutput io.Writer, err error) {
// Default to nil
logOutput = ioutil.Discard
logLevel := LogLevel()
if logLevel == LogOff {
return
}
// Logging is on, set output to STDERR
logOutput = os.Stderr
return
}
// Gets current Log Level from the ORACLE_LOG env var
func LogLevel() LogLevelType {
envLevel := os.Getenv("ORACLE_LOG")
if envLevel == "" {
return LogOff
} else {
return LogDebug
}
}

View File

@ -1,4 +1,4 @@
# mapstructure
# mapstructure [![Godoc](https://godoc.org/github.com/mitchell/mapstructure?status.svg)](https://godoc.org/github.com/mitchell/mapstructure)
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.

View File

@ -38,12 +38,6 @@ func DecodeHookExec(
raw DecodeHookFunc,
from reflect.Type, to reflect.Type,
data interface{}) (interface{}, error) {
// Build our arguments that reflect expects
argVals := make([]reflect.Value, 3)
argVals[0] = reflect.ValueOf(from)
argVals[1] = reflect.ValueOf(to)
argVals[2] = reflect.ValueOf(data)
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return f(from, to, data)
@ -72,7 +66,10 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
}
// Modify the from kind to be correct with the new data
f = reflect.ValueOf(data).Type()
f = nil
if val := reflect.ValueOf(data); val.IsValid() {
f = val.Type()
}
}
return data, nil
@ -118,6 +115,11 @@ func StringToTimeDurationHookFunc() DecodeHookFunc {
}
}
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
// the decoder.
//
// Note that this is significantly different from the WeaklyTypedInput option
// of the DecoderConfig.
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
@ -129,9 +131,8 @@ func WeaklyTypedHook(
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
} else {
return "0", nil
}
return "0", nil
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:

View File

@ -1,5 +1,5 @@
// The mapstructure package exposes functionality to convert an
// abitrary map[string]interface{} into a native Go structure.
// Package mapstructure exposes functionality to convert an arbitrary
// map[string]interface{} into a native Go structure.
//
// The Go structure can be arbitrarily complex, containing slices,
// other structs, etc. and the decoder will properly decode nested
@ -8,6 +8,7 @@
package mapstructure
import (
"encoding/json"
"errors"
"fmt"
"reflect"
@ -31,7 +32,12 @@ import (
// both.
type DecodeHookFunc interface{}
// DecodeHookFuncType is a DecodeHookFunc which has complete information about
// the source and target types.
type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the
// source and target types.
type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
// DecoderConfig is the configuration that is used to create a new decoder
@ -67,6 +73,10 @@ type DecoderConfig struct {
// FALSE, false, False. Anything else is an error)
// - empty array = empty map and vice versa
// - negative numbers to overflowed uint values (base 10)
// - slice of maps to a merged map
// - single values are converted to slices if required. Each
// element is weakly decoded. For example: "4" can become []int{4}
// if the target type is an int slice.
//
WeaklyTypedInput bool
@ -200,7 +210,7 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
d.config.DecodeHook,
dataVal.Type(), val.Type(), data)
if err != nil {
return err
return fmt.Errorf("error decoding '%s': %s", name, err)
}
}
@ -227,6 +237,10 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
err = d.decodePtr(name, data, val)
case reflect.Slice:
err = d.decodeSlice(name, data, val)
case reflect.Array:
err = d.decodeArray(name, data, val)
case reflect.Func:
err = d.decodeFunc(name, data, val)
default:
// If we reached this point then we weren't able to decode it
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
@ -245,6 +259,10 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
// value to "data" of that type.
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
if !dataVal.IsValid() {
dataVal = reflect.Zero(val.Type())
}
dataValType := dataVal.Type()
if !dataValType.AssignableTo(val.Type()) {
return fmt.Errorf(
@ -276,12 +294,22 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
case dataKind == reflect.Slice && d.config.WeaklyTypedInput:
case dataKind == reflect.Slice && d.config.WeaklyTypedInput,
dataKind == reflect.Array && d.config.WeaklyTypedInput:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
switch {
case elemKind == reflect.Uint8:
val.SetString(string(dataVal.Interface().([]uint8)))
switch elemKind {
case reflect.Uint8:
var uints []uint8
if dataKind == reflect.Array {
uints = make([]uint8, dataVal.Len(), dataVal.Len())
for i := range uints {
uints[i] = dataVal.Index(i).Interface().(uint8)
}
} else {
uints = dataVal.Interface().([]uint8)
}
val.SetString(string(uints))
default:
converted = false
}
@ -301,6 +329,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
dataType := dataVal.Type()
switch {
case dataKind == reflect.Int:
@ -322,6 +351,14 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
} else {
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
}
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
jn := data.(json.Number)
i, err := jn.Int64()
if err != nil {
return fmt.Errorf(
"error decoding json.Number into %s: %s", name, err)
}
val.SetInt(i)
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
@ -408,6 +445,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := getKind(dataVal)
dataType := dataVal.Type()
switch {
case dataKind == reflect.Int:
@ -415,7 +453,7 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value)
case dataKind == reflect.Uint:
val.SetFloat(float64(dataVal.Uint()))
case dataKind == reflect.Float32:
val.SetFloat(float64(dataVal.Float()))
val.SetFloat(dataVal.Float())
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetFloat(1)
@ -429,6 +467,14 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value)
} else {
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
}
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
jn := data.(json.Number)
i, err := jn.Float64()
if err != nil {
return fmt.Errorf(
"error decoding json.Number into %s: %s", name, err)
}
val.SetFloat(i)
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
@ -456,15 +502,30 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er
// Check input type
dataVal := reflect.Indirect(reflect.ValueOf(data))
if dataVal.Kind() != reflect.Map {
// Accept empty array/slice instead of an empty map in weakly typed mode
if d.config.WeaklyTypedInput &&
(dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) &&
dataVal.Len() == 0 {
val.Set(valMap)
return nil
} else {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
// In weak mode, we accept a slice of maps as an input...
if d.config.WeaklyTypedInput {
switch dataVal.Kind() {
case reflect.Array, reflect.Slice:
// Special case for BC reasons (covered by tests)
if dataVal.Len() == 0 {
val.Set(valMap)
return nil
}
for i := 0; i < dataVal.Len(); i++ {
err := d.decode(
fmt.Sprintf("%s[%d]", name, i),
dataVal.Index(i).Interface(), val)
if err != nil {
return err
}
}
return nil
}
}
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
// Accumulate errors
@ -507,7 +568,12 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
// into that. Then set the value of the pointer to this type.
valType := val.Type()
valElemType := valType.Elem()
realVal := reflect.New(valElemType)
realVal := val
if realVal.IsNil() || d.config.ZeroFields {
realVal = reflect.New(valElemType)
}
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
return err
}
@ -516,6 +582,19 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
return nil
}
func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
// Create an element of the concrete (non pointer) type and decode
// into that. Then set the value of the pointer to this type.
dataVal := reflect.Indirect(reflect.ValueOf(data))
if val.Type() != dataVal.Type() {
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
val.Set(dataVal)
return nil
}
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
@ -523,26 +602,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
valElemType := valType.Elem()
sliceType := reflect.SliceOf(valElemType)
// Check input type
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
// Accept empty map instead of array/slice in weakly typed mode
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
val.Set(reflect.MakeSlice(sliceType, 0, 0))
return nil
} else {
valSlice := val
if valSlice.IsNil() || d.config.ZeroFields {
// Check input type
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
if d.config.WeaklyTypedInput {
switch {
// Empty maps turn into empty slices
case dataValKind == reflect.Map:
if dataVal.Len() == 0 {
val.Set(reflect.MakeSlice(sliceType, 0, 0))
return nil
}
// All other types we try to convert to the slice type
// and "lift" it into it. i.e. a string becomes a string slice.
default:
// Just re-try this function with data as a slice.
return d.decodeSlice(name, []interface{}{data}, val)
}
}
return fmt.Errorf(
"'%s': source data must be an array or slice, got %s", name, dataValKind)
}
}
// Make a new slice to hold our result, same size as the original data.
valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
}
// Make a new slice to hold our result, same size as the original data.
valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
}
// Accumulate any errors
errors := make([]string, 0)
for i := 0; i < dataVal.Len(); i++ {
currentData := dataVal.Index(i).Interface()
for valSlice.Len() <= i {
valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
}
currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i)
@ -562,6 +659,73 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
return nil
}
func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
valType := val.Type()
valElemType := valType.Elem()
arrayType := reflect.ArrayOf(valType.Len(), valElemType)
valArray := val
if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields {
// Check input type
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
if d.config.WeaklyTypedInput {
switch {
// Empty maps turn into empty arrays
case dataValKind == reflect.Map:
if dataVal.Len() == 0 {
val.Set(reflect.Zero(arrayType))
return nil
}
// All other types we try to convert to the array type
// and "lift" it into it. i.e. a string becomes a string array.
default:
// Just re-try this function with data as a slice.
return d.decodeArray(name, []interface{}{data}, val)
}
}
return fmt.Errorf(
"'%s': source data must be an array or slice, got %s", name, dataValKind)
}
if dataVal.Len() > arrayType.Len() {
return fmt.Errorf(
"'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len())
}
// Make a new array to hold our result, same size as the original data.
valArray = reflect.New(arrayType).Elem()
}
// Accumulate any errors
errors := make([]string, 0)
for i := 0; i < dataVal.Len(); i++ {
currentData := dataVal.Index(i).Interface()
currentField := valArray.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i)
if err := d.decode(fieldName, currentData, currentField); err != nil {
errors = appendErrors(errors, err)
}
}
// Finally, set the value to the array we built up
val.Set(valArray)
// If there were errors, we return those
if len(errors) > 0 {
return &Error{errors}
}
return nil
}
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
@ -601,23 +765,20 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
// Compile the list of all the fields that we're going to be decoding
// from all the structs.
fields := make(map[*reflect.StructField]reflect.Value)
type field struct {
field reflect.StructField
val reflect.Value
}
fields := []field{}
for len(structs) > 0 {
structVal := structs[0]
structs = structs[1:]
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
fieldType := structType.Field(i)
if fieldType.Anonymous {
fieldKind := fieldType.Type.Kind()
if fieldKind != reflect.Struct {
errors = appendErrors(errors,
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
continue
}
}
fieldKind := fieldType.Type.Kind()
// If "squash" is specified in the tag, we squash the field down.
squash := false
@ -630,19 +791,26 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
}
if squash {
structs = append(structs, val.FieldByName(fieldType.Name))
if fieldKind != reflect.Struct {
errors = appendErrors(errors,
fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind))
} else {
structs = append(structs, structVal.FieldByName(fieldType.Name))
}
continue
}
// Normal struct field, store it away
fields[&fieldType] = structVal.Field(i)
fields = append(fields, field{fieldType, structVal.Field(i)})
}
}
for fieldType, field := range fields {
fieldName := fieldType.Name
// for fieldType, field := range fields {
for _, f := range fields {
field, fieldValue := f.field, f.val
fieldName := field.Name
tagValue := fieldType.Tag.Get(d.config.TagName)
tagValue := field.Tag.Get(d.config.TagName)
tagValue = strings.SplitN(tagValue, ",", 2)[0]
if tagValue != "" {
fieldName = tagValue
@ -653,7 +821,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
if !rawMapVal.IsValid() {
// Do a slower search by iterating over each key and
// doing case-insensitive search.
for dataValKey, _ := range dataValKeys {
for dataValKey := range dataValKeys {
mK, ok := dataValKey.Interface().(string)
if !ok {
// Not a string key
@ -677,14 +845,14 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
// Delete the key we're using from the unused map so we stop tracking
delete(dataValKeysUnused, rawMapKey.Interface())
if !field.IsValid() {
if !fieldValue.IsValid() {
// This should never happen
panic("field is not valid")
}
// If we can't set the field, then it is unexported or something,
// and we just continue onwards.
if !field.CanSet() {
if !fieldValue.CanSet() {
continue
}
@ -694,14 +862,14 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
}
if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil {
if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
errors = appendErrors(errors, err)
}
}
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
keys := make([]string, 0, len(dataValKeysUnused))
for rawKey, _ := range dataValKeysUnused {
for rawKey := range dataValKeysUnused {
keys = append(keys, rawKey.(string))
}
sort.Strings(keys)
@ -716,7 +884,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
// Add the unused keys to the list of unused keys if we're tracking metadata
if d.config.Metadata != nil {
for rawKey, _ := range dataValKeysUnused {
for rawKey := range dataValKeysUnused {
key := rawKey.(string)
if name != "" {
key = fmt.Sprintf("%s.%s", name, key)

29
vendor/vendor.json vendored
View File

@ -790,6 +790,30 @@
"path": "github.com/hashicorp/go-multierror",
"revision": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5"
},
{
"checksumSHA1": "Nf2Gdn9M1KlUS3sovKfymO1VJF4=",
"path": "github.com/hashicorp/go-oracle-terraform",
"revision": "5a9a298c54339d2296d2f1135eae55a3a8f5e8c2",
"revisionTime": "2018-01-11T20:31:13Z"
},
{
"checksumSHA1": "hjQfXn32Tvuu6IJACOTsMzm+AbA=",
"path": "github.com/hashicorp/go-oracle-terraform/client",
"revision": "5a9a298c54339d2296d2f1135eae55a3a8f5e8c2",
"revisionTime": "2018-01-11T20:31:13Z"
},
{
"checksumSHA1": "wce86V0j11J6xRSvJEanprjK7so=",
"path": "github.com/hashicorp/go-oracle-terraform/compute",
"revision": "5a9a298c54339d2296d2f1135eae55a3a8f5e8c2",
"revisionTime": "2018-01-11T20:31:13Z"
},
{
"checksumSHA1": "NuObCk0/ybL3w++EnltgrB1GQRc=",
"path": "github.com/hashicorp/go-oracle-terraform/opc",
"revision": "5a9a298c54339d2296d2f1135eae55a3a8f5e8c2",
"revisionTime": "2018-01-11T20:31:13Z"
},
{
"checksumSHA1": "ErJHGU6AVPZM9yoY/xV11TwSjQs=",
"path": "github.com/hashicorp/go-retryablehttp",
@ -957,9 +981,10 @@
"revision": "87b45ffd0e9581375c491fef3d32130bb15c5bd7"
},
{
"checksumSHA1": "4Js6Jlu93Wa0o6Kjt393L9Z7diE=",
"checksumSHA1": "1JtAhgmRN0x794LRNhs0DJ5t8io=",
"path": "github.com/mitchellh/mapstructure",
"revision": "281073eb9eb092240d33ef253c404f1cca550309"
"revision": "b4575eea38cca1123ec2dc90c26529b5c5acfcff",
"revisionTime": "2018-01-11T00:07:20Z"
},
{
"checksumSHA1": "m2L8ohfZiFRsMW3iynaH/TWgnSY=",

View File

@ -0,0 +1,105 @@
---
description:
The oracle-classic builder is able to create new custom images for use with Oracle
Compute Cloud.
layout: docs
page_title: 'Oracle Cloud Infrastructure Classic - Builders'
sidebar_current: 'docs-builders-oracle-classic'
---
# Oracle Cloud Infrastructure Classic Compute Builder
Type: `oracle-classic`
The `oracle-classic` Packer builder is able to create custom images for use
with [Oracle Cloud Infrastructure Classic Compute](https://cloud.oracle.com/compute-classic). The builder
takes a base image, runs any provisioning necessary on the base image after
launching it, and finally snapshots it creating a reusable custom image.
It is recommended that you familiarise yourself with the
[Key Concepts and Terminology](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsg/terminology.html)
prior to using this builder if you have not done so already.
The builder _does not_ manage images. Once it creates an image, it is up to you
to use it or delete it.
## Authorization
This builder authenticates API calls to Oracle Cloud Infrastructure Classic Compute using basic
authentication (user name and password).
To read more, see the [authentication documentation](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsa/Authentication.html)
## Configuration Reference
There are many configuration options available for the `oracle-classic` builder.
This builder currently only works with the SSH communicator.
### Required
- `api_endpoint` (string) - This is your custom API endpoint for sending
requests. Instructions for determining your API endpoint can be found
[here](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsa/SendRequests.html)
- `dest_image_list` (string) - Where to save the machine image to once you've
provisioned it. If the provided image list does not exist, Packer will create it.
- `identity_domain` (string) - This is your customer-specific identity domain
as generated by Oracle. If you don't know what your identity domain is, ask
your account administrator. For a little more information, see the Oracle
[documentation](https://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/ocuid/identity-domain-overview.html#GUID-7969F881-5F4D-443E-B86C-9044C8085B8A).
- `source_image_list` (string) - This is what image you want to use as your base image.
See the [documentation](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsg/listing-machine-images.html)
for more details. You may use either a public image list, or a private image list. To see what public image lists are available, you can use
the CLI, as described [here](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stopc/image-lists-stclr-and-nmcli.html#GUID-DB7E75FE-F752-4FF7-AB70-3C8DCDFCA0FA)
- `password` (string) - Your account password.
- `shape` (string) - The template that determines the number of
CPUs, amount of memory, and other resources allocated to a newly created
instance. For more information about shapes, see the documentation [here](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsg/machine-images-and-shapes.html).
- `username` (string) - Your account username.
### Optional
- `image_description` (string) - a description for your destination
image list. If you don't provide one, Packer will provide a generic description.
- `ssh_username` (string) - The username that Packer will use to SSH into the
instance; defaults to `opc`, the default oracle user, which has sudo
priveliges. If you have already configured users on your machine, you may
prompt Packer to use one of those instead. For more detail, see the
[documentation](https://docs.oracle.com/en/cloud/iaas/compute-iaas-cloud/stcsg/accessing-oracle-linux-instance-using-ssh.html).
- `image_name` (string) - The name to assign to the resulting custom image.
## Basic Example
Here is a basic example. Note that account specific configuration has been
obfuscated; you will need to add a working `username`, `password`,
`identity_domain`, and `api_endpoint` in order for the example to work.
``` {.json}
{
"builders": [
{
"type": "oracle-classic",
"username": "myuser@myaccount.com",
"password": "supersecretpasswordhere",
"identity_domain": "#######",
"api_endpoint": "https://api-###.compute.###.oraclecloud.com/",
"source_image_list": "/oracle/public/OL_7.2_UEKR4_x86_64",
"shape": "oc3",
"image_name": "Packer_Builder_Test_{{timestamp}}"
"dest_image_list": "Packer_Builder_Test_List"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["echo hello"]
}
]
}
```

View File

@ -131,6 +131,9 @@
<li<%= sidebar_current("docs-builders-openstack") %>>
<a href="/docs/builders/openstack.html">OpenStack</a>
</li>
<li<%= sidebar_current("docs-builders-oracle-classic") %>>
<a href="/docs/builders/oracle-classic.html">Oracle Classic</a>
</li>
<li<%= sidebar_current("docs-builders-oracle-oci") %>>
<a href="/docs/builders/oracle-oci.html">Oracle OCI</a>
</li>