Skip to content

Commit d7b785f

Browse files
committed
Adding k8s deploy action to cloud assets
1 parent a013912 commit d7b785f

File tree

20 files changed

+1005
-11
lines changed

20 files changed

+1005
-11
lines changed

enterprise/cloud.oracle/nbproject/project.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@
166166
<specification-version>2.61</specification-version>
167167
</run-dependency>
168168
</dependency>
169+
<dependency>
170+
<code-name-base>org.netbeans.libs.fabric8</code-name-base>
171+
<build-prerequisite/>
172+
<compile-dependency/>
173+
<run-dependency>
174+
<release-version>1</release-version>
175+
<specification-version>1.0</specification-version>
176+
</run-dependency>
177+
</dependency>
169178
<dependency>
170179
<code-name-base>org.netbeans.modules.project.dependency</code-name-base>
171180
<build-prerequisite/>
@@ -301,6 +310,15 @@
301310
<code-name-base>slf4j.api</code-name-base>
302311
<run-dependency/>
303312
</dependency>
313+
<dependency>
314+
<code-name-base>org.netbeans.libs.snakeyaml_engine</code-name-base>
315+
<build-prerequisite/>
316+
<compile-dependency/>
317+
<run-dependency>
318+
<release-version>2</release-version>
319+
<specification-version>2.15</specification-version>
320+
</run-dependency>
321+
</dependency>
304322
</module-dependencies>
305323
<test-dependencies>
306324
<test-type>
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.netbeans.modules.cloud.oracle.assets;
20+
21+
import com.oracle.bmc.Region;
22+
import com.oracle.bmc.http.Priorities;
23+
import com.oracle.bmc.http.client.HttpClient;
24+
import com.oracle.bmc.http.client.HttpRequest;
25+
import com.oracle.bmc.http.client.Method;
26+
import com.oracle.bmc.http.client.jersey.JerseyHttpProvider;
27+
import com.oracle.bmc.http.signing.RequestSigningFilter;
28+
import io.fabric8.kubernetes.api.model.apps.Deployment;
29+
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
30+
import io.fabric8.kubernetes.api.model.apps.DeploymentList;
31+
import io.fabric8.kubernetes.client.Config;
32+
import io.fabric8.kubernetes.client.KubernetesClient;
33+
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
34+
import java.awt.event.ActionEvent;
35+
import java.awt.event.ActionListener;
36+
import java.io.UnsupportedEncodingException;
37+
import java.net.URI;
38+
import java.net.URISyntaxException;
39+
import java.net.URLEncoder;
40+
import java.nio.charset.StandardCharsets;
41+
import java.time.Instant;
42+
import java.util.Base64;
43+
import java.util.List;
44+
import java.util.Map;
45+
import java.util.concurrent.ExecutionException;
46+
import java.util.function.Consumer;
47+
import org.netbeans.api.progress.ProgressHandle;
48+
import org.netbeans.api.project.Project;
49+
import org.netbeans.api.project.ProjectInformation;
50+
import org.netbeans.api.project.ProjectUtils;
51+
import org.netbeans.modules.cloud.oracle.OCIManager;
52+
import org.netbeans.modules.cloud.oracle.OCIProfile;
53+
import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
54+
import org.netbeans.modules.cloud.oracle.developer.ContainerTagItem;
55+
import org.netbeans.modules.cloud.oracle.steps.ProjectStep;
56+
import org.openide.awt.ActionID;
57+
import org.openide.awt.ActionRegistration;
58+
import org.openide.util.Exceptions;
59+
import org.openide.util.Lookup;
60+
import org.openide.util.NbBundle;
61+
import org.openide.util.RequestProcessor;
62+
import org.snakeyaml.engine.v2.api.Dump;
63+
import org.snakeyaml.engine.v2.api.DumpSettings;
64+
import org.snakeyaml.engine.v2.api.Load;
65+
import org.snakeyaml.engine.v2.api.LoadSettings;
66+
67+
/**
68+
*
69+
* @author Jan Horvath
70+
*/
71+
@ActionID(
72+
category = "Tools",
73+
id = "org.netbeans.modules.cloud.oracle.actions.RunInClusterAction"
74+
)
75+
@ActionRegistration(
76+
displayName = "#RunInCluster",
77+
asynchronous = true
78+
)
79+
80+
@NbBundle.Messages({
81+
"RunInCluster=Run in OKE Cluster",
82+
"Deploying=Deploying project \"{0}\" to the cluster \"{1}\""
83+
})
84+
public class RunInClusterAction implements ActionListener {
85+
86+
private final ContainerTagItem context;
87+
private static final String APP = "app"; //NOI18N
88+
private static final String DEFAULT = "default"; //NOI18N
89+
private static final RequestProcessor RP = new RequestProcessor(RunInClusterAction.class);
90+
91+
public RunInClusterAction(ContainerTagItem context) {
92+
this.context = context;
93+
}
94+
95+
@Override
96+
public void actionPerformed(ActionEvent e) {
97+
RP.post(() -> runInCluster());
98+
}
99+
100+
private void runInCluster() {
101+
ClusterItem cluster = CloudAssets.getDefault().getItem(ClusterItem.class);
102+
if (cluster.getConfig() == null) {
103+
cluster.update();
104+
}
105+
if (cluster.getConfig() == null) {
106+
throw new RuntimeException("Invalid cluster configuration");
107+
}
108+
ProgressHandle h;
109+
String projectName;
110+
try {
111+
Project project = Steps.getDefault()
112+
.executeMultistep(new ProjectStep(), Lookup.EMPTY)
113+
.thenApply(values -> {
114+
return values.getValueForStep(ProjectStep.class);
115+
})
116+
.get();
117+
ProjectInformation pi = ProjectUtils.getInformation(project);
118+
projectName = pi.getDisplayName();
119+
h = ProgressHandle.createHandle(Bundle.Deploying(projectName, cluster.getName()));
120+
} catch (InterruptedException | ExecutionException e) {
121+
throw new RuntimeException(e);
122+
}
123+
try {
124+
h.start();
125+
runWithClient(cluster, client -> {
126+
Deployment existingDeployment = null;
127+
DeploymentList dList = client.apps().deployments().list();
128+
for (Deployment deployment : dList.getItems()) {
129+
if (projectName.equals(deployment.getMetadata().getName())) {
130+
existingDeployment = deployment;
131+
}
132+
}
133+
if (existingDeployment != null) {
134+
client.apps()
135+
.deployments()
136+
.inNamespace(DEFAULT)
137+
.withName(projectName)
138+
.edit(d -> new DeploymentBuilder(d)
139+
.editSpec()
140+
.editTemplate()
141+
.editSpec()
142+
.editFirstContainer()
143+
.withImage(context.getUrl()) // New image version
144+
.endContainer()
145+
.endSpec()
146+
.endTemplate()
147+
.endSpec()
148+
.build());
149+
150+
client.apps()
151+
.deployments()
152+
.inNamespace(DEFAULT)
153+
.withName(projectName)
154+
.edit(d -> new DeploymentBuilder(d)
155+
.editSpec()
156+
.editTemplate()
157+
.editMetadata()
158+
.addToAnnotations("kubectl.kubernetes.io/restartedAt", Instant.now().toString()) //NOI18N
159+
.endMetadata()
160+
.endTemplate()
161+
.endSpec()
162+
.build());
163+
164+
} else {
165+
Deployment newDeployment = new DeploymentBuilder()
166+
.withNewMetadata()
167+
.withName(projectName)
168+
.addToLabels(APP, projectName)
169+
.endMetadata()
170+
.withNewSpec()
171+
.withReplicas(3)
172+
.withNewSelector()
173+
.addToMatchLabels(APP, projectName)
174+
.endSelector()
175+
.withNewTemplate()
176+
.withNewMetadata()
177+
.addToLabels(APP, projectName)
178+
.endMetadata()
179+
.withNewSpec()
180+
.addNewImagePullSecret().withName("docker-bearer-vscode-generated-ocirsecret").endImagePullSecret() //NOI18N
181+
.addNewContainer()
182+
.withName(projectName)
183+
.withImage(context.getUrl())
184+
.addNewPort()
185+
.withContainerPort(8080)
186+
.endPort()
187+
.endContainer()
188+
.endSpec()
189+
.endTemplate()
190+
.endSpec()
191+
.build();
192+
193+
client.apps()
194+
.deployments()
195+
.inNamespace(DEFAULT)
196+
.resource(newDeployment)
197+
.create();
198+
}
199+
});
200+
} finally {
201+
h.finish();
202+
}
203+
}
204+
205+
private void runWithClient(ClusterItem cluster, Consumer<KubernetesClient> consumer) {
206+
Config config = prepareConfig(cluster.getConfig(), cluster);
207+
try (KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();) {
208+
consumer.accept(client);
209+
} catch (Exception e) {
210+
Exceptions.printStackTrace(e);
211+
}
212+
}
213+
214+
private Config prepareConfig(String content, ClusterItem cluster) {
215+
String token = getBearerToken(cluster);
216+
LoadSettings settings = LoadSettings.builder().build();
217+
Load load = new Load(settings);
218+
Map<String, Object> data = (Map<String, Object>) load.loadFromString(content);
219+
220+
List<Map<String, Object>> users = (List<Map<String, Object>>) data.get("users"); //NOI18N
221+
if (users == null) {
222+
throw new RuntimeException("Invalid cluster configuration");
223+
}
224+
Map exec = null;
225+
for (Map<String, Object> userEntry : users) {
226+
Map<String, Object> user = (Map<String, Object>) userEntry.get("user"); //NOI18N
227+
if (user != null && user.containsKey("exec")) { //NOI18N
228+
exec = (Map) user.remove("exec"); //NOI18N
229+
}
230+
}
231+
String clusterId = null;
232+
if (exec != null && "oci".equals(exec.get("command"))) { //NOI18N
233+
List commandArgs = (List) exec.get("args"); //NOI18N
234+
boolean clusterIdNext = false;
235+
for (Object arg : commandArgs) {
236+
if ("--cluster-id".equals(arg)) {
237+
clusterIdNext = true;
238+
continue;
239+
}
240+
if (clusterIdNext) {
241+
clusterId = (String) arg;
242+
break;
243+
}
244+
245+
}
246+
}
247+
if (!cluster.getKey().getValue().equals(clusterId)) {
248+
throw new RuntimeException("Failed to read cluster config"); //NOI18N
249+
}
250+
Dump dump = new Dump(DumpSettings.builder().build());
251+
String noExec = dump.dumpToString(data);
252+
Config config = Config.fromKubeconfig(noExec);
253+
config.setOauthToken(token);
254+
return config;
255+
}
256+
257+
private String getBearerToken(ClusterItem cluster) {
258+
try {
259+
OCIProfile profile = OCIManager.getDefault().getActiveProfile(cluster);
260+
Region region = Region.fromRegionCodeOrId(cluster.getRegionCode());
261+
URI uri = new URI(String.format(
262+
"https://containerengine.%s.oraclecloud.com/cluster_request/%s", //NOI18N
263+
region.getRegionId(), cluster.getKey().getValue()
264+
));
265+
266+
RequestSigningFilter requestSigningFilter
267+
= RequestSigningFilter.fromAuthProvider(profile.getAuthenticationProvider());
268+
269+
HttpClient client = JerseyHttpProvider.getInstance().newBuilder()
270+
.registerRequestInterceptor(Priorities.AUTHENTICATION, requestSigningFilter)
271+
.baseUri(uri).build();
272+
273+
HttpRequest request = client.createRequest(Method.GET);
274+
275+
request.execute().toCompletableFuture();
276+
List<String> tokenUrlList = request.headers().get("authorization"); //NOI18N
277+
String authorization = URLEncoder.encode(tokenUrlList.get(0), "UTF-8"); //NOI18N
278+
List<String> dateList = request.headers().get("date"); //NOI18N
279+
String date = URLEncoder.encode(dateList.get(0), "UTF-8"); //NOI18N
280+
String baseString = String.format("%s?authorization=%s&date=%s", uri.toASCIIString(), authorization, date); //NOI18N
281+
byte[] urlBytes = baseString.getBytes(StandardCharsets.UTF_8);
282+
return Base64.getUrlEncoder().encodeToString(urlBytes);
283+
} catch (URISyntaxException | UnsupportedEncodingException ex) {
284+
throw new RuntimeException(ex);
285+
}
286+
}
287+
288+
}

enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterItem.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@
1818
*/
1919
package org.netbeans.modules.cloud.oracle.compute;
2020

21+
import com.oracle.bmc.containerengine.ContainerEngineClient;
22+
import com.oracle.bmc.containerengine.requests.CreateKubeconfigRequest;
23+
import com.oracle.bmc.containerengine.responses.CreateKubeconfigResponse;
24+
import java.io.IOException;
25+
import java.nio.charset.Charset;
26+
import org.netbeans.modules.cloud.oracle.OCIManager;
27+
import org.netbeans.modules.cloud.oracle.OCIProfile;
2128
import org.netbeans.modules.cloud.oracle.items.OCID;
2229
import org.netbeans.modules.cloud.oracle.items.OCIItem;
30+
import org.openide.util.Exceptions;
2331

2432
/**
2533
*
2634
* @author Jan Horvath
2735
*/
2836
public final class ClusterItem extends OCIItem {
37+
private String config = null;
2938

3039
public ClusterItem(OCID id, String compartmentId, String name, String tenancyId, String regionCode) {
3140
super(id, compartmentId, name, tenancyId, regionCode);
@@ -34,5 +43,30 @@ public ClusterItem(OCID id, String compartmentId, String name, String tenancyId,
3443
public ClusterItem() {
3544
super();
3645
}
46+
47+
public String getConfig() {
48+
return config;
49+
}
50+
51+
public void setConfig(String config) {
52+
this.config = config;
53+
}
3754

55+
public void update() {
56+
OCIProfile profile = OCIManager.getDefault().getActiveProfile(this);
57+
ContainerEngineClient containerEngineClient = profile.newClient(ContainerEngineClient.class);
58+
59+
60+
CreateKubeconfigRequest request = CreateKubeconfigRequest.builder()
61+
.clusterId(getKey().getValue())
62+
.build();
63+
64+
CreateKubeconfigResponse response = containerEngineClient.createKubeconfig(request);
65+
66+
try {
67+
setConfig(new String(response.getInputStream().readAllBytes(), Charset.defaultCharset()));
68+
} catch (IOException ex) {
69+
Exceptions.printStackTrace(ex);
70+
}
71+
}
3872
}

enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/compute/ClusterNode.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919
package org.netbeans.modules.cloud.oracle.compute;
2020

21-
import com.oracle.bmc.containerengine.ContainerEngineClient;
2221
import com.oracle.bmc.containerengine.requests.ListClustersRequest;
2322
import com.oracle.bmc.core.model.Instance;
2423
import java.util.stream.Collectors;
@@ -30,6 +29,9 @@
3029
import org.openide.nodes.Children;
3130
import org.openide.util.NbBundle;
3231

32+
import com.oracle.bmc.containerengine.ContainerEngineClient;
33+
34+
3335
/**
3436
*
3537
* @author Jan Horvath
@@ -85,4 +87,5 @@ public static ChildrenProvider.SessionAware<CompartmentItem, ClusterItem> getClu
8587
.collect(Collectors.toList());
8688
};
8789
}
90+
8891
}

0 commit comments

Comments
 (0)