pepr 0.2.6 → 0.2.8
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.
- package/LICENSE +201 -0
- package/README.md +31 -77
- package/dist/package.json +1 -1
- package/dist/src/cli/init/templates/data.json +1 -1
- package/dist/src/cli/init/templates.js +2 -2
- package/dist/src/lib/controller.d.ts +5 -2
- package/dist/src/lib/controller.js +15 -1
- package/dist/src/lib/fetch.d.ts +7 -11
- package/dist/src/lib/fetch.js +9 -1
- package/dist/src/lib/module.d.ts +9 -2
- package/dist/src/lib/module.js +6 -5
- package/docs/.prettierrc.json +13 -0
- package/docs/actions.md +58 -0
- package/docs/capabilities.md +17 -0
- package/docs/cli.md +54 -0
- package/docs/module.md +89 -0
- package/package.json +1 -1
- /package/dist/src/cli/init/templates/{samples.json → capabilities/hello-pepr.samples.json} +0 -0
package/LICENSE
CHANGED
|
@@ -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.
|
package/README.md
CHANGED
|
@@ -10,7 +10,31 @@ Pepr is on a mission to save Kubernetes from the tyranny of YAML, intimidating g
|
|
|
10
10
|
- Write capabilities in TypeScript and bundle them for in-cluster processing in [NodeJS](https://nodejs.org/).
|
|
11
11
|
- React to cluster resources by mutating them, creating new Kubernetes resources, or performing arbitrary exec/API operations.
|
|
12
12
|
|
|
13
|
+
## Example Pepr CapabilityAction
|
|
14
|
+
|
|
15
|
+
This quick sample shows how to react to a ConfigMap being created or updated in the cluster. It adds a label and annotation to the ConfigMap and adds some data to the ConfigMap. Finally, it logs a message to the Pepr controller logs. For more see [CapabilityActions](./docs/actions.md).
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
When(a.ConfigMap)
|
|
19
|
+
.IsCreatedOrUpdated()
|
|
20
|
+
.InNamespace("pepr-demo")
|
|
21
|
+
.WithLabel("unicorn", "rainbow")
|
|
22
|
+
.Then(request => {
|
|
23
|
+
// Add a label and annotation to the ConfigMap
|
|
24
|
+
request
|
|
25
|
+
.SetLabel("pepr", "was-here")
|
|
26
|
+
.SetAnnotation("pepr.dev", "annotations-work-too");
|
|
27
|
+
|
|
28
|
+
// Add some data to the ConfigMap
|
|
29
|
+
request.Raw.data["doug-says"] = "Pepr is awesome!";
|
|
30
|
+
|
|
31
|
+
// Log a message to the Pepr controller logs
|
|
32
|
+
Log.info("A 🦄 ConfigMap was created or updated:");
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
13
36
|
## Wow too many words! tl;dr;
|
|
37
|
+
|
|
14
38
|
```bash
|
|
15
39
|
# Install Pepr (you can also use npx)
|
|
16
40
|
npm i -g pepr
|
|
@@ -25,7 +49,7 @@ npm run k3d-setup
|
|
|
25
49
|
|
|
26
50
|
# Start playing with Pepr now
|
|
27
51
|
pepr dev
|
|
28
|
-
kubectl apply -f capabilities/hello-pepr.samples.yaml
|
|
52
|
+
kubectl apply -f capabilities/hello-pepr.samples.yaml
|
|
29
53
|
|
|
30
54
|
# Be amazed and ⭐️ this repo
|
|
31
55
|
```
|
|
@@ -46,9 +70,13 @@ https://user-images.githubusercontent.com/882485/230895880-c5623077-f811-4870-bb
|
|
|
46
70
|
|
|
47
71
|
A module is the top-level collection of capabilities. It is a single, complete TypeScript project that includes an entry point to load all the configuration and capabilities, along with their CapabilityActions. During the Pepr build process, each module produces a unique Kubernetes MutatingWebhookConfiguration and ValidatingWebhookConfiguration, along with a secret containing the transpiled and compressed TypeScript code. The webhooks and secret are deployed into the Kubernetes cluster for processing by a common Pepr controller.
|
|
48
72
|
|
|
73
|
+
See [Module](./docs/module.md) for more details.
|
|
74
|
+
|
|
49
75
|
### Capability
|
|
50
76
|
|
|
51
|
-
A capability is set of related CapabilityActions that work together to achieve a specific transformation or operation on Kubernetes resources. Capabilities are user-defined and can include one or more CapabilityActions. They are defined within a Pepr module and can be used in both MutatingWebhookConfigurations and ValidatingWebhookConfigurations. A Capability can have a specific scope, such as mutating or validating, and can be reused in multiple Pepr modules.
|
|
77
|
+
A capability is set of related CapabilityActions that work together to achieve a specific transformation or operation on Kubernetes resources. Capabilities are user-defined and can include one or more CapabilityActions. They are defined within a Pepr module and can be used in both MutatingWebhookConfigurations and ValidatingWebhookConfigurations. A Capability can have a specific scope, such as mutating or validating, and can be reused in multiple Pepr modules.
|
|
78
|
+
|
|
79
|
+
See [Capabilities](./docs/capabilities.md) for more details.
|
|
52
80
|
|
|
53
81
|
### CapabilityAction
|
|
54
82
|
|
|
@@ -56,81 +84,7 @@ CapabilityAction is a discrete set of behaviors defined in a single function tha
|
|
|
56
84
|
|
|
57
85
|
For example, a CapabilityAction could be responsible for adding a specific label to a Kubernetes resource, or for modifying a specific field in a resource's metadata. CapabilityActions can be grouped together within a Capability to provide a more comprehensive set of operations that can be performed on Kubernetes resources.
|
|
58
86
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Define a new capability can be done via [VSCode Snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets): create a file `capabilities/your-capability-name.ts` and then type `create` in the file, a suggestion should prompt you to generate the content from there.
|
|
62
|
-
|
|
63
|
-
https://user-images.githubusercontent.com/882485/230897379-0bb57dff-9832-479f-8733-79e103703135.mp4
|
|
64
|
-
|
|
65
|
-
Alternatively, you can use the `pepr new <capability-name>` command to this:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
pepr new hello-world
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
This will create a new file called `capabilities/hello-world.ts` with the following contents:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import { Capability, a } from "pepr";
|
|
75
|
-
|
|
76
|
-
export const HelloWorld = new Capability({
|
|
77
|
-
// The unique name of the capability
|
|
78
|
-
name: "hello-world",
|
|
79
|
-
// A short description of the capability
|
|
80
|
-
description: "Type a useful description here 🦄",
|
|
81
|
-
// Limit what namespaces the capability can be used in (optional)
|
|
82
|
-
namespaces: [],
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Use the 'When' function to create a new Capability Action
|
|
86
|
-
const { When } = HelloWorld;
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Next, we need to define some actions to perform when specific Kubernetes resources are created, updated or deleted in the cluster. Pepr provides a set of actions that can be used to react to Kubernetes resources, such as `a.Pod`, `a.Deployment`, `a.CronJob`, etc. These actions can be chained together to create complex conditions, such as `a.Pod.IsCreated().InNamespace("default")` or `a.Deployment.IsUpdated().WithLabel("changeme=true")`. Below is an example of a capability that reacts to the creation of a Deployment resource:
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
When(a.Deployment)
|
|
93
|
-
.IsCreated()
|
|
94
|
-
.ThenSet({
|
|
95
|
-
spec: {
|
|
96
|
-
minReadySeconds: 3,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Here's a more complex example that reacts to the creation of a Deployment resource:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
When(a.Deployment)
|
|
105
|
-
.IsCreatedOrUpdated()
|
|
106
|
-
.InNamespace("ns1", "ns2")
|
|
107
|
-
.WithLabel("changeme", "true")
|
|
108
|
-
.Then(request => {
|
|
109
|
-
request
|
|
110
|
-
.SetLabel("mutated", "true")
|
|
111
|
-
.SetLabel("test", "thing")
|
|
112
|
-
.SetAnnotation("test2", "thing")
|
|
113
|
-
.RemoveLabel("test3");
|
|
114
|
-
|
|
115
|
-
if (request.HasLabel("test")) {
|
|
116
|
-
request.SetLabel("test5", "thing");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const { spec } = request.Raw;
|
|
120
|
-
spec.strategy.type = "Recreate";
|
|
121
|
-
spec.minReadySeconds = 3;
|
|
122
|
-
|
|
123
|
-
if (request.PermitSideEffects) {
|
|
124
|
-
// Do side-effect inducing things
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Now you can build and bundle your capability:
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
pepr build hello-world
|
|
133
|
-
```
|
|
87
|
+
See [CapabilityActions](./docs/actions.md) for more details.
|
|
134
88
|
|
|
135
89
|
## Logical Pepr Flow
|
|
136
90
|
|
package/dist/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "gitignore": "# Ignore node_modules and Pepr build artifacts\nnode_modules\ndist\ninsecure*\n", "readme": "# Pepr Module\n\nThis is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a Kubernetes transformation system\nwritten in Typescript.\n\nThe `capabilities` directory contains all the capabilities for this module. By default,\na capability is a single typescript file in the format of `capability-name.ts` that is\nimported in the root `pepr.ts` file as `import { HelloPepr } from \"./capabilities/hello-pepr\";`.\nBecause this is typescript, you can organize this however you choose, e.g. creating a sub-folder\nper-capability or common logic in shared files or folders.\n\nExample Structure:\n\n```\nModule Root\n├── package.json\n├── pepr.ts\n└── capabilities\n ├── example-one.ts\n ├── example-three.ts\n └── example-two.ts\n```\n", "peprTS": "import { PeprModule } from \"pepr\";\nimport { HelloPepr } from \"./capabilities/hello-pepr\";\nimport cfg from \"./package.json\";\n\n/**\n * This is the main entrypoint for the Pepr module. It is the file that is run when the module is started.\n * This is where you register your configurations and capabilities with the module.\n */\nnew PeprModule(cfg, [\n // \"HelloPepr\" is a demo capability that is included with Pepr. You can remove it if you want.\n HelloPepr,\n\n // Your additional capabilities go here\n]);\n", "helloPeprTS": "import { Capability, PeprRequest, RegisterKind, a, fetch } from \"pepr\";\n\n/**\n * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.\n * To test this capability you can run `pepr dev` or `npm start` and then run the following command:\n * `kubectl apply -f capabilities/hello-pepr.samples.yaml`\n */\nexport const HelloPepr = new Capability({\n name: \"hello-pepr\",\n description: \"A simple example capability to show how things work.\",\n namespaces: [\"pepr-demo\"],\n});\n\n// Use the 'When' function to create a new Capability Action\nconst { When } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action removes the label `remove-me` when a Namespace is created.\n * Note we don't need to specify the namespace here, because we've already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Then(ns => ns.RemoveLabel(\"remove-me\"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single Capability Action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-1\")\n .Then(request =>\n request\n .SetLabel(\"pepr\", \"was-here\")\n .SetAnnotation(\"pepr.dev\", \"annotations-work-too\")\n );\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action does the exact same changes for example-2, except this time it uses\n * the `.ThenSet()` feature. You can stack multiple `.Then()` calls, but only a single `.ThenSet()`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-2\")\n .ThenSet({\n metadata: {\n labels: {\n pepr: \"was-here\",\n },\n annotations: {\n \"pepr.dev\": \"annotations-work-too\",\n },\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel(\"change\", \"by-label\")\n .Then(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data[\"username\"] = username;\n cm.data[\"uid\"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation(\"pepr.dev\", \"making-waves\");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action show how you can use the `Then()` function to make multiple changes to the\n * same object from different functions. This is useful if you want to keep your Capability Actions\n * small and focused on a single task, or if you want to reuse the same function in multiple\n * Capability Actions.\n *\n * Note that the order of the `.Then()` calls matters. The first call will be executed first,\n * then the second, and so on. Also note the functions are not called until the Capability Action\n * is triggered.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-4\")\n .Then(cm => cm.SetLabel(\"pepr.dev/first\", \"true\"))\n .Then(addSecond)\n .Then(addThird);\n\n//This function uses the complete type definition, but is not required.\nfunction addSecond(cm: PeprRequest<a.ConfigMap>) {\n cm.SetLabel(\"pepr.dev/second\", \"true\");\n}\n\n// This function has no type definition, so you won't have intellisense in the function body.\nfunction addThird(cm) {\n cm.SetLabel(\"pepr.dev/third\", \"true\");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any Capability Action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>(\"https://api.chucknorris.io/jokes/random?category=dev\");\n * const joke = await fetch(\"https://api.chucknorris.io/jokes/random?category=dev\") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch(\"https://api.chucknorris.io/jokes/random?category=dev\")\n * ```\n */\ninterface TheChuckNorrisJoke {\n icon_url: string;\n id: string;\n url: string;\n value: string;\n}\n\nWhen(a.ConfigMap)\n .IsCreated()\n .WithLabel(\"chuck-norris\")\n .Then(async change => {\n const response = await fetch<TheChuckNorrisJoke>(\n \"https://api.chucknorris.io/jokes/random?category=dev\"\n );\n\n if (response.ok) {\n // Add the Chuck Norris joke to the configmap\n change.Raw.data[\"chuck-says\"] = response.data.value;\n }\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the Capability Action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need ot wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: \"pepr.dev\",\n version: \"v1\",\n kind: \"Unicorn\",\n})\n .IsCreated()\n .WithName(\"example-1\")\n .ThenSet({\n spec: {\n message: \"Hello Pepr without type data!\",\n counter: Math.random(),\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the Capability Action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all Capability Actions, but we are putting it here for demonstration purposes.\n *\n * You will need ot wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = \"Hello Pepr!\";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: \"pepr.dev\",\n version: \"v1\",\n kind: \"Unicorn\",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName(\"example-2\")\n .ThenSet({\n spec: {\n message: \"Hello Pepr now with type data!\",\n counter: Math.random(),\n },\n });\n" }
|
|
1
|
+
{ "gitignore": "# Ignore node_modules and Pepr build artifacts\nnode_modules\ndist\ninsecure*\n", "readme": "# Pepr Module\n\nThis is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a Kubernetes transformation system\nwritten in Typescript.\n\nThe `capabilities` directory contains all the capabilities for this module. By default,\na capability is a single typescript file in the format of `capability-name.ts` that is\nimported in the root `pepr.ts` file as `import { HelloPepr } from \"./capabilities/hello-pepr\";`.\nBecause this is typescript, you can organize this however you choose, e.g. creating a sub-folder\nper-capability or common logic in shared files or folders.\n\nExample Structure:\n\n```\nModule Root\n├── package.json\n├── pepr.ts\n└── capabilities\n ├── example-one.ts\n ├── example-three.ts\n └── example-two.ts\n```\n", "peprTS": "import { Log, PeprModule } from \"pepr\";\nimport { HelloPepr } from \"./capabilities/hello-pepr\";\n// cfg loads your pepr configuration from package.json\nimport cfg from \"./package.json\";\n\n/**\n * This is the main entrypoint for this Pepr module. It is run when the module is started.\n * This is where you register your Pepr configurations and capabilities.\n */\nnew PeprModule(\n cfg,\n [\n // \"HelloPepr\" is a demo capability that is included with Pepr. Comment or delete the line below to remove it.\n HelloPepr,\n\n // Your additional capabilities go here\n ],\n {\n // Any actions you want to perform before the request is processed, including modifying the request.\n // Comment out or delete the line below to remove the default beforeHook.\n beforeHook: req => Log.debug(`beforeHook: ${req.uid}`),\n\n // Any actions you want to perform after the request is processed, including modifying the response.\n // Comment out or delete the line below to remove the default afterHook.\n afterHook: res => Log.debug(`afterHook: ${res.uid}`),\n }\n);\n", "helloPeprTS": "import {\n Capability,\n PeprRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n} from \"pepr\";\n\n/**\n * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.\n * To test this capability you can run `pepr dev` or `npm start` and then run the following command:\n * `kubectl apply -f capabilities/hello-pepr.samples.yaml`\n */\nexport const HelloPepr = new Capability({\n name: \"hello-pepr\",\n description: \"A simple example capability to show how things work.\",\n namespaces: [\"pepr-demo\"],\n});\n\n// Use the 'When' function to create a new Capability Action\nconst { When } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action removes the label `remove-me` when a Namespace is created.\n * Note we don't need to specify the namespace here, because we've already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Then(ns => ns.RemoveLabel(\"remove-me\"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single Capability Action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-1\")\n .Then(request =>\n request\n .SetLabel(\"pepr\", \"was-here\")\n .SetAnnotation(\"pepr.dev\", \"annotations-work-too\")\n );\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action does the exact same changes for example-2, except this time it uses\n * the `.ThenSet()` feature. You can stack multiple `.Then()` calls, but only a single `.ThenSet()`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-2\")\n .ThenSet({\n metadata: {\n labels: {\n pepr: \"was-here\",\n },\n annotations: {\n \"pepr.dev\": \"annotations-work-too\",\n },\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel(\"change\", \"by-label\")\n .Then(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data[\"username\"] = username;\n cm.data[\"uid\"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation(\"pepr.dev\", \"making-waves\");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action show how you can use the `Then()` function to make multiple changes to the\n * same object from different functions. This is useful if you want to keep your Capability Actions\n * small and focused on a single task, or if you want to reuse the same function in multiple\n * Capability Actions.\n *\n * Note that the order of the `.Then()` calls matters. The first call will be executed first,\n * then the second, and so on. Also note the functions are not called until the Capability Action\n * is triggered.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName(\"example-4\")\n .Then(cm => cm.SetLabel(\"pepr.dev/first\", \"true\"))\n .Then(addSecond)\n .Then(addThird);\n\n//This function uses the complete type definition, but is not required.\nfunction addSecond(cm: PeprRequest<a.ConfigMap>) {\n cm.SetLabel(\"pepr.dev/second\", \"true\");\n}\n\n// This function has no type definition, so you won't have intellisense in the function body.\nfunction addThird(cm) {\n cm.SetLabel(\"pepr.dev/third\", \"true\");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any Capability Action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>(\"https://api.chucknorris.io/jokes/random?category=dev\");\n * const joke = await fetch(\"https://api.chucknorris.io/jokes/random?category=dev\") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch(\"https://api.chucknorris.io/jokes/random?category=dev\")\n * ```\n */\ninterface TheChuckNorrisJoke {\n icon_url: string;\n id: string;\n url: string;\n value: string;\n}\n\nWhen(a.ConfigMap)\n .IsCreated()\n .WithLabel(\"chuck-norris\")\n .Then(async change => {\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(\n \"https://api.chucknorris.io/jokes/random?category=dev\"\n );\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n // Add the Chuck Norris joke to the configmap\n change.Raw.data[\"chuck-says\"] = response.data.value;\n return;\n }\n\n // You can also assert on different HTTP response codes\n if (response.status === fetchStatus.NOT_FOUND) {\n // Do something else\n return;\n }\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the Capability Action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need ot wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: \"pepr.dev\",\n version: \"v1\",\n kind: \"Unicorn\",\n})\n .IsCreated()\n .WithName(\"example-1\")\n .ThenSet({\n spec: {\n message: \"Hello Pepr without type data!\",\n counter: Math.random(),\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the Capability Action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all Capability Actions, but we are putting it here for demonstration purposes.\n *\n * You will need ot wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = \"Hello Pepr!\";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: \"pepr.dev\",\n version: \"v1\",\n kind: \"Unicorn\",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName(\"example-2\")\n .ThenSet({\n spec: {\n message: \"Hello Pepr now with type data!\",\n counter: Math.random(),\n },\n });\n" }
|
|
@@ -11,9 +11,9 @@ const util_1 = require("util");
|
|
|
11
11
|
const uuid_1 = require("uuid");
|
|
12
12
|
const package_json_1 = require("../../../package.json");
|
|
13
13
|
const _prettierrc_json_1 = __importDefault(require("./templates/.prettierrc.json"));
|
|
14
|
+
const hello_pepr_samples_json_1 = __importDefault(require("./templates/capabilities/hello-pepr.samples.json"));
|
|
14
15
|
const data_json_1 = __importDefault(require("./templates/data.json"));
|
|
15
16
|
const pepr_code_snippets_json_1 = __importDefault(require("./templates/pepr.code-snippets.json"));
|
|
16
|
-
const samples_json_1 = __importDefault(require("./templates/samples.json"));
|
|
17
17
|
const tsconfig_module_json_1 = __importDefault(require("./templates/tsconfig.module.json"));
|
|
18
18
|
const utils_1 = require("./utils");
|
|
19
19
|
function genPkgJSON(opts, pgkVerOverride) {
|
|
@@ -79,7 +79,7 @@ exports.gitIgnore = {
|
|
|
79
79
|
};
|
|
80
80
|
exports.samplesYaml = {
|
|
81
81
|
path: "hello-pepr.samples.yaml",
|
|
82
|
-
data:
|
|
82
|
+
data: hello_pepr_samples_json_1.default.map(r => (0, client_node_1.dumpYaml)(r, { noRefs: true })).join("---\n"),
|
|
83
83
|
};
|
|
84
84
|
exports.snippet = {
|
|
85
85
|
path: "pepr.code-snippets",
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { ModuleConfig } from "./types";
|
|
2
1
|
import { Capability } from "./capability";
|
|
2
|
+
import { Request, Response } from "./k8s/types";
|
|
3
|
+
import { ModuleConfig } from "./types";
|
|
3
4
|
export declare class Controller {
|
|
4
5
|
private readonly config;
|
|
5
6
|
private readonly capabilities;
|
|
7
|
+
private readonly beforeHook?;
|
|
8
|
+
private readonly afterHook?;
|
|
6
9
|
private readonly app;
|
|
7
10
|
private running;
|
|
8
|
-
constructor(config: ModuleConfig, capabilities: Capability[]);
|
|
11
|
+
constructor(config: ModuleConfig, capabilities: Capability[], beforeHook?: (req: Request) => void, afterHook?: (res: Response) => void);
|
|
9
12
|
/** Start the webhook server */
|
|
10
13
|
startServer: (port: number) => void;
|
|
11
14
|
private logger;
|
|
@@ -16,9 +16,11 @@ const options = {
|
|
|
16
16
|
cert: fs_1.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
|
|
17
17
|
};
|
|
18
18
|
class Controller {
|
|
19
|
-
constructor(config, capabilities) {
|
|
19
|
+
constructor(config, capabilities, beforeHook, afterHook) {
|
|
20
20
|
this.config = config;
|
|
21
21
|
this.capabilities = capabilities;
|
|
22
|
+
this.beforeHook = beforeHook;
|
|
23
|
+
this.afterHook = afterHook;
|
|
22
24
|
this.app = (0, express_1.default)();
|
|
23
25
|
this.running = false;
|
|
24
26
|
/** Start the webhook server */
|
|
@@ -54,12 +56,18 @@ class Controller {
|
|
|
54
56
|
};
|
|
55
57
|
this.mutate = async (req, res) => {
|
|
56
58
|
try {
|
|
59
|
+
// Run the before hook if it exists
|
|
60
|
+
this.beforeHook && this.beforeHook(req.body?.request || {});
|
|
57
61
|
const name = req.body?.request?.name || "";
|
|
58
62
|
const namespace = req.body?.request?.namespace || "";
|
|
59
63
|
const gvk = req.body?.request?.kind || { group: "", version: "", kind: "" };
|
|
60
64
|
console.log(`Mutate request: ${gvk.group}/${gvk.version}/${gvk.kind}`);
|
|
61
65
|
name && console.log(` ${namespace}/${name}\n`);
|
|
66
|
+
// Process the request
|
|
62
67
|
const response = await (0, processor_1.processor)(this.config, this.capabilities, req.body.request);
|
|
68
|
+
// Run the after hook if it exists
|
|
69
|
+
this.afterHook && this.afterHook(response);
|
|
70
|
+
// Log the response
|
|
63
71
|
console.debug(response);
|
|
64
72
|
// Send a no prob bob response
|
|
65
73
|
res.send({
|
|
@@ -81,6 +89,12 @@ class Controller {
|
|
|
81
89
|
this.app.get("/healthz", this.healthz);
|
|
82
90
|
// Mutate endpoint
|
|
83
91
|
this.app.post("/mutate", this.mutate);
|
|
92
|
+
if (beforeHook) {
|
|
93
|
+
console.info(`Using beforeHook: ${beforeHook}`);
|
|
94
|
+
}
|
|
95
|
+
if (afterHook) {
|
|
96
|
+
console.info(`Using afterHook: ${afterHook}`);
|
|
97
|
+
}
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
exports.Controller = Controller;
|
package/dist/src/lib/fetch.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import f, { RequestInfo, RequestInit } from "node-fetch";
|
|
3
3
|
export { f as fetchRaw };
|
|
4
|
+
export type FetchResponse<T> = {
|
|
5
|
+
data: T;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
status: number;
|
|
8
|
+
statusText: string;
|
|
9
|
+
};
|
|
4
10
|
/**
|
|
5
11
|
* Perform an async HTTP call and return the parsed JSON response, optionally
|
|
6
12
|
* as a specific type.
|
|
@@ -14,14 +20,4 @@ export { f as fetchRaw };
|
|
|
14
20
|
* @param init Additional options for the request
|
|
15
21
|
* @returns
|
|
16
22
|
*/
|
|
17
|
-
export declare function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<
|
|
18
|
-
data: T;
|
|
19
|
-
ok: boolean;
|
|
20
|
-
status: number;
|
|
21
|
-
statusText: string;
|
|
22
|
-
} | {
|
|
23
|
-
data: any;
|
|
24
|
-
ok: boolean;
|
|
25
|
-
status: any;
|
|
26
|
-
statusText: any;
|
|
27
|
-
}>;
|
|
23
|
+
export declare function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>>;
|
package/dist/src/lib/fetch.js
CHANGED
|
@@ -27,9 +27,17 @@ async function fetch(url, init) {
|
|
|
27
27
|
try {
|
|
28
28
|
logger_1.default.debug(`Fetching ${url}`);
|
|
29
29
|
const resp = await (0, node_fetch_1.default)(url, init);
|
|
30
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
30
31
|
let data;
|
|
31
32
|
if (resp.ok) {
|
|
32
|
-
|
|
33
|
+
// Parse the response as JSON if the content type is JSON
|
|
34
|
+
if (contentType.includes("application/json")) {
|
|
35
|
+
data = await resp.json();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Otherwise, return however the response was read
|
|
39
|
+
data = (await resp.text());
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
return {
|
|
35
43
|
data,
|
package/dist/src/lib/module.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { Capability } from "./capability";
|
|
2
|
+
import { Request, Response } from "./k8s/types";
|
|
2
3
|
import { ModuleConfig } from "./types";
|
|
3
4
|
export type PackageJSON = {
|
|
4
5
|
description: string;
|
|
5
6
|
pepr: ModuleConfig;
|
|
6
7
|
};
|
|
8
|
+
export type PeprModuleOptions = {
|
|
9
|
+
deferStart?: boolean;
|
|
10
|
+
/** A user-defined callback to pre-process or intercept a Pepr request from K8s immediately before it is processed */
|
|
11
|
+
beforeHook?: (req: Request) => void;
|
|
12
|
+
/** A user-defined callback to post-process or intercept a Pepr response just before it is returned to K8s */
|
|
13
|
+
afterHook?: (res: Response) => void;
|
|
14
|
+
};
|
|
7
15
|
export declare class PeprModule {
|
|
8
|
-
private readonly _deferStart;
|
|
9
16
|
private _controller;
|
|
10
17
|
/**
|
|
11
18
|
* Create a new Pepr runtime
|
|
@@ -14,7 +21,7 @@ export declare class PeprModule {
|
|
|
14
21
|
* @param capabilities The capabilities to be loaded into the Pepr runtime
|
|
15
22
|
* @param _deferStart (optional) If set to `true`, the Pepr runtime will not be started automatically. This can be used to start the Pepr runtime manually with `start()`.
|
|
16
23
|
*/
|
|
17
|
-
constructor({ description, pepr }: PackageJSON, capabilities?: Capability[],
|
|
24
|
+
constructor({ description, pepr }: PackageJSON, capabilities?: Capability[], opts?: PeprModuleOptions);
|
|
18
25
|
/**
|
|
19
26
|
* Start the Pepr runtime manually.
|
|
20
27
|
* Normally this is called automatically when the Pepr module is instantiated, but can be called manually if `deferStart` is set to `true` in the constructor.
|
package/dist/src/lib/module.js
CHANGED
|
@@ -20,14 +20,15 @@ class PeprModule {
|
|
|
20
20
|
* @param capabilities The capabilities to be loaded into the Pepr runtime
|
|
21
21
|
* @param _deferStart (optional) If set to `true`, the Pepr runtime will not be started automatically. This can be used to start the Pepr runtime manually with `start()`.
|
|
22
22
|
*/
|
|
23
|
-
constructor({ description, pepr }, capabilities = [],
|
|
24
|
-
this._deferStart = _deferStart;
|
|
23
|
+
constructor({ description, pepr }, capabilities = [], opts = {}) {
|
|
25
24
|
const config = ramda_1.default.mergeDeepWith(ramda_1.default.concat, pepr, alwaysIgnore);
|
|
26
25
|
config.description = description;
|
|
27
|
-
this._controller = new controller_1.Controller(config, capabilities);
|
|
28
|
-
if
|
|
29
|
-
|
|
26
|
+
this._controller = new controller_1.Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
27
|
+
// Stop processing if deferStart is set to true
|
|
28
|
+
if (opts.deferStart) {
|
|
29
|
+
return;
|
|
30
30
|
}
|
|
31
|
+
this.start();
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Start the Pepr runtime manually.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"arrowParens": "avoid",
|
|
3
|
+
"bracketSameLine": false,
|
|
4
|
+
"bracketSpacing": true,
|
|
5
|
+
"embeddedLanguageFormatting": "auto",
|
|
6
|
+
"insertPragma": false,
|
|
7
|
+
"printWidth": 80,
|
|
8
|
+
"quoteProps": "as-needed",
|
|
9
|
+
"requirePragma": false,
|
|
10
|
+
"semi": true,
|
|
11
|
+
"tabWidth": 2,
|
|
12
|
+
"useTabs": false
|
|
13
|
+
}
|
package/docs/actions.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CapabilityActions
|
|
2
|
+
|
|
3
|
+
A CapabilityAction is a discrete set of behaviors defined in a single function that acts on a given Kubernetes GroupVersionKind (GVK) passed in from Kubernetes. CapabilityActions are the atomic operations that are performed on Kubernetes resources by Pepr.
|
|
4
|
+
|
|
5
|
+
For example, a CapabilityAction could be responsible for adding a specific label to a Kubernetes resource, or for modifying a specific field in a resource's metadata. CapabilityActions can be grouped together within a Capability to provide a more comprehensive set of operations that can be performed on Kubernetes resources.
|
|
6
|
+
|
|
7
|
+
Let's look at some example CapabilityActions that are included in the `HelloPepr` capability that is created for you when you [`pepr init`](./cli.md#pepr-init):
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
In this first example, Pepr is adding a label and annotation to a ConfigMap with tne name `example-1` when it is created. Comments are added to each line to explain in more detail what is happening.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// When(a.<Kind>) tells Pepr what K8s GroupVersionKind (GVK) this CapabilityAction should act on.
|
|
15
|
+
When(a.ConfigMap)
|
|
16
|
+
// Next we tell Pepr to only act on new ConfigMaps that are created.
|
|
17
|
+
.IsCreated()
|
|
18
|
+
// Then we tell Pepr to only act on ConfigMaps with the name "example-1".
|
|
19
|
+
.WithName("example-1")
|
|
20
|
+
// Then() is where we define the actual behavior of this CapabilityAction.
|
|
21
|
+
.Then(request => {
|
|
22
|
+
// The request object is a wrapper around the K8s resource that Pepr is acting on.
|
|
23
|
+
request
|
|
24
|
+
// Here we are adding a label to the ConfigMap.
|
|
25
|
+
.SetLabel("pepr", "was-here")
|
|
26
|
+
// And here we are adding an annotation.
|
|
27
|
+
.SetAnnotation("pepr.dev", "annotations-work-too");
|
|
28
|
+
|
|
29
|
+
// Note that we are not returning anything here. This is because Pepr is tracking the changes in each CapabilityAction automatically.
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
This example is identical to the previous one, except we are acting on a different CongigMap name and using the `ThenSet()` shorthand to merge changes into the resource.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// Once again, we tell Pepr what K8s GVK this CapabilityAction should act on.
|
|
39
|
+
When(a.ConfigMap)
|
|
40
|
+
// Next we tell Pepr to only act on new ConfigMaps that are created.
|
|
41
|
+
.IsCreated()
|
|
42
|
+
// This time we are acting on a ConfigMap with the name "example-2".
|
|
43
|
+
.WithName("example-2")
|
|
44
|
+
// Instead of using Then(), we are using ThenSet() to merge changes into the resource without a function call.
|
|
45
|
+
.ThenSet({
|
|
46
|
+
// Using Typescript, we will get intellisense for the ConfigMap object and immediate type-validation for the values we are setting.
|
|
47
|
+
metadata: {
|
|
48
|
+
labels: {
|
|
49
|
+
pepr: "was-here",
|
|
50
|
+
},
|
|
51
|
+
annotations: {
|
|
52
|
+
"pepr.dev": "annotations-work-too",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
There are many more examples in the `HelloPepr` capability that you can use as a reference when creating your own CapabilityActions. Note that each time you run [`pepr update`](./cli.md#pepr-update), Pepr will automatically update the `HelloPepr` capability with the latest examples and best practices for you to reference and test directly in your Pepr Module.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Capabilities
|
|
2
|
+
|
|
3
|
+
A capability is set of related [CapabilityActions](./actions.md) that work together to achieve a specific transformation or operation on Kubernetes resources. Capabilities are user-defined and can include one or more CapabilityActions. They are defined within a Pepr module and can be used in both MutatingWebhookConfigurations and ValidatingWebhookConfigurations. A Capability can have a specific scope, such as mutating or validating, and can be reused in multiple Pepr modules.
|
|
4
|
+
|
|
5
|
+
When you [`pepr init`](./cli.md#pepr-init), a `capabilities` directory is created for you. This directory is where you will define your capabilities. You can create as many capabilities as you need, and each capability can contain one or more CapabilityActions. Pepr also automatically creates a `HelloPepr` capability with a number of example CapabilityActions to help you get started.
|
|
6
|
+
|
|
7
|
+
## Creating a Capability
|
|
8
|
+
|
|
9
|
+
Define a new capability can be done via a [VSCode Snippet](https://code.visualstudio.com/docs/editor/userdefinedsnippets) generated during [`pepr init`](./cli.md#pepr-init).
|
|
10
|
+
|
|
11
|
+
1. Create a new file in the `capabilities` directory with the name of your capability. For example, `capabilities/my-capability.ts`.
|
|
12
|
+
|
|
13
|
+
1. Open the new file in VSCode and type `create` in the file. A suggestion should prompt you to generate the content from there.
|
|
14
|
+
|
|
15
|
+
https://user-images.githubusercontent.com/882485/230897379-0bb57dff-9832-479f-8733-79e103703135.mp4
|
|
16
|
+
|
|
17
|
+
_If you prefer not to use VSCode, you can also modify or copy the `HelloPepr` capability to meet your needs instead._
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Pepr CLI
|
|
2
|
+
|
|
3
|
+
## `pepr init`
|
|
4
|
+
|
|
5
|
+
Initialize a new Pepr Module.
|
|
6
|
+
|
|
7
|
+
**Options:**
|
|
8
|
+
|
|
9
|
+
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
|
10
|
+
- `--skip-post-init` - Skip npm install, git init and VSCode launch
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
## `pepr update`
|
|
14
|
+
|
|
15
|
+
Update the current Pepr Module to the latest SDK version and update the global Pepr CLI to the same version.
|
|
16
|
+
|
|
17
|
+
**Options:**
|
|
18
|
+
|
|
19
|
+
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
|
20
|
+
- `--skip-template-update` - Skip updating the template files
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## `pepr dev`
|
|
25
|
+
|
|
26
|
+
Connect a local cluster to a local version of the Pepr Controller to do real-time debugging of your module.
|
|
27
|
+
|
|
28
|
+
**Options:**
|
|
29
|
+
|
|
30
|
+
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
|
31
|
+
- `-h, --host [host]` - Host to listen on (default: "host.docker.internal")
|
|
32
|
+
- `--confirm` - Skip confirmation prompt
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## `pepr deploy`
|
|
37
|
+
|
|
38
|
+
Deploy the current module into a Kubernetes cluster, useful for CI systems. Not recommended for production use.
|
|
39
|
+
|
|
40
|
+
**Options:**
|
|
41
|
+
|
|
42
|
+
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
|
43
|
+
- `-i, --image [image]` - Override the image tag
|
|
44
|
+
- `--confirm` - Skip confirmation prompt
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## `pepr build`
|
|
49
|
+
|
|
50
|
+
Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module. This includes everything needed to deploy Pepr and the current module into production environments.
|
|
51
|
+
|
|
52
|
+
**Options:**
|
|
53
|
+
|
|
54
|
+
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
package/docs/module.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Pepr Module
|
|
2
|
+
|
|
3
|
+
Each Pepr Module is it's own Typescript project, produced by [`pepr init`](./cli.md#pepr-init). Typically a module is maintained by a unique group or system. For example, a module for internal [Zarf](https://zarf.dev/) mutations would be different from a module for [Big Bang](https://p1.dso.mil/products/big-bang). An important idea with modules is that they are _wholly independent of one another_. This means that 2 different modules can be on completely different versions of Pepr and any other dependencies; their only interaction is through the standard K8s interfaces like any other webhook or controller.
|
|
4
|
+
|
|
5
|
+
## Module development lifecycle
|
|
6
|
+
|
|
7
|
+
1. **Create the module**:
|
|
8
|
+
|
|
9
|
+
Use [`pepr init`](./cli.md#pepr-init) to generate a new module.
|
|
10
|
+
|
|
11
|
+
1. **Quickly validate system setup**:
|
|
12
|
+
|
|
13
|
+
Every new module includes a sample Pepr Capability called `HelloPepr`. By default,
|
|
14
|
+
this capability is deployed and monitoring the `pepr-demo` namespace. There is a sample
|
|
15
|
+
yaml also included you can use to see Pepr in your cluster. Here's the quick steps to do
|
|
16
|
+
that after `pepr init`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# cd to the newly-created Pepr module folder
|
|
20
|
+
cd my-module-name
|
|
21
|
+
|
|
22
|
+
# If you don't already have a local K8s cluster, you can set one up with k3d
|
|
23
|
+
npm run k3d-setup
|
|
24
|
+
|
|
25
|
+
# Launch pepr dev mode (npm start or pepr dev)
|
|
26
|
+
pepr dev
|
|
27
|
+
|
|
28
|
+
# From another terminal, apply the sample yaml
|
|
29
|
+
kubectl apply -f capabilities/hello-pepr.samples.yaml
|
|
30
|
+
|
|
31
|
+
# Verify the configmaps were transformed using kubectl, k9s or another tool
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
1. **Create your custom Pepr Capabilities**
|
|
35
|
+
|
|
36
|
+
Now that you have confirmed Pepr is working, you can now create new [capabilities](./capabilities.md). You'll also want to disable the `HelloPepr` capability in your module (`pepr.ts`) before pushing to production. You can disable by commenting out or deleting the `HelloPepr` variable below:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
new PeprModule(cfg, [
|
|
40
|
+
// Remove or comment the line below to disable the HelloPepr capability
|
|
41
|
+
HelloPepr,
|
|
42
|
+
|
|
43
|
+
// Your additional capabilities go here
|
|
44
|
+
]);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
_Note: if you also delete the `capabilities/hello-pepr.ts` file, it will be added again on the next [`pepr update`](./cli.md#pepr-update) so you have the latest examples usages from the Pepr SDK. Therefore, it is sufficient to remove the entry from your `pepr.ts` module
|
|
48
|
+
config._
|
|
49
|
+
|
|
50
|
+
1. **Build and deploy the Pepr Module**
|
|
51
|
+
|
|
52
|
+
Most of the time, you'll likely be iterating on a module with `perp dev` for real-time feedback and validation Once you are ready to move beyond the local dev environment, Pepr provides deployment and build tools you can use.
|
|
53
|
+
|
|
54
|
+
`pepr deploy` - you can use this command to build your module and deploy it into any K8s cluster your current `kubecontext` has access to. This setup is ideal for CI systems during testing, but is not recommended for production use. See [`pepr deploy`](./cli.md#pepr-deploy) for more info.
|
|
55
|
+
|
|
56
|
+
## Advanced Module Configuration
|
|
57
|
+
|
|
58
|
+
By default, when you run `pepr init`, the module is not configured with any additional options. Currently, there are 3 options you can configure:
|
|
59
|
+
|
|
60
|
+
- `deferStart` - if set to `true`, the module will not start automatically. You will need to call `start()` manually. This is useful if you want to do some additional setup before the module controller starts. You can also use this to change the default port that the controller listens on.
|
|
61
|
+
|
|
62
|
+
- `beforeHook` - an optional callback that will be called before every request is processed. This is useful if you want to do some additional logging or validation before the request is processed.
|
|
63
|
+
|
|
64
|
+
- `afterHook` - an optional callback that will be called after every request is processed. This is useful if you want to do some additional logging or validation after the request is processed.
|
|
65
|
+
|
|
66
|
+
You can configure each of these by modifying the `pepr.ts` file in your module. Here's an example of how you would configure each of these options:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const module = new PeprModule(
|
|
70
|
+
cfg,
|
|
71
|
+
[
|
|
72
|
+
// Your capabilities go here
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
deferStart: true,
|
|
76
|
+
|
|
77
|
+
beforeHook: req => {
|
|
78
|
+
// Any actions you want to perform before the request is processed, including modifying the request.
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
afterHook: res => {
|
|
82
|
+
// Any actions you want to perform after the request is processed, including modifying the response.
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Do any additional setup before starting the controller
|
|
88
|
+
module.start();
|
|
89
|
+
```
|
package/package.json
CHANGED
|
File without changes
|