Skip to content

Commit 63b64d0

Browse files
committed
Add an s3 backend for kicks
1 parent 0911dea commit 63b64d0

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

nixops/plugin.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from nixops.storage import StorageBackend
44
from nixops.storage.legacy import LegacyBackend
55
from nixops.storage.memory import MemoryBackend
6+
from nixops.storage.s3 import S3Backend
67

78

89
@nixops.plugins.hookimpl
910
def register_backends() -> Dict[str, Type[StorageBackend]]:
10-
return {"legacy": LegacyBackend, "memory": MemoryBackend}
11+
return {"legacy": LegacyBackend, "s3": S3Backend, "memory": MemoryBackend}

nixops/storage/s3.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from __future__ import annotations
2+
from nixops.storage import StorageArgDescriptions, StorageArgValues
3+
import boto3
4+
from botocore.exceptions import ClientError
5+
import sys
6+
import os
7+
import typing
8+
from typing import Dict
9+
10+
if typing.TYPE_CHECKING:
11+
import nixops.statefile
12+
13+
14+
class S3Backend:
15+
@staticmethod
16+
def arguments() -> StorageArgDescriptions:
17+
raise NotImplementedError
18+
19+
def __init__(self, args: StorageArgValues) -> None:
20+
self.bucket = args["bucket"]
21+
self.key = args["key"]
22+
self.region = args["region"]
23+
self.profile = args["profile"]
24+
self.dynamodb_table = args["dynamodb_table"]
25+
self.s3_endpoint = args.get("s3_endpoint")
26+
self.kms_keyid = args.get("kms_keyid")
27+
self.aws = boto3.Session(region_name=self.region, profile_name=self.profile)
28+
29+
# fetchToFile: acquire a lock and download the state file to
30+
# the local disk. Note: no arguments will be passed over kwargs.
31+
# Making it part of the type definition allows adding new
32+
# arguments later.
33+
def fetchToFile(self, path: str, **kwargs) -> None:
34+
self.lock(path)
35+
try:
36+
with open(path, "wb") as f:
37+
self.aws.client("s3").download_fileobj(self.bucket, self.key, f)
38+
print("Fetched!")
39+
except ClientError as e:
40+
from pprint import pprint
41+
42+
pprint(e)
43+
if e.response["Error"]["Code"] == "404":
44+
self.aws.client("s3").put_object(
45+
Bucket=self.bucket, Key=self.key, Body=b"", **self.encargs()
46+
)
47+
48+
def onOpen(self, sf: nixops.statefile.StateFile, **kwargs) -> None:
49+
pass
50+
51+
# uploadFromFile: upload the new state file and release any locks
52+
# Note: no arguments will be passed over kwargs. Making it part of
53+
# the type definition allows adding new arguments later.
54+
def uploadFromFile(self, path: str, **kwargs) -> None:
55+
with open(path, "rb") as f:
56+
self.aws.client("s3").upload_fileobj(
57+
f, self.bucket, self.key, ExtraArgs=self.encargs()
58+
)
59+
60+
self.unlock(path)
61+
62+
def s3(self) -> None:
63+
self.aws.client("s3", endpoint_url=self.s3_endpoint)
64+
65+
def encargs(self) -> Dict[str, str]:
66+
if self.kms_keyid is not None:
67+
return {"ServerSideEncryption": "aws:kms", "SSEKMSKeyId": self.kms_keyid}
68+
else:
69+
return {}
70+
71+
def lock(self, path) -> None:
72+
r = self.aws.client("dynamodb").put_item(
73+
TableName=self.dynamodb_table,
74+
Item={"LockID": {"S": f"{self.bucket}/{self.key}"},},
75+
ConditionExpression="attribute_not_exists(LockID)",
76+
)
77+
78+
def unlock(self, path: str) -> None:
79+
self.aws.client("dynamodb").delete_item(
80+
TableName=self.dynamodb_table,
81+
Key={"LockID": {"S": f"{self.bucket}/{self.key}"},},
82+
)

release.nix

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ in rec {
8989
pythonPackages.prettytable
9090
pythonPackages.pluggy
9191
pythonPackages.typing-extensions
92+
pythonPackages.boto3
9293
] ++ pkgs.lib.traceValFn
9394
(x: "Using plugins: " + builtins.toJSON x)
9495
(map (d: d.build.${system}) (pluginSet allPlugins));

0 commit comments

Comments
 (0)