Skip to content

Commit 7a24afc

Browse files
authored
Mark an unserved API as served (#268)
It's possible that after marking an API as unserved and doing a release, there will be a need to follow-up that release with a variation of it that does serve that API. And maybe the commit that did the "unserve" step is buried under other things and is not easily reverted. In that case, use the `re-serve.py` tool. Signed-off-by: Dean Roehrich <dean.roehrich@hpe.com>
1 parent a9bc523 commit 7a24afc

3 files changed

Lines changed: 232 additions & 4 deletions

File tree

tools/crd-bumper/pkg/git_cli.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 Hewlett Packard Enterprise Development LP
1+
# Copyright 2024-2025 Hewlett Packard Enterprise Development LP
22
# Other additional copyright holders may be indicated within.
33
#
44
# The entirety of this work is licensed under the Apache License,
@@ -31,13 +31,16 @@ def __init__(self, dryrun, nocommit):
3131
nocommit = True
3232
self._nocommit = nocommit
3333

34-
def checkout_branch(self, branch):
34+
def checkout_branch(self, branch, create_branch=True):
3535
"""Checkout a named branch."""
3636

3737
if branch in ["main", "master", "releases/v0"]:
3838
raise ValueError("Branch name must not be main, master, or releases/v0")
3939
self.verify_clean()
40-
cmd = f"git checkout -b {branch}"
40+
args = ""
41+
if create_branch:
42+
args = "-b"
43+
cmd = f"git checkout {args} {branch}"
4144
if self._dryrun:
4245
print(f"Dryrun: {cmd}")
4346
else:

tools/crd-bumper/pkg/unserve.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 Hewlett Packard Enterprise Development LP
1+
# Copyright 2024-2025 Hewlett Packard Enterprise Development LP
22
# Other additional copyright holders may be indicated within.
33
#
44
# The entirety of this work is licensed under the Apache License,
@@ -128,3 +128,79 @@ def commit(self, git, stage):
128128
pass.
129129
"""
130130
git.commit_stage(stage, msg)
131+
132+
133+
class ReServe(Unserve):
134+
"""Tools to mark an API as served, after it has been marked as unserved."""
135+
136+
def __init__(self, dryrun, project, spoke_ver, preferred_alias):
137+
super().__init__(dryrun, project, spoke_ver, preferred_alias)
138+
self.in_re_serve = True
139+
140+
def set_served(self):
141+
"""
142+
Remove the kubebuilder:unservedversion marker in the specified spoke API,
143+
for any Kind that has it.
144+
"""
145+
146+
kinds = self._project.kinds(self._spoke_ver)
147+
for kind in kinds:
148+
fname = f"api/{self._spoke_ver}/{kind.lower()}_types.go"
149+
if os.path.isfile(fname):
150+
fu = FileUtil(self._dryrun, fname)
151+
line = fu.find_in_file("+kubebuilder:unservedversion")
152+
if not line:
153+
continue
154+
if fu.delete_from_file(line):
155+
fu.store()
156+
157+
def modify_conversion_webhook_suite_test(self):
158+
"""
159+
Modify the suite test that exercises the conversion webhook.
160+
161+
Update the tests for the specified spoke API.
162+
"""
163+
164+
conv_go = "internal/controller/conversion_test.go"
165+
if not os.path.isfile(conv_go):
166+
print(f"NOTE: Unable to find {conv_go}!")
167+
return
168+
169+
fu = FileUtil(self._dryrun, conv_go)
170+
171+
# Pattern to find the "PIt()" method so we can change it to "It()".
172+
pit_pat = r"^(\s+)PIt(\(.*)"
173+
it_pat = r"^(\s+)It(\(.*)"
174+
kinds = self._project.kinds(self._spoke_ver)
175+
for kind in kinds:
176+
spec = fu.find_in_file(
177+
f"""PIt("reads {kind} resource via hub and via spoke {self._spoke_ver}", func()"""
178+
)
179+
if spec is not None:
180+
newspec = spec
181+
m = re.search(pit_pat, spec)
182+
if m is not None:
183+
newspec = f"{m.group(1)}It{m.group(2)}"
184+
fu.replace_in_file(spec, newspec)
185+
186+
spec = fu.find_in_file(
187+
f"""It("is unable to read {kind} resource via spoke {self._spoke_ver}", func()"""
188+
)
189+
if spec is not None:
190+
newspec = spec
191+
m = re.search(it_pat, spec)
192+
if m is not None:
193+
newspec = f"{m.group(1)}PIt{m.group(2)}"
194+
fu.replace_in_file(spec, newspec)
195+
fu.store()
196+
197+
def commit(self, git, stage):
198+
"""Create a commit message."""
199+
200+
msg = f"""Remove the unserved marker from the {self._spoke_ver} API.
201+
202+
ACTION: Begin by running "make vet". Repair any issues that it finds.
203+
Then run "make test" and continue repairing issues until the tests
204+
pass.
205+
"""
206+
git.commit_stage(stage, msg)

tools/crd-bumper/re-serve.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright 2025 Hewlett Packard Enterprise Development LP
4+
# Other additional copyright holders may be indicated within.
5+
#
6+
# The entirety of this work is licensed under the Apache License,
7+
# Version 2.0 (the "License"); you may not use this file except
8+
# in compliance with the License.
9+
#
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
"""Mark the specified CRD version as served, after it had been marked as unserved."""
21+
22+
import argparse
23+
import sys
24+
25+
from pkg.conversion_gen import ConversionGen
26+
from pkg.git_cli import GitCLI
27+
from pkg.make_cmd import MakeCmd
28+
from pkg.project import Project
29+
from pkg.unserve import ReServe
30+
from pkg.hub_spoke_util import HubSpokeUtil
31+
32+
WORKING_DIR = "workingspace"
33+
BRANCH_SUFFIX = "re-serve"
34+
35+
PARSER = argparse.ArgumentParser()
36+
PARSER.add_argument(
37+
"--spoke-ver",
38+
type=str,
39+
required=True,
40+
help="Spoke API version to mark as served.",
41+
)
42+
PARSER.add_argument(
43+
"--repo",
44+
"-r",
45+
type=str,
46+
required=True,
47+
help="Git repository URL which has the Go code that provides the APIs.",
48+
)
49+
PARSER.add_argument(
50+
"--start-branch",
51+
type=str,
52+
help="Branch or tag to checkout first, if not master.",
53+
)
54+
PARSER.add_argument(
55+
"--branch",
56+
"-b",
57+
type=str,
58+
required=False,
59+
help=f"Branch name to create. Default is 'api-<spoke_ver>-{BRANCH_SUFFIX}'",
60+
)
61+
PARSER.add_argument(
62+
"--this-branch",
63+
action="store_true",
64+
dest="this_branch",
65+
help="Continue working in the current branch.",
66+
)
67+
PARSER.add_argument(
68+
"--dry-run",
69+
"-n",
70+
action="store_true",
71+
dest="dryrun",
72+
help="Dry run. Implies only one step.",
73+
)
74+
PARSER.add_argument(
75+
"--no-commit",
76+
"-C",
77+
action="store_true",
78+
dest="nocommit",
79+
help="Skip git-commit. Implies only one step.",
80+
)
81+
PARSER.add_argument(
82+
"--workdir",
83+
type=str,
84+
required=False,
85+
default=WORKING_DIR,
86+
help=f"Name for working directory. All repos will be cloned below this directory. Default: {WORKING_DIR}.",
87+
)
88+
89+
90+
def main():
91+
"""main"""
92+
93+
args = PARSER.parse_args()
94+
95+
gitcli = GitCLI(args.dryrun, args.nocommit)
96+
gitcli.clone_and_cd(args.repo, args.workdir)
97+
if args.start_branch is not None:
98+
gitcli.checkout_branch(args.start_branch, False)
99+
100+
project = Project(args.dryrun)
101+
102+
cgen = ConversionGen(args.dryrun, project, args.spoke_ver, None, None)
103+
makecmd = MakeCmd(args.dryrun, None, None, None)
104+
105+
if args.branch is None:
106+
args.branch = f"api-{args.spoke_ver}-{BRANCH_SUFFIX}"
107+
if args.this_branch:
108+
print("Continuing work in current branch")
109+
else:
110+
print(f"Creating branch {args.branch}")
111+
try:
112+
gitcli.checkout_branch(args.branch)
113+
except RuntimeError as ex:
114+
print(str(ex))
115+
print(
116+
"If you are continuing in an existing branch, then specify `--this-branch`."
117+
)
118+
sys.exit(1)
119+
120+
if not HubSpokeUtil.is_spoke(args.spoke_ver):
121+
print(f"API --spoke-ver {args.spoke_ver} is not a spoke.")
122+
sys.exit(1)
123+
124+
re_serve_api(args, project, makecmd, gitcli, cgen.preferred_api_alias())
125+
126+
127+
def re_serve_api(args, project, makecmd, git, preferred_api_alias):
128+
"""Mark the specified API version as served."""
129+
130+
re_serve = ReServe(args.dryrun, project, args.spoke_ver, preferred_api_alias)
131+
132+
print(f"Updating files to mark API {args.spoke_ver} as served.")
133+
134+
re_serve.set_served()
135+
re_serve.modify_conversion_webhook_suite_test()
136+
137+
makecmd.manifests()
138+
makecmd.generate()
139+
makecmd.generate_go_conversions()
140+
makecmd.fmt()
141+
makecmd.clean_bin()
142+
143+
re_serve.commit(git, "re-serve-api")
144+
145+
146+
if __name__ == "__main__":
147+
main()
148+
149+
sys.exit(0)

0 commit comments

Comments
 (0)