Skip to content

Commit 88f3218

Browse files
committed
Validate namespace existence
1 parent 007dfd9 commit 88f3218

File tree

7 files changed

+155
-35
lines changed

7 files changed

+155
-35
lines changed

src/main/java/cd/go/contrib/elasticagent/KubernetesPlugin.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public GoPluginApiResponse handle(GoPluginApiRequest request) throws UnhandledRe
5656
case PLUGIN_SETTINGS_GET_VIEW:
5757
return new GetViewRequestExecutor().execute();
5858
case PLUGIN_SETTINGS_VALIDATE_CONFIGURATION:
59-
return ValidatePluginSettings.fromJSON(request.requestBody()).executor(pluginRequest).execute();
59+
return ValidatePluginSettingsRequest.fromJSON(request.requestBody()).executor(pluginRequest).execute();
6060
case REQUEST_GET_PROFILE_METADATA:
6161
return new GetProfileMetadataExecutor().execute();
6262
case REQUEST_GET_PROFILE_VIEW:
@@ -67,13 +67,16 @@ public GoPluginApiResponse handle(GoPluginApiRequest request) throws UnhandledRe
6767
refreshInstances();
6868
return CreateAgentRequest.fromJSON(request.requestBody()).executor(agentInstances, pluginRequest).execute();
6969
case REQUEST_SHOULD_ASSIGN_WORK:
70+
refreshInstances();
7071
return ShouldAssignWorkRequest.fromJSON(request.requestBody()).executor(agentInstances).execute();
7172
case REQUEST_SERVER_PING:
7273
refreshInstances();
7374
return new ServerPingRequestExecutor(agentInstances, pluginRequest).execute();
7475
case REQUEST_STATUS_REPORT:
76+
refreshInstances();
7577
return new StatusReportExecutor(pluginRequest).execute();
7678
case REQUEST_ELASTIC_AGENT_STATUS_REPORT:
79+
refreshInstances();
7780
return AgentStatusReportRequest.fromJSON(request.requestBody()).executor(pluginRequest).execute();
7881
default:
7982
throw new UnhandledRequestTypeException(request.requestName());

