lambda-watcher 0.1.0__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.
- lambda_watcher-0.1.0/LICENSE +201 -0
- lambda_watcher-0.1.0/PKG-INFO +409 -0
- lambda_watcher-0.1.0/README.md +378 -0
- lambda_watcher-0.1.0/pyproject.toml +79 -0
- lambda_watcher-0.1.0/setup.cfg +4 -0
- lambda_watcher-0.1.0/src/lambda_watcher/__init__.py +4 -0
- lambda_watcher-0.1.0/src/lambda_watcher/__main__.py +4 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/__init__.py +115 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/deps.py +291 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/envvars.py +80 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/handler.py +111 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/inventory.py +118 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/runtime.py +117 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/secrets.py +178 -0
- lambda_watcher-0.1.0/src/lambda_watcher/analysis/services.py +76 -0
- lambda_watcher-0.1.0/src/lambda_watcher/cli.py +1406 -0
- lambda_watcher-0.1.0/src/lambda_watcher/config.py +324 -0
- lambda_watcher-0.1.0/src/lambda_watcher/db.py +466 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/__init__.py +14 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/build.py +51 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/compare.py +525 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/highlight.py +312 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/icons.py +132 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/intraline.py +162 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/render_html.py +697 -0
- lambda_watcher-0.1.0/src/lambda_watcher/diffing/render_text.py +198 -0
- lambda_watcher-0.1.0/src/lambda_watcher/extract.py +227 -0
- lambda_watcher-0.1.0/src/lambda_watcher/gitmirror.py +151 -0
- lambda_watcher-0.1.0/src/lambda_watcher/identify.py +201 -0
- lambda_watcher-0.1.0/src/lambda_watcher/ingest.py +480 -0
- lambda_watcher-0.1.0/src/lambda_watcher/notify.py +59 -0
- lambda_watcher-0.1.0/src/lambda_watcher/reindex.py +158 -0
- lambda_watcher-0.1.0/src/lambda_watcher/service.py +553 -0
- lambda_watcher-0.1.0/src/lambda_watcher/store.py +209 -0
- lambda_watcher-0.1.0/src/lambda_watcher/templates.py +124 -0
- lambda_watcher-0.1.0/src/lambda_watcher/utils.py +314 -0
- lambda_watcher-0.1.0/src/lambda_watcher/watcher.py +241 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/PKG-INFO +409 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/SOURCES.txt +52 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/dependency_links.txt +1 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/entry_points.txt +3 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/requires.txt +15 -0
- lambda_watcher-0.1.0/src/lambda_watcher.egg-info/top_level.txt +1 -0
- lambda_watcher-0.1.0/tests/test_analysis.py +132 -0
- lambda_watcher-0.1.0/tests/test_cli.py +409 -0
- lambda_watcher-0.1.0/tests/test_diff.py +159 -0
- lambda_watcher-0.1.0/tests/test_docs.py +167 -0
- lambda_watcher-0.1.0/tests/test_extract.py +121 -0
- lambda_watcher-0.1.0/tests/test_identify.py +119 -0
- lambda_watcher-0.1.0/tests/test_ingest.py +201 -0
- lambda_watcher-0.1.0/tests/test_render_html.py +341 -0
- lambda_watcher-0.1.0/tests/test_service.py +160 -0
- lambda_watcher-0.1.0/tests/test_utils.py +154 -0
- lambda_watcher-0.1.0/tests/test_watcher.py +202 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lambda-watcher
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Watch your Downloads folder for AWS Lambda deployment zips, archive them as versions, analyse them, and diff any two versions.
|
|
5
|
+
Author: Lambda Watcher contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/utkarsh5026/lambwatch
|
|
8
|
+
Project-URL: Documentation, https://utkarsh5026.github.io/lambwatch/
|
|
9
|
+
Project-URL: Issues, https://github.com/utkarsh5026/lambwatch/issues
|
|
10
|
+
Keywords: aws,lambda,diff,watchdog,versioning,backup
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: watchdog>=4.0
|
|
20
|
+
Requires-Dist: typer>=0.12
|
|
21
|
+
Requires-Dist: rich>=13.0
|
|
22
|
+
Requires-Dist: PyYAML>=6.0
|
|
23
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.15; extra == "dev"
|
|
28
|
+
Provides-Extra: lint
|
|
29
|
+
Requires-Dist: ruff>=0.15; extra == "lint"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# lambda-watcher
|
|
33
|
+
|
|
34
|
+
Watch your Downloads folder for AWS Lambda deployment zips, archive every
|
|
35
|
+
version automatically, and review what actually changed between any two of
|
|
36
|
+
them.
|
|
37
|
+
|
|
38
|
+
If your deploy ritual is *"download the current function zip as a backup, then
|
|
39
|
+
push the new one"*, you end up with a folder of near-identical archives and no
|
|
40
|
+
practical way to answer **"what changed between the 2nd one and the 10th?"**.
|
|
41
|
+
This tool turns that pile into a queryable, diffable history — with no change
|
|
42
|
+
to how you work. You keep downloading zips; it does the rest.
|
|
43
|
+
|
|
44
|
+
**[How it works, step by step →](https://utkarsh5026.github.io/lambwatch/)**
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
$ lambda-watcher watch
|
|
48
|
+
lambda-watcher 0.1.0 — archiving into ~/.lambda-watcher
|
|
49
|
+
watching ~/Downloads. Press Ctrl-C to stop.
|
|
50
|
+
new order-processor v0001 order-processor.zip — archived a new version
|
|
51
|
+
new order-processor v0002 order-processor (1).zip — 2 added, 2 modified, 3 renamed, 52 vendored
|
|
52
|
+
+24/-5 lines · new: 1 env var, 1 AWS service, 3 secrets
|
|
53
|
+
report: ~/.lambda-watcher/reports/order-processor/v0001-v0002.html
|
|
54
|
+
unchanged order-processor v0002 order-processor (2).zip — identical to version 2
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That third line is the one that matters: the same code, downloaded again, is
|
|
58
|
+
recorded as `unchanged` rather than becoming a bogus version 3.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What it does
|
|
63
|
+
|
|
64
|
+
Every time a `.zip` lands in your Downloads folder, lambda-watcher:
|
|
65
|
+
|
|
66
|
+
1. **waits** until the download has actually finished (no half-written files);
|
|
67
|
+
2. **works out which Lambda it is** from the filename, a sidecar
|
|
68
|
+
`get-function` JSON, or the archive's contents — `order-processor.zip`,
|
|
69
|
+
`order-processor (1).zip` and `order-processor-2026-03-01.zip` all land
|
|
70
|
+
under the same function;
|
|
71
|
+
3. **extracts** it safely (path traversal, zip bombs and encrypted archives are
|
|
72
|
+
refused, not trusted);
|
|
73
|
+
4. **hashes the content, not the file** — re-downloading unchanged code is
|
|
74
|
+
recorded as `unchanged` instead of creating a bogus new version;
|
|
75
|
+
5. **analyses** it: runtime, handler, dependencies (declared *and* the versions
|
|
76
|
+
actually vendored in the zip), environment variables the code reads, AWS
|
|
77
|
+
services it calls, and hardcoded credentials;
|
|
78
|
+
6. **archives** it as version *N* with a `manifest.json`, indexes it in SQLite,
|
|
79
|
+
and commits it to a per-function git repo tagged `v0004`.
|
|
80
|
+
|
|
81
|
+
Then you review:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
lw diff order-processor # last two versions, in the terminal
|
|
85
|
+
lw diff order-processor --from 2 --to 10 # any two versions
|
|
86
|
+
lw diff order-processor --html --open # a shareable HTML report
|
|
87
|
+
lw report order-processor # the whole history, browsable
|
|
88
|
+
lw open order-processor # the whole archive, in your editor
|
|
89
|
+
lw git order-processor log -p # or just use git
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Why the diffs are actually readable
|
|
93
|
+
|
|
94
|
+
A raw `diff -r` between two Lambda zips is unusable: thousands of vendored
|
|
95
|
+
dependency files drown three lines of real change. lambda-watcher fixes that by
|
|
96
|
+
answering the questions you actually have, in order:
|
|
97
|
+
|
|
98
|
+
| | |
|
|
99
|
+
|---|---|
|
|
100
|
+
| **Your code, separated from theirs** | `node_modules/`, `site-packages/` and friends are classified as vendored and hidden by default. Three changed files, not 3,000. |
|
|
101
|
+
| **Dependencies as versions, not files** | A `boto3` upgrade shows as `boto3 1.34.0 → 1.35.20`, parsed from the `dist-info` actually shipped in the zip — not 400 changed files. |
|
|
102
|
+
| **Config impact, called out** | A new `os.environ["QUEUE_URL"]` is flagged as *"this must exist in the function's environment before you deploy"*. A new `boto3.client("sqs")` is flagged as *"the execution role may need new IAM permissions"*. These are the changes that break a deploy and never show up in a file diff. |
|
|
103
|
+
| **Renames survive edits** | A file that moved *and* changed is shown as one rename with a diff, not an unrelated add plus delete. |
|
|
104
|
+
| **Secrets are diffed too** | An AWS key or Stripe token that appears between v7 and v8 gets its own section. Values are stored redacted; the secret itself never enters the index. |
|
|
105
|
+
|
|
106
|
+
Concretely — the two versions above, where a `diff -rq` reports 61 changed
|
|
107
|
+
files and 56 of them are `site-packages/`:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ lambda-watcher diff order-processor
|
|
111
|
+
╭──────────────────────────────────────────────────────────────────────╮
|
|
112
|
+
│ order-processor v0001 → v0002 │
|
|
113
|
+
│ 2 added 2 modified 3 renamed 52 vendored (hidden) +24 / -5 lines │
|
|
114
|
+
╰──────────────────────────────────────────────────────────────────────╯
|
|
115
|
+
size 8.1 KB → 8.9 KB (+782 B)
|
|
116
|
+
|
|
117
|
+
Dependencies
|
|
118
|
+
manager package from to origin
|
|
119
|
+
+ pip pydantic — 2.9.0 installed
|
|
120
|
+
~ pip boto3 1.34.0 1.35.20 installed
|
|
121
|
+
~ pip botocore 1.34.0 1.35.20 installed
|
|
122
|
+
|
|
123
|
+
Env vars added: QUEUE_URL
|
|
124
|
+
AWS services added: sqs
|
|
125
|
+
↑ these need to exist in the function's environment configuration
|
|
126
|
+
|
|
127
|
+
New findings
|
|
128
|
+
high aws-access-key-id config.py:6 AKIA…LE (20 chars)
|
|
129
|
+
high stripe-key config.py:7 sk_l…dc (32 chars)
|
|
130
|
+
low debug-flag config.py:8 DEBUG = True
|
|
131
|
+
|
|
132
|
+
Files
|
|
133
|
+
path + − size
|
|
134
|
+
~ lambda_function.py 9 2 +322
|
|
135
|
+
~ requirements.txt 2 1 +17
|
|
136
|
+
+ config.py 10 +235
|
|
137
|
+
+ helpers/__init__.py
|
|
138
|
+
→ {db → helpers/db}.py 1 +33
|
|
139
|
+
→ site-packages/boto3-1.{34.0 → 35.20}.dist-info/METADATA 1 1 +1
|
|
140
|
+
→ site-packages/botocore-1.{34.0 → 35.20}.dist-info/METADATA 1 1 +1
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The 52 vendored files became three version numbers, `db.py` moving into a
|
|
144
|
+
package is one rename rather than a delete plus an add, and the new environment
|
|
145
|
+
variable, the new AWS service and the three secret findings are changes that a
|
|
146
|
+
file diff cannot express at all.
|
|
147
|
+
|
|
148
|
+
That capture is not illustrative — it is the output of
|
|
149
|
+
[`docs/examples/build_demo.py`](docs/examples/build_demo.py), which builds the
|
|
150
|
+
two zips and runs the real pipeline over them. Run it yourself:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
.venv/bin/python docs/examples/build_demo.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The same comparison as a shareable HTML page — `--html --open` on any diff, or
|
|
157
|
+
`report` for the whole history — is published from this repository:
|
|
158
|
+
**[see the generated report](https://utkarsh5026.github.io/lambwatch/examples/report/v0001-v0002.html)**.
|
|
159
|
+
|
|
160
|
+
## Install
|
|
161
|
+
|
|
162
|
+
Python 3.10+.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv tool install lambda-watcher # or: pipx install lambda-watcher
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Either one puts `lambda-watcher` — and the shorter alias `lw`, used throughout
|
|
169
|
+
this README — on your `PATH` in its own environment. Plain `pip install
|
|
170
|
+
lambda-watcher` works too if you would rather manage the environment yourself.
|
|
171
|
+
|
|
172
|
+
<details>
|
|
173
|
+
<summary>From a checkout instead</summary>
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/utkarsh5026/lambwatch.git
|
|
177
|
+
cd lambwatch
|
|
178
|
+
python3 -m venv .venv
|
|
179
|
+
.venv/bin/pip install -e .
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
</details>
|
|
183
|
+
|
|
184
|
+
## Quick start
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
lw setup
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
That is the whole thing. `setup` writes a config you can edit, finds your
|
|
191
|
+
downloads folder, offers to import any zips already sitting in it, and starts
|
|
192
|
+
the watcher in the background — as a launchd agent on macOS, a systemd user
|
|
193
|
+
service on Linux, a scheduled task on Windows — so it comes back after a
|
|
194
|
+
reboot without you thinking about it.
|
|
195
|
+
|
|
196
|
+
Then just keep downloading zips. When you want to know what happened:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
lw # is it running, and what has it caught?
|
|
200
|
+
lw diff order-processor # what changed in the last version
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Every new version also writes its own comparison to
|
|
204
|
+
`~/.lambda-watcher/reports/<function>/latest.html` as it is archived, so the
|
|
205
|
+
answer is a bookmark rather than a command.
|
|
206
|
+
|
|
207
|
+
<details>
|
|
208
|
+
<summary>Running it by hand, or setting it up piece by piece</summary>
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
lw watch # run in the foreground instead; Ctrl-C stops it
|
|
212
|
+
lw start # install and start the background watcher
|
|
213
|
+
lw stop # stop it (--remove also unregisters it)
|
|
214
|
+
lw restart # after editing the config
|
|
215
|
+
lw doctor # check the config, watch folders, store, git and disk space
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Already have a folder of old backups? Import them oldest-first so the version
|
|
219
|
+
numbers match real history:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
lw backfill ~/Downloads/lambda-backups --dry-run # check the names first
|
|
223
|
+
lw backfill ~/Downloads/lambda-backups
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
If your platform's service manager is unavailable — WSL without a systemd user
|
|
227
|
+
session, say — `lw start` falls back to a plain background process and tells
|
|
228
|
+
you it will not survive a reboot. [docs/autostart.md](docs/autostart.md) has
|
|
229
|
+
the manual recipes.
|
|
230
|
+
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
## Commands
|
|
234
|
+
|
|
235
|
+
| Command | What it does |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `setup` | Config, background watcher and any history already on disk, in one go. `--no-service` skips the background watcher, `--yes` takes every default. |
|
|
238
|
+
| `status` | Is it running, and what has it archived? Also what bare `lw` prints. |
|
|
239
|
+
| `start` / `stop` | Register the background watcher with the OS, or stop it. `stop --remove` unregisters it too. |
|
|
240
|
+
| `restart` | Stop and start it — use after editing the config. |
|
|
241
|
+
| `watch` | Watch the download folders in the foreground. `--once` processes what is already there and exits. |
|
|
242
|
+
| `ingest FILE...` | Archive specific zips by hand. `--as NAME` overrides the detected function, `--label` annotates the version. |
|
|
243
|
+
| `backfill DIR` | Import a folder of old downloads, oldest first. `--dry-run` shows the names it would assign. |
|
|
244
|
+
| `ls` | Every function archived so far. |
|
|
245
|
+
| `versions FN` | Every archived version of one function. |
|
|
246
|
+
| `show FN [V]` | Runtime, handler, dependencies, env vars, services and findings for one version. `--files`, `--json`. |
|
|
247
|
+
| `diff FN` | Compare two versions. Defaults to the last two. `--from`/`--to`, `--html`, `--open`, `--vendor`, `--no-patch`, `--json`. |
|
|
248
|
+
| `report FN` | Build a browsable HTML history: an index plus a diff for every step. |
|
|
249
|
+
| `export FN [V]` | Get a version back out as a deployable zip (`--zip`) or a plain folder (`--tree`). |
|
|
250
|
+
| `open FN [V]` | Open the function's mirror in your editor — every version in one folder, with history. Name a version to open just its files. |
|
|
251
|
+
| `git FN ...` | Run git inside that function's mirror repo: `lw git order-processor log --oneline`. |
|
|
252
|
+
| `rename OLD NEW` | Fix a misidentified name. `--alias FRAGMENT` remembers the mapping for next time. |
|
|
253
|
+
| `merge SRC DST` | Combine two entries that are really the same Lambda, renumbering by archive time. |
|
|
254
|
+
| `label FN V TEXT` | Annotate a version, e.g. `label order-processor 7 "prod deploy 2026-03-01"`. |
|
|
255
|
+
| `search TERM` | Search filenames and dependencies across everything archived. |
|
|
256
|
+
| `log` | Recent activity, including downloads that were skipped and why. |
|
|
257
|
+
| `path FN [V]` | Print a path, for `cd "$(lw path order-processor 7)"`. |
|
|
258
|
+
| `rm FN` | Delete a function and everything archived for it. |
|
|
259
|
+
| `reindex` | Rebuild the SQLite index from the manifests on disk. |
|
|
260
|
+
| `doctor` | Check the config, watch folders, store, git and disk space. |
|
|
261
|
+
|
|
262
|
+
Version arguments accept `7`, `v7`, `latest`, `first`, or `-1` / `-2` counting
|
|
263
|
+
back from the newest.
|
|
264
|
+
|
|
265
|
+
## Where things are kept
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
~/.lambda-watcher/
|
|
269
|
+
├── config.yaml
|
|
270
|
+
├── index.db # rebuildable index (see `reindex`)
|
|
271
|
+
├── logs/watcher.log
|
|
272
|
+
├── reports/ # generated HTML
|
|
273
|
+
├── quarantine/ # archives that failed, with a reason file
|
|
274
|
+
├── repos/
|
|
275
|
+
│ └── order-processor/ # git mirror: one commit per version, tagged v0001…
|
|
276
|
+
└── functions/
|
|
277
|
+
└── order-processor/
|
|
278
|
+
└── versions/
|
|
279
|
+
├── 0001-bd9f77c8/
|
|
280
|
+
│ ├── code/ # the extracted tree
|
|
281
|
+
│ ├── manifest.json # the full analysis
|
|
282
|
+
│ └── package.zip # the original download
|
|
283
|
+
└── 0002-73d375ad/
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The directories are the source of truth. `index.db` is a cache you can delete
|
|
287
|
+
and rebuild with `lw reindex`, and the whole store is portable —
|
|
288
|
+
copy it to another machine and reindex.
|
|
289
|
+
|
|
290
|
+
### The git mirror
|
|
291
|
+
|
|
292
|
+
Each function gets its own git repository at `repos/<name>/`, whose working tree
|
|
293
|
+
is the latest version, with one commit per archived version tagged `v0001`,
|
|
294
|
+
`v0002`, … The folder is named after the function on purpose: it is what an
|
|
295
|
+
editor shows as the workspace root, so an open window says `order-processor`
|
|
296
|
+
rather than something generic. Every tool you already know works on it:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
lw open order-processor # VS Code, on the whole repo
|
|
300
|
+
|
|
301
|
+
cd "$(lw path order-processor --repo)"
|
|
302
|
+
git diff v0002 v0010 # the diff you originally wanted
|
|
303
|
+
git log --oneline --stat
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`open` finds VS Code, Cursor, Windsurf, VSCodium, Zed or Sublime on your `PATH`
|
|
307
|
+
— set `editor:` in the config (or `LAMBDA_WATCHER_EDITOR`, or `--editor`) to
|
|
308
|
+
name a different one. What you get is a folder of real files, not a diff: the
|
|
309
|
+
sidebar reads `order-processor`, and the editor's own file tree, search, Source
|
|
310
|
+
Control panel and timeline all work, with every earlier version a tag away. Name
|
|
311
|
+
a version — `lw open order-processor 3` — to open that version's
|
|
312
|
+
files on their own instead.
|
|
313
|
+
|
|
314
|
+
One repo per function is the point: your 2nd and 10th version of *one* Lambda
|
|
315
|
+
sit next to each other, with no other function's history in the way.
|
|
316
|
+
|
|
317
|
+
## Configuration
|
|
318
|
+
|
|
319
|
+
`lw init` writes an annotated `~/.lambda-watcher/config.yaml`.
|
|
320
|
+
Everything is optional. The settings worth knowing:
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
watch:
|
|
324
|
+
dirs: ["~/Downloads"] # add more if you download from several places
|
|
325
|
+
stable_seconds: 2.0 # how long a file must stop changing before it is read
|
|
326
|
+
force_polling: false # turn on for network shares, VM mounts, WSL
|
|
327
|
+
arrival_max_age_seconds: 300 # ignore "modified" events for files older than this
|
|
328
|
+
|
|
329
|
+
store:
|
|
330
|
+
on_ingest: copy # copy | move | leave
|
|
331
|
+
# `move` takes the zip out of Downloads once archived
|
|
332
|
+
strip_wrapper_dir: true # lift a lone `myrepo-1.2.3/` wrapper to the root, so a
|
|
333
|
+
# source archive's ref does not read as a full rewrite
|
|
334
|
+
max_versions_per_function: 0 # 0 keeps everything
|
|
335
|
+
|
|
336
|
+
naming:
|
|
337
|
+
rules: # explicit filename → function name mappings
|
|
338
|
+
- pattern: "^prod[-_](.+?)[-_]deploy"
|
|
339
|
+
name: '\1'
|
|
340
|
+
|
|
341
|
+
diff:
|
|
342
|
+
ignore_vendor: true # hide vendored dependency files in diffs
|
|
343
|
+
context_lines: 3
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Set `LAMBDA_WATCHER_HOME` to relocate the whole archive, or
|
|
347
|
+
`LAMBDA_WATCHER_CONFIG` to point at a different config file — useful for
|
|
348
|
+
keeping work and personal archives separate.
|
|
349
|
+
|
|
350
|
+
### When it guesses the wrong name
|
|
351
|
+
|
|
352
|
+
The filename is a guess, and `lw log` records which strategy was
|
|
353
|
+
used and how confident it was. Two commands fix any mistake:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
lw rename unknown-a1b2c3d4 order-processor --alias "a1b2c3d4"
|
|
357
|
+
lw merge order-processor-old order-processor
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`--alias` teaches it permanently: any future download whose filename contains
|
|
361
|
+
that fragment maps straight to the right function.
|
|
362
|
+
|
|
363
|
+
## Notes and limits
|
|
364
|
+
|
|
365
|
+
- **Only the code is archived.** A deployment package does not contain the
|
|
366
|
+
function's configuration — memory, timeout, environment variable *values*,
|
|
367
|
+
IAM role, layers or triggers. lambda-watcher infers what it can from the code
|
|
368
|
+
(which env vars are read, which services are called) and flags it, but if you
|
|
369
|
+
want the real configuration archived too, save
|
|
370
|
+
`aws lambda get-function --function-name X > X.json` next to the zip: it is
|
|
371
|
+
picked up as a naming hint, and it is a genuinely useful thing to keep.
|
|
372
|
+
- **Secret scanning is a tripwire, not a security tool.** It catches the
|
|
373
|
+
obvious cases — an AWS key, a private key block, a live Stripe token — and it
|
|
374
|
+
skips placeholders. Treat a finding as a prompt to look, not a verdict.
|
|
375
|
+
- **Layers are separate functions in AWS**, and they are downloaded separately;
|
|
376
|
+
they will be archived as their own entries.
|
|
377
|
+
- **Source archives work too.** A zip from GitHub (or npm, or `git archive`)
|
|
378
|
+
wraps everything in a directory named after the ref — `myrepo-1.2.3/` — and
|
|
379
|
+
names the file the same way. Both are handled: the wrapper is lifted to the
|
|
380
|
+
root so a re-download diffs as an edit rather than a total rewrite, the ref
|
|
381
|
+
becomes the version's label, and the ref is stripped from the name so
|
|
382
|
+
`myrepo-1.2.3.zip`, `myrepo-main.zip` and `myrepo-a1b2c3d.zip` all land as
|
|
383
|
+
versions of one `myrepo`. A trailing `-v2` is still left alone: it is part of
|
|
384
|
+
a name far more often than it is a tag.
|
|
385
|
+
- **A filesystem event is not proof that a file was written.** Windows reports
|
|
386
|
+
a zip as modified when an antivirus scan, the search indexer or OneDrive so
|
|
387
|
+
much as touches it — watchdog asks the OS for attribute and last-access
|
|
388
|
+
changes too — so a background sweep re-announces every zip in the folder at
|
|
389
|
+
once. Events claiming a write to a file nothing has written to are ignored
|
|
390
|
+
(`watch.arrival_max_age_seconds`), and `store.on_ingest: move` only clears
|
|
391
|
+
out a download the watcher saw arrive: a zip that a startup scan or a
|
|
392
|
+
`backfill` merely found is archived where it lies, never deleted.
|
|
393
|
+
- Large vendored packages make for large archives. `store.on_ingest: leave`
|
|
394
|
+
and `store.keep_zip: false` trade the original zips for disk space, and
|
|
395
|
+
`store.max_versions_per_function` caps history.
|
|
396
|
+
|
|
397
|
+
## Development
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
.venv/bin/pip install -e ".[dev]"
|
|
401
|
+
.venv/bin/python -m pytest
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The design decisions behind the analysis and diff layers are written up in
|
|
405
|
+
[docs/design.md](docs/design.md).
|
|
406
|
+
|
|
407
|
+
## Licence
|
|
408
|
+
|
|
409
|
+
Apache 2.0. See [LICENSE](LICENSE).
|