|
1 | 1 | import json
|
| 2 | +import os |
2 | 3 | import tempfile
|
3 | 4 | from pathlib import Path
|
4 | 5 |
|
5 | 6 | import yaml
|
6 | 7 | from kubernetes import client, config
|
7 | 8 | from kubernetes.client.models import CoreV1Event, V1PodList
|
8 | 9 | from kubernetes.dynamic import DynamicClient
|
| 10 | +from kubernetes.stream import stream |
9 | 11 |
|
10 | 12 | from .constants import DEFAULT_NAMESPACE, KUBECONFIG
|
11 | 13 | from .process import run_command, stream_command
|
@@ -115,3 +117,113 @@ def get_default_namespace() -> str:
|
115 | 117 | command = "kubectl config view --minify -o jsonpath='{..namespace}'"
|
116 | 118 | kubectl_namespace = run_command(command)
|
117 | 119 | return kubectl_namespace if kubectl_namespace else DEFAULT_NAMESPACE
|
| 120 | + |
| 121 | + |
| 122 | +def snapshot_bitcoin_datadir( |
| 123 | + pod_name: str, chain: str, local_path: str = "./", filters: list[str] = None |
| 124 | +) -> None: |
| 125 | + namespace = get_default_namespace() |
| 126 | + sclient = get_static_client() |
| 127 | + |
| 128 | + try: |
| 129 | + sclient.read_namespaced_pod(name=pod_name, namespace=namespace) |
| 130 | + |
| 131 | + # Filter down to the specified list of directories and files |
| 132 | + # This allows for creating snapshots of only the relevant data, e.g., |
| 133 | + # we may want to snapshot the blocks but not snapshot peers.dat or the node |
| 134 | + # wallets. |
| 135 | + # |
| 136 | + # TODO: never snapshot bitcoin.conf, as this is managed by the helm config |
| 137 | + if filters: |
| 138 | + find_command = [ |
| 139 | + "find", |
| 140 | + f"/root/.bitcoin/{chain}", |
| 141 | + "(", |
| 142 | + "-type", |
| 143 | + "f", |
| 144 | + "-o", |
| 145 | + "-type", |
| 146 | + "d", |
| 147 | + ")", |
| 148 | + "(", |
| 149 | + "-name", |
| 150 | + filters[0], |
| 151 | + ] |
| 152 | + for f in filters[1:]: |
| 153 | + find_command.extend(["-o", "-name", f]) |
| 154 | + find_command.append(")") |
| 155 | + else: |
| 156 | + # If no filters, get everything in the Bitcoin directory (TODO: exclude bitcoin.conf) |
| 157 | + find_command = ["find", f"/root/.bitcoin/{chain}"] |
| 158 | + |
| 159 | + resp = stream( |
| 160 | + sclient.connect_get_namespaced_pod_exec, |
| 161 | + pod_name, |
| 162 | + namespace, |
| 163 | + command=find_command, |
| 164 | + stderr=True, |
| 165 | + stdin=False, |
| 166 | + stdout=True, |
| 167 | + tty=False, |
| 168 | + _preload_content=False, |
| 169 | + ) |
| 170 | + |
| 171 | + file_list = [] |
| 172 | + while resp.is_open(): |
| 173 | + resp.update(timeout=1) |
| 174 | + if resp.peek_stdout(): |
| 175 | + file_list.extend(resp.read_stdout().strip().split("\n")) |
| 176 | + if resp.peek_stderr(): |
| 177 | + print(f"Error: {resp.read_stderr()}") |
| 178 | + |
| 179 | + resp.close() |
| 180 | + if not file_list: |
| 181 | + print("No matching files or directories found.") |
| 182 | + return |
| 183 | + tar_command = ["tar", "-czf", "/tmp/bitcoin_data.tar.gz", "-C", f"/root/.bitcoin/{chain}"] |
| 184 | + tar_command.extend( |
| 185 | + [os.path.relpath(f, f"/root/.bitcoin/{chain}") for f in file_list if f.strip()] |
| 186 | + ) |
| 187 | + resp = stream( |
| 188 | + sclient.connect_get_namespaced_pod_exec, |
| 189 | + pod_name, |
| 190 | + namespace, |
| 191 | + command=tar_command, |
| 192 | + stderr=True, |
| 193 | + stdin=False, |
| 194 | + stdout=True, |
| 195 | + tty=False, |
| 196 | + _preload_content=False, |
| 197 | + ) |
| 198 | + while resp.is_open(): |
| 199 | + resp.update(timeout=1) |
| 200 | + if resp.peek_stdout(): |
| 201 | + print(f"Tar output: {resp.read_stdout()}") |
| 202 | + if resp.peek_stderr(): |
| 203 | + print(f"Error: {resp.read_stderr()}") |
| 204 | + resp.close() |
| 205 | + local_file_path = Path(local_path) / f"{pod_name}_bitcoin_data.tar.gz" |
| 206 | + copy_command = ( |
| 207 | + f"kubectl cp {namespace}/{pod_name}:/tmp/bitcoin_data.tar.gz {local_file_path}" |
| 208 | + ) |
| 209 | + if not stream_command(copy_command): |
| 210 | + raise Exception("Failed to copy tar file from pod to local machine") |
| 211 | + |
| 212 | + print(f"Bitcoin data exported successfully to {local_file_path}") |
| 213 | + cleanup_command = ["rm", "/tmp/bitcoin_data.tar.gz"] |
| 214 | + stream( |
| 215 | + sclient.connect_get_namespaced_pod_exec, |
| 216 | + pod_name, |
| 217 | + namespace, |
| 218 | + command=cleanup_command, |
| 219 | + stderr=True, |
| 220 | + stdin=False, |
| 221 | + stdout=True, |
| 222 | + tty=False, |
| 223 | + ) |
| 224 | + |
| 225 | + print("To untar and repopulate the directory, use the following command:") |
| 226 | + print(f"tar -xzf {local_file_path} -C /path/to/destination/.bitcoin/{chain}") |
| 227 | + |
| 228 | + except Exception as e: |
| 229 | + print(f"An error occurred: {str(e)}") |
0 commit comments