Skip to content
Merged
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
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