Skip to content

Instantly share code, notes, and snippets.

@kdorff
Last active October 31, 2021 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kdorff/2f208b85ef465dbc34dff14d02a55d57 to your computer and use it in GitHub Desktop.
Save kdorff/2f208b85ef465dbc34dff14d02a55d57 to your computer and use it in GitHub Desktop.
Groovy script to perform diff against the last two autorestic snapshots for a list of backends.
#!/usr/bin/env groovy
import groovy.yaml.YamlSlurper
/**
*
* Script to perform a diff from latest:latest to latest on the
* list of autorestic backends passed in as command line arguments.
*
* REQUIREMENTS
* * restic (tested with 0.12.1)
* * autorestic (tested with 1.3.0, configured and working, with at least two snapshots)
* * java 1.8+ (tested with openjdk 1.8-292)
* * groovy 3.0.0+ (tested with 3.0.9, I install via sdkman)
*
* example .autorestic.yml
* ---------------------------
* backends:
* local-docker-compose:
* type: local
* path: /local-restic-repo-folder/docker-compose/
* key: "local_restic_repo_key"
* remote-docker-compose:
* type: b2
* path: bucketname:/docker-compose/
* key: "remote_restic_repo_key"
* env:
* b2_account_id: my_b2_account_id
* b2_account_key: my_b2_account_key
* locations:
* docker-compose:
* from: /folder/containing/data/to/backup/
* to:
* - local-docker-compose
* - remote-docker-compose
* hooks:
* after:
* - /root/autorestic-scripts/last_diff.groovy local-docker-compose remote-docker-compose
*/
class last_diff {
/**
* configuration: where to find the restic binary.
*/
static String RESTIC_BIN = '/usr/bin/restic'
/**
* configuration: where to find the autorestic config file.
*/
static String AUTORESTIC_CONFIG = '/root/.autorestic.yml'
/**
* The autorestic config File.
*/
File autoresticConfigFile = new File(AUTORESTIC_CONFIG)
/**
* the backends to diff.
*/
List<String> backendsToDiff = []
/**
* Kick it all off.
*/
static void main(String[] args) {
new last_diff(args).run()
}
/**
* Constructor. Parse args and such.
*/
last_diff(String[] args) {
backendsToDiff.addAll(args)
if (!autoresticConfigFile.exists()) {
System.err.println("The autorestic config file doesn't exist ${AUTORESTIC_CONFIG}")
System.exit(-1)
}
}
/**
* Primary execution method.
*/
void run() {
def config = readAutoResticConfig()
backendsToDiff.eachWithIndex { backendToDiff, i ->
if (i) {
println ""
}
def backendConfig = config.backends."${backendToDiff}"
List<String> env = configureExecEnv(backendConfig)
diffBackend(backendToDiff, env)
}
}
/**
* Try to diff from latest:latest to latest for one backend.
*/
void diffBackend(String backend, List<String> env) {
//
// Get the list of snapshots
//
def cmd = "${RESTIC_BIN} snapshots"
// println "Executing ${cmd} with ${env}"
def snapshotsProc = cmd.execute(env, null)
def snapshotsBuffer = new StringBuffer()
// Capture the output
snapshotsProc.waitForProcessOutput(snapshotsBuffer, System.err)
if (snapshotsProc.exitValue() != 0) {
System.err.println "!! Command ${cmd} failed with exit code ${snapshotsProc.exitValue()}"
return
}
List<String> snapshots = snapshotsBuffer.toString().split('[\n\r]')
if (snapshots.size() < 6) {
System.err.println("Not enough snaphots found.")
System.err.println("The snapshots command returned ${snapshots}")
return
}
String[] diffFromParts = snapshots[-4].split(' +')
String diffFromId = diffFromParts[0]
String diffFromTime = diffFromParts[1] + ' ' + diffFromParts[2]
String[] diffToParts = snapshots[-3].split(' +')
String diffToId = diffToParts[0]
String diffToTime = diffToParts[1] + ' ' + diffToParts[2]
println "++"
println "++ Diffs for ${backend}"
println "++ From ${diffFromTime} (${diffFromId})"
println "++ To ${diffToTime} (${diffToId})"
println "++"
//
// Diff from latest:latest to latest
//
cmd = "${RESTIC_BIN} diff ${diffFromId} ${diffToId}"
// println "Executing ${cmd} with ${env}"
def diffProc = cmd.execute(env, null)
// Stream the output for the diff
diffProc.waitForProcessOutput(System.out, System.err)
if (diffProc.exitValue() != 0) {
System.err.println "!! Command ${cmd} failed with exit code ${diffProc.exitValue()}"
return
}
}
/**
* Parse the autorestic configuration file.
*/
def readAutoResticConfig() {
return new YamlSlurper().parse(autoresticConfigFile)
}
/**
* Create the appropriate environment variables for
* restic to be able to run against the current
* backend.
*/
List<String> configureExecEnv(backendConfig) {
List<String> env = []
System.getenv().each { k, v ->
// Include the current environment variables
if (v) {
env.add("${k}=${v}")
}
}
switch (backendConfig.type) {
case 'local':
env.addAll(
"RESTIC_REPOSITORY=${backendConfig.path}",
"RESTIC_PASSWORD=${backendConfig.key}",
)
break;
case 'b2':
env.addAll(
"RESTIC_REPOSITORY=b2:${backendConfig.path}",
"RESTIC_PASSWORD=${backendConfig.key}",
)
break;
default:
System.err.println("Unsupported backend type ${backendConfig.type}")
System.exit(-2)
}
for (String envEntry in backendConfig.env) {
// Include environment variables defined on the autorestic backend
String[] envParts = envEntry.split('=', 2)
// autorestic may rewrite the YML with env names in lower case. fix that.
env.add "${envParts[0].toUpperCase()}=${envParts[1]}"
}
return env
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment