Skip to content

Commit

Permalink
fixes #6688: google-secret-manager: extend test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
JiriOndrusek authored and jamesnetherton committed Nov 22, 2024
1 parent a6d1b60 commit 741d6dc
Show file tree
Hide file tree
Showing 7 changed files with 457 additions and 14 deletions.
14 changes: 14 additions & 0 deletions integration-tests-jvm/google-secret-manager/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
The integration tests are executed only when valid service credentials are provided.
These tests do not use a mocked backend.

=== Real Google API

Below are the environment variables that have to be configured:

[source,shell]
----
export GOOGLE_SERVICE_ACCOUNT_KEY=<path-to-google-credentials-json-file>
export GOOGLE_PROJECT=<name-of-your-project>
----

If any of the required variables is not defined, the tests are automatically skipped.
31 changes: 31 additions & 0 deletions integration-tests-jvm/google-secret-manager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
Expand All @@ -70,6 +78,16 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-integration-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand All @@ -95,6 +113,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</profile>
</profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,28 @@
*/
package org.apache.camel.quarkus.component.google.secret.manager.it;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import com.google.cloud.secretmanager.v1.SecretVersion;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.google.secret.manager.GoogleSecretManagerOperations;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

@Path("/google-secret-manager")
Expand All @@ -32,19 +46,83 @@ public class GoogleSecretManagerResource {

private static final Logger LOG = Logger.getLogger(GoogleSecretManagerResource.class);

private static final String COMPONENT_GOOGLE_SECRET_MANAGER = "google-secret-manager";
@ConfigProperty(name = "cq.google-secrets-manager.path-to-service-account-key")
String accountKey;

@ConfigProperty(name = "cq.google-secrets-manager.project-name")
String projectName;

@Inject
CamelContext context;
ProducerTemplate producerTemplate;

@Path("/load/component/google-secret-manager")
@Path("/list/{secretName}")
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response loadComponentGoogleSecretManager() throws Exception {
/* This is an autogenerated test */
if (context.getComponent(COMPONENT_GOOGLE_SECRET_MANAGER) != null) {
return Response.ok().build();
public List<String> getSecret(@PathParam("secretName") String secretName) {
SecretManagerServiceClient.ListSecretsPagedResponse secrets = producerTemplate.requestBody("direct:listSecrets", "",
SecretManagerServiceClient.ListSecretsPagedResponse.class);
LinkedList<String> result = new LinkedList<>();
SecretManagerServiceClient.ListSecretsPage page = secrets.getPage();
while (page != null) {
page.getValues().iterator().forEachRemaining(s -> result.add(s.getName()));
page = page.getNextPage();
}
LOG.warnf("Could not load [%s] from the Camel context", COMPONENT_GOOGLE_SECRET_MANAGER);
return Response.status(500, COMPONENT_GOOGLE_SECRET_MANAGER + " could not be loaded from the Camel context").build();

return result;
}

@Path("/getGcpSecret")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String loadGcpPassword() {
return producerTemplate.requestBody("direct:loadGcpPassword", "", String.class);
}

@Path("/operation/{operation}")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response operation(@PathParam("operation") String operation, @QueryParam("body") String body,
Map<String, Object> headers) {

Exchange ex = producerTemplate.send(String.format("google-secret-manager://%s" +
"?serviceAccountKey=file:%s" +
"&operation=%s", projectName, accountKey, operation),
e -> {
e.getIn().setHeaders(headers == null ? Collections.emptyMap() : headers);
e.getIn().setBody(body == null ? "" : body);
});

Object result = null;
switch (GoogleSecretManagerOperations.valueOf(operation)) {
case listSecrets:

LinkedList<String> listedSecrets = new LinkedList<>();
SecretManagerServiceClient.ListSecretsPagedResponse response = ex.getIn()
.getBody(SecretManagerServiceClient.ListSecretsPagedResponse.class);
SecretManagerServiceClient.ListSecretsPage page = response.getPage();
while (page != null) {
page.getValues().iterator().forEachRemaining(s -> listedSecrets.add(s.getName()));
page = page.getNextPage();
}

result = listedSecrets;
break;
case createSecret:
SecretVersion createdSecret = ex.getIn().getBody(SecretVersion.class);
result = createdSecret.getName();
break;
case deleteSecret:
result = true;
break;
case getSecretVersion:
result = ex.getIn().getBody(String.class);
break;
default:
return Response.status(500).build();
}

return Response.ok(result).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.quarkus.component.google.secret.manager.it;

import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.language.SimpleExpression;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@ApplicationScoped
public class GoogleSecretManagerRoutes extends RouteBuilder {

@ConfigProperty(name = "gcpSecretId")
String gcpSecretId;

@Override
public void configure() throws Exception {
from("direct:loadGcpPassword")
.setBody(new SimpleExpression("{{gcp:%s@1}}".formatted(gcpSecretId)));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## ---------------------------------------------------------------------------
## Licensed to the Apache Software Foundation (ASF) under one or more
## contributor license agreements. See the NOTICE file distributed with
## this work for additional information regarding copyright ownership.
## The ASF licenses this file to You under the Apache License, Version 2.0
## (the "License"); you may not use this file except in compliance with
## the License. You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
## ---------------------------------------------------------------------------
cq.google-secrets-manager.path-to-service-account-key=${GOOGLE_SERVICE_ACCOUNT_KEY}
cq.google-secrets-manager.project-name=${GOOGLE_PROJECT_NAME}
camel.vault.gcp.serviceAccountKey=file:${GOOGLE_SERVICE_ACCOUNT_KEY}
camel.vault.gcp.projectId=${GOOGLE_PROJECT_NAME}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,144 @@
*/
package org.apache.camel.quarkus.component.google.secret.manager.it;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import io.quarkus.logging.Log;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.apache.camel.component.google.secret.manager.GoogleSecretManagerConstants;
import org.apache.camel.component.google.secret.manager.GoogleSecretManagerOperations;
import org.apache.camel.quarkus.test.mock.backend.MockBackendUtils;
import org.awaitility.Awaitility;
import org.eclipse.microprofile.config.ConfigProvider;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Todo use MockBackendUtils
*/
@QuarkusTest
@QuarkusTestResource(GoogleSecretManagerTestResource.class)
@EnabledIfEnvironmentVariables({
@EnabledIfEnvironmentVariable(named = "GOOGLE_SERVICE_ACCOUNT_KEY", matches = ".+"),
@EnabledIfEnvironmentVariable(named = "GOOGLE_PROJECT_NAME", matches = ".+")
})
class GoogleSecretManagerTest {

@Test
public void loadComponentGoogleSecretManager() {
/* A simple autogenerated test */
RestAssured.get("/google-secret-manager/load/component/google-secret-manager")
void secretCreateListDelete() {
final String secretToCreate = "firstSecret!";
final String secretId = "CQTestSecret" + System.currentTimeMillis();
String createdName;

boolean deleted = false;

try {
//create secret
createdName = createSecret(secretId, secretToCreate);
assertTrue(createdName.contains(secretId));

//parse the name without /version/...
String name = createdName.substring(0, createdName.indexOf("/version"));
String version = createdName.substring(createdName.lastIndexOf("/") + 1);

//get secret
RestAssured.given()
.contentType(ContentType.JSON)
.body(Map.of(GoogleSecretManagerConstants.SECRET_ID, secretId, GoogleSecretManagerConstants.VERSION_ID,
version))
.post("/google-secret-manager/operation/" + GoogleSecretManagerOperations.getSecretVersion)
.then()
.statusCode(200)
.body(is(secretToCreate));

// list secrets
RestAssured.given()
.contentType(ContentType.JSON)
.post("/google-secret-manager/operation/" + GoogleSecretManagerOperations.listSecrets)
.then()
.statusCode(200)
.body(containsString(name));

//delete secret
deleteSecret(secretId);

//verify that the secret is gone
RestAssured.given()
.contentType(ContentType.JSON)
.post("/google-secret-manager/operation/" + GoogleSecretManagerOperations.listSecrets)
.then()
.statusCode(200)
.body(not(containsString(name)));

deleted = true;

} finally {
if (!deleted && !MockBackendUtils.startMockBackend(false)) {
String file = ConfigProvider.getConfig().getValue("cq.google-secrets-manager.path-to-service-account-key",
String.class);
String projectName = ConfigProvider.getConfig().getValue("cq.google-secrets-manager.project-name",
String.class);
GoogleSecretManagerTestResource.deleteSecret(secretId, file, projectName);
}
}
}

@Test
void loadGcpSecretTest() {
String expectedSecret = ConfigProvider.getConfig().getValue("gcpSecretValue", String.class);

RestAssured
.get("/google-secret-manager/getGcpSecret/")
.then()
.statusCode(200);
.statusCode(200)
.body(is(expectedSecret));
}

protected String createSecret(String secretName, String secretValue) {
String createdArn = Awaitility.await()
.pollInterval(5, TimeUnit.SECONDS)
.atMost(1, TimeUnit.MINUTES)
.until(() -> {
try {
return RestAssured.given()
.contentType(ContentType.JSON)
.body(Collections.singletonMap(GoogleSecretManagerConstants.SECRET_ID, secretName))
.queryParam("body", secretValue)
.post("/google-secret-manager/operation/" + GoogleSecretManagerOperations.createSecret)
.then()
.statusCode(200)
.extract().asString();
} catch (Exception e) {
return null;
}
}, Objects::nonNull);

return createdArn;
}

protected void deleteSecret(String secretId) {
if (secretId != null) {
Log.info("Deleting secret: " + secretId);
RestAssured.given()
.contentType(ContentType.JSON)
.body(Collections.singletonMap(GoogleSecretManagerConstants.SECRET_ID, secretId))
.post("/google-secret-manager/operation/" + GoogleSecretManagerOperations.deleteSecret)
.then()
.statusCode(200)
.body(CoreMatchers.is("true"));
}
}
}
Loading

0 comments on commit 741d6dc

Please sign in to comment.