Skip to content

Commit 8f993d9

Browse files
authored
Merge pull request #595 from influxdata/BNP_release_workflow
build: automatically create PR for enterprise releases
2 parents 2f72e6b + 4097e11 commit 8f993d9

File tree

7 files changed

+359
-0
lines changed

7 files changed

+359
-0
lines changed

.circleci/config.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
---
12
version: 2.1
23

4+
master_filter: &master_filter
5+
filters:
6+
tags:
7+
ignore: /.*/
8+
branches:
9+
only:
10+
- master
11+
312
workflows:
413
version: 2
514
ci:
@@ -9,6 +18,8 @@ workflows:
918
matrix:
1019
parameters:
1120
version: ["2.0", "2.1", "2.2"]
21+
- publish_docker_images:
22+
<<: *master_filter
1223

1324
jobs:
1425
build:
@@ -33,3 +44,33 @@ jobs:
3344
- store_artifacts:
3445
path: influxdb/test/logs
3546
destination: container-logs
47+
48+
publish_docker_images:
49+
docker:
50+
- image: 'cimg/python:3.6'
51+
steps:
52+
- checkout
53+
- run:
54+
name: Install Prerequisite Packages
55+
command: |
56+
sudo bash -s \<<EOF
57+
#!/bin/bash
58+
set -o errexit \
59+
-o nounset \
60+
-o pipefail
61+
62+
export DEBIAN_FRONTEND=noninteractive
63+
apt-get update
64+
apt-get install --yes git
65+
EOF
66+
67+
python3 -m pip install -r \
68+
.circleci/scripts/update_manifest_file/requirements.txt
69+
- run:
70+
name: Get Enterprise Information
71+
command: |
72+
python3 .circleci/scripts/get_enterprise_info
73+
- run:
74+
name: Do Enterprise Release
75+
command: |
76+
.circleci/scripts/do-enterprise-release

