SetupInstanceTask.groovy
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.instance
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.Image
024 import com.oracle.bmc.core.model.Instance
025 import com.oracle.bmc.core.model.InternetGateway
026 import com.oracle.bmc.core.model.Shape
027 import com.oracle.bmc.core.model.Subnet
028 import com.oracle.bmc.core.model.Vcn
029 import com.oracle.bmc.core.requests.GetSubnetRequest
030 import com.oracle.bmc.core.requests.GetVcnRequest
031 import com.oracle.bmc.core.requests.ListSubnetsRequest
032 import com.oracle.bmc.identity.IdentityClient
033 import com.oracle.bmc.identity.model.AvailabilityDomain
034 import com.oracle.bmc.identity.requests.ListAvailabilityDomainsRequest
035 import groovy.transform.CompileStatic
036 import org.gradle.api.Transformer
037 import org.gradle.api.file.RegularFile
038 import org.gradle.api.provider.Property
039 import org.gradle.api.provider.Provider
040 import org.gradle.api.tasks.Internal
041 import org.gradle.api.tasks.OutputFile
042 import org.gradle.internal.hash.HashUtil
043 import org.kordamp.gradle.plugin.oci.tasks.AbstractOCITask
044 import org.kordamp.gradle.plugin.oci.tasks.interfaces.OCITask
045 import org.kordamp.gradle.plugin.oci.tasks.traits.CompartmentIdAwareTrait
046 import org.kordamp.gradle.plugin.oci.tasks.traits.ImageAwareTrait
047 import org.kordamp.gradle.plugin.oci.tasks.traits.InstanceNameAwareTrait
048 import org.kordamp.gradle.plugin.oci.tasks.traits.OptionalAvailabilityDomainAwareTrait
049 import org.kordamp.gradle.plugin.oci.tasks.traits.OptionalDnsLabelAwareTrait
050 import org.kordamp.gradle.plugin.oci.tasks.traits.OptionalSubnetIdAwareTrait
051 import org.kordamp.gradle.plugin.oci.tasks.traits.OptionalUserDataFileAwareTrait
052 import org.kordamp.gradle.plugin.oci.tasks.traits.PublicKeyFileAwareTrait
053 import org.kordamp.gradle.plugin.oci.tasks.traits.ShapeAwareTrait
054 import org.kordamp.gradle.plugin.oci.tasks.traits.VerboseAwareTrait
055 import org.kordamp.jipsy.annotations.TypeProviderFor
056 
057 import static org.kordamp.gradle.plugin.oci.tasks.create.CreateInstanceTask.maybeCreateInstance
058 import static org.kordamp.gradle.plugin.oci.tasks.create.CreateInternetGatewayTask.maybeCreateInternetGateway
059 import static org.kordamp.gradle.plugin.oci.tasks.create.CreateSubnetTask.maybeCreateSubnet
060 import static org.kordamp.gradle.plugin.oci.tasks.create.CreateVcnTask.maybeCreateVcn
061 import static org.kordamp.gradle.plugin.oci.tasks.get.GetInstancePublicIpTask.getInstancePublicIp
062 import static org.kordamp.gradle.util.StringUtils.isNotBlank
063 
064 /**
065  @author Andres Almiray
066  @since 0.1.0
067  */
068 @CompileStatic
069 @TypeProviderFor(OCITask)
070 class SetupInstanceTask extends AbstractOCITask implements CompartmentIdAwareTrait,
071     InstanceNameAwareTrait,
072     ImageAwareTrait,
073     ShapeAwareTrait,
074     PublicKeyFileAwareTrait,
075     OptionalUserDataFileAwareTrait,
076     OptionalDnsLabelAwareTrait,
077     OptionalAvailabilityDomainAwareTrait,
078     OptionalSubnetIdAwareTrait,
079     VerboseAwareTrait {
080     static final String TASK_DESCRIPTION = 'Setups an Instance with Vcn, InternetGateway, Subnets, InstanceConsoleConnection, and Volume.'
081 
082     private final Property<String> createdInstanceId = project.objects.property(String)
083     private final Provider<RegularFile> output
084 
085     SetupInstanceTask() {
086         output = getResolvedInstanceName().map(new Transformer<RegularFile, String>() {
087             @Override
088             RegularFile transform(String s) {
089                 return project.layout.buildDirectory.file("oci/instance/${s}.properties").get()
090             }
091         })
092     }
093 
094     @Internal
095     Property<String> getCreatedInstanceId() {
096         this.@createdInstanceId
097     }
098 
099     @OutputFile
100     Provider<RegularFile> getOutput() {
101         this.@output
102     }
103 
104     String outputProperty(String key) {
105         Properties props = new Properties()
106         props.load(output.get().asFile.newInputStream())
107         props.get(key)
108     }
109 
110     @Override
111     protected void doExecuteTask() {
112         validateCompartmentId()
113         validateImage()
114         validateShape()
115         validatePublicKeyFile()
116 
117         output.get().asFile.parentFile.mkdirs()
118 
119         Properties props = new Properties()
120         props.put('compartment.id', getResolvedCompartmentId().get())
121 
122         ComputeClient computeClient = createComputeClient()
123         IdentityClient identityClient = createIdentityClient()
124         VirtualNetworkClient vcnClient = createVirtualNetworkClient()
125         BlockstorageClient blockstorageClient = createBlockstorageClient()
126 
127         Image _image = validateImage(computeClient, getResolvedCompartmentId().get())
128         Shape _shape = validateShape(computeClient, getResolvedCompartmentId().get())
129 
130         File publicKeyFile = getResolvedPublicKeyFile().get().asFile
131         File userDataFile = getResolvedUserDataFile()?.get()?.asFile
132         String internetGatewayDisplayName = getResolvedInstanceName().get() '-internet-gateway'
133         String kmsKeyId = ''
134 
135         AvailabilityDomain availabilityDomain = null
136         Subnet subnet = null
137         Vcn vcn = null
138 
139         if (isNotBlank(getResolvedAvailabilityDomain().orNull)) {
140             for (AvailabilityDomain ad : identityClient.listAvailabilityDomains(ListAvailabilityDomainsRequest.builder()
141                 .compartmentId(getResolvedCompartmentId().get())
142                 .build()).items) {
143                 if (ad.id == getResolvedAvailabilityDomain().get()) {
144                     availabilityDomain = ad
145                     break
146                 }
147             }
148         else if (isNotBlank(getResolvedSubnetId().orNull)) {
149             try {
150                 subnet = vcnClient.getSubnet(GetSubnetRequest.builder()
151                     .subnetId(getResolvedSubnetId().get())
152                     .build())
153                     .subnet
154 
155                 vcn = vcnClient.getVcn(GetVcnRequest.builder()
156                     .vcnId(subnet.vcnId)
157                     .build())
158                     .vcn
159             catch (Exception ignored) {
160                 // ignored
161             }
162         }
163 
164         if (!subnet) {
165             String networkCidrBlock = '10.0.0.0/16'
166             String vcnDisplayName = getResolvedInstanceName().get() '-vcn'
167             String dnsLabel = normalizeDnsLabel(isNotBlank(getResolvedDnsLabel().orNull? getResolvedDnsLabel().get() : getResolvedInstanceName().get())
168             vcn = maybeCreateVcn(this,
169                 vcnClient,
170                 getResolvedCompartmentId().get(),
171                 vcnDisplayName,
172                 dnsLabel,
173                 networkCidrBlock,
174                 true,
175                 getResolvedVerbose().get())
176         }
177 
178         props.put('vcn.id', vcn.id)
179         props.put('vcn.name', vcn.displayName)
180         props.put('vcn.security-list.id', vcn.defaultSecurityListId)
181         props.put('vcn.route-table.id', vcn.defaultRouteTableId)
182 
183         InternetGateway internetGateway = maybeCreateInternetGateway(this,
184             vcnClient,
185             getResolvedCompartmentId().get(),
186             vcn.id,
187             internetGatewayDisplayName,
188             true,
189             getResolvedVerbose().get())
190         props.put('internet-gateway.id', internetGateway.id)
191 
192         if (!subnet) {
193             if (availabilityDomain) {
194                 for (Subnet s : vcnClient.listSubnets(ListSubnetsRequest.builder()
195                     .compartmentId(getResolvedCompartmentId().get())
196                     .vcnId(vcn.id)
197                     .build()).items) {
198                     if (s.availabilityDomain == availabilityDomain.name) {
199                         subnet = s
200                         props.put('vcn.subnets', '1')
201                         props.put('subnet.0.id'.toString(), s.id)
202                         props.put('subnet.0.name'.toString(), s.displayName)
203                         break
204                     }
205                 }
206             else {
207                 int subnetIndex = 0
208                 // create a Subnet per AvailabilityDomain
209                 List<AvailabilityDomain> availabilityDomains = identityClient.listAvailabilityDomains(ListAvailabilityDomainsRequest.builder()
210                     .compartmentId(getResolvedCompartmentId().get())
211                     .build()).items
212                 props.put('vcn.subnets', availabilityDomains.size().toString())
213                 for (AvailabilityDomain domain : availabilityDomains) {
214                     String subnetDnsLabel = 'sub' + HashUtil.sha1(vcn.id.bytes).asHexString()[0..8(subnetIndex.toString().padLeft(3'0'))
215 
216                     Subnet s = maybeCreateSubnet(this,
217                         vcnClient,
218                         getResolvedCompartmentId().get(),
219                         vcn.id,
220                         subnetDnsLabel,
221                         domain.name,
222                         'Subnet ' + domain.name,
223                         "10.0.${subnetIndex}.0/24".toString(),
224                         true,
225                         getResolvedVerbose().get())
226                     props.put("subnet.${subnetIndex}.id".toString(), s.id)
227                     props.put("subnet.${subnetIndex}.name".toString(), s.displayName)
228 
229                     // save the first one
230                     if (subnet == nullsubnet = s
231                     if (availabilityDomain == nullavailabilityDomain = domain
232                     subnetIndex++
233                 }
234             }
235         }
236 
237         Instance instance = maybeCreateInstance(this,
238             computeClient,
239             vcnClient,
240             blockstorageClient,
241             identityClient,
242             getResolvedCompartmentId().get(),
243             getResolvedInstanceName().get(),
244             _image,
245             _shape,
246             availabilityDomain,
247             subnet,
248             publicKeyFile,
249             userDataFile,
250             kmsKeyId,
251             true)
252         createdInstanceId.set(instance.id)
253         props.put('instance.id', instance.id)
254         props.put('instance.name', instance.displayName)
255 
256         Set<String> publicIps = getInstancePublicIp(this,
257             computeClient,
258             vcnClient,
259             getResolvedCompartmentId().get(),
260             instance.id)
261 
262         props.put('instance.public-ips', publicIps.size().toString())
263         int publicIpIndex = 0
264         for (String publicIp : publicIps) {
265             props.put("instance.public-ip.${publicIpIndex++}".toString(), publicIp)
266         }
267 
268         props.store(new FileWriter(getOutput().get().asFile)'')
269         println("Result stored at ${console.yellow(getOutput().get().asFile.absolutePath)}")
270     }
271 
272     private String normalizeDnsLabel(String dnsLabel) {
273         String label = dnsLabel?.replace('.''')?.replace('-''')
274         if (label?.length() 15label = HashUtil.sha1(dnsLabel.bytes).asHexString()[0..14]
275         label
276     }
277 }