|
6 | 6 | from typing import List |
7 | 7 |
|
8 | 8 | from clit.config import JsonConfig |
9 | | -from clit.files import existing_directory_type, shell, shell_find |
| 9 | +from clit.files import existing_directory_type, existing_file_type, shell, shell_find |
10 | 10 | from clit.types import JsonDict |
11 | 11 |
|
12 | 12 | YML_DIRS = JsonConfig("docker-find-yml-dirs.json") |
@@ -137,3 +137,68 @@ def docker_find(): |
137 | 137 | return |
138 | 138 | args.chosen_function(parser, args) |
139 | 139 | return |
| 140 | + |
| 141 | + |
| 142 | +def backup(parser, args): |
| 143 | + """Backup a Docker volume.""" |
| 144 | + for volume in args.volume_name: |
| 145 | + # TODO: when piping from stdin, stdout is printed only at the end (buffered) |
| 146 | + shell( |
| 147 | + "docker run --rm -i -v /var/lib/docker/volumes:/volumes -v {dir}:/backup busybox " |
| 148 | + "tar czf /backup/{volume}.tgz /volumes/{volume}".format(dir=args.backup_dir, volume=volume) |
| 149 | + ) |
| 150 | + |
| 151 | + |
| 152 | +def restore(parser, args): |
| 153 | + """Restore a Docker volume.""" |
| 154 | + tgz_file: Path = args.tgz_file |
| 155 | + backup_dir = tgz_file.parent |
| 156 | + new_volume_name = args.volume_name if args.volume_name else tgz_file.stem |
| 157 | + |
| 158 | + busybox = "docker run --rm -i -v /var/lib/docker:/docker -v {backup_dir}:/backup busybox ".format( |
| 159 | + backup_dir=backup_dir |
| 160 | + ) |
| 161 | + |
| 162 | + # Delete the destination directory before restoring |
| 163 | + shell(busybox + "rm -rf /docker/volumes/{new_volume_name}".format(new_volume_name=new_volume_name)) |
| 164 | + |
| 165 | + # Create the full path |
| 166 | + shell(busybox + "mkdir /docker/volumes/{new_volume_name}".format(new_volume_name=new_volume_name)) |
| 167 | + |
| 168 | + # Restore the .tgz file in the new empty directory |
| 169 | + shell( |
| 170 | + busybox |
| 171 | + + "tar xzf /backup/{tgz} -C /docker/volumes/{new_volume_name}/ --strip-components 2".format( |
| 172 | + tgz=tgz_file.name, new_volume_name=new_volume_name |
| 173 | + ) |
| 174 | + ) |
| 175 | + |
| 176 | + |
| 177 | +# TODO: Convert to click |
| 178 | +def docker_volume(): |
| 179 | + """Backup and restore Docker volumes. |
| 180 | +
|
| 181 | + See also https://stackoverflow.com/a/23778599/1391315. |
| 182 | + """ |
| 183 | + parser = argparse.ArgumentParser(description="backup and restore Docker volumes") |
| 184 | + parser.set_defaults(chosen_function=None) |
| 185 | + subparsers = parser.add_subparsers(title="commands") |
| 186 | + |
| 187 | + parser_backup = subparsers.add_parser("backup", aliases=["b"], help="backup a Docker volume") |
| 188 | + parser_backup.add_argument("backup_dir", type=existing_directory_type, help="directory to store the backups") |
| 189 | + parser_backup.add_argument("volume_name", nargs="+", help="Docker volume name") |
| 190 | + parser_backup.set_defaults(chosen_function=backup) |
| 191 | + |
| 192 | + parser_restore = subparsers.add_parser("restore", aliases=["r"], help="restore a Docker volume") |
| 193 | + parser_restore.add_argument( |
| 194 | + "tgz_file", type=existing_file_type, help="full path of the .tgz file created by the 'backup' command" |
| 195 | + ) |
| 196 | + parser_restore.add_argument("volume_name", nargs="?", help="volume name (default: basename of .tgz file)") |
| 197 | + parser_restore.set_defaults(chosen_function=restore) |
| 198 | + |
| 199 | + args = parser.parse_args() |
| 200 | + if not args.chosen_function: |
| 201 | + parser.print_help() |
| 202 | + return |
| 203 | + args.chosen_function(parser, args) |
| 204 | + return |
0 commit comments