-
-
Notifications
You must be signed in to change notification settings - Fork 644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New kubernetes backend #21796
base: main
Are you sure you want to change the base?
New kubernetes backend #21796
Changes from 15 commits
a594341
c13360d
e9cbcbf
420d6b9
9409599
7a0d00d
8ff08ab
086029f
596e947
fb2c8e5
db2d216
f5e11d6
4e5ba6b
f5df270
4b8a446
6efda67
6bcb4e4
9d84c9e
ed0be1f
72722dd
a50b099
da4a3f7
9081ba1
d31a981
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
"""Script to fetch external tool versions. | ||
|
||
Example: | ||
|
||
pants run build-support/bin:external-tool-versions -- --tool pants.backend.k8s.kubectl_subsystem:Kubectl > list.txt | ||
""" | ||
import argparse | ||
import hashlib | ||
import importlib | ||
import logging | ||
import re | ||
import xml.etree.ElementTree as ET | ||
from collections.abc import Callable, Iterator | ||
from dataclasses import dataclass | ||
from multiprocessing.pool import ThreadPool | ||
from string import Formatter | ||
from urllib.parse import urlparse | ||
|
||
import requests | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class VersionHash: | ||
version: str | ||
platform: str | ||
size: int | ||
sha256: str | ||
|
||
|
||
def format_string_to_regex(format_string: str) -> re.Pattern: | ||
"""Converts a format string to a regex. | ||
|
||
>>> format_string_to_regex("/release/v{version}/bin/{platform}/kubectl") | ||
re.compile('^\\/release\\/v(?P<version>.*)\\/bin\\/(?P<platform>.*)\\/kubectl$') | ||
""" | ||
result_regex = ["^"] | ||
parts = Formatter().parse(format_string) | ||
for literal_text, field_name, format_spec, conversion in parts: | ||
escaped_text = literal_text.replace("/", r"\/") | ||
result_regex.append(escaped_text) | ||
if field_name is not None: | ||
result_regex.append(rf"(?P<{field_name}>.*)") | ||
result_regex.append("$") | ||
return re.compile("".join(result_regex)) | ||
|
||
|
||
def fetch_text(url: str) -> str: | ||
response = requests.get(url) | ||
return response.text | ||
|
||
|
||
def _parse_k8s_xml(text: str) -> Iterator[str]: | ||
regex = re.compile(r"release\/stable-(?P<version>[0-9\.]+).txt") | ||
root = ET.fromstring(text) | ||
tag = "{http://doc.s3.amazonaws.com/2006-03-01}" | ||
for item in root.iter(f"{tag}Contents"): | ||
key_element = item.find(f"{tag}Key") | ||
if key_element is None: | ||
raise RuntimeError("Failed to parse xml, did it change?") | ||
|
||
key = key_element.text | ||
if key and regex.match(key): | ||
yield f"https://cdn.dl.k8s.io/{key}" | ||
|
||
|
||
def get_k8s_versions(url_template: str, pool: ThreadPool) -> Iterator[str]: | ||
response = requests.get("https://cdn.dl.k8s.io/", allow_redirects=True) | ||
urls = _parse_k8s_xml(response.text) | ||
for v in pool.imap_unordered(fetch_text, urls): | ||
yield v.strip().lstrip("v") | ||
|
||
|
||
DOMAIN_TO_VERSIONS_MAPPING: dict[str, Callable[[str, ThreadPool], Iterator[str]]] = { | ||
# TODO github.com | ||
"dl.k8s.io": get_k8s_versions, | ||
} | ||
|
||
|
||
def fetch_version(url_template: str, version: str, platform: str) -> VersionHash | None: | ||
url = url_template.format(version=version, platform=platform) | ||
response = requests.get(url, allow_redirects=True) | ||
if response.status_code != 200: | ||
logger.error("failed to fetch version: %s\n%s", version, response.text) | ||
return None | ||
|
||
size = len(response.content) | ||
sha256 = hashlib.sha256(response.content) | ||
return VersionHash( | ||
version=version, | ||
platform=platform, | ||
size=size, | ||
sha256=sha256.hexdigest(), | ||
) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"-t", | ||
"--tool", | ||
help="Python tool location, for example: pants.backend.tools.taplo.subsystem:Taplo", | ||
required=True, | ||
) | ||
parser.add_argument( | ||
"--platforms", | ||
default="macos_arm64,macos_x86_64,linux_arm64,linux_x86_64", | ||
help="Comma separated list of platforms", | ||
) | ||
parser.add_argument( | ||
"-w", | ||
"--workers", | ||
default=16, | ||
help="Thread pool size", | ||
) | ||
parser.add_argument( | ||
"-v", | ||
"--verbose", | ||
action=argparse.BooleanOptionalAction, | ||
default=False, | ||
help="Verbose output", | ||
) | ||
|
||
args = parser.parse_args() | ||
|
||
level = logging.DEBUG if args.verbose else logging.INFO | ||
logging.basicConfig(level=level, format="%(message)s") | ||
|
||
module_string, class_name = args.tool.split(":") | ||
module = importlib.import_module(module_string) | ||
cls = getattr(module, class_name) | ||
|
||
platforms = args.platforms.split(",") | ||
platform_mapping = cls.default_url_platform_mapping | ||
mapped_platforms = {platform_mapping.get(p) for p in platforms} | ||
|
||
domain = urlparse(cls.default_url_template).netloc | ||
get_versions = DOMAIN_TO_VERSIONS_MAPPING[domain] | ||
pool = ThreadPool(processes=args.workers) | ||
results = [] | ||
for version in get_versions(cls.default_url_template, pool): | ||
for platform in mapped_platforms: | ||
logger.debug("fetching version: %s %s", version, platform) | ||
results.append( | ||
pool.apply_async(fetch_version, args=(cls.default_url_template, version, platform)) | ||
) | ||
|
||
backward_platform_mapping = {v: k for k, v in platform_mapping.items()} | ||
for result in results: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could have these output in semver order (instead of lexical order) by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure 9d84c9e |
||
v = result.get(60) | ||
grihabor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if v is None: | ||
continue | ||
print( | ||
"|".join( | ||
[ | ||
v.version, | ||
backward_platform_mapping[v.platform], | ||
v.sha256, | ||
str(v.size), | ||
] | ||
) | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "Docker", | ||
"position": 9 | ||
"position": 8 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "Go", | ||
"position": 6 | ||
"position": 5 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "JVM", | ||
"position": 7 | ||
"position": 6 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"label": "Kubernetes", | ||
"position": 9 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
--- | ||
title: Kubernetes Overview | ||
sidebar_position: 999 | ||
--- | ||
|
||
--- | ||
|
||
:::caution Kubernetes support is in alpha stage | ||
Pants is currently building support for Kubernetes. Simple use cases might be | ||
supported, but many options are missing. | ||
|
||
Please share feedback for what you need to use Pants with your Kubernetes queries by | ||
either [opening a GitHub | ||
issue](https://github.com/pantsbuild/pants/issues/new/choose) or [joining our | ||
Slack](/community/getting-help)! | ||
::: | ||
|
||
## Initial setup | ||
|
||
First, activate the relevant backend in `pants.toml`: | ||
|
||
```toml title="pants.toml" | ||
[GLOBAL] | ||
backend_packages = [ | ||
... | ||
"pants.backend.experimental.k8s", | ||
... | ||
] | ||
``` | ||
|
||
The Kubernetes backend adds [`k8s_source`](../../reference/targets/k8s_source.mdx) and | ||
[`k8s_sources`](../../reference/targets/k8s_sources.mdx) target types for Kubernetes object | ||
files. The `tailor` goal will automatically generate the targets for | ||
your .yaml files. | ||
|
||
For example, create a file `src/k8s/configmap.yaml`: | ||
|
||
```yaml title="src/k8s/configmap.yaml" | ||
--- | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: spark-defaults-conf | ||
data: | ||
spark-defaults.conf: | | ||
spark.driver.memory=1g | ||
spark.executor.cores=1 | ||
spark.executor.instances=1 | ||
spark.executor.memory=2g | ||
``` | ||
|
||
Now run: | ||
|
||
```bash | ||
pants tailor src/k8s: | ||
``` | ||
``` | ||
Created src/k8s/BUILD: | ||
- Add k8s_sources target k8s | ||
``` | ||
|
||
## Deploying objects to a cluster | ||
|
||
We'll be using a local [kind](https://kind.sigs.k8s.io/) cluster throughout the | ||
tutorial. First, spin up a cluster: | ||
|
||
```bash | ||
kind create cluster | ||
``` | ||
``` | ||
Creating cluster "kind" ... | ||
✓ Ensuring node image (kindest/node:v1.25.3) 🖼 | ||
✓ Preparing nodes 📦 | ||
✓ Writing configuration 📜 | ||
✓ Starting control-plane 🕹️ | ||
✓ Installing CNI 🔌 | ||
✓ Installing StorageClass 💾 | ||
Set kubectl context to "kind-kind" | ||
``` | ||
|
||
Second, configure the list of available contexts in `pants.toml`: | ||
|
||
```toml title="pants.toml" | ||
... | ||
|
||
[k8s] | ||
available_contexts = [ | ||
"kind-kind", | ||
] | ||
``` | ||
|
||
Third, create a deployable target `k8s_bundle` in `src/k8s/BUILD`: | ||
|
||
```python title="src/k8s/BUILD" | ||
k8s_sources() | ||
k8s_bundle( | ||
name="configmap", | ||
sources=("src/k8s/configmap.yaml",), | ||
context="kind-kind", | ||
) | ||
``` | ||
|
||
Now you can deploy the target: | ||
|
||
```bash | ||
pants experimental-deploy src/k8s:configmap | ||
``` | ||
``` | ||
✓ src/k8s:configmap deployed to context kind-kind | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "Python", | ||
"position": 5 | ||
"position": 4 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "Shell", | ||
"position": 8 | ||
"position": 7 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"label": "Using Pants", | ||
"position": 4 | ||
"position": 3 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
python_sources() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
from pants.backend.k8s import k8s_subsystem, kubectl_subsystem | ||
from pants.backend.k8s import target_types as k8s_target_types | ||
from pants.backend.k8s.goals import deploy, tailor | ||
|
||
|
||
def rules(): | ||
return [ | ||
*deploy.rules(), | ||
*k8s_subsystem.rules(), | ||
*kubectl_subsystem.rules(), | ||
*tailor.rules(), | ||
] | ||
|
||
|
||
def target_types(): | ||
return k8s_target_types.target_types() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
python_sources() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
python_sources() | ||
python_tests(name="tests") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use
pants.core.util_rules.external_tool.ExternalToolVersion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not quite the same thing, because
platform
here is not a predefined platform that pants uses, but a platform that needs to be mapped usingurl_platform_mapping
. But I can make it work like this 6efda67