Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ applicationDefaultJvmArgs += listOf(

## Supported APIs


### Tucked Builder

Tucked Builder is an iteration of the Builder pattern that reduces boilerplate and leverages static typing and autocompletion to help API discovery.
Expand Down Expand Up @@ -193,6 +194,7 @@ WeaviateClient wcd = WeaviateClient.connectToWeaviateCloud("my-cluster-url.io",
> ```
> WeaviateClient will be automatically closed when execution exits the block.


#### Authentication

Weaviate supports several authentication methods:
Expand All @@ -214,6 +216,7 @@ WeaviateClient.connectToCustom(

Follow the [documentation](https://docs.weaviate.io/deploy/configuration/authentication) for a detailed discussion.


### Collection management

```java
Expand Down Expand Up @@ -249,6 +252,7 @@ Other methods in `collections` namespace include:
- `list()` to fetch collection configurations for all existing collections
- `deleteAll()` to drop all collections and their data


#### Using a Collection Handle

Once a collection is created, you can obtain another client object that's scoped to that collection, called a _"handle"_.
Expand All @@ -274,6 +278,7 @@ Thread.run(() -> popSongs.forEach(song -> songs.data.insert(song)));

For the rest of the document, assume `songs` is handle for the "Songs" collection defined elsewhere.


#### Generic `PropertiesT`

Weaviate client lets you insert object properties in different "shapes". The compile-time type in which the properties must be passed is determined by a generic paramter in CollectionHandle object.
Expand All @@ -283,10 +288,12 @@ In practice this means you'll be passing an instance of `Map<String, Object>` to

If you prefer stricter typing, you can leverage our built-in ORM to work with properties as custom Java types. We will return to this in the **ORM** section later. Assume for now that properties are always being passed around as an "untyped" map.


### Ingesting data

Data operations are concentrated behind the `.data` namespace.


#### Insert single object

```java
Expand Down Expand Up @@ -401,6 +408,7 @@ songs.query.nearImage("base64-encoded-image");
> [!TIP]
> The first object returned in a NearObject query will _always_ be the search object itself. To filter it out, use the `.excludeSelf()` helper as in the example above.


#### Keyword and Hybrid search

```java
Expand Down Expand Up @@ -481,6 +489,7 @@ Where.property("title").like("summer").not();

Passing `null` and and empty `Where[]` to any of the logical operators as well as to the `.where()` method is safe -- the empty operators will simply be ignored.


#### Grouping results

Every query above has an overloaded variant that accepts a group-by clause.
Expand All @@ -502,6 +511,7 @@ songs.query.bm25(

The shape of the response object is different too, see [`QueryResponseGrouped`](./src/main/java/io/weaviate/client6/v1/api/collections/query/QueryResponseGrouped.java).


### Pagination

Paginating a Weaviate collection is straighforward and its API should is instantly familiar. `CursorSpliterator` powers 2 patterns for iterating over objects:
Expand Down Expand Up @@ -700,6 +710,7 @@ System.out.println(

Some of these features may be added in future releases.


### Collection alias

```java
Expand All @@ -710,6 +721,85 @@ client.collections.update("Songs_Alias", "PopSongs");
client.collections.delete("Songs_Alias");
```

### RBAC

#### Roles

The client supports all permission types existing as of `v1.33`.

```java
import io.weaviate.client6.v1.api.rbac.Permission;

client.roles.create(
"ManagerRole",
Permission.collections("Songs", CollectionsPermission.Action.READ, CollectionsPermission.Action.DELETE),
Permission.backups("Albums", BackupsPermission.Action.MANAGE)
);
assert !client.roles.hasPermission("ManagerRole", Permission.collections("Songs", CollectionsPermission.Action.UPDATE));

client.roles.create(
"ArtistRole",
Permission.collections("Songs", CollectionsPermission.Action.CREATE)
);

client.roles.delete("PromoterRole");
```

#### Users

> [!NOTE]
> Not all modifications which can be done to _DB_ users (managed by Weaviate) are equally applicable to _OIDC_ users (managed by an external IdP).
> For this reason their APIs are separated into two distinct namespaces: `users.db` and `users.oidc`.

```java
// DB users must be either defined in the server's environment configuration or created explicitly
if (!client.users.db.exists("ManagerUser")) {
client.users.db.create("ManagerUser");
}

client.users.db.assignRole("ManagerUser", "ManagerRole");


// OIDC users originate from the IdP and do not need to be (and cannot) be created.
client.users.oidc.assignRole("DaveMustaine", "ArtistRole");
client.users.oidc.assignRole("Tarkan", "ArtistRole");


// There's a number of other actions you can take on a DB user:
Optional<DbUser> user = client.users.db.byName("ManagerUser");
assert user.isPresent();

DbUser manager = user.get();
if (!manager.active()) {
client.users.db.activate(manager.id());
}

String newApiKey = client.users.db.rotateKey(manager.id());
client.users.db.deactivate(manager.id());
client.users.db.delete(manager.id());
```

You can get a brief information about the currently authenticated user:

```java
User current = client.users.myUser();
System.out.println(current.userType()); // Prints "DB_USER", "DB_ENV", or "OIDC".
```

#### Groups

RBAC groups are created by assigning roles to a previously-inexisted groups and remove when no roles are longer assigned to a group.

```java
client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");

assert client.groups.knownGroupNames().size() == 1; // "./friend-group"
assert client.groups.assignedRoles("./friend-group").size() == 2;

client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");
assert client.groups.knownGroupNames().isEmpty();
```

## Useful resources

- [Documentation](https://weaviate.io/developers/weaviate/current/client-libraries/java.html).
Expand Down
59 changes: 57 additions & 2 deletions src/it/java/io/weaviate/containers/Weaviate.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
import io.weaviate.client6.v1.internal.ObjectBuilder;

public class Weaviate extends WeaviateContainer {
public static final String VERSION = "1.32.3";
public static final String VERSION = "1.33.0";
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";
public static String OIDC_ISSUER = "https://auth.wcs.api.weaviate.io/auth/realms/SeMI";

private volatile SharedClient clientInstance;

Expand All @@ -31,6 +32,12 @@ public WeaviateClient getClient() {
* The lifetime of this client is tied to that of its container, which means
* that you do not need to {@code close} it manually. It will only truly close
* after the parent Testcontainer is stopped.
*
* FIXME: we cannot return the same client for 2 different sets of
* configurations.
* What we should do is: {@link #getClient()} returns the shared client, while
* this one always constructs a new instance.
* Otherwise we'll get a race condition once the tests are parallelized.
*/
public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
if (!isRunning()) {
Expand All @@ -51,6 +58,8 @@ public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> f
.httpPort(getMappedPort(8080))
.grpcPort(getMappedPort(50051)));
var config = customFn.apply(new Config.Custom()).build();
if (config.authentication() != null) {
}
try {
clientInstance = new SharedClient(config, this);
} catch (Exception e) {
Expand Down Expand Up @@ -92,7 +101,8 @@ public static Weaviate.Builder custom() {
public static class Builder {
private String versionTag;
private Set<String> enableModules = new HashSet<>();

private Set<String> adminUsers = new HashSet<>();
private Set<String> viewerUsers = new HashSet<>();
private Map<String, String> environment = new HashMap<>();

public Builder() {
Expand Down Expand Up @@ -137,6 +147,37 @@ public Builder withOffloadS3(String accessKey, String secretKey) {
return this;
}

public Builder withAdminUsers(String... admins) {
adminUsers.addAll(Arrays.asList(admins));
return this;
}

public Builder withViewerUsers(String... viewers) {
viewerUsers.addAll(Arrays.asList(viewers));
return this;
}

/** Enable RBAC authorization for this container. */
public Builder withRbac() {
environment.put("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false");
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
environment.put("AUTHORIZATION_RBAC_ENABLED", "true");
environment.put("AUTHENTICATION_DB_USERS_ENABLED", "true");
return this;
}

/**
* Enable API-Key authentication for this container.
*
* @param apiKeys Allowed API keys.
*/
public Builder withApiKeys(String... apiKeys) {
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
environment.put("AUTHENTICATION_APIKEY_ALLOWED_KEYS", String.join(",",
apiKeys));
return this;
}

public Builder enableTelemetry(boolean enable) {
environment.put("DISABLE_TELEMETRY", Boolean.toString(!enable));
return this;
Expand Down Expand Up @@ -170,6 +211,20 @@ public Weaviate build() {
c.withEnv("ENABLE_MODULES", String.join(",", enableModules));
}

var apiKeyUsers = new HashSet<String>();
apiKeyUsers.addAll(adminUsers);
apiKeyUsers.addAll(viewerUsers);

if (!adminUsers.isEmpty()) {
environment.put("AUTHORIZATION_ADMIN_USERS", String.join(",", adminUsers));
}
if (!viewerUsers.isEmpty()) {
environment.put("AUTHORIZATION_VIEWER_USERS", String.join(",", viewerUsers));
}
if (!apiKeyUsers.isEmpty()) {
environment.put("AUTHENTICATION_APIKEY_USERS", String.join(",", apiKeyUsers));
}

environment.forEach((name, value) -> c.withEnv(name, value));
c.withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
return c;
Expand Down
Loading