Skip to content

Commit f3a8a44

Browse files
Generated best practices file
1 parent 12db9d1 commit f3a8a44

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

best_practices.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
2+
<b>Pattern 1: When a feature needs extra operational visibility (CI/E2E workflows, controllers), explicitly add deterministic debug/diagnostic steps and wait for readiness (e.g., rollout status/wait) rather than relying on eventual config propagation or implicit restarts.
3+
</b>
4+
5+
Example code before:
6+
```
7+
# Patch config and assume it will be picked up immediately
8+
kubectl -n app patch configmap mycfg --type merge -p '{"data":{"loglevel":"debug"}}'
9+
# ...continue without restart/wait...
10+
```
11+
12+
Example code after:
13+
```
14+
set -euo pipefail
15+
kubectl -n app patch configmap mycfg --type merge -p '{"data":{"loglevel":"debug"}}'
16+
kubectl -n app rollout restart deployment/my-controller
17+
kubectl -n app rollout status deployment/my-controller --timeout=120s
18+
```
19+
20+
<details><summary>Examples for relevant past discussions:</summary>
21+
22+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2432#discussion_r2743633510
23+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2432#discussion_r2743637739
24+
</details>
25+
26+
27+
___
28+
29+
<b>Pattern 2: Avoid creating user-facing failures for non-critical follow-up operations (e.g., patching annotations/status updates) when the main action succeeded; instead, log the failure and add tests that assert the expected log/behavior rather than expecting an error return.
30+
</b>
31+
32+
Example code before:
33+
```
34+
if err := patchResource(obj); err != nil {
35+
return nil, fmt.Errorf("cannot patch resource: %w", err)
36+
}
37+
return obj, nil
38+
```
39+
40+
Example code after:
41+
```
42+
if err := patchResource(obj); err != nil {
43+
logger.Warnw("cannot patch resource", "err", err)
44+
// continue; main operation succeeded
45+
}
46+
return obj, nil
47+
```
48+
49+
<details><summary>Examples for relevant past discussions:</summary>
50+
51+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2338#discussion_r2570635760
52+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2587664872
53+
</details>
54+
55+
56+
___
57+
58+
<b>Pattern 3: Prefer structured/typed error handling over broad fallbacks: classify provider/API errors (e.g., state transition vs permission) to decide whether to retry, skip secondary actions like MR comments, or surface actionable messages to users.
59+
</b>
60+
61+
Example code before:
62+
```
63+
_, _, err := api.SetStatus(id, sha, opts)
64+
if err != nil {
65+
// always post a comment on failure
66+
_ = api.CreateComment(id, "failed to set status")
67+
}
68+
```
69+
70+
Example code after:
71+
```
72+
_, _, err := api.SetStatus(id, sha, opts)
73+
if err != nil {
74+
if errors.Is(err, ErrStatusAlreadySet) || strings.Contains(err.Error(), "Cannot transition status") {
75+
logger.Debugw("skipping comment; non-actionable status transition error", "err", err)
76+
return nil
77+
}
78+
logger.Warnw("failed to set status", "err", err)
79+
_ = api.CreateComment(id, "failed to set status; check token permissions")
80+
}
81+
```
82+
83+
<details><summary>Examples for relevant past discussions:</summary>
84+
85+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2587664872
86+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2680946184
87+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2341#discussion_r2684949199
88+
</details>
89+
90+
91+
___
92+
93+
<b>Pattern 4: In webhook parsing and templating, guard only the truly unsafe accesses: rely on SDK nil-safe getters where available, but add explicit nil checks for direct field/slice accesses (no getters) and handle nil/null payload bodies by treating them as empty objects.
94+
</b>
95+
96+
Example code before:
97+
```
98+
// may panic if PullRequest is nil
99+
for _, l := range evt.GetPullRequest().Labels {
100+
labels = append(labels, l.GetName())
101+
}
102+
// may fail if body is null
103+
_ = json.Unmarshal(bodyBytes, &m)
104+
```
105+
106+
Example code after:
107+
```
108+
pr := evt.GetPullRequest()
109+
if pr != nil {
110+
for _, l := range pr.Labels { // direct slice; safe because pr != nil
111+
labels = append(labels, l.GetName())
112+
}
113+
}
114+
if len(bodyBytes) == 0 || string(bodyBytes) == "null" {
115+
bodyBytes = []byte(`{}`)
116+
}
117+
_ = json.Unmarshal(bodyBytes, &m)
118+
```
119+
120+
<details><summary>Examples for relevant past discussions:</summary>
121+
122+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2348#discussion_r2610606932
123+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2348#discussion_r2610613693
124+
</details>
125+
126+
127+
___
128+
129+
<b>Pattern 5: Keep PRs tightly scoped: avoid mixing functional changes with refactors/deprecation cleanups in the same PR; if ancillary refactors are needed, split them into follow-up PRs/commits for clearer review and tracking.
130+
</b>
131+
132+
Example code before:
133+
```
134+
# PR includes new feature + large refactor + deprecation cleanup in one change set
135+
add_feature()
136+
refactor_unrelated_modules()
137+
remove_deprecated_paths()
138+
```
139+
140+
Example code after:
141+
```
142+
# PR 1: add_feature()
143+
# PR 2: refactor_unrelated_modules()
144+
# PR 3: remove_deprecated_paths()
145+
add_feature()
146+
```
147+
148+
<details><summary>Examples for relevant past discussions:</summary>
149+
150+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2290#discussion_r2432282457
151+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2317#discussion_r2517263653
152+
</details>
153+
154+
155+
___
156+
157+
<b>Pattern 6: Centralize and reuse common logic (fallback values, error variables, naming conventions) to avoid duplication and inconsistencies: use getters/helpers for defaults, reuse existing variables instead of introducing err2, and follow consistent local variable casing.
158+
</b>
159+
160+
Example code before:
161+
```
162+
func (c *Console) URL() string {
163+
if c.host == "" { return "https://fallback" }
164+
return "https://" + c.host
165+
}
166+
func (c *Console) DetailURL(ns, name string) string {
167+
if c.host == "" { return "https://fallback/..." } // duplicated fallback
168+
return fmt.Sprintf("https://%s/...", c.host)
169+
}
170+
if _, err2 := doThing(); err2 == nil { ... }
171+
OrgAndRepo := fmt.Sprintf("%s/%s", org, repo)
172+
```
173+
174+
Example code after:
175+
```
176+
func (c *Console) Host() string {
177+
if c.host == "" { return "openshift.url.is.not.configured" }
178+
return c.host
179+
}
180+
func (c *Console) URL() string { return "https://" + c.Host() }
181+
func (c *Console) DetailURL(ns, name string) string {
182+
return fmt.Sprintf("%s/k8s/ns/%s/.../%s", c.URL(), ns, name)
183+
}
184+
if _, err := doThing(); err == nil { ... }
185+
orgAndRepo := fmt.Sprintf("%s/%s", org, repo)
186+
```
187+
188+
<details><summary>Examples for relevant past discussions:</summary>
189+
190+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2288#discussion_r2424435108
191+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2340#discussion_r2609490729
192+
- https://github.com/openshift-pipelines/pipelines-as-code/pull/2317#discussion_r2547733683
193+
</details>
194+
195+
196+
___

0 commit comments

Comments
 (0)