Migrations(migrations)
Migrations let you prepare or modify a Spaceport instance without fully booting the server. They run in a lightweight runtime with access to your configuration manifest and the connected database, so you can:
- Create databases and views
- Seed data
- Create or promote users (for example, a Spaceport administrator)
- Make one-off maintenance changes (like rotating a password)
Migrations are typically run while the server is spun down, but they can also be executed while it’s running if that suits your workflow.
When to use migrations
Use a migration when you need repeatable, scriptable changes to your instance that should be tracked in source control and re-run safely across environments (local, staging, production). Favor idempotent scripts (safe to run more than once).
How migrations work
- You write Groovy scripts that execute inside a small command environment.
- The migration runtime provides helper functions for user prompts and formatted output.
- Your script can read the config manifest and use the configured memory core (database) objects.
- The CLI connects to the database using the manifest and shows an interactive menu of available migrations to run.
- Output from your script is printed to the console and can be used as a “report.”
Running migrations
- From the CLI: pass your manifest path with the migrate flag.
- Example:
spaceport --migrate /path/to/config.spaceport
What happens next:
- Spaceport loads the manifest and connects to the configured main memory core(s).
- A menu of available migrations is displayed. Pick one to run.
- You’ll see live output as the script executes, including any prompts.
Notes:
- If the database isn’t reachable, the CLI will exit before showing the menu.
- You can generally re-run a migration if it is written to be idempotent.
Writing a migration
A migration is a Groovy script that uses the command environment. The minimal structure looks like this:
import spaceport.bridge.Command
Command.with {
printBox("""
Friendly title shown before we start.
""")
// Perform checks
// Prompt for input if needed
// Make database updates
success('Done!')
}
Inside the block you can call helpers like promptInput, printBox, success, and error, and interact with the database through the Spaceport APIs you normally use.
Runtime helpers and APIs
- Command helpers
- printBox(text): prints a nicely formatted banner.
- promptInput(message): prompts for input and returns a string (or empty/null if none provided).
- success(message): prints a success message and may add emphasis/green styling.
- error(message): prints an error message and may add emphasis/red styling.
- Database and documents
- Spaceport.main_memory_core: access to the configured main memory core (database).
- containsDatabase(name): boolean
- createDatabase(name): creates the database
- spaceport.computer.memory.physical.Document
- static exists(id, dbName): boolean — checks if a document exists
- spaceport.personnel.ClientDocument
- static getClientDocument(username)
- static createNewClientDocument(username, password)
- hasPermission(permission)
- addPermission(permission)
- changePassword(password)
These are the same classes you’d use in normal Spaceport code; migrations just run them in a small, CLI-friendly context.
Generating a report
Migrations can serve as their own report by printing context and results as they run. Use:
- printBox(...) for an overview of what will happen
- success(...) to declare completed steps
- error(...) to flag issues or abort conditions
All output is shown in the CLI. If you need a persistent record, capture the CLI output to a file with your shell or add explicit file writing to your script.
Examples
Create or promote a Spaceport administrator
This script ensures the users database exists, then creates a new administrator user or promotes an existing user to spaceport-administrator.
import spaceport.Spaceport
import spaceport.bridge.Command
import spaceport.computer.memory.physical.Document
import spaceport.personnel.ClientDocument
/
* This migration will create a Spaceport administrator
* and a 'users' database if they do not already exist,
* or promote an existing user to 'spaceport-administrator'.
*/
Command.with {
printBox("""
This migration will create a default Spaceport administrator
and a 'users' database if they do not already exist.
""")
// Check for database, and create it if it does not exist.
if (!Spaceport.main_memory_core.containsDatabase('users')) {
Spaceport.main_memory_core.createDatabase('users')
success("Created 'users' database.")
}
// Get a username and password from the user.
def username
def password = ''
username = promptInput('Enter a new username (default: administrator)')
if (!username) username = 'administrator'
// Check if the user already exists in the database, and promote if necessary.
def user
if (Document.exists(username, 'users')) {
user = ClientDocument.getClientDocument(username)
if (user.hasPermission('spaceport-administrator')) {
success("User ${username} already exists and is already an administrator.")
return
}
error("User already exists. Promote instead?")
def promote = promptInput('Promote user to administrator? (y/(n))')
if (promote == 'y' || promote == 'Y') {
user.addPermission('spaceport-administrator')
success("User ${username} promoted to administrator.")
return
} else {
error("User already exists. Exiting.")
return
}
}
while (!password) {
password = promptInput('Enter a new password')
if (password != promptInput('Re-enter password to confirm')) {
error('Passwords do not match. Please try again.')
password = ''
}
}
// Create a new ClientDocument for the administrator, and add the 'spaceport-administrator' permission.
user = ClientDocument.createNewClientDocument(username, password)
if (user) {
user.addPermission('spaceport-administrator')
success("Created new administrator: ${username}")
return
} else {
error("Failed to create new administrator.")
return
}
}
Change an existing user’s password
This script prompts for a username in the users database, confirms the user exists, and then changes the password.
import spaceport.Spaceport
import spaceport.bridge.Command
import spaceport.computer.memory.physical.Document
import spaceport.personnel.ClientDocument
/
* This migration will change the password for an existing user
* in the 'users' database.
*/
Command.with {
printBox("""
This migration will change the password for an existing user
in the 'users' database.
""")
// Check for database existence
if (!Spaceport.main_memory_core.containsDatabase('users')) {
error("'users' database does not exist. Exiting.")
return
}
// Prompt for username
def username = promptInput('Enter the username whose password you want to change')
if (!username) {
error('No username entered. Exiting.')
return
}
// Check if user exists
if (!Document.exists(username, 'users')) {
error("User ${username} does not exist in 'users' database. Exiting.")
return
}
def password = ''
while (!password) {
password = promptInput('Enter a new password')
if (password != promptInput('Re-enter password to confirm')) {
error('Passwords do not match. Please try again.')
password = ''
}
}
// Update the user's password
def user = ClientDocument.getClientDocument(username)
if (user) {
user.changePassword(password)
success("Password for user ${username} has been changed.")
return
} else {
error("Failed to update password for user ${username}.")
return
}
}
Best practices
- Make scripts idempotent
- Check for existence before creating or modifying resources.
- Prefer updates that can safely re-apply without harm.
- Be explicit and noisy
- Explain what will happen up front with printBox(...).
- Use success(...) and error(...) to communicate outcomes.
- Keep migrations small and focused
- One concern per migration; chain multiple migrations instead of one giant script.
- Handle interactive vs. non-interactive contexts
- Provide sensible defaults if inputs are empty.
- Consider reading from environment variables for automation (System.getenv()).
- Validate assumptions early
- Check database connectivity and required collections/documents first.
Troubleshooting
- The menu doesn’t show my migration
- Ensure your migration is available on the runtime classpath and loaded by the CLI for your project setup. If you’re packaging migrations with your app, follow your build’s conventions for including them.
- Database connection fails
- Verify credentials and address in your manifest (see
manifest-configuration.md). Ensure the database service is reachable. - Scripts exit early
- Look for error(...) output. Many migrations intentionally bail on unmet preconditions.
See also
manifest-configuration.md(how Spaceport connects to databases)spaceport-cli.md(general CLI usage)
SPACEPORT DOCS