2017-09-12 11:30:39 -04:00
|
|
|
package oci
|
2017-02-13 05:35:14 -05:00
|
|
|
|
|
|
|
import (
|
2018-04-11 05:20:40 -04:00
|
|
|
"context"
|
2017-02-13 05:35:14 -05:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-02-09 10:00:19 -05:00
|
|
|
"math"
|
|
|
|
"math/rand"
|
2020-10-15 15:28:38 -04:00
|
|
|
"regexp"
|
2021-02-09 10:00:19 -05:00
|
|
|
"sync/atomic"
|
2018-04-11 05:20:40 -04:00
|
|
|
"time"
|
2017-02-13 05:35:14 -05:00
|
|
|
|
2021-02-09 01:50:07 -05:00
|
|
|
"github.com/oracle/oci-go-sdk/common"
|
2018-04-11 05:20:40 -04:00
|
|
|
core "github.com/oracle/oci-go-sdk/core"
|
2017-02-13 05:35:14 -05:00
|
|
|
)
|
|
|
|
|
2017-09-12 11:30:39 -04:00
|
|
|
// driverOCI implements the Driver interface and communicates with Oracle
|
|
|
|
// OCI.
|
|
|
|
type driverOCI struct {
|
2018-04-11 05:20:40 -04:00
|
|
|
computeClient core.ComputeClient
|
|
|
|
vcnClient core.VirtualNetworkClient
|
|
|
|
cfg *Config
|
2018-06-26 05:05:56 -04:00
|
|
|
context context.Context
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2021-02-09 01:50:07 -05:00
|
|
|
var retryPolicy = &common.RetryPolicy{
|
|
|
|
MaximumNumberAttempts: 10,
|
|
|
|
ShouldRetryOperation: func(res common.OCIOperationResponse) bool {
|
2021-02-09 10:00:19 -05:00
|
|
|
var e common.ServiceError
|
|
|
|
if errors.As(res.Error, &e) {
|
|
|
|
if e.GetHTTPStatusCode() == 429 || e.GetHTTPStatusCode() == 500 || e.GetHTTPStatusCode() == 503 {
|
|
|
|
return true
|
|
|
|
}
|
2021-02-09 01:50:07 -05:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
2021-02-09 10:00:19 -05:00
|
|
|
NextDuration: func(res common.OCIOperationResponse) time.Duration {
|
|
|
|
x := uint64(res.AttemptNumber)
|
|
|
|
d := time.Duration(math.Pow(2, float64(atomic.LoadUint64(&x)))) * time.Second
|
|
|
|
j := time.Duration(rand.Float64()*(2000)) * time.Millisecond
|
|
|
|
w := d + j
|
|
|
|
return w
|
2021-02-09 01:50:07 -05:00
|
|
|
},
|
|
|
|
}
|
2021-02-09 10:00:19 -05:00
|
|
|
|
2021-02-09 01:50:07 -05:00
|
|
|
var requestMetadata = common.RequestMetadata{
|
|
|
|
RetryPolicy: retryPolicy,
|
|
|
|
}
|
|
|
|
|
2018-04-11 05:20:40 -04:00
|
|
|
// NewDriverOCI Creates a new driverOCI with a connected compute client and a connected vcn client.
|
2017-09-12 11:30:39 -04:00
|
|
|
func NewDriverOCI(cfg *Config) (Driver, error) {
|
2019-10-14 10:08:16 -04:00
|
|
|
coreClient, err := core.NewComputeClientWithConfigurationProvider(cfg.configProvider)
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
|
2019-10-14 10:08:16 -04:00
|
|
|
vcnClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(cfg.configProvider)
|
2018-04-11 05:20:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &driverOCI{
|
|
|
|
computeClient: coreClient,
|
|
|
|
vcnClient: vcnClient,
|
|
|
|
cfg: cfg,
|
|
|
|
}, nil
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateInstance creates a new compute instance.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (string, error) {
|
2018-04-11 05:20:40 -04:00
|
|
|
metadata := map[string]string{
|
|
|
|
"ssh_authorized_keys": publicKey,
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
2018-07-17 11:41:19 -04:00
|
|
|
if d.cfg.Metadata != nil {
|
|
|
|
for key, value := range d.cfg.Metadata {
|
2018-07-02 04:48:08 -04:00
|
|
|
metadata[key] = value
|
|
|
|
}
|
|
|
|
}
|
2018-03-28 16:27:41 -04:00
|
|
|
if d.cfg.UserData != "" {
|
2018-04-11 05:20:40 -04:00
|
|
|
metadata["user_data"] = d.cfg.UserData
|
2018-03-28 16:27:41 -04:00
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
|
2020-10-14 17:09:09 -04:00
|
|
|
// Create VNIC details for instance
|
2020-09-05 14:45:16 -04:00
|
|
|
CreateVnicDetails := core.CreateVnicDetails{
|
|
|
|
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
|
|
|
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
|
|
|
HostnameLabel: d.cfg.CreateVnicDetails.HostnameLabel,
|
|
|
|
NsgIds: d.cfg.CreateVnicDetails.NsgIds,
|
|
|
|
PrivateIp: d.cfg.CreateVnicDetails.PrivateIp,
|
|
|
|
SkipSourceDestCheck: d.cfg.CreateVnicDetails.SkipSourceDestCheck,
|
|
|
|
SubnetId: d.cfg.CreateVnicDetails.SubnetId,
|
2020-09-08 14:33:50 -04:00
|
|
|
DefinedTags: d.cfg.CreateVnicDetails.DefinedTags,
|
|
|
|
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
2020-08-25 12:13:34 -04:00
|
|
|
}
|
|
|
|
|
2020-10-14 17:09:09 -04:00
|
|
|
// Determine base image ID
|
|
|
|
var imageId *string
|
|
|
|
if d.cfg.BaseImageID != "" {
|
|
|
|
imageId = &d.cfg.BaseImageID
|
|
|
|
} else {
|
2020-10-15 15:28:38 -04:00
|
|
|
// Pull images and determine which image ID to use, if BaseImageId not specified
|
2020-10-14 17:09:09 -04:00
|
|
|
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
|
|
|
|
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
|
|
|
|
DisplayName: d.cfg.BaseImageFilter.DisplayName,
|
|
|
|
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
|
|
|
|
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
|
|
|
|
Shape: d.cfg.BaseImageFilter.Shape,
|
|
|
|
LifecycleState: "AVAILABLE",
|
|
|
|
SortBy: "TIMECREATED",
|
|
|
|
SortOrder: "DESC",
|
2021-02-09 01:50:07 -05:00
|
|
|
RequestMetadata: requestMetadata,
|
2020-10-14 17:09:09 -04:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(response.Items) == 0 {
|
|
|
|
return "", errors.New("base_image_filter returned no images")
|
|
|
|
}
|
2020-10-15 15:28:38 -04:00
|
|
|
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
|
|
|
|
// Return most recent image that matches regex
|
|
|
|
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
for _, image := range response.Items {
|
|
|
|
if imageNameRegex.MatchString(*image.DisplayName) {
|
|
|
|
imageId = image.Id
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if imageId == nil {
|
|
|
|
return "", errors.New("No image matched display_name_search criteria")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If no regex provided, simply return most recent image pulled
|
|
|
|
imageId = response.Items[0].Id
|
|
|
|
}
|
2020-10-14 17:09:09 -04:00
|
|
|
}
|
2020-09-05 14:45:16 -04:00
|
|
|
|
2020-09-30 06:31:37 -04:00
|
|
|
// Create Source details which will be used to Launch Instance
|
2020-10-14 17:09:09 -04:00
|
|
|
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
|
|
|
|
ImageId: imageId,
|
2020-09-30 06:31:37 -04:00
|
|
|
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
|
|
|
}
|
|
|
|
|
2020-10-14 17:09:09 -04:00
|
|
|
// Build instance details
|
|
|
|
instanceDetails := core.LaunchInstanceDetails{
|
|
|
|
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
|
|
|
CompartmentId: &d.cfg.CompartmentID,
|
|
|
|
CreateVnicDetails: &CreateVnicDetails,
|
|
|
|
DefinedTags: d.cfg.InstanceDefinedTags,
|
|
|
|
DisplayName: d.cfg.InstanceName,
|
|
|
|
FreeformTags: d.cfg.InstanceTags,
|
|
|
|
Shape: &d.cfg.Shape,
|
|
|
|
SourceDetails: InstanceSourceDetails,
|
|
|
|
Metadata: metadata,
|
|
|
|
}
|
|
|
|
|
2021-02-09 01:50:07 -05:00
|
|
|
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{
|
|
|
|
LaunchInstanceDetails: instanceDetails,
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2018-04-11 05:20:40 -04:00
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2018-04-11 05:20:40 -04:00
|
|
|
return *instance.Id, nil
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateImage creates a new custom image.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) {
|
|
|
|
res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{
|
2020-10-02 22:27:34 -04:00
|
|
|
CompartmentId: &d.cfg.ImageCompartmentID,
|
2018-04-11 05:20:40 -04:00
|
|
|
InstanceId: &id,
|
|
|
|
DisplayName: &d.cfg.ImageName,
|
2018-06-04 08:14:59 -04:00
|
|
|
FreeformTags: d.cfg.Tags,
|
2019-09-27 08:49:37 -04:00
|
|
|
DefinedTags: d.cfg.DefinedTags,
|
2020-10-17 14:24:45 -04:00
|
|
|
LaunchMode: core.CreateImageDetailsLaunchModeEnum(d.cfg.LaunchMode),
|
2021-02-09 01:50:07 -05:00
|
|
|
},
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2018-04-11 05:20:40 -04:00
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
2018-04-11 05:20:40 -04:00
|
|
|
return core.Image{}, err
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-04-11 05:20:40 -04:00
|
|
|
return res.Image, nil
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteImage deletes a custom image.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) DeleteImage(ctx context.Context, id string) error {
|
2021-02-09 01:50:07 -05:00
|
|
|
_, err := d.computeClient.DeleteImage(ctx, core.DeleteImageRequest{
|
|
|
|
ImageId: &id,
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2018-04-11 05:20:40 -04:00
|
|
|
return err
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-02-13 08:20:26 -05:00
|
|
|
// GetInstanceIP returns the public or private IP corresponding to the given instance id.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) GetInstanceIP(ctx context.Context, id string) (string, error) {
|
|
|
|
vnics, err := d.computeClient.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
|
2021-02-09 01:50:07 -05:00
|
|
|
InstanceId: &id,
|
|
|
|
CompartmentId: &d.cfg.CompartmentID,
|
|
|
|
RequestMetadata: requestMetadata,
|
2018-04-11 05:20:40 -04:00
|
|
|
})
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2018-04-11 05:20:40 -04:00
|
|
|
if len(vnics.Items) == 0 {
|
2017-02-13 05:35:14 -05:00
|
|
|
return "", errors.New("instance has zero VNICs")
|
|
|
|
}
|
|
|
|
|
2021-02-09 01:50:07 -05:00
|
|
|
vnic, err := d.vcnClient.GetVnic(ctx, core.GetVnicRequest{
|
|
|
|
VnicId: vnics.Items[0].VnicId,
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2017-02-13 05:35:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error getting VNIC details: %s", err)
|
|
|
|
}
|
|
|
|
|
2018-02-13 08:20:26 -05:00
|
|
|
if d.cfg.UsePrivateIP {
|
2018-04-11 05:20:40 -04:00
|
|
|
return *vnic.PrivateIp, nil
|
2018-02-13 08:20:26 -05:00
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
|
|
|
|
if vnic.PublicIp == nil {
|
|
|
|
return "", fmt.Errorf("Error getting VNIC Public Ip for: %s", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
return *vnic.PublicIp, nil
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) GetInstanceInitialCredentials(ctx context.Context, id string) (string, string, error) {
|
|
|
|
credentials, err := d.computeClient.GetWindowsInstanceInitialCredentials(ctx, core.GetWindowsInstanceInitialCredentialsRequest{
|
2021-02-09 01:50:07 -05:00
|
|
|
InstanceId: &id,
|
|
|
|
RequestMetadata: requestMetadata,
|
2018-05-24 13:47:37 -04:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return *credentials.InstanceCredentials.Username, *credentials.InstanceCredentials.Password, err
|
|
|
|
}
|
|
|
|
|
2017-02-13 05:35:14 -05:00
|
|
|
// TerminateInstance terminates a compute instance.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) TerminateInstance(ctx context.Context, id string) error {
|
|
|
|
_, err := d.computeClient.TerminateInstance(ctx, core.TerminateInstanceRequest{
|
2021-02-09 01:50:07 -05:00
|
|
|
InstanceId: &id,
|
|
|
|
RequestMetadata: requestMetadata,
|
2018-04-11 05:20:40 -04:00
|
|
|
})
|
|
|
|
return err
|
2017-02-13 05:35:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// WaitForImageCreation waits for a provisioning custom image to reach the
|
|
|
|
// "AVAILABLE" state.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) WaitForImageCreation(ctx context.Context, id string) error {
|
2018-04-11 05:20:40 -04:00
|
|
|
return waitForResourceToReachState(
|
|
|
|
func(string) (string, error) {
|
2021-02-09 01:50:07 -05:00
|
|
|
image, err := d.computeClient.GetImage(ctx, core.GetImageRequest{
|
|
|
|
ImageId: &id,
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2018-04-11 05:20:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(image.LifecycleState), nil
|
|
|
|
},
|
2017-02-13 05:35:14 -05:00
|
|
|
id,
|
|
|
|
[]string{"PROVISIONING"},
|
|
|
|
"AVAILABLE",
|
2018-04-11 05:20:40 -04:00
|
|
|
0, //Unlimited Retries
|
|
|
|
5*time.Second, //5 second wait between retries
|
2017-02-13 05:35:14 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WaitForInstanceState waits for an instance to reach the a given terminal
|
|
|
|
// state.
|
2018-06-26 05:05:56 -04:00
|
|
|
func (d *driverOCI) WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error {
|
2018-04-11 05:20:40 -04:00
|
|
|
return waitForResourceToReachState(
|
|
|
|
func(string) (string, error) {
|
2021-02-09 01:50:07 -05:00
|
|
|
instance, err := d.computeClient.GetInstance(ctx, core.GetInstanceRequest{
|
|
|
|
InstanceId: &id,
|
|
|
|
RequestMetadata: requestMetadata,
|
|
|
|
})
|
2018-04-11 05:20:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(instance.LifecycleState), nil
|
|
|
|
},
|
2017-02-13 05:35:14 -05:00
|
|
|
id,
|
|
|
|
waitStates,
|
|
|
|
terminalState,
|
2018-04-11 05:20:40 -04:00
|
|
|
0, //Unlimited Retries
|
|
|
|
5*time.Second, //5 second wait between retries
|
2017-02-13 05:35:14 -05:00
|
|
|
)
|
|
|
|
}
|
2018-04-11 05:20:40 -04:00
|
|
|
|
|
|
|
// WaitForResourceToReachState checks the response of a request through a
|
|
|
|
// polled get and waits until the desired state or until the max retried has
|
|
|
|
// been reached.
|
|
|
|
func waitForResourceToReachState(getResourceState func(string) (string, error), id string, waitStates []string, terminalState string, maxRetries int, waitDuration time.Duration) error {
|
|
|
|
for i := 0; maxRetries == 0 || i < maxRetries; i++ {
|
|
|
|
state, err := getResourceState(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if stringSliceContains(waitStates, state) {
|
|
|
|
time.Sleep(waitDuration)
|
|
|
|
continue
|
|
|
|
} else if state == terminalState {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Unexpected resource state %q, expecting a waiting state %s or terminal state %q ", state, waitStates, terminalState)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %q", maxRetries, terminalState)
|
|
|
|
}
|
|
|
|
|
|
|
|
// stringSliceContains loops through a slice of strings returning a boolean
|
|
|
|
// based on whether a given value is contained in the slice.
|
|
|
|
func stringSliceContains(slice []string, value string) bool {
|
|
|
|
for _, elem := range slice {
|
|
|
|
if elem == value {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|