Skip to content

Commit

Permalink
implement basic HTTP request support
Browse files Browse the repository at this point in the history
this adds a new change type which allows generic HTTP requests. while
this isn't great to support working over multiple OpenSearch major
releases (in case the REST API had breaking changes) it already offers
an easy way to trigger any possible action on OpenSearch.
latter versions might add additional change types for specific
operations on OpenSearch which can then also abstract away from the
exact API version (if possible).
  • Loading branch information
rursprung authored and filipelautert committed May 14, 2024
1 parent c98d6f8 commit 9690c01
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package liquibase.ext.opensearch.change;

import liquibase.change.AbstractChange;
import liquibase.change.ChangeMetaData;
import liquibase.change.DatabaseChange;
import liquibase.database.Database;
import liquibase.ext.opensearch.statement.HttpRequestStatement;
import liquibase.statement.SqlStatement;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Optional;

@DatabaseChange(name = "httpRequest",
description = "Execute an arbitrary HTTP request with the provided payload",
priority = ChangeMetaData.PRIORITY_DATABASE)
@NoArgsConstructor
@Getter
@Setter
public class HttpRequestChange extends AbstractChange {

private String method;
private String path;
private String body;

@Override
public String getConfirmationMessage() {
return String.format("executed the %s HTTP request against %s (with a body of size %d)",
this.getMethod(),
this.getPath(),
Optional.ofNullable(this.getBody()).map(String::length).orElse(0));
}

@Override
public SqlStatement[] generateStatements(final Database database) {
return new SqlStatement[] {
new HttpRequestStatement(this.getMethod(), this.getPath(), this.getBody())
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import liquibase.exception.DatabaseException;
import liquibase.executor.AbstractExecutor;
import liquibase.ext.opensearch.database.OpenSearchLiquibaseDatabase;
import liquibase.ext.opensearch.statement.OpenSearchExecuteStatement;
import liquibase.ext.opensearch.statement.OpenSearchUpdateStatement;
import liquibase.logging.Logger;
import liquibase.servicelocator.LiquibaseService;
import liquibase.sql.visitor.SqlVisitor;
Expand Down Expand Up @@ -125,13 +127,21 @@ public void execute(final SqlStatement sql) throws DatabaseException {

@Override
public void execute(final SqlStatement sql, final List<SqlVisitor> sqlVisitors) throws DatabaseException {
throw new DatabaseException("liquibase-opensearch extension cannot execute changeset \n" +
"Unknown type: " + sql.getClass().getName() +
"\nPlease check the following common causes:\n" +
"- Verify change set definitions for common error such as: changeType name, changeSet attributes spelling " +
"(such as runWith, context, etc.), and punctuation.\n" +
"- Verify that changesets have all the required changeset attributes and do not have invalid attributes for the designated change type.\n" +
"- Double-check to make sure your basic setup includes all needed extensions in your Java classpath");
if (sql instanceof OpenSearchExecuteStatement) {
try {
((OpenSearchExecuteStatement) sql).execute(getDatabase());
} catch (final Exception e) {
throw new DatabaseException("Could not execute", e);
}
} else {
throw new DatabaseException("liquibase-opensearch extension cannot execute changeset \n" +
"Unknown type: " + sql.getClass().getName() +
"\nPlease check the following common causes:\n" +
"- Verify change set definitions for common error such as: changeType name, changeSet attributes spelling " +
"(such as runWith, context, etc.), and punctuation.\n" +
"- Verify that changesets have all the required changeset attributes and do not have invalid attributes for the designated change type.\n" +
"- Double-check to make sure your basic setup includes all needed extensions in your Java classpath");
}
}

@Override
Expand All @@ -141,7 +151,15 @@ public int update(final SqlStatement sql) throws DatabaseException {

@Override
public int update(final SqlStatement sql, final List<SqlVisitor> sqlVisitors) throws DatabaseException {
throw new IllegalArgumentException();
if (sql instanceof OpenSearchUpdateStatement) {
try {
return ((OpenSearchUpdateStatement) sql).update(getDatabase());
} catch (final Exception e) {
throw new DatabaseException("Could not execute", e);
}
} else {
throw new IllegalArgumentException();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package liquibase.ext.opensearch.statement;

import liquibase.Scope;
import liquibase.exception.DatabaseException;
import liquibase.ext.opensearch.database.OpenSearchLiquibaseDatabase;
import liquibase.logging.Logger;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.opensearch.client.opensearch.generic.Body;
import org.opensearch.client.opensearch.generic.OpenSearchGenericClient.ClientOptions;
import org.opensearch.client.opensearch.generic.Requests;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

@AllArgsConstructor
@Getter
@EqualsAndHashCode(callSuper = true)
public class HttpRequestStatement extends AbstractOpenSearchStatement implements OpenSearchExecuteStatement {

private final Logger log = Scope.getCurrentScope().getLog(getClass());

private String method;
private String path;
private String body;

@Override
public String toString() {
return String.format("%s HTTP request against %s (with a body of size %d)",
this.getMethod(),
this.getPath(),
Optional.ofNullable(this.getBody()).map(String::length).orElse(0));
}

@Override
public void execute(final OpenSearchLiquibaseDatabase database) throws DatabaseException {
log.info(this.toString());

final var httpClient = this.getOpenSearchClient(database).generic().withClientOptions(ClientOptions.throwOnHttpErrors());

final var request = Requests.builder()
.endpoint(this.getPath())
.method(this.getMethod())
.body(Body.from(this.getBody().getBytes(StandardCharsets.UTF_8), "application/json"))
.build();

try (final var response = httpClient.execute(request)) {
if (response.getStatus() >= 400) {
throw new DatabaseException(String.format("HTTP request failed with code %d: %s", response.getStatus(), response));
}
} catch (final IOException e) {
throw new DatabaseException("failed to execute the HTTP request", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package liquibase.ext.opensearch.statement;

/*-
* #%L
* Liquibase NoSql Extension
* %%
* Copyright (C) 2020 Mastercard
* %%
* Licensed 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.
* #L%
*/

import liquibase.exception.DatabaseException;
import liquibase.ext.opensearch.database.OpenSearchLiquibaseDatabase;

public interface OpenSearchExecuteStatement {

void execute(final OpenSearchLiquibaseDatabase database) throws DatabaseException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package liquibase.ext.opensearch.statement;

/*-
* #%L
* Liquibase NoSql Extension
* %%
* Copyright (C) 2020 Mastercard
* %%
* Licensed 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.
* #L%
*/

import liquibase.ext.opensearch.database.OpenSearchLiquibaseDatabase;

public interface OpenSearchUpdateStatement {

int update(final OpenSearchLiquibaseDatabase database);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
liquibase.ext.opensearch.change.HttpRequestChange
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ public void itCreatesTheChangelogAndLockIndices() {
assertThat(this.indexExists(this.database.getDatabaseChangeLogTableName())).isTrue();
}

@SneakyThrows
@Test
public void itExecutesAHttpRequestAndCreatesTheIndex() {
this.doLiquibaseUpdate("liquibase/ext/changelog.httprequest.yaml");
assertThat(this.indexExists("testindex")).isTrue();
}

}
21 changes: 21 additions & 0 deletions src/test/resources/liquibase/ext/changelog.httprequest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
databaseChangeLog:
- changeSet:
id: 1
author: test
labels: httpRequestLabel
context: httpRequestContext
comment: httpRequestComment
changes:
- httpRequest:
method: PUT
path: /testindex
body: >
{
"mappings": {
"properties": {
"testfield": {
"type": "text"
}
}
}
}

0 comments on commit 9690c01

Please sign in to comment.