1password-secrets 0.0.1.dev241__tar.gz → 0.0.1.dev247__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/PKG-INFO +1 -1
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/PKG-INFO +1 -1
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/cli.py +23 -14
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/commands.py +45 -28
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/fly.py +41 -14
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/op.py +7 -2
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/prompts.py +41 -5
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/SOURCES.txt +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/dependency_links.txt +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/entry_points.txt +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/requires.txt +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/top_level.txt +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/README.md +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__init__.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__main__.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/constants.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/envs.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/errors.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/git.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/logs.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/utils.py +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/pyproject.toml +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/setup.cfg +0 -0
- {1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/setup.py +0 -0
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/cli.py
RENAMED
|
@@ -12,7 +12,7 @@ from .commands import (
|
|
|
12
12
|
)
|
|
13
13
|
from .constants import DEFAULT_REMOTE_NAME
|
|
14
14
|
from .errors import UserError
|
|
15
|
-
from .logs import logger
|
|
15
|
+
from .logs import console, logger
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
APP_VERSION = version("1password-secrets")
|
|
@@ -85,19 +85,28 @@ def main():
|
|
|
85
85
|
logger.setLevel(logging.DEBUG)
|
|
86
86
|
|
|
87
87
|
try:
|
|
88
|
-
|
|
89
|
-
if args.action == "import":
|
|
90
|
-
import_1password_secrets_to_fly(args.app_name, vault=args.vault)
|
|
91
|
-
elif args.action == "edit":
|
|
92
|
-
edit_1password_fly_secrets(args.app_name, vault=args.vault)
|
|
93
|
-
|
|
94
|
-
elif args.subcommand == "local":
|
|
95
|
-
if args.action == "pull":
|
|
96
|
-
pull_local_secrets(remote=args.remote, vault=args.vault)
|
|
97
|
-
elif args.action == "push":
|
|
98
|
-
push_local_secrets(remote=args.remote, vault=args.vault)
|
|
99
|
-
elif args.action == "create":
|
|
100
|
-
create_local_secrets(args.secrets_file_path, vault=args.vault, remote=args.remote)
|
|
88
|
+
run_subcommand(args)
|
|
101
89
|
|
|
102
90
|
except UserError:
|
|
103
91
|
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
except KeyboardInterrupt:
|
|
94
|
+
console.print()
|
|
95
|
+
console.print("[yellow]Aborted by user[/yellow]")
|
|
96
|
+
sys.exit(130)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_subcommand(args):
|
|
100
|
+
if args.subcommand == "fly":
|
|
101
|
+
if args.action == "import":
|
|
102
|
+
import_1password_secrets_to_fly(args.app_name, vault=args.vault)
|
|
103
|
+
elif args.action == "edit":
|
|
104
|
+
edit_1password_fly_secrets(args.app_name, vault=args.vault)
|
|
105
|
+
|
|
106
|
+
elif args.subcommand == "local":
|
|
107
|
+
if args.action == "pull":
|
|
108
|
+
pull_local_secrets(remote=args.remote, vault=args.vault)
|
|
109
|
+
elif args.action == "push":
|
|
110
|
+
push_local_secrets(remote=args.remote, vault=args.vault)
|
|
111
|
+
elif args.action == "create":
|
|
112
|
+
create_local_secrets(args.secrets_file_path, vault=args.vault, remote=args.remote)
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/commands.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""High level workflows wiring 1Password, Fly and the local filesystem together."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
from datetime import datetime, timezone
|
|
6
7
|
from tempfile import NamedTemporaryFile
|
|
@@ -21,7 +22,7 @@ from .op import (
|
|
|
21
22
|
update_1password_custom_field,
|
|
22
23
|
update_1password_secrets,
|
|
23
24
|
)
|
|
24
|
-
from .prompts import boolean_prompt, prompt_secret_diff
|
|
25
|
+
from .prompts import SecretDiff, boolean_prompt, prompt_secret_diff
|
|
25
26
|
from .utils import get_file_contents
|
|
26
27
|
|
|
27
28
|
|
|
@@ -43,37 +44,47 @@ def edit_1password_fly_secrets(app_id, vault=None):
|
|
|
43
44
|
|
|
44
45
|
current_raw_secrets = get_envs_from_1password(item_id, vault=vault)
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
# delete=False so that the file is still around for the editor to open on every platform,
|
|
48
|
+
# cleaned up in the finally block below no matter how we leave this function.
|
|
49
|
+
file = NamedTemporaryFile("w+", suffix=".env", delete=False) # noqa: SIM115
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
with file:
|
|
53
|
+
file.writelines(current_raw_secrets)
|
|
54
|
+
file.flush()
|
|
55
|
+
|
|
56
|
+
console.print()
|
|
57
|
+
console.print(
|
|
58
|
+
Panel(
|
|
59
|
+
"[bold]Edit the secrets in your editor, then save and close the file to continue.[/bold]\n"
|
|
60
|
+
"[dim]Waiting for editor to close...[/dim]",
|
|
61
|
+
title="Editor",
|
|
62
|
+
border_style="cyan",
|
|
63
|
+
)
|
|
57
64
|
)
|
|
58
|
-
)
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
editor_command_args = ["code", "--wait", "--disable-extensions", file.name]
|
|
67
|
+
debug_command(editor_command_args)
|
|
68
|
+
editor_output = subprocess.check_output(editor_command_args) # noqa: S603
|
|
69
|
+
debug_command(editor_command_args, output=editor_output, returncode=0)
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
console.print("[green]Editor closed.[/green]")
|
|
72
|
+
console.print()
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
file.seek(0)
|
|
75
|
+
new_raw_secrets = file.read()
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
update_1password_secrets(
|
|
78
|
+
item_id,
|
|
79
|
+
new_raw_secrets=new_raw_secrets,
|
|
80
|
+
previous_raw_secrets=current_raw_secrets,
|
|
81
|
+
vault=vault,
|
|
82
|
+
)
|
|
83
|
+
finally:
|
|
84
|
+
try:
|
|
85
|
+
os.unlink(file.name)
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
pass
|
|
77
88
|
|
|
78
89
|
console.print()
|
|
79
90
|
if boolean_prompt(f"Secrets updated in 1Password. Import to Fly app '{app_id}'"):
|
|
@@ -91,11 +102,17 @@ def pull_local_secrets(remote=DEFAULT_REMOTE_NAME, vault=None):
|
|
|
91
102
|
previous_raw_secrets = get_file_contents(env_file_name, raise_if_not_found=False)
|
|
92
103
|
|
|
93
104
|
if previous_raw_secrets:
|
|
94
|
-
prompt_secret_diff(
|
|
105
|
+
diff = prompt_secret_diff(
|
|
95
106
|
previous_raw_secrets=previous_raw_secrets,
|
|
96
107
|
new_raw_secrets=secrets,
|
|
108
|
+
apply_prompt=f"Pull these changes into {env_file_name}",
|
|
109
|
+
cosmetic_prompt=f"Pull the file into {env_file_name} anyway",
|
|
97
110
|
)
|
|
98
111
|
|
|
112
|
+
if diff == SecretDiff.IDENTICAL:
|
|
113
|
+
console.print(f"[dim]No changes detected, {env_file_name} is already up to date[/dim]")
|
|
114
|
+
return
|
|
115
|
+
|
|
99
116
|
with open(env_file_name, "w") as file:
|
|
100
117
|
file.writelines(secrets)
|
|
101
118
|
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/fly.py
RENAMED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import subprocess
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
|
+
from rich.table import Table
|
|
7
8
|
|
|
8
9
|
from .constants import FLY_API_TIMEOUT_SECONDS, FLY_MACHINES_API_ENDPOINT
|
|
9
10
|
from .errors import raise_error
|
|
@@ -90,13 +91,8 @@ def set_fly_secrets(app_id, values, token=None) -> None:
|
|
|
90
91
|
)
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
def
|
|
94
|
-
"""
|
|
95
|
-
console.print()
|
|
96
|
-
console.print(f"[bold cyan]Deploying secrets to Fly app '{app_id}'...[/bold cyan]")
|
|
97
|
-
console.print()
|
|
98
|
-
|
|
99
|
-
command_args = ["fly", "secrets", "deploy", "-a", app_id]
|
|
94
|
+
def run_fly_command(command_args, failure_message):
|
|
95
|
+
"""Run a fly CLI command, raising a user-facing error if it is unavailable or fails."""
|
|
100
96
|
debug_command(command_args)
|
|
101
97
|
|
|
102
98
|
try:
|
|
@@ -107,7 +103,26 @@ def deploy_fly_secrets(app_id):
|
|
|
107
103
|
debug_command(command_args, returncode=result.returncode)
|
|
108
104
|
|
|
109
105
|
if result.returncode != 0:
|
|
110
|
-
raise_error(f"
|
|
106
|
+
raise_error(f"{failure_message} (exit code {result.returncode})")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def deploy_fly_secrets(app_id):
|
|
110
|
+
"""Sync and deploy secrets to a Fly app using the fly CLI."""
|
|
111
|
+
console.print()
|
|
112
|
+
console.print(
|
|
113
|
+
f"[bold cyan]Syncing secrets on Fly app '{app_id}' (fly secrets sync)...[/bold cyan]"
|
|
114
|
+
)
|
|
115
|
+
console.print()
|
|
116
|
+
|
|
117
|
+
run_fly_command(["fly", "secrets", "sync", "-a", app_id], "Failed to sync secrets")
|
|
118
|
+
|
|
119
|
+
console.print()
|
|
120
|
+
console.print(
|
|
121
|
+
f"[bold cyan]Deploying secrets to Fly app '{app_id}' (fly secrets deploy)...[/bold cyan]"
|
|
122
|
+
)
|
|
123
|
+
console.print()
|
|
124
|
+
|
|
125
|
+
run_fly_command(["fly", "secrets", "deploy", "-a", app_id], "Failed to deploy secrets")
|
|
111
126
|
|
|
112
127
|
console.print()
|
|
113
128
|
console.print(f"[bold green]Secrets deployed to Fly app '{app_id}'[/bold green]")
|
|
@@ -122,12 +137,24 @@ def update_fly_secrets(app_id, secrets):
|
|
|
122
137
|
|
|
123
138
|
deletions = {}
|
|
124
139
|
|
|
125
|
-
if len(secrets_names_in_fly_only) > 0
|
|
126
|
-
|
|
127
|
-
"
|
|
140
|
+
if len(secrets_names_in_fly_only) > 0:
|
|
141
|
+
table = Table(
|
|
142
|
+
title="Secrets that only exist on Fly.io",
|
|
143
|
+
show_header=True,
|
|
144
|
+
header_style="bold",
|
|
128
145
|
)
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
table.add_column("#", style="dim", justify="right")
|
|
147
|
+
table.add_column("Secret", style="red")
|
|
148
|
+
|
|
149
|
+
for index, name in enumerate(sorted(secrets_names_in_fly_only), start=1):
|
|
150
|
+
table.add_row(str(index), name)
|
|
151
|
+
|
|
152
|
+
console.print()
|
|
153
|
+
console.print(table)
|
|
154
|
+
console.print()
|
|
155
|
+
|
|
156
|
+
if boolean_prompt(f"Delete these {len(secrets_names_in_fly_only)} secrets from Fly.io"):
|
|
157
|
+
deletions = dict.fromkeys(secrets_names_in_fly_only, None)
|
|
131
158
|
|
|
132
159
|
values = {**secrets, **deletions}
|
|
133
160
|
|
|
@@ -141,5 +168,5 @@ def update_fly_secrets(app_id, secrets):
|
|
|
141
168
|
console.print()
|
|
142
169
|
console.print(f"[bold green]Secrets staged on Fly app '{app_id}'[/bold green]")
|
|
143
170
|
|
|
144
|
-
if boolean_prompt("Deploy secrets now (
|
|
171
|
+
if boolean_prompt("Deploy secrets now ( fly secrets sync && fly secrets deploy)?"):
|
|
145
172
|
deploy_fly_secrets(app_id)
|
|
@@ -12,7 +12,7 @@ from .constants import (
|
|
|
12
12
|
)
|
|
13
13
|
from .errors import raise_error
|
|
14
14
|
from .logs import console, debug_command, logger
|
|
15
|
-
from .prompts import prompt_secret_diff
|
|
15
|
+
from .prompts import SecretDiff, boolean_prompt, prompt_secret_diff
|
|
16
16
|
from .utils import first
|
|
17
17
|
|
|
18
18
|
|
|
@@ -159,11 +159,16 @@ def update_1password_secrets(item_id, new_raw_secrets, previous_raw_secrets=None
|
|
|
159
159
|
if previous_raw_secrets is None:
|
|
160
160
|
previous_raw_secrets = get_envs_from_1password(item_id)
|
|
161
161
|
|
|
162
|
-
prompt_secret_diff(
|
|
162
|
+
diff = prompt_secret_diff(
|
|
163
163
|
previous_raw_secrets=previous_raw_secrets,
|
|
164
164
|
new_raw_secrets=new_raw_secrets,
|
|
165
165
|
)
|
|
166
166
|
|
|
167
|
+
if diff == SecretDiff.IDENTICAL:
|
|
168
|
+
console.print("[dim]No changes detected[/dim]")
|
|
169
|
+
if not boolean_prompt("Proceed anyway"):
|
|
170
|
+
raise_error("Aborted by user")
|
|
171
|
+
|
|
167
172
|
logger.debug(f"Updating 1password secret note content for item {item_id!r}")
|
|
168
173
|
run_1password_command(
|
|
169
174
|
"item",
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/prompts.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
1
3
|
import inquirer
|
|
2
4
|
from rich.table import Table
|
|
3
5
|
from rich.text import Text
|
|
@@ -22,7 +24,30 @@ def boolean_prompt(prompt: str, default: bool = False) -> bool:
|
|
|
22
24
|
return answers["confirm"]
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
class SecretDiff(str, Enum):
|
|
28
|
+
"""Outcome of comparing two raw env file contents."""
|
|
29
|
+
|
|
30
|
+
CHANGED = "changed"
|
|
31
|
+
"""Env var keys were added, removed or had their value changed."""
|
|
32
|
+
|
|
33
|
+
COSMETIC = "cosmetic"
|
|
34
|
+
"""No env var differences, but the file contents differ (comments, ordering, spacing)."""
|
|
35
|
+
|
|
36
|
+
IDENTICAL = "identical"
|
|
37
|
+
"""Both files are equivalent."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def prompt_secret_diff(
|
|
41
|
+
previous_raw_secrets,
|
|
42
|
+
new_raw_secrets,
|
|
43
|
+
apply_prompt: str = "Apply these changes",
|
|
44
|
+
cosmetic_prompt: str = "Apply these changes anyway",
|
|
45
|
+
) -> SecretDiff:
|
|
46
|
+
"""Show the diff between two env files and ask for confirmation when relevant.
|
|
47
|
+
|
|
48
|
+
Returns the kind of difference found. Nothing is prompted when the contents are
|
|
49
|
+
equivalent - it is up to the caller to decide what to do in that case.
|
|
50
|
+
"""
|
|
26
51
|
previous_parsed_secrets = get_secrets_from_envs(previous_raw_secrets)
|
|
27
52
|
new_parsed_secrets = get_secrets_from_envs(new_raw_secrets)
|
|
28
53
|
|
|
@@ -38,10 +63,19 @@ def prompt_secret_diff(previous_raw_secrets, new_raw_secrets):
|
|
|
38
63
|
]
|
|
39
64
|
|
|
40
65
|
if len(deleted_keys) == 0 and len(added_keys) == 0 and len(keys_whos_value_changed) == 0:
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
if previous_raw_secrets.strip() == new_raw_secrets.strip():
|
|
67
|
+
return SecretDiff.IDENTICAL
|
|
68
|
+
|
|
69
|
+
console.print(
|
|
70
|
+
"[dim]No environment variable differences, "
|
|
71
|
+
"but the file structure or comments changed[/dim]"
|
|
72
|
+
)
|
|
73
|
+
console.print()
|
|
74
|
+
|
|
75
|
+
if not boolean_prompt(cosmetic_prompt, default=True):
|
|
43
76
|
raise_error("Aborted by user")
|
|
44
|
-
|
|
77
|
+
|
|
78
|
+
return SecretDiff.COSMETIC
|
|
45
79
|
|
|
46
80
|
# Build a rich table for the diff
|
|
47
81
|
table = Table(title="Change Summary", show_header=True, header_style="bold")
|
|
@@ -67,5 +101,7 @@ def prompt_secret_diff(previous_raw_secrets, new_raw_secrets):
|
|
|
67
101
|
console.print(table)
|
|
68
102
|
console.print()
|
|
69
103
|
|
|
70
|
-
if not boolean_prompt(
|
|
104
|
+
if not boolean_prompt(apply_prompt):
|
|
71
105
|
raise_error("Aborted by user")
|
|
106
|
+
|
|
107
|
+
return SecretDiff.CHANGED
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__init__.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__main__.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/constants.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/envs.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/errors.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/git.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/logs.py
RENAMED
|
File without changes
|
{1password_secrets-0.0.1.dev241 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|