Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ env:
WEAVIATE_129: 1.29.8
WEAVIATE_130: 1.30.7
WEAVIATE_131: 1.31.0
WEAVIATE_132: 1.32.0-rc.1
WEAVIATE_132: 1.32.4-59530f4.amd64


concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down
7 changes: 7 additions & 0 deletions src/backup/backupRestorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class BackupRestorer extends CommandBase {
private statusGetter: BackupRestoreStatusGetter;
private waitForCompletion?: boolean;
private config?: RestoreConfig;
private overwriteAlias?: boolean;

constructor(client: Connection, statusGetter: BackupRestoreStatusGetter) {
super(client);
Expand Down Expand Up @@ -65,6 +66,11 @@ export default class BackupRestorer extends CommandBase {
return this;
}

withOverwriteAlias(overwriteAlias: boolean) {
this.overwriteAlias = overwriteAlias;
return this;
}

withConfig(cfg: RestoreConfig) {
this.config = cfg;
return this;
Expand All @@ -89,6 +95,7 @@ export default class BackupRestorer extends CommandBase {
config: this.config,
include: this.includeClassNames,
exclude: this.excludeClassNames,
overwriteAlias: this.overwriteAlias,
} as BackupRestoreRequest;

if (this.waitForCompletion) {
Expand Down
3 changes: 3 additions & 0 deletions src/collections/backup/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export const backup = (connection: Connection): Backup => {
if (args.excludeCollections) {
builder = builder.withExcludeClassNames(...args.excludeCollections);
}
if (args.config?.overwriteAlias) {
builder = builder.withOverwriteAlias(args.config?.overwriteAlias);
}
if (args.config) {
builder = builder.withConfig({
CPUPercentage: args.config.cpuPercentage,
Expand Down
1 change: 0 additions & 1 deletion src/collections/backup/collection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Backend } from '../../backup/index.js';
import Connection from '../../connection/index.js';
import { WeaviateInvalidInputError } from '../../errors.js';
import { backup } from './client.js';
import { BackupReturn, BackupStatusArgs, BackupStatusReturn } from './types.js';

Expand Down
82 changes: 82 additions & 0 deletions src/collections/backup/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* eslint-disable no-await-in-loop */
import { requireAtLeast } from '../../../test/version.js';
import { Backend } from '../../backup/index.js';
import { WeaviateBackupFailed } from '../../errors.js';
import weaviate, { Collection, WeaviateClient } from '../../index.js';

// These must run sequentially because Weaviate is not capable of running multiple backups at the same time
Expand Down Expand Up @@ -117,6 +118,87 @@ describe('Integration testing of backups', () => {
.then(testCollectionWaitForCompletion)
.then(testCollectionNoWaitForCompletion));

requireAtLeast(1, 32, 3).describe('overwrite alias', () => {
test('overwriteAlias=true', async () => {
const client = await clientPromise;

const things = await client.collections.create({ name: 'ThingsTrue' });
await client.alias.create({ collection: things.name, alias: `${things.name}Alias` });

const backup = await client.backup.create({
backend: 'filesystem',
backupId: randomBackupId(),
includeCollections: [things.name],
waitForCompletion: true,
});

await client.collections.delete(things.name);
await client.alias.delete(`${things.name}Alias`);

// Change alias to point to a different collection
const inventory = await client.collections.create({ name: 'InventoryTrue' });
await client.alias.create({ collection: inventory.name, alias: `${things.name}Alias` });

// Restore backup with overwriteAlias=true
await client.backup.restore({
backend: 'filesystem',
backupId: backup.id,
includeCollections: [things.name],
waitForCompletion: true,
config: { overwriteAlias: true },
});

// Assert: alias points to the original collection
const alias = await client.alias.get(`${things.name}Alias`);
expect(alias.collection).toEqual(things.name);
});

test('overwriteAlias=false', async () => {
const client = await clientPromise;

const things = await client.collections.create({ name: 'ThingsFalse' });
await client.alias.create({ collection: things.name, alias: `${things.name}Alias` });

const backup = await client.backup.create({
backend: 'filesystem',
backupId: randomBackupId(),
includeCollections: [things.name],
waitForCompletion: true,
});

await client.collections.delete(things.name);
await client.alias.delete(`${things.name}Alias`);

// Change alias to point to a different collection
const inventory = await client.collections.create({ name: 'InventoryFalse' });
await client.alias.create({ collection: inventory.name, alias: `${things.name}Alias` });

// Restore backup with overwriteAlias=true
const restored = client.backup.restore({
backend: 'filesystem',
backupId: backup.id,
includeCollections: [things.name],
waitForCompletion: true,
config: { overwriteAlias: false },
});

// Assert: fails with "alias already exists" error
await expect(restored).rejects.toThrowError(WeaviateBackupFailed);
});

it('cleanup', async () => {
await clientPromise.then(async (c) => {
await Promise.all(
['ThingsTrue', 'ThingsFalse', 'InventoryTrue', 'InventoryFalse'].map((name) =>
c.collections.delete(name).catch((e) => {})
)
);
await c.alias.delete('ThingsFalseAlias').catch((e) => {});
await c.alias.delete('ThingsTrueAlias').catch((e) => {});
});
});
});

requireAtLeast(1, 32, 0).it('get all exising backups', async () => {
await clientPromise.then(async (client) => {
await client.collections.create({ name: 'TestListBackups' }).then((col) => col.data.insert());
Expand Down
2 changes: 2 additions & 0 deletions src/collections/backup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type BackupConfigCreate = {
export type BackupConfigRestore = {
/** The percentage of CPU to use for the backuop restoration job. */
cpuPercentage?: number;
/** Allows ovewriting the collection alias if there is a conflict. */
overwriteAlias?: boolean;
};

/** The arguments required to create and restore backups. */
Expand Down
150 changes: 146 additions & 4 deletions src/openapi/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ export interface paths {
'/authz/roles/{id}/user-assignments': {
get: operations['getUsersForRole'];
};
'/authz/roles/{id}/group-assignments': {
/** Retrieves a list of all groups that have been assigned a specific role, identified by its name. */
get: operations['getGroupsForRole'];
};
'/authz/users/{id}/roles': {
get: operations['getRolesForUserDeprecated'];
};
Expand All @@ -128,6 +132,14 @@ export interface paths {
'/authz/groups/{id}/revoke': {
post: operations['revokeRoleFromGroup'];
};
'/authz/groups/{id}/roles/{groupType}': {
/** Retrieves a list of all roles assigned to a specific group. The group must be identified by both its name (`id`) and its type (`db` or `oidc`). */
get: operations['getRolesForGroup'];
};
'/authz/groups/{groupType}': {
/** Retrieves a list of all available group names for a specified group type (`oidc` or `db`). */
get: operations['getGroups'];
};
'/objects': {
/** Lists all Objects in reverse order of creation, owned by the user that belongs to the used token. */
get: operations['objects.list'];
Expand Down Expand Up @@ -303,6 +315,11 @@ export interface definitions {
* @enum {string}
*/
UserTypeInput: 'db' | 'oidc';
/**
* @description If the group contains OIDC or database users.
* @enum {string}
*/
GroupType: 'db' | 'oidc';
/**
* @description the type of user
* @enum {string}
Expand Down Expand Up @@ -399,6 +416,15 @@ export interface definitions {
*/
users?: string;
};
/** @description Resources applicable for group actions. */
groups?: {
/**
* @description A string that specifies which groups this permission applies to. Can be an exact group name or a regex pattern. The default value `*` applies the permission to all groups.
* @default *
*/
group?: string;
groupType?: definitions['GroupType'];
};
/** @description resources applicable for tenant actions */
tenants?: {
/**
Expand Down Expand Up @@ -496,7 +522,9 @@ export interface definitions {
| 'create_aliases'
| 'read_aliases'
| 'update_aliases'
| 'delete_aliases';
| 'delete_aliases'
| 'assign_and_revoke_groups'
| 'read_groups';
};
/** @description list of roles */
RolesListResponse: definitions['Role'][];
Expand Down Expand Up @@ -1171,8 +1199,6 @@ export interface definitions {
BackupListResponse: {
/** @description The ID of the backup. Must be URL-safe and work as a filesystem path, only lowercase, numbers, underscore, minus characters allowed. */
id?: string;
/** @description destination path of backup files proper to selected backend */
path?: string;
/** @description The list of classes for which the existed backup process */
classes?: string[];
/**
Expand All @@ -1191,6 +1217,8 @@ export interface definitions {
exclude?: string[];
/** @description Allows overriding the node names stored in the backup with different ones. Useful when restoring backups to a different environment. */
node_mapping?: { [key: string]: string };
/** @description Allows ovewriting the collection alias if there is a conflict */
overwriteAlias?: boolean;
};
/** @description The definition of a backup restore response body */
BackupRestoreResponse: {
Expand Down Expand Up @@ -1789,7 +1817,9 @@ export interface definitions {
| 'WithinGeoRange'
| 'IsNull'
| 'ContainsAny'
| 'ContainsAll';
| 'ContainsAll'
| 'ContainsNone'
| 'Not';
/**
* @description path to the property currently being filtered
* @example [
Expand Down Expand Up @@ -2827,6 +2857,42 @@ export interface operations {
};
};
};
/** Retrieves a list of all groups that have been assigned a specific role, identified by its name. */
getGroupsForRole: {
parameters: {
path: {
/** The unique name of the role. */
id: string;
};
};
responses: {
/** Successfully retrieved the list of groups that have the role assigned. */
200: {
schema: ({
groupId?: string;
groupType: definitions['GroupType'];
} & {
name: unknown;
})[];
};
/** Bad request */
400: {
schema: definitions['ErrorResponse'];
};
/** Unauthorized or invalid credentials. */
401: unknown;
/** Forbidden */
403: {
schema: definitions['ErrorResponse'];
};
/** The specified role was not found. */
404: unknown;
/** An error has occurred while trying to fulfill the request. Most likely the ErrorResponse will contain more information about the error. */
500: {
schema: definitions['ErrorResponse'];
};
};
};
getRolesForUserDeprecated: {
parameters: {
path: {
Expand Down Expand Up @@ -2985,6 +3051,7 @@ export interface operations {
body: {
/** @description the roles that assigned to group */
roles?: string[];
groupType?: definitions['GroupType'];
};
};
};
Expand Down Expand Up @@ -3019,6 +3086,7 @@ export interface operations {
body: {
/** @description the roles that revoked from group */
roles?: string[];
groupType?: definitions['GroupType'];
};
};
};
Expand All @@ -3043,6 +3111,80 @@ export interface operations {
};
};
};
/** Retrieves a list of all roles assigned to a specific group. The group must be identified by both its name (`id`) and its type (`db` or `oidc`). */
getRolesForGroup: {
parameters: {
path: {
/** The unique name of the group. */
id: string;
/** The type of the group. */
groupType: 'oidc';
};
query: {
/** If true, the response will include the full role definitions with all associated permissions. If false, only role names are returned. */
includeFullRoles?: boolean;
};
};
responses: {
/** A list of roles assigned to the specified group. */
200: {
schema: definitions['RolesListResponse'];
};
/** Bad request */
400: {
schema: definitions['ErrorResponse'];
};
/** Unauthorized or invalid credentials. */
401: unknown;
/** Forbidden */
403: {
schema: definitions['ErrorResponse'];
};
/** The specified group was not found. */
404: unknown;
/** The request syntax is correct, but the server couldn't process it due to semantic issues. */
422: {
schema: definitions['ErrorResponse'];
};
/** An error has occurred while trying to fulfill the request. Most likely the ErrorResponse will contain more information about the error. */
500: {
schema: definitions['ErrorResponse'];
};
};
};
/** Retrieves a list of all available group names for a specified group type (`oidc` or `db`). */
getGroups: {
parameters: {
path: {
/** The type of group to retrieve. */
groupType: 'oidc';
};
};
responses: {
/** A list of group names for the specified type. */
200: {
schema: string[];
};
/** Bad request */
400: {
schema: definitions['ErrorResponse'];
};
/** Unauthorized or invalid credentials. */
401: unknown;
/** Forbidden */
403: {
schema: definitions['ErrorResponse'];
};
/** The request syntax is correct, but the server couldn't process it due to semantic issues. */
422: {
schema: definitions['ErrorResponse'];
};
/** An error has occurred while trying to fulfill the request. Most likely the ErrorResponse will contain more information about the error. */
500: {
schema: definitions['ErrorResponse'];
};
};
};
/** Lists all Objects in reverse order of creation, owned by the user that belongs to the used token. */
'objects.list': {
parameters: {
Expand Down