mirror of
https://github.com/bitwarden/server
synced 2026-02-20 03:13:35 +00:00
Refactoring structure of the CLI to be more maintainable long-term (#7042)
* Refactoring structure of the CLI to be more maintainable long-term * Remove obvious comments & put back XML comments
This commit is contained in:
@@ -131,7 +131,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.IntegrationTest", "tes
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seeder", "util\Seeder\Seeder.csproj", "{9A612EBA-1C0E-42B8-982B-62F0EE81000A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederUtility", "util\SeederUtility\SeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\RustSdk.csproj", "{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}"
|
||||
EndProject
|
||||
|
||||
@@ -28,7 +28,7 @@ $projects = @{
|
||||
Scim = "../bitwarden_license/src/Scim"
|
||||
IntegrationTests = "../test/Infrastructure.IntegrationTest"
|
||||
SeederApi = "../util/SeederApi"
|
||||
SeederUtility = "../util/DbSeederUtility"
|
||||
SeederUtility = "../util/SeederUtility"
|
||||
}
|
||||
|
||||
foreach ($key in $projects.keys) {
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using CommandDotNet;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
return new AppRunner<Program>()
|
||||
.Run(args);
|
||||
}
|
||||
|
||||
[Command("organization", Description = "Seed an organization and organization users")]
|
||||
public void Organization(
|
||||
[Option('n', "Name", Description = "Name of organization")]
|
||||
string name,
|
||||
[Option('u', "users", Description = "Number of users to generate")]
|
||||
int users,
|
||||
[Option('d', "domain", Description = "Email domain for users")]
|
||||
string domain
|
||||
)
|
||||
{
|
||||
// Create service provider with necessary services
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Get a scoped DB context
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
var db = scopedServices.GetRequiredService<DatabaseContext>();
|
||||
|
||||
var mapper = scopedServices.GetRequiredService<IMapper>();
|
||||
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
var recipe = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
recipe.Seed(name: name, domain: domain, users: users);
|
||||
}
|
||||
|
||||
[Command("vault-organization", Description = "Seed an organization with users and encrypted vault data (ciphers, collections, groups)")]
|
||||
public void VaultOrganization(VaultOrganizationArgs args)
|
||||
{
|
||||
args.Validate();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services, enableMangling: args.Mangle);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
var recipe = new OrganizationWithVaultRecipe(
|
||||
scopedServices.GetRequiredService<DatabaseContext>(),
|
||||
scopedServices.GetRequiredService<IMapper>(),
|
||||
scopedServices.GetRequiredService<IPasswordHasher<User>>(),
|
||||
manglerService);
|
||||
|
||||
recipe.Seed(args.ToOptions());
|
||||
|
||||
if (!manglerService.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = manglerService.GetMangleMap();
|
||||
Console.WriteLine("--- Mangled Data Map ---");
|
||||
foreach (var (original, mangled) in map)
|
||||
{
|
||||
Console.WriteLine($"{original} -> {mangled}");
|
||||
}
|
||||
}
|
||||
|
||||
[Command("seed", Description = "Seed database using fixture-based presets")]
|
||||
public void Seed(SeedArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
args.Validate();
|
||||
|
||||
// Handle list mode - no database needed
|
||||
if (args.List)
|
||||
{
|
||||
var available = OrganizationFromPresetRecipe.ListAvailable();
|
||||
PrintAvailableSeeds(available);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create service provider - same pattern as other commands
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services, enableMangling: args.Mangle);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
|
||||
var db = scopedServices.GetRequiredService<DatabaseContext>();
|
||||
var mapper = scopedServices.GetRequiredService<IMapper>();
|
||||
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
|
||||
// Create recipe - CLI is "dumb", recipe handles complexity
|
||||
var recipe = new OrganizationFromPresetRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
Console.WriteLine($"Seeding organization from preset '{args.Preset}'...");
|
||||
var result = recipe.Seed(args.Preset!, args.Password);
|
||||
|
||||
stopwatch.Stop();
|
||||
PrintSeedResult(result, stopwatch.Elapsed);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
|
||||
{
|
||||
Console.Error.WriteLine($"Error: {ex.Message}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintAvailableSeeds(AvailableSeeds available)
|
||||
{
|
||||
Console.WriteLine("Available Presets:");
|
||||
foreach (var preset in available.Presets)
|
||||
{
|
||||
Console.WriteLine($" - {preset}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Available Fixtures:");
|
||||
foreach (var (category, fixtures) in available.Fixtures.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
// Guard: Skip empty or single-character categories to prevent IndexOutOfRangeException
|
||||
if (string.IsNullOrEmpty(category) || category.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var categoryName = char.ToUpperInvariant(category[0]) + category[1..];
|
||||
Console.WriteLine($" {categoryName}:");
|
||||
foreach (var fixture in fixtures)
|
||||
{
|
||||
Console.WriteLine($" - {fixture}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Use: DbSeeder.exe seed --preset <name>");
|
||||
}
|
||||
|
||||
private static void PrintSeedResult(SeedResult result, TimeSpan elapsed)
|
||||
{
|
||||
Console.WriteLine($"✓ Created organization (ID: {result.OrganizationId})");
|
||||
|
||||
if (result.OwnerEmail is not null)
|
||||
{
|
||||
Console.WriteLine($"✓ Owner: {result.OwnerEmail}");
|
||||
}
|
||||
|
||||
if (result.UsersCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.UsersCount} users");
|
||||
}
|
||||
|
||||
if (result.GroupsCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.GroupsCount} groups");
|
||||
}
|
||||
|
||||
if (result.CollectionsCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.CollectionsCount} collections");
|
||||
}
|
||||
|
||||
if (result.CiphersCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.CiphersCount} ciphers");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Done in {elapsed.TotalSeconds:F1}s");
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
**For detailed pattern descriptions (Factories, Recipes, Models, Scenes, Queries, Data), read `README.md`.**
|
||||
|
||||
**For detailed usages of the Seeder library, read `util/DbSeederUtility/README.md` and `util/SeederApi/README.md`**
|
||||
**For detailed usages of the Seeder library, read `util/SeederUtility/README.md` and `util/SeederApi/README.md`**
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ The Seeder is organized around six core patterns, each with a specific responsib
|
||||
**Configuration:**
|
||||
|
||||
- SeederApi: Enabled when `GlobalSettings.TestPlayIdTrackingEnabled` is true
|
||||
- DbSeederUtility: Enabled with `--mangle` CLI flag
|
||||
- SeederUtility: Enabled with `--mangle` CLI flag
|
||||
|
||||
---
|
||||
|
||||
|
||||
40
util/SeederUtility/Commands/OrganizationCommand.cs
Normal file
40
util/SeederUtility/Commands/OrganizationCommand.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Bit.SeederUtility.Configuration;
|
||||
using CommandDotNet;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.SeederUtility.Commands;
|
||||
|
||||
[Command("organization", Description = "Seed an organization and organization users")]
|
||||
public class OrganizationCommand
|
||||
{
|
||||
[DefaultCommand]
|
||||
public void Execute(
|
||||
[Option('n', "Name", Description = "Name of organization")]
|
||||
string name,
|
||||
[Option('u', "users", Description = "Number of users to generate")]
|
||||
int users,
|
||||
[Option('d', "domain", Description = "Email domain for users")]
|
||||
string domain
|
||||
)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
var db = scopedServices.GetRequiredService<DatabaseContext>();
|
||||
|
||||
var mapper = scopedServices.GetRequiredService<IMapper>();
|
||||
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
var recipe = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
recipe.Seed(name: name, domain: domain, users: users);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommandDotNet;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
namespace Bit.SeederUtility.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// CLI argument model for the seed command.
|
||||
@@ -22,17 +22,14 @@ public class SeedArgs : IArgumentModel
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
// List mode is standalone
|
||||
if (List)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Must specify preset
|
||||
if (string.IsNullOrEmpty(Preset))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"--preset must be specified. Use --list to see available presets.");
|
||||
throw new ArgumentException("--preset must be specified. Use --list to see available presets.");
|
||||
}
|
||||
}
|
||||
}
|
||||
115
util/SeederUtility/Commands/SeedCommand.cs
Normal file
115
util/SeederUtility/Commands/SeedCommand.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Bit.SeederUtility.Configuration;
|
||||
using CommandDotNet;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.SeederUtility.Commands;
|
||||
|
||||
[Command("seed", Description = "Seed database using fixture-based presets")]
|
||||
public class SeedCommand
|
||||
{
|
||||
[DefaultCommand]
|
||||
public void Execute(SeedArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
args.Validate();
|
||||
|
||||
if (args.List)
|
||||
{
|
||||
var available = OrganizationFromPresetRecipe.ListAvailable();
|
||||
PrintAvailableSeeds(available);
|
||||
return;
|
||||
}
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services, enableMangling: args.Mangle);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
|
||||
var db = scopedServices.GetRequiredService<DatabaseContext>();
|
||||
var mapper = scopedServices.GetRequiredService<IMapper>();
|
||||
var passwordHasher = scopedServices.GetRequiredService<IPasswordHasher<User>>();
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
|
||||
var recipe = new OrganizationFromPresetRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
Console.WriteLine($"Seeding organization from preset '{args.Preset}'...");
|
||||
var result = recipe.Seed(args.Preset!, args.Password);
|
||||
|
||||
PrintSeedResult(result);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
|
||||
{
|
||||
Console.Error.WriteLine($"Error: {ex.Message}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintAvailableSeeds(AvailableSeeds available)
|
||||
{
|
||||
Console.WriteLine("Available Presets:");
|
||||
foreach (var preset in available.Presets)
|
||||
{
|
||||
Console.WriteLine($" - {preset}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Available Fixtures:");
|
||||
foreach (var (category, fixtures) in available.Fixtures.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
// Guard: Skip empty or single-character categories to prevent IndexOutOfRangeException
|
||||
if (string.IsNullOrEmpty(category) || category.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var categoryName = char.ToUpperInvariant(category[0]) + category[1..];
|
||||
Console.WriteLine($" {categoryName}:");
|
||||
foreach (var fixture in fixtures)
|
||||
{
|
||||
Console.WriteLine($" - {fixture}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Use: SeederUtility seed --preset <name>");
|
||||
}
|
||||
|
||||
private static void PrintSeedResult(SeedResult result)
|
||||
{
|
||||
Console.WriteLine($"✓ Created organization (ID: {result.OrganizationId})");
|
||||
|
||||
if (result.OwnerEmail is not null)
|
||||
{
|
||||
Console.WriteLine($"✓ Owner: {result.OwnerEmail}");
|
||||
}
|
||||
|
||||
if (result.UsersCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.UsersCount} users");
|
||||
}
|
||||
|
||||
if (result.GroupsCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.GroupsCount} groups");
|
||||
}
|
||||
|
||||
if (result.CollectionsCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.CollectionsCount} collections");
|
||||
}
|
||||
|
||||
if (result.CiphersCount > 0)
|
||||
{
|
||||
Console.WriteLine($"✓ Created {result.CiphersCount} ciphers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Bit.Seeder.Factories;
|
||||
using Bit.Seeder.Options;
|
||||
using CommandDotNet;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
namespace Bit.SeederUtility.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// CLI argument model for the vault-organization command.
|
||||
49
util/SeederUtility/Commands/VaultOrganizationCommand.cs
Normal file
49
util/SeederUtility/Commands/VaultOrganizationCommand.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Bit.SeederUtility.Configuration;
|
||||
using CommandDotNet;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.SeederUtility.Commands;
|
||||
|
||||
[Command("vault-organization", Description = "Seed an organization with users and encrypted vault data (ciphers, collections, groups)")]
|
||||
public class VaultOrganizationCommand
|
||||
{
|
||||
[DefaultCommand]
|
||||
public void Execute(VaultOrganizationArgs args)
|
||||
{
|
||||
args.Validate();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ServiceCollectionExtension.ConfigureServices(services, enableMangling: args.Mangle);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
|
||||
var manglerService = scopedServices.GetRequiredService<IManglerService>();
|
||||
var recipe = new OrganizationWithVaultRecipe(
|
||||
scopedServices.GetRequiredService<DatabaseContext>(),
|
||||
scopedServices.GetRequiredService<IMapper>(),
|
||||
scopedServices.GetRequiredService<IPasswordHasher<User>>(),
|
||||
manglerService);
|
||||
|
||||
recipe.Seed(args.ToOptions());
|
||||
|
||||
if (!manglerService.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = manglerService.GetMangleMap();
|
||||
Console.WriteLine("--- Mangled Data Map ---");
|
||||
foreach (var (original, mangled) in map)
|
||||
{
|
||||
Console.WriteLine($"{original} -> {mangled}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
namespace Bit.SeederUtility.Configuration;
|
||||
|
||||
public static class GlobalSettingsFactory
|
||||
{
|
||||
@@ -20,7 +20,7 @@ public static class GlobalSettingsFactory
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
|
||||
.AddUserSecrets("bitwarden-Api") // Load user secrets from the API project
|
||||
.AddUserSecrets("bitwarden-Api")
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
var configuration = configBuilder.Build();
|
||||
@@ -7,16 +7,14 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.DbSeederUtility;
|
||||
namespace Bit.SeederUtility.Configuration;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static void ConfigureServices(ServiceCollection services, bool enableMangling = false)
|
||||
{
|
||||
// Load configuration using the GlobalSettingsFactory
|
||||
var globalSettings = GlobalSettingsFactory.GlobalSettings;
|
||||
|
||||
// Register services
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.AddConsole();
|
||||
@@ -27,9 +25,7 @@ public static class ServiceCollectionExtension
|
||||
services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
|
||||
services.TryAddSingleton<ISeedReader, SeedReader>();
|
||||
|
||||
// Add Data Protection services
|
||||
services.AddDataProtection()
|
||||
.SetApplicationName("Bitwarden");
|
||||
services.AddDataProtection().SetApplicationName("Bitwarden");
|
||||
|
||||
services.AddDatabaseRepositories(globalSettings);
|
||||
|
||||
22
util/SeederUtility/Program.cs
Normal file
22
util/SeederUtility/Program.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Bit.SeederUtility.Commands;
|
||||
using CommandDotNet;
|
||||
|
||||
namespace Bit.SeederUtility;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
return new AppRunner<Program>()
|
||||
.Run(args);
|
||||
}
|
||||
|
||||
[Subcommand]
|
||||
public OrganizationCommand Organization { get; set; } = null!;
|
||||
|
||||
[Subcommand]
|
||||
public VaultOrganizationCommand VaultOrganization { get; set; } = null!;
|
||||
|
||||
[Subcommand]
|
||||
public SeedCommand Seed { get; set; } = null!;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
# Bitwarden Database Seeder Utility
|
||||
# Bitwarden Seeder Utility
|
||||
|
||||
A CLI wrapper around the Seeder library for generating test data in your local Bitwarden database.
|
||||
A CLI wrapper around the Seeder library for generating test data in a Bitwarden database.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Build and run from the `util/DbSeederUtility` directory:
|
||||
Build and run from the `util/SeederUtility` directory:
|
||||
|
||||
```bash
|
||||
dotnet build
|
||||
@@ -15,6 +15,16 @@ dotnet run -- <command> [options]
|
||||
|
||||
## Commands
|
||||
|
||||
### `organization` - Users Only (No Vault Data)
|
||||
|
||||
```bash
|
||||
# 100 users
|
||||
dotnet run -- organization -n MyOrgNoCiphers -u 100 -d myorg-no-ciphers.com
|
||||
|
||||
# 10,000 users for load testing
|
||||
dotnet run -- organization -n LargeOrgNoCiphers -u 10000 -d large-org-no-ciphers.test
|
||||
```
|
||||
|
||||
### `seed` - Fixture-Based Seeding
|
||||
|
||||
```bash
|
||||
@@ -22,25 +32,17 @@ dotnet run -- <command> [options]
|
||||
dotnet run -- seed --list
|
||||
|
||||
# Load the Dunder Mifflin preset (58 users, 14 groups, 15 collections, ciphers)
|
||||
dotnet run -- seed --preset dunder-mifflin-full
|
||||
dotnet run -- seed --preset dunder-mifflin-enterprise-full
|
||||
|
||||
# Load with ID mangling for test isolation
|
||||
dotnet run -- seed --preset dunder-mifflin-full --mangle
|
||||
dotnet run -- seed --preset dunder-mifflin-enterprise-full --mangle
|
||||
|
||||
dotnet run -- seed --preset stark-free-basic --mangle
|
||||
|
||||
# Large enterprise preset for performance testing
|
||||
dotnet run -- seed --preset large-enterprise
|
||||
|
||||
dotnet run -- seed --preset dunder-mifflin-full --password "MyTestPassword1" --mangle
|
||||
```
|
||||
|
||||
### `organization` - Users Only (No Vault Data)
|
||||
|
||||
```bash
|
||||
# 100 users
|
||||
dotnet run -- organization -n MyOrg -u 100 -d myorg.com
|
||||
|
||||
# 10,000 users for load testing
|
||||
dotnet run -- organization -n seeded -u 10000 -d large.test
|
||||
dotnet run -- seed --preset dunder-mifflin-enterprise-full --password "MyTestPassword1" --mangle
|
||||
```
|
||||
|
||||
### `vault-organization` - Users + Encrypted Vault Data
|
||||
@@ -5,8 +5,8 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Bit.DbSeederUtility</RootNamespace>
|
||||
<AssemblyName>DbSeeder</AssemblyName>
|
||||
<RootNamespace>Bit.SeederUtility</RootNamespace>
|
||||
<AssemblyName>SeederUtility</AssemblyName>
|
||||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
|
||||
<UserSecretsId>2294c6ba-7cd0-4293-a797-3882e41c61cb</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
Reference in New Issue
Block a user