Skip to content

Commit 31626ef

Browse files
committed
Initial scripts to create dev env for velero/oadp.
1 parent 1bb2ba8 commit 31626ef

6 files changed

+546
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
temp
2+
.venv

Makefile

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#
2+
# Copyright Red Hat
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
#
16+
17+
# Variable setup and preflight checks
18+
19+
# may override with environment variable
20+
CONTAINER_ENGINE?=podman
21+
GO_CONTAINER_IMAGE?=docker.io/golang:1.20
22+
PYTHON_BINARY?=python3
23+
24+
ifndef OADP_VENV
25+
OADP_VENV=.venv
26+
endif
27+
28+
SYS_PYTHON_VER=$(shell $(PYTHON_BINARY) -c 'from sys import version_info; \
29+
print("%d.%d" % version_info[0:2])')
30+
$(info Found system python version: $(SYS_PYTHON_VER));
31+
PYTHON_VER_CHECK=$(shell $(PYTHON_BINARY) scripts/python-version-check.py)
32+
33+
ifneq ($(strip $(PYTHON_VER_CHECK)),)
34+
$(error $(PYTHON_VER_CHECK). You may set the PYTHON_BINARY env var to specify a compatible version)
35+
endif
36+
37+
SHELLCHECK=$(shell which shellcheck)
38+
39+
.PHONY: default
40+
default: \
41+
dev-env
42+
43+
.PHONY: all
44+
all: default
45+
46+
# note the following is required for the makefile help
47+
## TARGET: DESCRIPTION
48+
## ------: -----------
49+
## help: print each make target with a description
50+
.PHONY: help
51+
help:
52+
@echo ""
53+
@(printf ""; sed -n 's/^## //p' Makefile) | column -t -s :
54+
55+
56+
# Environment setup
57+
58+
.PHONY: go-container-image
59+
go-container-image:
60+
${CONTAINER_ENGINE} pull ${GO_CONTAINER_IMAGE}
61+
62+
$(OADP_VENV):go-container-image
63+
test -d ${OADP_VENV} || ${PYTHON_BINARY} -m venv ${OADP_VENV}
64+
. ${OADP_VENV}/bin/activate && \
65+
pip install -U pip && \
66+
pip install -r ./requirements-dev.txt
67+
touch ${OADP_VENV}
68+
69+
## cli_dev_tools: install all necessary CLI dev tools
70+
.PHONY: cli_dev_tools
71+
cli_dev_tools: $(OADP_VENV)
72+
. ${OADP_VENV}/bin/activate && \
73+
./scripts/install_dev_tools.sh -v $(OADP_VENV)
74+
75+
## dev-env: set up everything needed for development (install tools, set up virtual environment, git configuration)
76+
dev-env: $(OADP_VENV) cli_dev_tools
77+
@echo
78+
@echo "**** To run VENV:"
79+
@echo " $$ source ${OADP_VENV}/bin/activate"
80+
@echo
81+
@echo "**** To later deactivate VENV:"
82+
@echo " $$ deactivate"
83+
@echo
84+
85+
## shellcheck: run various tests on a shell scripts
86+
shellcheck: $(OADP_VENV) $(OADP_VENV)/bin/shellcheck
87+
. ${OADP_VENV}/bin/activate && \
88+
if [[ -z shellcheck ]]; then echo "Shellcheck is not installed" >&2; false; fi && \
89+
echo "🐚 📋 Linting shell scripts with shellcheck" && \
90+
shellcheck $(shell find . -name '*.sh' -type f | grep -v 'venv/\|git/\|.pytest_cache/\|htmlcov/\|_test/test_helper/\|_test/bats\|_test/conftest')
91+
92+
93+
# Cleanup
94+
95+
## clean-dev-env: remove the virtual environment, clean up all .pyc files and container image(s)
96+
clean-dev-env:
97+
rm -rf ${OADP_VENV}
98+
find . -iname "*.pyc" -delete
99+
${CONTAINER_ENGINE} rmi ${GO_CONTAINER_IMAGE}

requirements-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
semver

scripts/get_tool_dl_url.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright Red Hat
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
#
17+
18+
"""
19+
Extract the download URL for software hosted on github, taking into account OS and Architecture.
20+
"""
21+
import argparse
22+
import enum
23+
import platform
24+
import re
25+
import sys
26+
from typing import Callable, Iterable, Literal, NamedTuple, cast
27+
28+
import requests
29+
import semver
30+
31+
GITHUB_RELEASE_TEMPLATE = "https://api.github.com/repos/{}/releases"
32+
33+
34+
SUPPORTED_SYSTEMS = {"Linux", "Darwin"}
35+
36+
_os = platform.system()
37+
if _os not in SUPPORTED_SYSTEMS:
38+
sys.exit(f"Unsupported OS {_os}")
39+
40+
OS = cast(Literal["Linux", "Darwin"], _os)
41+
42+
X86_64_ARCH_NAMES = {"x86_64", "amd64"}
43+
SUPPORTED_ARCHES = X86_64_ARCH_NAMES | {"arm64"}
44+
45+
_arch = platform.machine()
46+
if _arch not in SUPPORTED_ARCHES:
47+
sys.exit(f"Unsupported architecture {_arch}")
48+
49+
ARCH = cast(Literal["x86_64", "arm64", "amd64"], _arch)
50+
51+
52+
# TOOLS:
53+
# these are ways to test the URLs of each release asset
54+
# to match our operating system and architecture.
55+
# the special exceptions have their repo name also, for convenience.
56+
57+
58+
class StandardTool:
59+
"The URL pattern for most tools we use."
60+
61+
TAR_GZ_PATTERN = re.compile(r"https://.*\.tar\.[gx]z")
62+
KERNEL_PATTERN = re.compile(OS, re.IGNORECASE)
63+
64+
if OS == "Darwin":
65+
# some projects ship "universal binaries" which support x86 and ARM.
66+
# they usually use "all" in the "arch" portion.
67+
if ARCH in X86_64_ARCH_NAMES:
68+
ARCH_PATTERN = re.compile("all|" + "|".join(X86_64_ARCH_NAMES))
69+
elif ARCH == "arm64":
70+
ARCH_PATTERN = re.compile("all|arm64")
71+
else:
72+
sys.exit(f"Unsupported architecture {ARCH}")
73+
elif OS == "Linux" and ARCH in X86_64_ARCH_NAMES:
74+
if ARCH in X86_64_ARCH_NAMES:
75+
ARCH_PATTERN = re.compile("|".join(X86_64_ARCH_NAMES))
76+
else:
77+
sys.exit(f"Unsupported architecture {ARCH}")
78+
else:
79+
sys.exit(f"Unsupported OS {OS}")
80+
81+
PATTERNS = TAR_GZ_PATTERN, KERNEL_PATTERN, ARCH_PATTERN
82+
83+
@classmethod
84+
def url_matches(cls, url: str) -> bool:
85+
return all(pat.search(url) for pat in cls.PATTERNS)
86+
87+
88+
class Nooba:
89+
"Nooba's URL pattern."
90+
91+
repo = "noobaa/noobaa-operator"
92+
arch = "mac" if OS == "Darwin" else "linux"
93+
pattern = re.compile(f"https://(.*)-{arch}-(.*)[0-9]")
94+
95+
@classmethod
96+
def url_matches(cls, url: str) -> bool:
97+
return cls.pattern.search(url) is not None
98+
99+
100+
class Tool(enum.Enum):
101+
"Maps the dev tools we want to their repos and respective URL matchers."
102+
103+
def __init__(self, repo: str, matcher: Callable[[str], bool]):
104+
self.repo = repo
105+
self.matcher = matcher
106+
107+
tkn = "tektoncd/cli", StandardTool.url_matches
108+
109+
shellcheck = "koalaman/shellcheck", StandardTool.url_matches
110+
111+
noobaa = Nooba.repo, Nooba.url_matches
112+
113+
velero = "vmware-tanzu/velero", StandardTool.url_matches
114+
115+
116+
class ReleaseAsset(NamedTuple):
117+
"A file attached to a github release."
118+
119+
name: str
120+
"The name of the file (for debug purposes)"
121+
122+
url: str
123+
"The download url, not the release or asset URL."
124+
125+
@classmethod
126+
def from_json(cls, asset_dict: dict):
127+
name = asset_dict["name"]
128+
url = asset_dict["browser_download_url"]
129+
return cls(name, url)
130+
131+
132+
def oc_url():
133+
"Get the URL to download the OpenShift client (`oc`)"
134+
if OS == "Darwin":
135+
# there's currently a bug with how go handles certificates on macOS (https://github.com/golang/go/issues/52010),
136+
# and thus affects `oc`` >= 4.11 (https://bugzilla.redhat.com/show_bug.cgi?id=2097830).
137+
# The workaround is to install a 4.10 client.
138+
version = "4.10.40"
139+
140+
if ARCH == "arm64":
141+
filename_suffix = "mac-arm64"
142+
else:
143+
filename_suffix = "mac"
144+
else:
145+
filename_suffix = "linux"
146+
version = "stable"
147+
148+
# I can't say for sure why arm64 bins are available under x86_64, but this isn't a typo.
149+
BASE_URL = "https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp"
150+
return f"{BASE_URL}/{version}/openshift-client-{filename_suffix}.tar.gz"
151+
152+
153+
def get_latest_assets(repo: str) -> Iterable[ReleaseAsset]:
154+
"Get the release assets for the latest release."
155+
url = GITHUB_RELEASE_TEMPLATE.format(repo)
156+
157+
response = requests.get(url)
158+
response.raise_for_status()
159+
160+
body = response.json()
161+
162+
highest_version = semver.VersionInfo(0, 0, 0)
163+
latest_release = None
164+
165+
for release in body:
166+
version_string = release["tag_name"]
167+
if version_string.startswith("v"):
168+
version_string = version_string[1:]
169+
if version_string.endswith("+stringlabels"):
170+
# Special case for Prometheus, where there are
171+
# two different releases. We are interested in the
172+
# one without stringlabels:
173+
# https://prometheus.io/blog/2023/03/21/stringlabel/
174+
continue
175+
if semver.VersionInfo.is_valid(version_string):
176+
version = semver.VersionInfo.parse(version_string)
177+
if version > highest_version:
178+
highest_version = version
179+
latest_release = release
180+
181+
if latest_release:
182+
for asset in latest_release["assets"]:
183+
yield ReleaseAsset.from_json(asset)
184+
185+
186+
CLI_NAMES = {name.replace("_", "-") for name in Tool._member_names_} | {"oc"}
187+
188+
parser = argparse.ArgumentParser(description=__doc__)
189+
parser.add_argument(
190+
"software",
191+
metavar="executable",
192+
type=str,
193+
help=f"The executable to retrieve the URL for.\nSupported: {', '.join(CLI_NAMES)}",
194+
choices=CLI_NAMES,
195+
)
196+
197+
if __name__ == "__main__":
198+
args = parser.parse_args()
199+
software: str = args.software.replace("-", "_")
200+
201+
if software == "oc":
202+
print(oc_url())
203+
sys.exit()
204+
205+
tool = Tool[software]
206+
207+
for asset in get_latest_assets(tool.repo):
208+
if tool.matcher(asset.url):
209+
print(asset.url)
210+
sys.exit()
211+
212+
sys.exit(f"No matching download URL found for {software} on {OS} {ARCH}")

0 commit comments

Comments
 (0)