pilfer 1.0.0__py3-none-any.whl
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.
- pilfer/__init__.py +13 -0
- pilfer/cli.py +255 -0
- pilfer-1.0.0.dist-info/METADATA +228 -0
- pilfer-1.0.0.dist-info/RECORD +7 -0
- pilfer-1.0.0.dist-info/WHEEL +5 -0
- pilfer-1.0.0.dist-info/entry_points.txt +2 -0
- pilfer-1.0.0.dist-info/top_level.txt +1 -0
pilfer/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pilfer - Bulk De/recrypt all ansible vault files in a project recursively for search/editing.
|
|
3
|
+
|
|
4
|
+
This package provides a command-line tool for decrypting all Ansible vault files
|
|
5
|
+
in a project recursively for search/editing, then re-encrypting them when done.
|
|
6
|
+
|
|
7
|
+
Borrows from the excellent, but no longer supported Ansible Toolkit (https://github.com/dellis23/ansible-toolkit)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
__author__ = "Tom Paine"
|
|
12
|
+
__email__ = "github@aioue.net"
|
|
13
|
+
__description__ = "Decrypt all ansible vault files in a project recursively for search/editing, then re-encrypt when done."
|
pilfer/cli.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
# heavily borrows from this excellent repo https://github.com/dellis23/ansible-toolkit
|
|
4
|
+
|
|
5
|
+
# pilfer - decrypt all ansible vault files recursively for search/editing
|
|
6
|
+
# pilfer [open|close]
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import errno
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import configparser
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Use Ansible's official vault implementation instead of third-party library
|
|
18
|
+
from ansible.constants import DEFAULT_VAULT_ID_MATCH
|
|
19
|
+
from ansible.parsing.vault import VaultLib, VaultSecret
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
temp_vault_file_list_path = "vaultedFileList.json"
|
|
23
|
+
list_of_vault_encrypted_files = []
|
|
24
|
+
temp_hidden_encrypted_copies_directory_path = '.vault'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_vault_password_file():
|
|
28
|
+
"""Get vault password file from ansible.cfg or fall back to default locations"""
|
|
29
|
+
# First try to read from ansible.cfg
|
|
30
|
+
try:
|
|
31
|
+
config = configparser.ConfigParser()
|
|
32
|
+
config.read('ansible.cfg')
|
|
33
|
+
if 'defaults' in config and 'vault_password_file' in config['defaults']:
|
|
34
|
+
vault_file = config['defaults']['vault_password_file']
|
|
35
|
+
# Expand tilde for home directory
|
|
36
|
+
vault_file = os.path.expanduser(vault_file)
|
|
37
|
+
if os.path.exists(vault_file):
|
|
38
|
+
return vault_file
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Fall back to common locations
|
|
43
|
+
fallback_locations = [
|
|
44
|
+
"../../vault_password_file", # Original default
|
|
45
|
+
"~/.ansible-vault/.vault-file", # Common location
|
|
46
|
+
".vault_password", # Local project file
|
|
47
|
+
"vault_password_file" # Simple local file
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for location in fallback_locations:
|
|
51
|
+
expanded_location = os.path.expanduser(location)
|
|
52
|
+
if os.path.exists(expanded_location):
|
|
53
|
+
return expanded_location
|
|
54
|
+
|
|
55
|
+
raise FileNotFoundError("Could not find vault password file. Please ensure it exists or specify with -p argument.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# find all files that have the ansible vault header and write it to disk
|
|
59
|
+
def write_vaulted_file_list():
|
|
60
|
+
walk_dir = os.path.abspath(os.getcwd())
|
|
61
|
+
|
|
62
|
+
for dirpath, dirnames, filenames in os.walk(walk_dir):
|
|
63
|
+
|
|
64
|
+
for name in filenames:
|
|
65
|
+
# print name
|
|
66
|
+
|
|
67
|
+
# disable filename filter since we might have encrypted everything
|
|
68
|
+
# yamlExtensions = ('yml','yaml')
|
|
69
|
+
# if name.endswith(yamlExtensions):
|
|
70
|
+
# print name
|
|
71
|
+
|
|
72
|
+
# full path
|
|
73
|
+
filePath = os.path.join(dirpath, name)
|
|
74
|
+
# print filePath
|
|
75
|
+
|
|
76
|
+
# find all files with the ansible vault header
|
|
77
|
+
try:
|
|
78
|
+
with open(filePath, 'rb') as open_file:
|
|
79
|
+
first_line = open_file.readline()
|
|
80
|
+
|
|
81
|
+
if first_line.startswith(b'$ANSIBLE_VAULT;'):
|
|
82
|
+
# print filePath
|
|
83
|
+
list_of_vault_encrypted_files.append(filePath)
|
|
84
|
+
except (IOError, OSError, PermissionError):
|
|
85
|
+
# Skip files we can't read
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# print vaultedFileList
|
|
89
|
+
with open(temp_vault_file_list_path, 'w') as open_file:
|
|
90
|
+
json.dump(list_of_vault_encrypted_files, open_file, indent=2)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def decrypt_vault_files(vault_password_file_path=None):
|
|
94
|
+
# load the list of encrypted files
|
|
95
|
+
with open(temp_vault_file_list_path, 'r') as vaultListFile:
|
|
96
|
+
vaultedFileList = json.load(vaultListFile)
|
|
97
|
+
|
|
98
|
+
# list the detected encrypted files
|
|
99
|
+
# print json.dumps(vaultedFileList, indent=2)
|
|
100
|
+
|
|
101
|
+
# determine vault password file
|
|
102
|
+
if vault_password_file_path:
|
|
103
|
+
vault_file = vault_password_file_path
|
|
104
|
+
else:
|
|
105
|
+
vault_file = get_vault_password_file()
|
|
106
|
+
|
|
107
|
+
# load vault password into memory
|
|
108
|
+
with open(vault_file, 'r') as vault_password_file:
|
|
109
|
+
vaultPassword = vault_password_file.read().strip()
|
|
110
|
+
|
|
111
|
+
# create VaultLib instance using Ansible's official implementation
|
|
112
|
+
vault = VaultLib([
|
|
113
|
+
(DEFAULT_VAULT_ID_MATCH, VaultSecret(vaultPassword.encode('utf-8')))
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
# iterate over the list of vaulted files
|
|
117
|
+
for vaultedFilePath in vaultedFileList:
|
|
118
|
+
try:
|
|
119
|
+
# recursively build a mirror directory structure for this file
|
|
120
|
+
mkdir_p(os.path.join(temp_hidden_encrypted_copies_directory_path + vaultedFilePath))
|
|
121
|
+
|
|
122
|
+
# make a copy of the encrypted file
|
|
123
|
+
shutil.copy2(vaultedFilePath, temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/encrypted')
|
|
124
|
+
|
|
125
|
+
# decrypt the file using Ansible's official vault implementation
|
|
126
|
+
# Read encrypted data as bytes to preserve exact formatting
|
|
127
|
+
with open(vaultedFilePath, 'rb') as f:
|
|
128
|
+
encrypted_data = f.read()
|
|
129
|
+
# VaultLib.decrypt() returns bytes, preserving binary data and line endings
|
|
130
|
+
decrypted_bytes = vault.decrypt(encrypted_data)
|
|
131
|
+
|
|
132
|
+
# write a hash of the decrypted content (bytes) to disk in the temporary directory
|
|
133
|
+
file_hash = hashlib.sha256(decrypted_bytes).hexdigest()
|
|
134
|
+
with open(temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/hash', 'w') as decryptedVaultFileHash:
|
|
135
|
+
decryptedVaultFileHash.write(file_hash)
|
|
136
|
+
|
|
137
|
+
# write the decrypted data to disk as bytes to preserve exact formatting
|
|
138
|
+
with open(vaultedFilePath, 'wb') as decryptedVaultFile:
|
|
139
|
+
decryptedVaultFile.write(decrypted_bytes)
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"Failed to decrypt {vaultedFilePath}: {e}")
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def mkdir_p(path):
|
|
147
|
+
try:
|
|
148
|
+
os.makedirs(path)
|
|
149
|
+
except OSError as exc: # Python >2.5
|
|
150
|
+
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
|
151
|
+
pass
|
|
152
|
+
else:
|
|
153
|
+
raise
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def recrypt_vault_files(vault_password_file_path=None):
|
|
157
|
+
with open(temp_vault_file_list_path, 'r') as vaultListFile:
|
|
158
|
+
vaultedFileList = json.load(vaultListFile)
|
|
159
|
+
|
|
160
|
+
# determine vault password file
|
|
161
|
+
if vault_password_file_path:
|
|
162
|
+
vault_file = vault_password_file_path
|
|
163
|
+
else:
|
|
164
|
+
vault_file = get_vault_password_file()
|
|
165
|
+
|
|
166
|
+
# load vault password into memory
|
|
167
|
+
with open(vault_file, 'r') as vault_password_file:
|
|
168
|
+
vaultPassword = vault_password_file.read().strip()
|
|
169
|
+
|
|
170
|
+
# create VaultLib instance using Ansible's official implementation
|
|
171
|
+
vault = VaultLib([
|
|
172
|
+
(DEFAULT_VAULT_ID_MATCH, VaultSecret(vaultPassword.encode('utf-8')))
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
# iterate over the list of vaulted files
|
|
176
|
+
for vaultedFilePath in vaultedFileList:
|
|
177
|
+
try:
|
|
178
|
+
# Load stored encrypted data as bytes
|
|
179
|
+
with open(temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/encrypted', 'rb') as f:
|
|
180
|
+
old_encrypted_data = f.read()
|
|
181
|
+
|
|
182
|
+
with open(temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/hash', 'r') as f:
|
|
183
|
+
old_hash = f.read().strip()
|
|
184
|
+
|
|
185
|
+
# Load (potentially) new data from original path as bytes
|
|
186
|
+
with open(vaultedFilePath, 'rb') as f:
|
|
187
|
+
new_data_bytes = f.read()
|
|
188
|
+
new_hash = hashlib.sha256(new_data_bytes).hexdigest()
|
|
189
|
+
|
|
190
|
+
# Determine whether to re-encrypt
|
|
191
|
+
if old_hash != new_hash:
|
|
192
|
+
# File was modified, re-encrypt it using Ansible's official vault implementation
|
|
193
|
+
# VaultLib.encrypt() expects and returns bytes
|
|
194
|
+
new_encrypted_data = vault.encrypt(new_data_bytes)
|
|
195
|
+
else:
|
|
196
|
+
# File unchanged, restore original encrypted version
|
|
197
|
+
new_encrypted_data = old_encrypted_data
|
|
198
|
+
|
|
199
|
+
# Update file with bytes to preserve exact formatting
|
|
200
|
+
with open(vaultedFilePath, 'wb') as f:
|
|
201
|
+
f.write(new_encrypted_data)
|
|
202
|
+
|
|
203
|
+
# Clean vault
|
|
204
|
+
try:
|
|
205
|
+
os.remove(temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/encrypted')
|
|
206
|
+
os.remove(temp_hidden_encrypted_copies_directory_path + vaultedFilePath + '/hash')
|
|
207
|
+
os.removedirs(temp_hidden_encrypted_copies_directory_path + vaultedFilePath)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
print(f"Warning: Failed to clean temp files for {vaultedFilePath}: {e}")
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"Failed to process {vaultedFilePath}: {e}")
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
os.removedirs(temp_hidden_encrypted_copies_directory_path)
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
os.remove(temp_vault_file_list_path)
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def main():
|
|
226
|
+
"""Main CLI entry point for pilfer"""
|
|
227
|
+
# Parse Args
|
|
228
|
+
parser = argparse.ArgumentParser(
|
|
229
|
+
prog='pilfer',
|
|
230
|
+
description='Decrypt all ansible vault files in a project recursively for search/editing, then re-encrypt them when done'
|
|
231
|
+
)
|
|
232
|
+
parser.add_argument('action', choices=['open', 'close'],
|
|
233
|
+
help="'open' to decrypt all vault files, 'close' to re-encrypt modified files")
|
|
234
|
+
parser.add_argument('-p', '--vault-password-file', type=str,
|
|
235
|
+
help="Path to vault password file")
|
|
236
|
+
args = parser.parse_args()
|
|
237
|
+
|
|
238
|
+
# Open / Close Vault
|
|
239
|
+
if args.action == 'open':
|
|
240
|
+
# check for an existing encrypted file list
|
|
241
|
+
# if one exists, decrypt the files
|
|
242
|
+
# if it doesn't, make one
|
|
243
|
+
if Path(temp_vault_file_list_path).is_file():
|
|
244
|
+
# print "path exists, skipping file creation"
|
|
245
|
+
decrypt_vault_files(args.vault_password_file)
|
|
246
|
+
else:
|
|
247
|
+
write_vaulted_file_list()
|
|
248
|
+
decrypt_vault_files(args.vault_password_file)
|
|
249
|
+
|
|
250
|
+
elif args.action == 'close':
|
|
251
|
+
recrypt_vault_files(args.vault_password_file)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == '__main__':
|
|
255
|
+
main()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pilfer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Decrypt all ansible vault files in a project recursively for search/editing, then re-encrypt when done.
|
|
5
|
+
Author-email: Tom Paine <github@aioue.net>
|
|
6
|
+
Project-URL: Homepage, https://github.com/aioue/pilfer
|
|
7
|
+
Project-URL: Repository, https://github.com/aioue/pilfer
|
|
8
|
+
Project-URL: Issues, https://github.com/aioue/pilfer/issues
|
|
9
|
+
Keywords: ansible,vault,encryption,devops,automation
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.6
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: ansible>=2.9.0
|
|
28
|
+
|
|
29
|
+
# pilfer
|
|
30
|
+
Decrypt *all* ansible vault files in a project recursively for search/editing, then re-encrypt them all at once when you're done.
|
|
31
|
+
|
|
32
|
+
Borrows heavily from the excellent, but no longer supported [Ansible Toolkit](https://github.com/dellis23/ansible-toolkit).
|
|
33
|
+
|
|
34
|
+
**Updated for Python 3 compatibility with modern features and ansible.cfg integration.**
|
|
35
|
+
|
|
36
|
+
Tested with Ansible v2.18.x and Python 3.12.x
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Python 3 compatible** - Modernized for current Python versions
|
|
41
|
+
- **ansible.cfg integration** - Automatically reads `vault_password_file` from your ansible.cfg
|
|
42
|
+
- **Change detection** - Only re-encrypts files that were actually modified (using SHA256)
|
|
43
|
+
- **Safe operation** - Preserves original encrypted content for unchanged files
|
|
44
|
+
- **No third-party dependencies** - Uses Ansible's official vault implementation directly
|
|
45
|
+
- **Binary data preservation** - Preserves exact line endings and formatting (critical for certificates)
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
```
|
|
49
|
+
pilfer [open|close] [-p VAULT_PASSWORD_FILE]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Basic Usage
|
|
53
|
+
|
|
54
|
+
**Option 1: Standalone Script (No Installation)**
|
|
55
|
+
- Download `pilfer.py` and place it in your Ansible project directory
|
|
56
|
+
- Run `python pilfer.py open` to decrypt all vaulted files recursively
|
|
57
|
+
- Edit/search plaintext as needed
|
|
58
|
+
- Run `python pilfer.py close` to re-encrypt any changed files
|
|
59
|
+
|
|
60
|
+
**Option 2: Installed via pipx (Recommended)**
|
|
61
|
+
- Install pilfer via pipx: `pipx install pilfer`
|
|
62
|
+
- Run `pilfer open` to decrypt all vaulted files recursively
|
|
63
|
+
- Edit/search plaintext as needed
|
|
64
|
+
- Run `pilfer close` to re-encrypt any changed files
|
|
65
|
+
|
|
66
|
+
Any unchanged files will be returned to their original state.
|
|
67
|
+
|
|
68
|
+
### Vault Password File Detection
|
|
69
|
+
|
|
70
|
+
The script automatically detects your vault password file in this order:
|
|
71
|
+
|
|
72
|
+
1. **Command line argument**: `-p /path/to/vault/file`
|
|
73
|
+
2. **ansible.cfg**: Reads `vault_password_file` from `[defaults]` section
|
|
74
|
+
3. **Common locations**:
|
|
75
|
+
- `~/.ansible-vault/.vault-file`
|
|
76
|
+
- `../../vault_password_file`
|
|
77
|
+
- `.vault_password`
|
|
78
|
+
- `vault_password_file`
|
|
79
|
+
|
|
80
|
+
### Examples
|
|
81
|
+
|
|
82
|
+
**Using the installed version:**
|
|
83
|
+
```bash
|
|
84
|
+
# Use ansible.cfg vault_password_file setting (recommended)
|
|
85
|
+
pilfer open
|
|
86
|
+
|
|
87
|
+
# Specify custom vault password file
|
|
88
|
+
pilfer open -p ~/.my-vault-password
|
|
89
|
+
|
|
90
|
+
# Close and re-encrypt modified files
|
|
91
|
+
pilfer close
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Using the standalone script:**
|
|
95
|
+
```bash
|
|
96
|
+
# Use ansible.cfg vault_password_file setting (recommended)
|
|
97
|
+
python pilfer.py open
|
|
98
|
+
|
|
99
|
+
# Specify custom vault password file
|
|
100
|
+
python pilfer.py open -p ~/.my-vault-password
|
|
101
|
+
|
|
102
|
+
# Close and re-encrypt modified files
|
|
103
|
+
python pilfer.py close
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
### Option 1: Standalone Script (No Installation Required)
|
|
109
|
+
|
|
110
|
+
Download and use the standalone script directly:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Download the standalone script
|
|
114
|
+
curl -O https://raw.githubusercontent.com/aioue/pilfer/main/pilfer.py
|
|
115
|
+
|
|
116
|
+
# Make it executable (required for ./pilfer.py usage)
|
|
117
|
+
chmod +x pilfer.py
|
|
118
|
+
|
|
119
|
+
# Use it directly
|
|
120
|
+
./pilfer.py open
|
|
121
|
+
# OR
|
|
122
|
+
python pilfer.py open
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Option 2: Install via pipx (Recommended for Regular Use)
|
|
126
|
+
|
|
127
|
+
**Python 3.6+** is required. Install pilfer using pipx for isolated CLI tool management:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Install pilfer via pipx (recommended)
|
|
131
|
+
pipx install pilfer
|
|
132
|
+
|
|
133
|
+
# Verify installation
|
|
134
|
+
pilfer --help
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Alternative Installation Methods
|
|
138
|
+
|
|
139
|
+
If you prefer other installation methods:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Install from source (in development mode)
|
|
143
|
+
git clone https://github.com/aioue/pilfer.git
|
|
144
|
+
cd pilfer
|
|
145
|
+
pip install -e .
|
|
146
|
+
|
|
147
|
+
# Direct pip installation (not recommended for CLI tools)
|
|
148
|
+
pip install pilfer
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Requirements
|
|
152
|
+
|
|
153
|
+
Pilfer requires **Ansible** to be available. If not already installed:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Using pipx (recommended for CLI tools)
|
|
157
|
+
pipx install ansible
|
|
158
|
+
|
|
159
|
+
# Using pip
|
|
160
|
+
pip install ansible
|
|
161
|
+
|
|
162
|
+
# System package manager
|
|
163
|
+
# Ubuntu/Debian:
|
|
164
|
+
sudo apt update && sudo apt install ansible
|
|
165
|
+
|
|
166
|
+
# RHEL/CentOS/Fedora:
|
|
167
|
+
sudo dnf install ansible
|
|
168
|
+
|
|
169
|
+
# macOS:
|
|
170
|
+
brew install ansible
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### ansible.cfg Setup (Recommended)
|
|
174
|
+
|
|
175
|
+
Add to your `ansible.cfg`:
|
|
176
|
+
```ini
|
|
177
|
+
[defaults]
|
|
178
|
+
vault_password_file = ~/.ansible-vault/.vault-file
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This eliminates the need to manually configure vault password paths.
|
|
182
|
+
|
|
183
|
+
## Development and Publishing
|
|
184
|
+
|
|
185
|
+
### For Developers
|
|
186
|
+
|
|
187
|
+
To set up for development:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Clone the repository
|
|
191
|
+
git clone https://github.com/aioue/pilfer.git
|
|
192
|
+
cd pilfer
|
|
193
|
+
|
|
194
|
+
# Install in development mode
|
|
195
|
+
pip install -e .
|
|
196
|
+
|
|
197
|
+
# Make changes and test
|
|
198
|
+
pilfer --help
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Publishing to PyPI
|
|
202
|
+
|
|
203
|
+
Prerequisites:
|
|
204
|
+
```bash
|
|
205
|
+
# Install build tools
|
|
206
|
+
pip install build twine
|
|
207
|
+
|
|
208
|
+
# Configure PyPI credentials
|
|
209
|
+
# ~/.pypirc or use environment variables
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Build and publish:
|
|
213
|
+
```bash
|
|
214
|
+
# Make the script executable
|
|
215
|
+
chmod +x build_and_publish.sh
|
|
216
|
+
|
|
217
|
+
# Publish to TestPyPI first
|
|
218
|
+
./build_and_publish.sh test
|
|
219
|
+
|
|
220
|
+
# After testing, publish to production PyPI
|
|
221
|
+
./build_and_publish.sh prod
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The build script will:
|
|
225
|
+
1. Clean previous builds
|
|
226
|
+
2. Build the package using modern Python packaging
|
|
227
|
+
3. Upload to PyPI/TestPyPI using twine
|
|
228
|
+
4. Provide installation instructions
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pilfer/__init__.py,sha256=3N7TCsFQL2mNLlrM2WJT2c8tPo7km-zwU16n7KIkUtU,584
|
|
2
|
+
pilfer/cli.py,sha256=cjJbjDWR0eXNq7DnldZIY2zTd_RgySnc6ur-BfDF-KE,9709
|
|
3
|
+
pilfer-1.0.0.dist-info/METADATA,sha256=VtKu9hZkuiLm7jqTo6y3ULzQo5ZKYXdadBeBhuWYmu4,6113
|
|
4
|
+
pilfer-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
pilfer-1.0.0.dist-info/entry_points.txt,sha256=z0AIRa_eNRWZUUqPzZc2nQNpYYXI3sKvxjpPO-KKiRI,43
|
|
6
|
+
pilfer-1.0.0.dist-info/top_level.txt,sha256=hl-KQi0BEX35OJTEv3Fl0HKr1k0mRzXtPHrJyur0tCM,7
|
|
7
|
+
pilfer-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pilfer
|