github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/builder/oracle/oci/driver_oci.go (about) 1 package oci 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 core "github.com/oracle/oci-go-sdk/core" 10 ) 11 12 // driverOCI implements the Driver interface and communicates with Oracle 13 // OCI. 14 type driverOCI struct { 15 computeClient core.ComputeClient 16 vcnClient core.VirtualNetworkClient 17 cfg *Config 18 context context.Context 19 } 20 21 // NewDriverOCI Creates a new driverOCI with a connected compute client and a connected vcn client. 22 func NewDriverOCI(cfg *Config) (Driver, error) { 23 coreClient, err := core.NewComputeClientWithConfigurationProvider(cfg.ConfigProvider) 24 if err != nil { 25 return nil, err 26 } 27 28 vcnClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(cfg.ConfigProvider) 29 if err != nil { 30 return nil, err 31 } 32 33 return &driverOCI{ 34 computeClient: coreClient, 35 vcnClient: vcnClient, 36 cfg: cfg, 37 }, nil 38 } 39 40 // CreateInstance creates a new compute instance. 41 func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (string, error) { 42 metadata := map[string]string{ 43 "ssh_authorized_keys": publicKey, 44 } 45 if d.cfg.UserData != "" { 46 metadata["user_data"] = d.cfg.UserData 47 } 48 49 instanceDetails := core.LaunchInstanceDetails{ 50 AvailabilityDomain: &d.cfg.AvailabilityDomain, 51 CompartmentId: &d.cfg.CompartmentID, 52 ImageId: &d.cfg.BaseImageID, 53 Shape: &d.cfg.Shape, 54 SubnetId: &d.cfg.SubnetID, 55 Metadata: metadata, 56 } 57 58 // When empty, the default display name is used. 59 if d.cfg.InstanceName != "" { 60 instanceDetails.DisplayName = &d.cfg.InstanceName 61 } 62 63 instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails}) 64 65 if err != nil { 66 return "", err 67 } 68 69 return *instance.Id, nil 70 } 71 72 // CreateImage creates a new custom image. 73 func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) { 74 res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{ 75 CompartmentId: &d.cfg.CompartmentID, 76 InstanceId: &id, 77 DisplayName: &d.cfg.ImageName, 78 }}) 79 80 if err != nil { 81 return core.Image{}, err 82 } 83 84 return res.Image, nil 85 } 86 87 // DeleteImage deletes a custom image. 88 func (d *driverOCI) DeleteImage(ctx context.Context, id string) error { 89 _, err := d.computeClient.DeleteImage(ctx, core.DeleteImageRequest{ImageId: &id}) 90 return err 91 } 92 93 // GetInstanceIP returns the public or private IP corresponding to the given instance id. 94 func (d *driverOCI) GetInstanceIP(ctx context.Context, id string) (string, error) { 95 vnics, err := d.computeClient.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{ 96 InstanceId: &id, 97 CompartmentId: &d.cfg.CompartmentID, 98 }) 99 if err != nil { 100 return "", err 101 } 102 103 if len(vnics.Items) == 0 { 104 return "", errors.New("instance has zero VNICs") 105 } 106 107 vnic, err := d.vcnClient.GetVnic(ctx, core.GetVnicRequest{VnicId: vnics.Items[0].VnicId}) 108 if err != nil { 109 return "", fmt.Errorf("Error getting VNIC details: %s", err) 110 } 111 112 if d.cfg.UsePrivateIP { 113 return *vnic.PrivateIp, nil 114 } 115 116 if vnic.PublicIp == nil { 117 return "", fmt.Errorf("Error getting VNIC Public Ip for: %s", id) 118 } 119 120 return *vnic.PublicIp, nil 121 } 122 123 func (d *driverOCI) GetInstanceInitialCredentials(ctx context.Context, id string) (string, string, error) { 124 credentials, err := d.computeClient.GetWindowsInstanceInitialCredentials(ctx, core.GetWindowsInstanceInitialCredentialsRequest{ 125 InstanceId: &id, 126 }) 127 if err != nil { 128 return "", "", err 129 } 130 131 return *credentials.InstanceCredentials.Username, *credentials.InstanceCredentials.Password, err 132 } 133 134 // TerminateInstance terminates a compute instance. 135 func (d *driverOCI) TerminateInstance(ctx context.Context, id string) error { 136 _, err := d.computeClient.TerminateInstance(ctx, core.TerminateInstanceRequest{ 137 InstanceId: &id, 138 }) 139 return err 140 } 141 142 // WaitForImageCreation waits for a provisioning custom image to reach the 143 // "AVAILABLE" state. 144 func (d *driverOCI) WaitForImageCreation(ctx context.Context, id string) error { 145 return waitForResourceToReachState( 146 func(string) (string, error) { 147 image, err := d.computeClient.GetImage(ctx, core.GetImageRequest{ImageId: &id}) 148 if err != nil { 149 return "", err 150 } 151 return string(image.LifecycleState), nil 152 }, 153 id, 154 []string{"PROVISIONING"}, 155 "AVAILABLE", 156 0, //Unlimited Retries 157 5*time.Second, //5 second wait between retries 158 ) 159 } 160 161 // WaitForInstanceState waits for an instance to reach the a given terminal 162 // state. 163 func (d *driverOCI) WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error { 164 return waitForResourceToReachState( 165 func(string) (string, error) { 166 instance, err := d.computeClient.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &id}) 167 if err != nil { 168 return "", err 169 } 170 return string(instance.LifecycleState), nil 171 }, 172 id, 173 waitStates, 174 terminalState, 175 0, //Unlimited Retries 176 5*time.Second, //5 second wait between retries 177 ) 178 } 179 180 // WaitForResourceToReachState checks the response of a request through a 181 // polled get and waits until the desired state or until the max retried has 182 // been reached. 183 func waitForResourceToReachState(getResourceState func(string) (string, error), id string, waitStates []string, terminalState string, maxRetries int, waitDuration time.Duration) error { 184 for i := 0; maxRetries == 0 || i < maxRetries; i++ { 185 state, err := getResourceState(id) 186 if err != nil { 187 return err 188 } 189 190 if stringSliceContains(waitStates, state) { 191 time.Sleep(waitDuration) 192 continue 193 } else if state == terminalState { 194 return nil 195 } 196 return fmt.Errorf("Unexpected resource state %q, expecting a waiting state %s or terminal state %q ", state, waitStates, terminalState) 197 } 198 return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %q", maxRetries, terminalState) 199 } 200 201 // stringSliceContains loops through a slice of strings returning a boolean 202 // based on whether a given value is contained in the slice. 203 func stringSliceContains(slice []string, value string) bool { 204 for _, elem := range slice { 205 if elem == value { 206 return true 207 } 208 } 209 return false 210 }