github.phpd.cn/hashicorp/packer@v1.3.2/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.Metadata != nil { 46 for key, value := range d.cfg.Metadata { 47 metadata[key] = value 48 } 49 } 50 if d.cfg.UserData != "" { 51 metadata["user_data"] = d.cfg.UserData 52 } 53 54 instanceDetails := core.LaunchInstanceDetails{ 55 AvailabilityDomain: &d.cfg.AvailabilityDomain, 56 CompartmentId: &d.cfg.CompartmentID, 57 ImageId: &d.cfg.BaseImageID, 58 Shape: &d.cfg.Shape, 59 SubnetId: &d.cfg.SubnetID, 60 Metadata: metadata, 61 } 62 63 // When empty, the default display name is used. 64 if d.cfg.InstanceName != "" { 65 instanceDetails.DisplayName = &d.cfg.InstanceName 66 } 67 68 instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails}) 69 70 if err != nil { 71 return "", err 72 } 73 74 return *instance.Id, nil 75 } 76 77 // CreateImage creates a new custom image. 78 func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) { 79 res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{ 80 CompartmentId: &d.cfg.CompartmentID, 81 InstanceId: &id, 82 DisplayName: &d.cfg.ImageName, 83 FreeformTags: d.cfg.Tags, 84 }}) 85 86 if err != nil { 87 return core.Image{}, err 88 } 89 90 return res.Image, nil 91 } 92 93 // DeleteImage deletes a custom image. 94 func (d *driverOCI) DeleteImage(ctx context.Context, id string) error { 95 _, err := d.computeClient.DeleteImage(ctx, core.DeleteImageRequest{ImageId: &id}) 96 return err 97 } 98 99 // GetInstanceIP returns the public or private IP corresponding to the given instance id. 100 func (d *driverOCI) GetInstanceIP(ctx context.Context, id string) (string, error) { 101 vnics, err := d.computeClient.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{ 102 InstanceId: &id, 103 CompartmentId: &d.cfg.CompartmentID, 104 }) 105 if err != nil { 106 return "", err 107 } 108 109 if len(vnics.Items) == 0 { 110 return "", errors.New("instance has zero VNICs") 111 } 112 113 vnic, err := d.vcnClient.GetVnic(ctx, core.GetVnicRequest{VnicId: vnics.Items[0].VnicId}) 114 if err != nil { 115 return "", fmt.Errorf("Error getting VNIC details: %s", err) 116 } 117 118 if d.cfg.UsePrivateIP { 119 return *vnic.PrivateIp, nil 120 } 121 122 if vnic.PublicIp == nil { 123 return "", fmt.Errorf("Error getting VNIC Public Ip for: %s", id) 124 } 125 126 return *vnic.PublicIp, nil 127 } 128 129 func (d *driverOCI) GetInstanceInitialCredentials(ctx context.Context, id string) (string, string, error) { 130 credentials, err := d.computeClient.GetWindowsInstanceInitialCredentials(ctx, core.GetWindowsInstanceInitialCredentialsRequest{ 131 InstanceId: &id, 132 }) 133 if err != nil { 134 return "", "", err 135 } 136 137 return *credentials.InstanceCredentials.Username, *credentials.InstanceCredentials.Password, err 138 } 139 140 // TerminateInstance terminates a compute instance. 141 func (d *driverOCI) TerminateInstance(ctx context.Context, id string) error { 142 _, err := d.computeClient.TerminateInstance(ctx, core.TerminateInstanceRequest{ 143 InstanceId: &id, 144 }) 145 return err 146 } 147 148 // WaitForImageCreation waits for a provisioning custom image to reach the 149 // "AVAILABLE" state. 150 func (d *driverOCI) WaitForImageCreation(ctx context.Context, id string) error { 151 return waitForResourceToReachState( 152 func(string) (string, error) { 153 image, err := d.computeClient.GetImage(ctx, core.GetImageRequest{ImageId: &id}) 154 if err != nil { 155 return "", err 156 } 157 return string(image.LifecycleState), nil 158 }, 159 id, 160 []string{"PROVISIONING"}, 161 "AVAILABLE", 162 0, //Unlimited Retries 163 5*time.Second, //5 second wait between retries 164 ) 165 } 166 167 // WaitForInstanceState waits for an instance to reach the a given terminal 168 // state. 169 func (d *driverOCI) WaitForInstanceState(ctx context.Context, id string, waitStates []string, terminalState string) error { 170 return waitForResourceToReachState( 171 func(string) (string, error) { 172 instance, err := d.computeClient.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &id}) 173 if err != nil { 174 return "", err 175 } 176 return string(instance.LifecycleState), nil 177 }, 178 id, 179 waitStates, 180 terminalState, 181 0, //Unlimited Retries 182 5*time.Second, //5 second wait between retries 183 ) 184 } 185 186 // WaitForResourceToReachState checks the response of a request through a 187 // polled get and waits until the desired state or until the max retried has 188 // been reached. 189 func waitForResourceToReachState(getResourceState func(string) (string, error), id string, waitStates []string, terminalState string, maxRetries int, waitDuration time.Duration) error { 190 for i := 0; maxRetries == 0 || i < maxRetries; i++ { 191 state, err := getResourceState(id) 192 if err != nil { 193 return err 194 } 195 196 if stringSliceContains(waitStates, state) { 197 time.Sleep(waitDuration) 198 continue 199 } else if state == terminalState { 200 return nil 201 } 202 return fmt.Errorf("Unexpected resource state %q, expecting a waiting state %s or terminal state %q ", state, waitStates, terminalState) 203 } 204 return fmt.Errorf("Maximum number of retries (%d) exceeded; resource did not reach state %q", maxRetries, terminalState) 205 } 206 207 // stringSliceContains loops through a slice of strings returning a boolean 208 // based on whether a given value is contained in the slice. 209 func stringSliceContains(slice []string, value string) bool { 210 for _, elem := range slice { 211 if elem == value { 212 return true 213 } 214 } 215 return false 216 }