.circleci/scripts/create-draft-pr

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
set -o errexit \
3+
-o nounset \
4+
-o pipefail
5+
6+
# ${1} -> ENDPOINT
7+
# ${2} -> HEAD
8+
# ${3} -> TITLE
9+
10+
read -d '' -r DATA <<EOF || true
11+
{
12+
"title": "${3}",
13+
"head": "${2}",
14+
"base": "master",
15+
"draft": true
16+
}
17+
EOF
18+
19+
curl \
20+
-X POST \
21+
-H "Accept: application/vnd.github.v3+json" \
22+
-H "Authorization: Bearer ${GITHUB_MACHINE_TOKEN}" \
23+
"${1}" -d "${DATA}"
24+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
set -o errexit \
3+
-o nounset \
4+
-o pipefail
5+
6+
if [[ ${PRODUCT:-} != 'influxdb' ]] || \
7+
[[ ${VERSION_MAJOR:-} != '1' ]] || \
8+
[[ ${VERSION_MINOR:-} != '9' ]]
9+
then
10+
printf 'Release is not Enterprise skipping...\n'; exit 0
11+
fi
12+
13+
git clone [email protected]:/docker-library/official-images
14+
15+
python3 .circleci/scripts/update_manifest_file official-images/library/influxdb
16+
17+
pushd official-images
18+
19+
# CircleCI preloads the ssh-agent with the secret key required to clone
20+
# the repository. Unfortunately, this secret key does not have the
21+
# required permissions to `push`. This flushes the ssh-agent of
22+
# keys so that the `${SSH_MACHINE_SECKEY}` is always used.
23+
ssh-add -D && base64 -d <<<"${SSH_MACHINE_SECKEY}" | ssh-add -
24+
25+
git config user.name "${GITHUB_MACHINE_NAME}"
26+
git config user.email "${GITHUB_MACHINE_EMAIL}"
27+
28+
# We always branch from 'origin/master' to use the most up-to-date
29+
# source and avoid merge conflicts.
30+
git checkout -b "CI_release_${VERSION}" "origin/master"
31+
32+
git add . && git commit -m "feat: release Enterprise ${VERSION}"
33+
34+
# If this workflow is executed multiple times, it's possible that a branch
35+
# named `CI_release_${VERSION}` already exists. If this is the
36+
# case, we overwrite it with our commits.
37+
git remote add influxdata [email protected]:/influxdata/official-images
38+
git push -f --set-upstream influxdata "CI_release_${VERSION}"
39+
40+
popd
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/python3
2+
from typing import Dict
3+
from typing import Generator
4+
from typing import Optional
5+
import os
6+
import re
7+
import subprocess
8+
import enum
9+
10+
11+
def getenv(variable: str) -> str:
12+
"""
13+
Retrieves `variable` from the environment.
14+
"""
15+
# `os.getenv` returns `Dict[str]` which is incompatible with functions
16+
# that require `str` parameters. If `os.getenv` returns a value, it is
17+
# unwrapped and returned to the caller.
18+
value = os.getenv(variable)
19+
if value is not None:
20+
return value
21+
22+
raise RuntimeError('Missing environment variable "{}".'.format(variable))
23+
24+
25+
class GitDiff(enum.Enum):
26+
# fmt: off
27+
ADDED = "\+"
28+
REMOVED = "\-"
29+
# fmt: on
30+
31+
def __str__(self):
32+
return str(self.value)
33+
34+
35+
def parse_env_line(line: str, diff: GitDiff) -> Optional[Dict[str, str]]:
36+
"""
37+
Matches version line in dockerfile.
38+
"""
39+
# fmt: off
40+
matches = re.match(
41+
r"^" + str(diff) + # changed?
42+
r"ENV\s+INFLUXDB_VERSION\s+" + # prelude
43+
r"((\d+)" + # major cg: 2
44+
r"(?:\.(\d+))?" + # minor? cg: 3
45+
r"(?:\.(\d+))?" + # patch? cg: 4
46+
r"(?:\-?(rc\d+))?)" + # rc? cg: 5
47+
r"-c" + # interlude
48+
r"(\d+)" + # repeat major cg: 6
49+
r"(?:\.(\d+))?" + # repeat minor? cg: 7
50+
r"(?:\.(\d+))?" + # repeat patch? cg: 8
51+
r"(?:\-?(rc\d+))?", # repeat rc? cg: 9
52+
line)
53+
54+
if matches is not None:
55+
# I tried using capture-group back-references within the regular
56+
# expression. However, it couldn't handle optional capture-
57+
# groups. So, instead, we check for equality here.
58+
if (matches.group(2) == matches.group(6) and
59+
matches.group(3) == matches.group(7) and
60+
matches.group(4) == matches.group(8) and
61+
matches.group(5) == matches.group(9)):
62+
return {
63+
"VERSION": matches.group(1),
64+
"VERSION_MAJOR": matches.group(2),
65+
"VERSION_MINOR": matches.group(3) if matches.group(3) else "",
66+
"VERSION_PATCH": matches.group(4) if matches.group(4) else "",
67+
"VERSION_RC": matches.group(5) if matches.group(5) else "",
68+
}
69+
return None
70+
# fmt: on
71+
72+
73+
def parse_version() -> Optional[str]:
74+
"""
75+
Parse version from "ENV" line in git.
76+
"""
77+
# Retrieve all lines that have changed since the commit between
78+
# HEAD~1 and HEAD. This is more robust than just parsing the
79+
# current Dockerfile as it ensures that `INFLUXDB_VERSION`
80+
# actually changed.
81+
# fmt: off
82+
process = subprocess.run(
83+
["git", "diff", "--unified=0", "HEAD~1..HEAD", "influxdb" ],
84+
stdout=subprocess.PIPE,
85+
stderr=subprocess.PIPE,
86+
)
87+
# fmt: on
88+
89+
version_prev = None
90+
version_curr = None
91+
for line in process.stdout.decode("utf-8").split("\n"):
92+
line = line.rstrip(" \t")
93+
prev = parse_env_line(line, GitDiff.REMOVED)
94+
curr = parse_env_line(line, GitDiff.ADDED)
95+
96+
if prev is not None:
97+
version_prev = prev
98+
if curr is not None:
99+
version_curr = curr
100+
101+
# fmt: off
102+
if (version_prev is not None and
103+
version_curr is not None):
104+
# if the versions differ, then this must be a release
105+
if version_prev["VERSION"] != version_curr["VERSION"]:
106+
return version_curr
107+
# fmt: on
108+
109+
# If the `INFLUXDB_VERSION` line has changed but the version has
110+
# not changed, reset so the next encounter of `version_prev`
111+
# does not cause this to return the incorrect `version_curr`.
112+
if version_curr != None:
113+
version_prev = None
114+
version_curr = None
115+
116+
# no version change in dockerfile
117+
return None
118+
119+
120+
version = parse_version()
121+
if version is not None:
122+
with open(getenv("BASH_ENV"), "a") as stream:
123+
stream.write("export PRODUCT=influxdb\n")
124+
for key, value in version.items():
125+
stream.write("export {}={}\n".format(key, value))
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from debian.deb822 import Deb822
2+
from debian.deb822 import Deb822Dict
3+
import argparse
4+
import os
5+
import re
6+
import sys
7+
8+
def getenv(variable: str) -> str:
9+
"""
10+
Retrieves `variable` from the environment.
11+
"""
12+
# `os.getenv` returns `Dict[str]` which is incompatible with functions
13+
# that require `str` parameters. If `os.getenv` returns a value, it is
14+
# unwrapped and returned to the caller.
15+
value = os.getenv(variable)
16+
if value is not None:
17+
return value
18+
19+
raise RuntimeError('Missing environment variable "{}".'.format(variable))
20+
21+
def reg_major(major: int) -> str:
22+
"""
23+
reg_major
24+
25+
Generates a partial-regular expression that matches the
26+
supplied `major` version.
27+
"""
28+
# This forces `major` to be an integer by using the integer format
29+
# specifier. This sanitization is important so that regex cannot
30+
# be accidentally inserted into `re.sub`. If `major` is not an
31+
# integer, this aborts the program.
32+
return r"(" + "{0:d}".format(major) + r")"
33+
34+
35+
def reg_minor(minor: int) -> str:
36+
"""
37+
reg_minor
38+
39+
Generates a partial-regular expression that matches the
40+
supplied `minor` version.
41+
"""
42+
# This forces `minor` to be an integer by using the integer format
43+
# specifier. This sanitization is important so that regex cannot
44+
# be accidentally inserted into `re.sub`. If `minor` is not an
45+
# integer, this aborts the program.
46+
return r"(?:\.(" + "{0:d}".format(minor) + r"))"
47+
48+
49+
def reg_patch() -> str:
50+
"""
51+
reg_patch
52+
53+
Generates a partial-regular expression that matches any patch
54+
version.
55+
"""
56+
return r"(?:\.(\d+))"
57+
58+
59+
def paragraph_cb(index, paragraph):
60+
"""
61+
paragraph_cb
62+
"""
63+
64+
def paragraph_repo_cb(paragraph):
65+
# This callback is executed for the 'repository' paragraph. This
66+
# is currently defined as the first paragraph. This paragraph
67+
# usually has the keys 'Maintainers', 'GitRepo', and
68+
# 'GitCommit'.
69+
70+
def item_cb(key, value):
71+
if key == "GitCommit":
72+
return getenv("CIRCLE_SHA1")
73+
else:
74+
return value
75+
76+
return {k: item_cb(k, v) for (k, v) in paragraph.items()}
77+
78+
def paragraph_rele_cb(paragraph):
79+
# This callback is executed for every 'release' paragraph. This
80+
# is currently defined as every paragraph following the first.
81+
# This paragraph usually has the keys 'Tags', 'Architectures',
82+
# and 'Directory'.
83+
84+
def item_cb(key, value):
85+
# If this has encountered a 'Tags:' key-value pair, update all
86+
# matching 'Major.Minor.Patch' versions. `major` and `minor`
87+
# must match `VERSION_MAJOR` and `VERSION_MINOR`. `patch`
88+
# can be any integer.
89+
if key == "Tags":
90+
# fmt: off
91+
return re.sub(
92+
reg_major(int(getenv("VERSION_MAJOR"))) +
93+
reg_minor(int(getenv("VERSION_MINOR"))) +
94+
reg_patch(),
95+
getenv("VERSION"),
96+
value,
97+
)
98+
# fmt: on
99+
else:
100+
return value
101+
102+
return {k: item_cb(k, v) for (k, v) in paragraph.items()}
103+
104+
# fmt: off
105+
return (
106+
paragraph_repo_cb(paragraph) if index == 0 else
107+
paragraph_rele_cb(paragraph)
108+
)
109+
# fmt: on
110+
111+
112+
with open(sys.argv[1], "rb") as content:
113+
# fmt: off
114+
document = [
115+
paragraph_cb(index, parser) for index, parser in
116+
enumerate(Deb822.iter_paragraphs(content))
117+
]
118+
# fmt: on
119+
120+
with open(sys.argv[1], "w") as output:
121+
for paragraph in document:
122+
# The `Deb822` constructor requires a `Sequence`-like object. Since
123+
# each `paragraph` within the `document` is a `Dict` (which does
124+
# not implement the `Sequence` methods), this constructs a
125+
# `Deb822dict` instance as an intermediate.
126+
output.write("{0:s}\n".format(Deb822(Deb822Dict(paragraph)).dump()))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python-debian==0.1.43
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
chardet==4.0.0
2+
python-debian==0.1.43

0 commit comments

Comments
 (0)