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 == null) subnet = s
231 if (availabilityDomain == null) availabilityDomain = 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() > 15) label = HashUtil.sha1(dnsLabel.bytes).asHexString()[0..14]
275 label
276 }
277 }
|