Skip to content

Commit 3f3e47e

Browse files
committed
add use_chart_version config
- prefix version with `-0.dev.git.{n}.h{sha}` or `-existingPreRelease.git.{n}.h{sha}` - optionally get base version from Chart.version - reset only strips git suffix instead of clobbering with resetVersion
1 parent 5630fc8 commit 3f3e47e

File tree

6 files changed

+222
-69
lines changed

6 files changed

+222
-69
lines changed

README.md

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ information.
3939
1. If `tag` (like `0.10.0` or `0.10.0-beta.1`) contains `-`,
4040
indicating that it is a prerelease,
4141
the chartpress suffix will be added to the prerelease fields,
42-
e.g. `0.10.0-beta.1.0git.5.habc123`
43-
format will be used instead of a `tag-0git.n.h` format to be SemVer 2 compliant.
42+
e.g. `0.10.0-beta.1.git.5.habc123`
43+
format will be used instead of a `tag-0.dev.git.n.h` format to be SemVer 2 compliant.
4444
1. If `--long` is specified or not. If `--long` is specified, tagged commits
4545
will always be written out with the `n.h` part appended to it, looking something
46-
like `1.0.0-0git.0.habcd123`
46+
like `1.0.0-0.dev.git.0.habcd123`
47+
48+
When producing a development version (with `.git.n.hash` on the end),
49+
The _base_ version can come from one of two places,
50+
depending on your configuration.
51+
See [Controlling development version](#controlling-development-versions-in-chart.yaml) for more info.
4752

4853
### Examples chart versions and image tags
4954

@@ -52,13 +57,13 @@ order that could come from using chartpress.
5257

5358
```
5459
0.8.0
55-
0.8.0-0git.4.hasdf123
56-
0.8.0-0git.10.hsdfg234
60+
0.8.0-0.dev.git.4.hasdf123
61+
0.8.0-0.dev.git.10.hsdfg234
5762
0.9.0-beta.1
58-
0.9.0-beta.1.0git.1.hdfgh345
59-
0.9.0-beta.1.0git.5.hfghj456
63+
0.9.0-beta.1.git.1.hdfgh345
64+
0.9.0-beta.1.git.5.hfghj456
6065
0.9.0-beta.2
61-
0.9.0-beta.2.0git.1.hghjk567
66+
0.9.0-beta.2.git.1.hghjk567
6267
0.9.0-beta.3
6368
0.9.0
6469
```
@@ -170,6 +175,12 @@ charts:
170175
# --reset flag. It defaults to "0.0.1-set.by.chartpress". This is a valid
171176
# SemVer 2 version, which is required for a helm lint command to succeed.
172177
resetVersion: 1.2.3
178+
# Use the version in Chart.yaml for the base version,
179+
# instead of the latest tag from `git describe`
180+
# This gives you more control over development version tags
181+
# and lets you ensure prerelease tags are always sorted in the right order
182+
useChartVersion: true
183+
173184
# The git repo whose gh-pages contains the charts. This can be a local
174185
# path such as "." as well but if matching <organization>/<repo> will be
175186
# assumed to be a separate GitHub repository.
@@ -223,6 +234,83 @@ charts:
223234
- linux/arm64
224235
```
225236
237+
### Controlling development versions in Chart.yaml
238+
239+
Like some "package version in version control" tools,
240+
you don't need to manage versions at all in chartpress except by tagging commits.
241+
242+
However, relying only on tags results in "development versions" (versions published from commits after a release)
243+
having somewhat confusing prerelease versions.
244+
245+
After publishing e.g. `1.2.3`, the next version will be `1.2.3-0.dev.git.1.habc`.
246+
According to Semantic Versioning,
247+
this is a "pre release" (good, it should be excluded from default installation),
248+
but it means it comes _before_ 1.2.3 (wrong! it's 1 commit _newer_ than 1.2.3).
249+
This is because prereleases should be defined relative to the _next_ release,
250+
not the _last_ release. But git tags only store the _last_ release!
251+
252+
Chartpress 2.0 adds an option `chart.useChartVersion`,
253+
which changes the base version to use when setting the chart version.
254+
Instead of using the version of the last tag found via `git describe`,
255+
it uses the version found in Chart.yaml.
256+
257+
The main benefits of this are:
258+
259+
1. ensuring that published prerelease versions show up in the right order, and
260+
2. giving you control over the version of a prerelease chart (is it 2.0.0-0.dev or 1.3.1-0.dev?)
261+
262+
Instead of publishing the sequence:
263+
264+
- 1.2.3
265+
- 1.2.3-0.dev.git.1.habc (later than 1.2.3, but sorts _before_ 1.2.3!)
266+
267+
You can publish
268+
269+
- 1.2.3
270+
- 1.3.0-0.dev.git.1.habc (prerelease based on the _next_ version, not _last_)
271+
272+
where chartpress will use the version in your Chart.yaml as the base version,
273+
instead of the last tag on the branch.
274+
275+
This takes some extra configuration, and steps in your release process
276+
to update the version in your Chart.yaml.
277+
278+
1. You must set `useChartVersion: true` in your chartpress.yaml:
279+
280+
```yaml
281+
charts:
282+
- name: mychart
283+
useChartVersion: true
284+
```
285+
286+
2. You must control the version in Chart.yaml, especially after making a release.
287+
288+
A release process would generally look like this:
289+
290+
```bash
291+
V=1.2.3
292+
chartpress --reset --tag "$V"
293+
git commit -am "release $V"
294+
git tag -am "release $V" "$V"
295+
git push --atomic --follow-tags
296+
297+
# back to development
298+
NEXT_V=1.3.0-0.dev
299+
chartpress --reset --tag "$NEXT_V"
300+
git commit -am "Back to $NEXT_V"
301+
git push --atomic
302+
```
303+
304+
Any prerelease fields (such as `-0.dev` above, or `-alpha.1`)
305+
will be left as-is, with the `.git.n.hash` suffix added.
306+
307+
If the version in Chart.yaml is not a prerelease,
308+
a prelease version will be constructed with `-0.dev.git.n.hash`.
309+
310+
When you use this configuration,
311+
`chartpress --reset` strips the `.git...` suffix from the chart version,
312+
ignoring the `resetVersion` config.
313+
226314
## Caveats
227315

228316
### Shallow clones

chartpress.py

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@
2929
# name of possible repository keys used in image value
3030
IMAGE_REPOSITORY_KEYS = {"name", "repository"}
3131

32-
# prefix in prerelease tag for git commit count
33-
PRERELEASE_PREFIX = "0git."
32+
# prefixes added to prerelease versions
33+
# these do not include the trailing '.'
34+
# which makes them valid prerelease fields on their own,
35+
# but they cannot be empty.
36+
37+
# GIT is always added to start our git info
38+
GIT_PREFIX = "git"
39+
# this is the _full_ prefix we add to non-prerelease versions
40+
PRERELEASE_PREFIX = f"0.dev.{GIT_PREFIX}"
3441

3542
# Container builders
3643
class Builder(Enum):
@@ -442,7 +449,7 @@ def _image_needs_building(image, platforms):
442449
return _image_needs_pushing(image, platforms)
443450

444451

445-
def _get_identifier_from_paths(*paths, long=False):
452+
def _get_identifier_from_paths(*paths, long=False, base_version=None):
446453
latest_commit = _get_latest_commit_tagged_or_modifying_paths(*paths, echo=False)
447454

448455
try:
@@ -456,24 +463,29 @@ def _get_identifier_from_paths(*paths, long=False):
456463
.strip()
457464
)
458465
latest_tag_in_branch, n_commits, sha = git_describe.rsplit("-", maxsplit=2)
466+
n_commits = int(n_commits)
467+
if base_version is None:
468+
base_version = latest_tag_in_branch
459469
# remove "g" prefix output by the git describe command
460470
# ref: https://git-scm.com/docs/git-describe#_examples
461471
sha = sha[1:]
462-
463-
return _get_identifier_from_parts(latest_tag_in_branch, n_commits, sha, long)
464472
except subprocess.CalledProcessError:
465473
# no tags on branch, so assume 0.0.1 and
466474
# calculate n_commits from latest_commit
467-
n_commits = (
475+
n_commits = int(
468476
_check_output(
469477
["git", "rev-list", "--count", latest_commit],
470478
echo=False,
471479
)
472480
.decode("utf-8")
473481
.strip()
474482
)
483+
sha = latest_commit
484+
485+
if base_version is None:
486+
base_version = "0.0.1"
475487

476-
return _get_identifier_from_parts("0.0.1", n_commits, latest_commit, long)
488+
return _get_identifier_from_parts(base_version, n_commits, sha, long)
477489

478490

479491
def _get_identifier_from_parts(tag, n_commits, commit, long):
@@ -487,25 +499,34 @@ def _get_identifier_from_parts(tag, n_commits, commit, long):
487499
with h to ensure we don't get a numerical hash starting with 0 because
488500
those are invalid SemVer 2 versions.
489501
490-
We lead with '0git.' to ensure our suffixes sort as older than any explicit tags,
491-
such as `0.1.2-alpha.1 > 0.1.2-alpha.0git.5.habc > 0.1.2-0git.3.h123`
502+
If tag is already a prelease, we append fields so we sort later than the prerelease.
503+
If the tag is a stable release, we start the prerelease with `-0.dev.` to ensure we sort lower
504+
than labeled prereleases such as 'alpha'.
505+
506+
Typical tags in descending order:
507+
508+
- 0.1.2
509+
- 0.1.2-alpha.2
510+
- 0.1.2-alpha.1.git.5.habc
511+
- 0.1.2-alpha.1
512+
- 0.1.2-0.dev.git.3.habc
492513
493514
Example:
494515
tag="0.1.2", n_commits="5", commit="asdf1234", long=True,
495-
should return "0.1.2-0git.5.hasdf1234".
516+
should return "0.1.2-0.dev.git.5.hasdf1234".
496517
"""
497518
n_commits = int(n_commits)
498519

499520
if n_commits > 0 or long:
500521
if "-" in tag:
501-
# add a field to an existing prerelease tag
502-
# 0.1.2-alpha.1 -> 0.1.2-{PRERELEASE_PREFIX}5.hsha
503-
sep = "."
522+
# add fields to an existing prerelease tag
523+
# 0.1.2-alpha.1 -> 0.1.2-alpha.1.git.n.hsha
524+
pre = f".{GIT_PREFIX}"
504525
else:
505-
# create prerelease tag
506-
# 0.1.2-alpha.1 -> 0.1.2-{PRERELEASE_PREFIX}5.hsha
507-
sep = "-"
508-
return f"{tag}{sep}{PRERELEASE_PREFIX}{n_commits}.h{commit}"
526+
# create a new '-0.dev.' prerelease tag
527+
# 0.1.2 -> 0.1.2-0.dev.git.5.hsha
528+
pre = f"-{PRERELEASE_PREFIX}"
529+
return f"{tag}{pre}.{n_commits}.h{commit}"
509530
else:
510531
return f"{tag}"
511532

@@ -522,6 +543,7 @@ def build_images(
522543
*,
523544
builder=Builder.DOCKER_BUILD,
524545
platforms=None,
546+
base_version=None,
525547
):
526548
"""Build a collection of docker images
527549
@@ -549,11 +571,11 @@ def build_images(
549571
550572
Example 1:
551573
- long=False: 0.9.0
552-
- long=True: 0.9.0-0git.0.hasdf1234
574+
- long=True: 0.9.0-0.dev.git.0.hasdf1234
553575
554576
Example 2:
555-
- long=False: 0.9.0-0git.4.hsdfg2345
556-
- long=True: 0.9.0-0git.4.hsdfg2345
577+
- long=False: 0.9.0-0.dev.git.4.hsdfg2345
578+
- long=True: 0.9.0-0.dev.git.4.hsdfg2345
557579
558580
builder (str):
559581
The container build engine.
@@ -567,7 +589,9 @@ def build_images(
567589
all_image_paths = _get_all_image_paths(name, options)
568590

569591
if tag is None:
570-
image_tag = _get_identifier_from_paths(*all_image_paths, long=long)
592+
image_tag = _get_identifier_from_paths(
593+
*all_image_paths, long=long, base_version=base_version
594+
)
571595
else:
572596
image_tag = tag
573597

@@ -709,23 +733,44 @@ def _update_values_file_with_modifications(name, modifications):
709733
yaml.dump(values, f)
710734

711735

736+
_suffix_version_pat = re.compile(r"(.*)\.git\.\d+\.h[a-f0-9]+$")
737+
738+
739+
def _trim_version_suffix(version):
740+
"""Trim trailing .git... suffix from a version
741+
742+
Turns 1.0.0-0.dev.git.5.habc back into 1.0.0-0.dev
743+
"""
744+
m = _suffix_version_pat.match(version)
745+
if m:
746+
# matches, strip suffix
747+
return m.group(1)
748+
return version
749+
750+
712751
def build_chart(
713-
name, version=None, paths=None, long=False, reset=False, strict_version=False
752+
name,
753+
version=None,
754+
paths=None,
755+
long=False,
756+
reset=False,
757+
strict_version=False,
758+
use_chart_version=False,
714759
):
715760
"""
716761
Update Chart.yaml's version, using specified version or by constructing one.
717762
718763
Chart versions are constructed using:
719-
a) the latest commit that is tagged,
764+
a) a base version, derived from either Chart.yaml or the latest tag
720765
b) the latest commit that modified provided paths
721766
722767
Example versions constructed:
768+
- 0.9.0-0.dev.git.2.hdfgh3456
723769
- 0.9.0-alpha.1
724-
- 0.9.0-alpha.1.0git.0.hasdf1234 (--long)
725-
- 0.9.0-alpha.1.0git.5.hsdfg2345
726-
- 0.9.0-alpha.1.0git.5.hsdfg2345 (--long)
770+
- 0.9.0-alpha.1.git.0.hasdf1234 (--long)
771+
- 0.9.0-alpha.1.git.5.hsdfg2345
772+
- 0.9.0-alpha.1.git.5.hsdfg2345 (--long)
727773
- 0.9.0
728-
- 0.9.0-0git.2.hdfgh3456
729774
"""
730775
# read Chart.yaml
731776
chart_file = os.path.join(name, "Chart.yaml")
@@ -734,7 +779,23 @@ def build_chart(
734779

735780
# decide a version string
736781
if version is None:
737-
version = _get_identifier_from_paths(*paths, long=long)
782+
if use_chart_version:
783+
# avoid adding .git... to chart version multiple times!
784+
base_version = _trim_version_suffix(chart["version"])
785+
# TODO: trim -set.by.chartpress?
786+
# if base_version.endswith("-set.by.chartpress"):
787+
# base_version = base_version.rsplit("-", 1)[0]
788+
else:
789+
base_version = None
790+
if reset:
791+
# resetting, use the base version without '.git...'
792+
version = base_version
793+
else:
794+
# derive the full version, with possible '.git...'
795+
version = _get_identifier_from_paths(
796+
*paths, long=long, base_version=base_version
797+
)
798+
738799
if not reset:
739800
version = _fix_chart_version(version, strict=strict_version)
740801

@@ -1008,10 +1069,16 @@ def main(args=None):
10081069
# - push chart (--publish-chart, --extra-message)
10091070
for chart in config["charts"]:
10101071
forced_version = None
1072+
# TODO: maybe warn and switch default in the future?
1073+
use_chart_version = chart.get("useChartVersion", False)
1074+
10111075
if args.tag:
1076+
# tag specified, use that version
10121077
forced_version = args.tag
1013-
elif args.reset:
1014-
forced_version = chart.get("resetVersion", "0.0.1-set.by.chartpress")
1078+
elif args.reset and not use_chart_version:
1079+
# resetting, get version from chrtpress.yaml,
1080+
# ignoring current version in Chart.yaml
1081+
forced_version = chart.get("resetVersion", "0.0.1-0.dev")
10151082

10161083
if not args.list_images:
10171084
# update Chart.yaml with a version
@@ -1022,6 +1089,7 @@ def main(args=None):
10221089
long=args.long,
10231090
reset=args.reset,
10241091
strict_version=args.publish_chart,
1092+
use_chart_version=use_chart_version,
10251093
)
10261094

10271095
if "images" in chart:

0 commit comments

Comments
 (0)