src/main/java/cd/go/contrib/elasticagent/executors/GetPluginConfigurationExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class GetPluginConfigurationExecutor implements RequestExecutor {
3232
public static final Field AUTO_REGISTER_TIMEOUT = new PositiveNumberField("auto_register_timeout", "Agent auto-register timeout (in minutes)", "10", false, false, "1");
3333
public static final Field MAX_PENDING_PODS = new PositiveNumberField("pending_pods_count", "Maximum pending pods", "10", false, false, "2");
3434
public static final Field CLUSTER_URL = new HttpsURLField("kubernetes_cluster_url", "Cluster URL", true, "3");
35-
public static final Field NAMESPACE = new Field("namespace", "Namespace", null, false, false, "4");
35+
public static final Field NAMESPACE = new Field("namespace", "Namespace", "default", false, false, "4");
3636
public static final Field OAUTH_TOKEN = new NonBlankField("oauth_token", "Oauth token", true, "5");
3737
public static final Field CLUSTER_CA_CERT = new Field("kubernetes_cluster_ca_cert", "Cluster ca certificate", null, false, true, "6");
3838

src/main/java/cd/go/contrib/elasticagent/executors/ValidateConfigurationExecutor.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,55 +16,83 @@
1616

1717
package cd.go.contrib.elasticagent.executors;
1818

19+
import cd.go.contrib.elasticagent.KubernetesClientFactory;
1920
import cd.go.contrib.elasticagent.PluginRequest;
2021
import cd.go.contrib.elasticagent.RequestExecutor;
2122
import cd.go.contrib.elasticagent.ServerRequestFailedException;
2223
import cd.go.contrib.elasticagent.model.Field;
2324
import cd.go.contrib.elasticagent.model.ServerInfo;
24-
import cd.go.contrib.elasticagent.requests.ValidatePluginSettings;
25+
import cd.go.contrib.elasticagent.requests.ValidatePluginSettingsRequest;
2526
import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse;
2627
import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse;
28+
import io.fabric8.kubernetes.api.model.Namespace;
29+
import io.fabric8.kubernetes.client.KubernetesClient;
2730

2831
import java.util.ArrayList;
2932
import java.util.HashMap;
3033
import java.util.List;
3134
import java.util.Map;
3235

3336
import static cd.go.contrib.elasticagent.KubernetesPlugin.LOG;
34-
import static cd.go.contrib.elasticagent.executors.GetPluginConfigurationExecutor.FIELDS;
35-
import static cd.go.contrib.elasticagent.executors.GetPluginConfigurationExecutor.GO_SERVER_URL;
37+
import static cd.go.contrib.elasticagent.executors.GetPluginConfigurationExecutor.*;
3638
import static cd.go.contrib.elasticagent.utils.Util.GSON;
39+
import static java.text.MessageFormat.format;
3740
import static org.apache.commons.lang3.StringUtils.isBlank;
3841

3942
public class ValidateConfigurationExecutor implements RequestExecutor {
40-
private final ValidatePluginSettings settings;
43+
private final ValidatePluginSettingsRequest validatePluginSettingsRequest;
4144
private PluginRequest pluginRequest;
45+
private final KubernetesClientFactory factory;
4246
private List<Map<String, String>> result = new ArrayList<>();
4347

44-
public ValidateConfigurationExecutor(ValidatePluginSettings settings, PluginRequest pluginRequest) {
45-
this.settings = settings;
48+
public ValidateConfigurationExecutor(ValidatePluginSettingsRequest validatePluginSettingsRequest, PluginRequest pluginRequest) {
49+
this(validatePluginSettingsRequest, pluginRequest, KubernetesClientFactory.instance());
50+
}
51+
52+
ValidateConfigurationExecutor(ValidatePluginSettingsRequest validatePluginSettingsRequest, PluginRequest pluginRequest, KubernetesClientFactory factory) {
53+
this.validatePluginSettingsRequest = validatePluginSettingsRequest;
4654
this.pluginRequest = pluginRequest;
55+
this.factory = factory;
4756
}
4857

4958
public GoPluginApiResponse execute() throws ServerRequestFailedException {
5059
LOG.debug("Validating plugin settings.");
5160

5261
for (Map.Entry<String, Field> entry : FIELDS.entrySet()) {
5362
Field field = entry.getValue();
54-
Map<String, String> validationError = field.validate(settings.get(entry.getKey()));
63+
Map<String, String> validationError = field.validate(validatePluginSettingsRequest.get(entry.getKey()));
5564

5665
if (!validationError.isEmpty()) {
5766
result.add(validationError);
5867
}
5968
}
6069

6170
validateGoServerUrl();
71+
validateNamespaceExistence();
6272

6373
return DefaultGoPluginApiResponse.success(GSON.toJson(result));
6474
}
6575

76+
private void validateNamespaceExistence() {
77+
try {
78+
final String namespace = validatePluginSettingsRequest.getPluginSettingsMap().getNamespace();
79+
if (isBlank(namespace)) {
80+
return;
81+
}
82+
final KubernetesClient client = factory.client(validatePluginSettingsRequest.getPluginSettingsMap());
83+
final List<Namespace> namespaceList = client.namespaces().list().getItems();
84+
if (namespaceList.stream().anyMatch(n -> n.getMetadata().getName().equals(namespace))) {
85+
return;
86+
}
87+
88+
result.add(error(NAMESPACE.key(), format("Namespace `{0}` does not exist in you cluster. Run \"kubectl create namespace {1}\" to create a namespace.", namespace, namespace)));
89+
} catch (Exception e) {
90+
result.add(error(NAMESPACE.key(), format("Failed to validate namespace existence: {0} Please check plugin log for more detail.", e.getMessage())));
91+
}
92+
}
93+
6694
private void validateGoServerUrl() {
67-
if (isBlank(settings.get(GO_SERVER_URL.key()))) {
95+
if (isBlank(validatePluginSettingsRequest.get(GO_SERVER_URL.key()))) {
6896
ServerInfo severInfo = pluginRequest.getSeverInfo();
6997
if (isBlank(severInfo.getSecureSiteUrl())) {
7098
Map<String, String> error = error(GO_SERVER_URL.key(), "Secure site url is not configured. Please specify Go Server Url.");

src/main/java/cd/go/contrib/elasticagent/requests/ValidatePluginSettings.java renamed to src/main/java/cd/go/contrib/elasticagent/requests/ValidatePluginSettingsRequest.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,44 +17,65 @@
1717
package cd.go.contrib.elasticagent.requests;
1818

1919
import cd.go.contrib.elasticagent.PluginRequest;
20+
import cd.go.contrib.elasticagent.PluginSettings;
2021
import cd.go.contrib.elasticagent.RequestExecutor;
2122
import cd.go.contrib.elasticagent.executors.ValidateConfigurationExecutor;
23+
import com.google.gson.JsonObject;
2224
import com.google.gson.annotations.Expose;
2325
import com.google.gson.annotations.SerializedName;
2426

2527
import java.util.HashMap;
2628

2729
import static cd.go.contrib.elasticagent.utils.Util.GSON;
2830

29-
public class ValidatePluginSettings {
31+
public class ValidatePluginSettingsRequest {
3032
@Expose
3133
@SerializedName("plugin-settings")
32-
private PluginSettings pluginSettings = new PluginSettings();
34+
private PluginSettingsMap pluginSettingsMap = new PluginSettingsMap();
3335

34-
public ValidatePluginSettings() {
36+
public ValidatePluginSettingsRequest() {
3537
}
3638

37-
public static ValidatePluginSettings fromJSON(String json) {
38-
return GSON.fromJson(json, ValidatePluginSettings.class);
39+
public static ValidatePluginSettingsRequest fromJSON(String json) {
40+
return GSON.fromJson(json, ValidatePluginSettingsRequest.class);
3941
}
4042

4143
public RequestExecutor executor(PluginRequest pluginRequest) {
4244
return new ValidateConfigurationExecutor(this, pluginRequest);
4345
}
4446

4547
public String get(String key) {
46-
if (pluginSettings == null || pluginSettings.get(key) == null) {
48+
if (pluginSettingsMap == null || pluginSettingsMap.get(key) == null) {
4749
return null;
4850
}
4951

50-
return pluginSettings.get(key).value;
52+
return pluginSettingsMap.get(key).value;
53+
}
54+
55+
public PluginSettings getPluginSettingsMap() {
56+
return pluginSettingsMap.toPluginSettings();
5157
}
5258

5359
public void put(String key, String value) {
54-
pluginSettings.put(key, new Value(value));
60+
pluginSettingsMap.put(key, new Value(value));
5561
}
5662

57-
private static class PluginSettings extends HashMap<String, Value> {
63+
private static class PluginSettingsMap extends HashMap<String, Value> {
64+
private PluginSettings pluginSettings;
65+
66+
public PluginSettings toPluginSettings() {
67+
if (pluginSettings != null) {
68+
return pluginSettings;
69+
}
70+
71+
final JsonObject jsonObject = new JsonObject();
72+
for (String key : keySet()) {
73+
jsonObject.addProperty(key, get(key).value);
74+
}
75+
76+
pluginSettings = PluginSettings.fromJSON(jsonObject.toString());
77+
return pluginSettings;
78+
}
5879
}
5980

6081
private static class Value {

src/test/java/cd/go/contrib/elasticagent/executors/GetPluginConfigurationExecutorTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public void assertJsonStructure() throws Exception {
7575
" },\n" +
7676
" \"namespace\": {\n" +
7777
" \"display-name\": \"Namespace\",\n" +
78+
" \"default-value\": \"default\",\n" +
7879
" \"required\": false,\n" +
7980
" \"secure\": false,\n" +
8081
" \"display-order\": \"4\"\n" +

src/test/java/cd/go/contrib/elasticagent/executors/ValidateConfigurationExecutorTest.java

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,47 @@
1616

1717
package cd.go.contrib.elasticagent.executors;
1818

19+
import cd.go.contrib.elasticagent.KubernetesClientFactory;
1920
import cd.go.contrib.elasticagent.PluginRequest;
2021
import cd.go.contrib.elasticagent.model.ServerInfo;
21-
import cd.go.contrib.elasticagent.requests.ValidatePluginSettings;
22+
import cd.go.contrib.elasticagent.requests.ValidatePluginSettingsRequest;
2223
import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse;
24+
import io.fabric8.kubernetes.api.model.DoneableNamespace;
25+
import io.fabric8.kubernetes.api.model.Namespace;
26+
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
27+
import io.fabric8.kubernetes.api.model.NamespaceList;
28+
import io.fabric8.kubernetes.client.KubernetesClient;
29+
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
30+
import io.fabric8.kubernetes.client.dsl.Resource;
2331
import org.json.JSONException;
2432
import org.junit.Before;
2533
import org.junit.Test;
2634
import org.mockito.Mock;
2735
import org.skyscreamer.jsonassert.JSONAssert;
2836

37+
import java.util.Arrays;
38+
import java.util.Collections;
39+
import java.util.List;
40+
import java.util.stream.Collectors;
41+
2942
import static org.hamcrest.Matchers.is;
3043
import static org.junit.Assert.assertThat;
44+
import static org.mockito.ArgumentMatchers.any;
3145
import static org.mockito.Mockito.when;
3246
import static org.mockito.MockitoAnnotations.initMocks;
3347

3448
public class ValidateConfigurationExecutorTest {
35-
3649
@Mock
3750
private PluginRequest pluginRequest;
51+
52+
@Mock
53+
KubernetesClientFactory factory;
54+
@Mock
55+
private KubernetesClient client;
56+
@Mock
57+
private NonNamespaceOperation<Namespace, NamespaceList, DoneableNamespace, Resource<Namespace, DoneableNamespace>> mockedOperation;
58+
@Mock
59+
NamespaceList namespaceList;
3860
private ServerInfo serverInfo;
3961

4062
@Before
@@ -46,12 +68,17 @@ public void setUp() {
4668
"\"secure_site_url\": \"https://example.com:8154/go\"\n" +
4769
"}");
4870
when(pluginRequest.getSeverInfo()).thenReturn(serverInfo);
71+
when(factory.client(any())).thenReturn(client);
72+
when(client.namespaces()).thenReturn(mockedOperation);
73+
when(mockedOperation.list()).thenReturn(namespaceList);
4974
}
5075

5176
@Test
5277
public void shouldValidateABadConfiguration() throws Exception {
53-
ValidatePluginSettings settings = new ValidatePluginSettings();
54-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest).execute();
78+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
79+
80+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
81+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest, factory).execute();
5582

5683
assertThat(response.responseCode(), is(200));
5784
JSONAssert.assertEquals("[\n" +
@@ -68,23 +95,27 @@ public void shouldValidateABadConfiguration() throws Exception {
6895

6996
@Test
7097
public void shouldValidateAGoodConfiguration() throws Exception {
71-
ValidatePluginSettings settings = new ValidatePluginSettings();
98+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
99+
100+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
72101
settings.put("go_server_url", "https://ci.example.com/go");
73102
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
74103
settings.put("oauth_token", "some-token");
75-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, null).execute();
104+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, null, factory).execute();
76105

77106
assertThat(response.responseCode(), is(200));
78107
JSONAssert.assertEquals("[]", response.responseBody(), true);
79108
}
80109

81110
@Test
82111
public void shouldValidateGoServerUrl() throws Exception {
83-
ValidatePluginSettings settings = new ValidatePluginSettings();
112+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
113+
114+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
84115
serverInfo.setSecureSiteUrl(null);
85116
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
86117
settings.put("oauth_token", "some-token");
87-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest).execute();
118+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest, factory).execute();
88119

89120
assertThat(response.responseCode(), is(200));
90121
JSONAssert.assertEquals("[" +
@@ -97,11 +128,13 @@ public void shouldValidateGoServerUrl() throws Exception {
97128

98129
@Test
99130
public void shouldValidateGoServerHTTPSUrlFormat() throws Exception {
100-
ValidatePluginSettings settings = new ValidatePluginSettings();
131+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
132+
133+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
101134
settings.put("go_server_url", "foo.com/go(");
102135
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
103136
settings.put("oauth_token", "some-token");
104-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest).execute();
137+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest, factory).execute();
105138

106139
assertThat(response.responseCode(), is(200));
107140
JSONAssert.assertEquals("[" +
@@ -114,11 +147,13 @@ public void shouldValidateGoServerHTTPSUrlFormat() throws Exception {
114147

115148
@Test
116149
public void shouldValidateGoServerUrlFormat() throws Exception {
117-
ValidatePluginSettings settings = new ValidatePluginSettings();
150+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
151+
152+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
118153
settings.put("go_server_url", "https://foo.com");
119154
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
120155
settings.put("oauth_token", "some-token");
121-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest).execute();
156+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest, factory).execute();
122157

123158
assertThat(response.responseCode(), is(200));
124159
JSONAssert.assertEquals("[" +
@@ -131,11 +166,13 @@ public void shouldValidateGoServerUrlFormat() throws Exception {
131166

132167
@Test
133168
public void shouldValidateOAuthTokenWhenAuthenticationStrategyIsSetToOauthToken() throws JSONException {
134-
ValidatePluginSettings settings = new ValidatePluginSettings();
169+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
170+
171+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
135172
settings.put("go_server_url", "https://foo.com/go");
136173
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
137174

138-
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest).execute();
175+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, pluginRequest, factory).execute();
139176

140177
assertThat(response.responseCode(), is(200));
141178

@@ -146,4 +183,34 @@ public void shouldValidateOAuthTokenWhenAuthenticationStrategyIsSetToOauthToken(
146183
" }\n" +
147184
"]", response.responseBody(), true);
148185
}
186+
187+
@Test
188+
public void shouldValidateNamespaceExistence() throws JSONException {
189+
when(namespaceList.getItems()).thenReturn(getNamespaceList("default"));
190+
191+
ValidatePluginSettingsRequest settings = new ValidatePluginSettingsRequest();
192+
settings.put("go_server_url", "https://ci.example.com/go");
193+
settings.put("kubernetes_cluster_url", "https://cluster.example.com");
194+
settings.put("oauth_token", "some-token");
195+
settings.put("namespace", "gocd");
196+
GoPluginApiResponse response = new ValidateConfigurationExecutor(settings, null, factory).execute();
197+
198+
assertThat(response.responseCode(), is(200));
199+
JSONAssert.assertEquals("[\n" +
200+
" {\n" +
201+
" \"message\": \"Namespace `gocd` does not exist in you cluster. Run \\\"kubectl create namespace gocd\\\" to create a namespace.\",\n" +
202+
" \"key\": \"namespace\"\n" +
203+
" }\n" +
204+
"]", response.responseBody(), true);
205+
}
206+
207+
private List<Namespace> getNamespaceList(String... namespaces) {
208+
if (namespaces == null || namespaces.length == 0) {
209+
return Collections.emptyList();
210+
}
211+
212+
return Arrays.asList(namespaces).stream()
213+
.map(namespaceName -> new NamespaceBuilder().withNewMetadata().withName("default").endMetadata().build())
214+
.collect(Collectors.toList());
215+
}
149216
}

0 commit comments

Comments
 (0)