001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2019-2022 Andres Almiray.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.kordamp.gradle.plugin.oci.tasks.create
019
020 import com.oracle.bmc.core.BlockstorageClient
021 import com.oracle.bmc.core.ComputeClient
022 import com.oracle.bmc.core.VirtualNetworkClient
023 import com.oracle.bmc.core.model.BootVolume
024 import com.oracle.bmc.core.model.BootVolumeSourceDetails
025 import com.oracle.bmc.core.model.BootVolumeSourceFromBootVolumeDetails
026 import com.oracle.bmc.core.model.CreateBootVolumeDetails
027 import com.oracle.bmc.core.model.CreateVnicDetails
028 import com.oracle.bmc.core.model.Image
029 import com.oracle.bmc.core.model.Instance
030 import com.oracle.bmc.core.model.InstanceSourceViaImageDetails
031 import com.oracle.bmc.core.model.LaunchInstanceAgentConfigDetails
032 import com.oracle.bmc.core.model.LaunchInstanceDetails
033 import com.oracle.bmc.core.model.Shape
034 import com.oracle.bmc.core.model.Subnet
035 import com.oracle.bmc.core.requests.CreateBootVolumeRequest
036 import com.oracle.bmc.core.requests.GetBootVolumeRequest
037 import com.oracle.bmc.core.requests.GetInstanceRequest
038 import com.oracle.bmc.core.requests.GetSubnetRequest
039 import com.oracle.bmc.core.requests.LaunchInstanceRequest
040 import com.oracle.bmc.core.requests.ListBootVolumesRequest
041 import com.oracle.bmc.core.requests.ListInstancesRequest
042 import com.oracle.bmc.core.requests.ListShapesRequest
043 import com.oracle.bmc.core.requests.ListSubnetsRequest
044 import com.oracle.bmc.identity.IdentityClient
045 import com.oracle.bmc.identity.model.AvailabilityDomain
046 import com.oracle.bmc.identity.requests.ListAvailabilityDomainsRequest
047 import groovy.transform.CompileStatic
048 import org.apache.commons.codec.binary.Base64
049 import org.gradle.api.provider.Property
050 import org.gradle.api.tasks.Internal
051 import org.kordamp.gradle.plugin.oci.tasks.AbstractOCITask
052 import org.kordamp.gradle.plugin.oci.tasks.interfaces.OCITask
053 import org.kordamp.gradle.plugin.oci.tasks.traits.CompartmentIdAwareTrait
054 import org.kordamp.gradle.plugin.oci.tasks.traits.ImageAwareTrait
055 import org.kordamp.gradle.plugin.oci.tasks.traits.InstanceNameAwareTrait
056 import org.kordamp.gradle.plugin.oci.tasks.traits.OptionalUserDataFileAwareTrait
057 import org.kordamp.gradle.plugin.oci.tasks.traits.PublicKeyFileAwareTrait
058 import org.kordamp.gradle.plugin.oci.tasks.traits.ShapeAwareTrait
059 import org.kordamp.gradle.plugin.oci.tasks.traits.SubnetIdAwareTrait
060 import org.kordamp.gradle.plugin.oci.tasks.traits.VerboseAwareTrait
061 import org.kordamp.jipsy.annotations.TypeProviderFor
062
063 import static org.kordamp.gradle.plugin.oci.tasks.create.CreateInstanceConsoleConnectionTask.maybeCreateInstanceConsoleConnection
064 import static org.kordamp.gradle.plugin.oci.tasks.get.GetInstancePublicIpTask.getInstancePublicIp
065 import static org.kordamp.gradle.plugin.oci.tasks.printers.BootVolumePrinter.printBootVolume
066 import static org.kordamp.gradle.plugin.oci.tasks.printers.InstancePrinter.printInstance
067 import static org.kordamp.gradle.util.StringUtils.isBlank
068 import static org.kordamp.gradle.util.StringUtils.isNotBlank
069
070 /**
071 * @author Andres Almiray
072 * @since 0.1.0
073 */
074 @CompileStatic
075 @TypeProviderFor(OCITask)
076 class CreateInstanceTask extends AbstractOCITask implements CompartmentIdAwareTrait,
077 SubnetIdAwareTrait,
078 InstanceNameAwareTrait,
079 ImageAwareTrait,
080 ShapeAwareTrait,
081 PublicKeyFileAwareTrait,
082 OptionalUserDataFileAwareTrait,
083 VerboseAwareTrait {
084 static final String TASK_DESCRIPTION = 'Creates an Instance.'
085
086 private final Property<String> createdInstanceId = project.objects.property(String)
087
088 @Internal
089 Property<String> getCreatedInstanceId() {
090 this.@createdInstanceId
091 }
092
093 @Override
094 void doExecuteTask() {
095 validateCompartmentId()
096 validateSubnetId()
097 validateImage()
098 validateShape()
099 validatePublicKeyFile()
100
101 ComputeClient computeClient = createComputeClient()
102
103 Image _image = validateImage(computeClient, getResolvedCompartmentId().get())
104 Shape _shape = validateShape(computeClient, getResolvedCompartmentId().get())
105
106 File publicKeyFile = getResolvedPublicKeyFile().get().asFile
107 File userDataFile = getResolvedUserDataFile()?.get()?.asFile
108 String kmsKeyId = ''
109
110 VirtualNetworkClient vcnClient = createVirtualNetworkClient()
111 BlockstorageClient blockstorageClient = createBlockstorageClient()
112 IdentityClient identityClient = createIdentityClient()
113
114 Subnet subnet = vcnClient.getSubnet(GetSubnetRequest.builder()
115 .subnetId(getResolvedSubnetId().get())
116 .build())
117 .subnet
118
119 Instance instance = maybeCreateInstance(this,
120 computeClient,
121 vcnClient,
122 blockstorageClient,
123 identityClient,
124 getResolvedCompartmentId().get(),
125 getResolvedInstanceName().get(),
126 _image,
127 _shape,
128 null,
129 subnet,
130 publicKeyFile,
131 userDataFile,
132 kmsKeyId,
133 getResolvedVerbose().get())
134 createdInstanceId.set(instance.id)
135 }
136
137 static Instance maybeCreateInstance(OCITask owner,
138 ComputeClient computeClient,
139 VirtualNetworkClient vcnClient,
140 BlockstorageClient blockstorageClient,
141 IdentityClient identityClient,
142 String compartmentId,
143 String instanceName,
144 Image image,
145 Shape shape,
146 AvailabilityDomain availabilityDomain,
147 Subnet subnet,
148 File publicKeyFile,
149 File userDataFile,
150 String kmsKeyId,
151 boolean verbose) {
152 AvailabilityDomain ad = availabilityDomain ?: findMatchingAvailabilityDomain(
153 owner,
154 computeClient,
155 identityClient,
156 compartmentId,
157 shape.shape)
158
159 subnet = subnet ?: findMatchingSubnet(owner,
160 vcnClient,
161 compartmentId,
162 subnet.vcnId,
163 ad)
164
165 Instance instance = doMaybeCreateInstance(owner,
166 computeClient,
167 vcnClient,
168 compartmentId,
169 instanceName,
170 ad?.name ?: subnet.availabilityDomain,
171 image.id,
172 shape.shape,
173 subnet.id,
174 publicKeyFile,
175 userDataFile,
176 kmsKeyId,
177 verbose)
178
179 maybeCreateInstanceConsoleConnection(owner,
180 computeClient,
181 compartmentId,
182 instance.id,
183 publicKeyFile,
184 true,
185 verbose)
186
187 maybeCreateBootVolume(owner,
188 blockstorageClient,
189 compartmentId,
190 ad?.name ?: subnet.availabilityDomain,
191 instance.imageId,
192 instance.displayName + '-boot-volume',
193 kmsKeyId,
194 verbose)
195
196 instance
197 }
198
199 private static AvailabilityDomain findMatchingAvailabilityDomain(OCITask owner,
200 ComputeClient computeClient,
201 IdentityClient identityClient,
202 String compartmentId,
203 String shapeName) {
204 for (AvailabilityDomain availabilityDomain : identityClient.listAvailabilityDomains(ListAvailabilityDomainsRequest.builder()
205 .compartmentId(compartmentId)
206 .build()).items) {
207 for (Shape shape : computeClient.listShapes(ListShapesRequest.builder()
208 .compartmentId(compartmentId)
209 .availabilityDomain(availabilityDomain.name)
210 .build()).items) {
211 if (shape.shape == shapeName) {
212 return availabilityDomain
213 }
214 }
215 }
216
217 null
218 }
219
220 private static Subnet findMatchingSubnet(OCITask owner,
221 VirtualNetworkClient vcnClient,
222 String compartmentId,
223 String vcnId,
224 AvailabilityDomain availabilityDomain) {
225 for (Subnet subnet : vcnClient.listSubnets(ListSubnetsRequest.builder()
226 .compartmentId(compartmentId)
227 .vcnId(vcnId)
228 .build()).items) {
229 if (subnet.availabilityDomain == availabilityDomain.name) {
230 return subnet
231 }
232 }
233
234 null
235 }
236
237 private static Instance doMaybeCreateInstance(OCITask owner,
238 ComputeClient client,
239 VirtualNetworkClient vcnClient,
240 String compartmentId,
241 String instanceName,
242 String availabilityDomain,
243 String imageId,
244 String shape,
245 String subnetId,
246 File publicKeyFile,
247 File userDataFile,
248 String kmsKeyId,
249 boolean verbose) {
250 // 1. Check if it exists
251 List<Instance> instances = client.listInstances(ListInstancesRequest.builder()
252 .compartmentId(compartmentId)
253 .availabilityDomain(availabilityDomain)
254 .displayName(instanceName)
255 .build())
256 .items
257
258 if (!instances.empty) {
259 Instance instance = instances[0]
260 println("Instance '${instanceName}' already exists. id = ${owner.console.yellow(instance.id)}")
261 if (verbose) printInstance(owner, instance, 0)
262 return instances[0]
263 }
264
265 Map<String, String> metadata = new HashMap<>('ssh_authorized_keys': publicKeyFile.text)
266 owner.printKeyValue("Public key file", publicKeyFile.absolutePath, 0)
267 if (userDataFile && userDataFile?.exists() && isNotBlank(userDataFile?.text)) {
268 owner.printKeyValue("Cloud init script", userDataFile.absolutePath, 0)
269 metadata.put("user_data", Base64.encodeBase64String(userDataFile.getBytes()))
270 }
271
272 InstanceSourceViaImageDetails details =
273 (isBlank(kmsKeyId))
274 ? InstanceSourceViaImageDetails.builder()
275 .imageId(imageId)
276 .build()
277 : InstanceSourceViaImageDetails.builder()
278 .imageId(imageId)
279 .kmsKeyId(kmsKeyId)
280 .build()
281
282 Instance instance = client.launchInstance(LaunchInstanceRequest.builder()
283 .launchInstanceDetails(LaunchInstanceDetails.builder()
284 .availabilityDomain(availabilityDomain)
285 .compartmentId(compartmentId)
286 .displayName(instanceName)
287 .metadata(metadata)
288 .shape(shape)
289 .sourceDetails(details)
290 .createVnicDetails(CreateVnicDetails.builder()
291 .subnetId(subnetId)
292 .build())
293 .agentConfig(LaunchInstanceAgentConfigDetails.builder()
294 .isMonitoringDisabled(false)
295 .build())
296 .build())
297 .build())
298 .instance
299
300 println("Waiting for Instance to be ${owner.state('Available')}")
301 client.waiters.forInstance(GetInstanceRequest.builder()
302 .instanceId(instance.id)
303 .build(),
304 Instance.LifecycleState.Running)
305 .execute()
306
307 println("Instance '${instanceName}' has been provisioned. id = ${owner.console.yellow(instance.id)}")
308 if (verbose) printInstance(owner, instance, 0)
309
310 Set<String> publicIps = getInstancePublicIp(owner,
311 client,
312 vcnClient,
313 compartmentId,
314 instance.id)
315
316 owner.printCollection('Ip Addresses', publicIps, 0)
317
318 instance
319 }
320
321 private static BootVolume maybeCreateBootVolume(OCITask owner,
322 BlockstorageClient client,
323 String compartmentId,
324 String availabilityDomain,
325 String imageId,
326 String displayName,
327 String kmsKeyId,
328 boolean verbose) {
329 List<BootVolume> bootVolumes = client.listBootVolumes(ListBootVolumesRequest.builder()
330 .availabilityDomain(availabilityDomain)
331 .compartmentId(compartmentId)
332 .build())
333 .items
334
335 String bootVolumeId = null
336 for (BootVolume bootVolume : bootVolumes) {
337 if (bootVolume.lifecycleState.equals(BootVolume.LifecycleState.Available)
338 && bootVolume.imageId != null
339 && bootVolume.imageId.equals(imageId)
340 && bootVolume.displayName.equals(displayName)) {
341 return bootVolume
342 }
343 if (bootVolume.lifecycleState.equals(BootVolume.LifecycleState.Available)
344 && bootVolume.imageId != null
345 && bootVolume.imageId.equals(imageId)) {
346 bootVolumeId = bootVolume.id
347 break
348 }
349 }
350
351 BootVolumeSourceDetails bootVolumeSourceDetails = BootVolumeSourceFromBootVolumeDetails.builder()
352 .id(bootVolumeId)
353 .build()
354
355 CreateBootVolumeDetails.Builder details = CreateBootVolumeDetails.builder()
356 .availabilityDomain(availabilityDomain)
357 .compartmentId(compartmentId)
358 .displayName(displayName)
359 .sourceDetails(bootVolumeSourceDetails)
360
361 if (isNotBlank(kmsKeyId)) {
362 details = details.kmsKeyId(kmsKeyId)
363 }
364
365 BootVolume bootVolume = client.createBootVolume(CreateBootVolumeRequest.builder()
366 .createBootVolumeDetails(details.build())
367 .build())
368 .bootVolume
369
370 println("Waiting for BootVolume to be ${owner.state('Available')}")
371 client.waiters.forBootVolume(
372 GetBootVolumeRequest.builder().bootVolumeId(bootVolumeId).build(),
373 BootVolume.LifecycleState.Available)
374 .execute()
375 .bootVolume
376
377 println("BootVolume '${bootVolume.displayName}' has been provisioned. id = ${owner.console.yellow(bootVolume.id)}")
378 if (verbose) printBootVolume(owner, bootVolume, 0)
379 bootVolume
380 }
381 }
|