1password-secrets 0.0.1.dev243__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.
Files changed (24) hide show
  1. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/PKG-INFO +1 -1
  2. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/PKG-INFO +1 -1
  3. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/cli.py +23 -14
  4. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/commands.py +45 -28
  5. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/fly.py +18 -5
  6. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/op.py +7 -2
  7. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/prompts.py +41 -5
  8. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/SOURCES.txt +0 -0
  9. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/dependency_links.txt +0 -0
  10. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/entry_points.txt +0 -0
  11. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/requires.txt +0 -0
  12. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/1password_secrets.egg-info/top_level.txt +0 -0
  13. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/README.md +0 -0
  14. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__init__.py +0 -0
  15. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/__main__.py +0 -0
  16. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/constants.py +0 -0
  17. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/envs.py +0 -0
  18. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/errors.py +0 -0
  19. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/git.py +0 -0
  20. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/logs.py +0 -0
  21. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/onepassword_secrets/utils.py +0 -0
  22. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/pyproject.toml +0 -0
  23. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/setup.cfg +0 -0
  24. {1password_secrets-0.0.1.dev243 → 1password_secrets-0.0.1.dev247}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1password-secrets
3
- Version: 0.0.1.dev243
3
+ Version: 0.0.1.dev247
4
4
  Summary: 1password-secrets is a set of utilities to sync 1password secrets.
5
5
  Home-page: https://github.com/significa/fly-1password-secrets
6
6
  Author: Significa
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1password-secrets
3
- Version: 0.0.1.dev243
3
+ Version: 0.0.1.dev247
4
4
  Summary: 1password-secrets is a set of utilities to sync 1password secrets.
5
5
  Home-page: https://github.com/significa/fly-1password-secrets
6
6
  Author: Significa
@@ -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
- if args.subcommand == "fly":
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)
@@ -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
- with NamedTemporaryFile("w+", suffix=".env") as file:
47
- file.writelines(current_raw_secrets)
48
- file.flush()
49
-
50
- console.print()
51
- console.print(
52
- Panel(
53
- "[bold]Edit the secrets in your editor, then save and close the file to continue.[/bold]\n"
54
- "[dim]Waiting for editor to close...[/dim]",
55
- title="Editor",
56
- border_style="cyan",
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
- editor_command_args = ["code", "--wait", "--disable-extensions", file.name]
61
- debug_command(editor_command_args)
62
- editor_output = subprocess.check_output(editor_command_args) # noqa: S603
63
- debug_command(editor_command_args, output=editor_output, returncode=0)
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
- console.print("[green]Editor closed.[/green]")
66
- console.print()
71
+ console.print("[green]Editor closed.[/green]")
72
+ console.print()
67
73
 
68
- file.seek(0)
69
- new_raw_secrets = file.read()
74
+ file.seek(0)
75
+ new_raw_secrets = file.read()
70
76
 
71
- update_1password_secrets(
72
- item_id,
73
- new_raw_secrets=new_raw_secrets,
74
- previous_raw_secrets=current_raw_secrets,
75
- vault=vault,
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
 
@@ -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
@@ -136,12 +137,24 @@ def update_fly_secrets(app_id, secrets):
136
137
 
137
138
  deletions = {}
138
139
 
139
- if len(secrets_names_in_fly_only) > 0 and boolean_prompt(
140
- "The following secrets will be deleted from Fly.io: {}. Are you sure".format(
141
- ", ".join(sorted(secrets_names_in_fly_only))
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",
142
145
  )
143
- ):
144
- deletions = dict.fromkeys(secrets_names_in_fly_only, None)
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)
145
158
 
146
159
  values = {**secrets, **deletions}
147
160
 
@@ -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",
@@ -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
- def prompt_secret_diff(previous_raw_secrets, new_raw_secrets):
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
- console.print("[dim]No changes detected[/dim]")
42
- if not boolean_prompt("Proceed anyway"):
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
- return
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("Apply these changes"):
104
+ if not boolean_prompt(apply_prompt):
71
105
  raise_error("Aborted by user")
106
+
107
+ return SecretDiff.CHANGED