-
-
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 all commits
a594341
c13360d
e9cbcbf
420d6b9
9409599
7a0d00d
8ff08ab
086029f
596e947
fb2c8e5
db2d216
f5e11d6
4e5ba6b
f5df270
4b8a446
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 |
||||||
v = result.get(60) | ||||||
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. clarify that this is for the timeout
Suggested change
|
||||||
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