oras-pants-plugin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oras_pants_plugin/__init__.py +0 -0
- oras_pants_plugin/fields.py +113 -0
- oras_pants_plugin/goals/__init__.py +0 -0
- oras_pants_plugin/goals/publish.py +203 -0
- oras_pants_plugin/py.typed +0 -0
- oras_pants_plugin/register.py +20 -0
- oras_pants_plugin/subsystem.py +84 -0
- oras_pants_plugin/version.py +1 -0
- oras_pants_plugin-0.1.0.dist-info/METADATA +163 -0
- oras_pants_plugin-0.1.0.dist-info/RECORD +13 -0
- oras_pants_plugin-0.1.0.dist-info/WHEEL +5 -0
- oras_pants_plugin-0.1.0.dist-info/licenses/LICENSE +201 -0
- oras_pants_plugin-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""The fields the plugin adds to `pex_binary` and `pex_binaries` targets."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from pants.backend.python.target_types import PexBinariesGeneratorTarget, PexBinary
|
|
7
|
+
from pants.engine.addresses import Address
|
|
8
|
+
from pants.engine.target import BoolField, Field, InvalidFieldException, StringField, StringSequenceField
|
|
9
|
+
from pants.util.strutil import softwrap
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pants_plugin_developer_utils import CollectedRules
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_untagged_reference(reference: str) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Whether `reference` is a `<registry>/<repository>` OCI reference without a `:<tag>` or `@<digest>` suffix.
|
|
18
|
+
|
|
19
|
+
A registry host may carry a port (`localhost:5000/app`), so only the last path segment is inspected.
|
|
20
|
+
"""
|
|
21
|
+
registry, separator, repository = reference.rpartition("/")
|
|
22
|
+
return bool(registry and separator and repository) and ":" not in repository and "@" not in repository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OrasRepositoriesField(StringSequenceField):
|
|
26
|
+
"""Where `oras` pushes the target's package output, as untagged OCI references."""
|
|
27
|
+
|
|
28
|
+
alias = "oci_repositories"
|
|
29
|
+
help = softwrap(
|
|
30
|
+
"""
|
|
31
|
+
The OCI repositories to push this target's package output to as an OCI artifact, given as
|
|
32
|
+
`<registry>/<repository>` references such as `ghcr.io/my-org/my-app` or `localhost:5000/my-app`.
|
|
33
|
+
`pants publish` runs one `oras push` per entry, tagging the artifact with every `oci_tags` entry, so a
|
|
34
|
+
reference MUST NOT carry a `:<tag>` or `@<digest>` suffix. Leave unset to skip the target.
|
|
35
|
+
"""
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def compute_value(cls, raw_value: Iterable[str] | None, address: Address) -> tuple[str, ...] | None:
|
|
40
|
+
value = super().compute_value(raw_value, address)
|
|
41
|
+
invalid = [reference for reference in value or () if not is_untagged_reference(reference)]
|
|
42
|
+
if invalid:
|
|
43
|
+
raise InvalidFieldException(
|
|
44
|
+
softwrap(
|
|
45
|
+
f"""
|
|
46
|
+
The `{cls.alias}` field in target {address} must contain `<registry>/<repository>` references
|
|
47
|
+
without a tag or digest; the tags come from the `{OrasTagsField.alias}` field.
|
|
48
|
+
Invalid entries: {invalid}.
|
|
49
|
+
"""
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OrasTagsField(StringSequenceField):
|
|
56
|
+
"""The tags `oras` applies to the pushed artifact in every repository."""
|
|
57
|
+
|
|
58
|
+
alias = "oci_tags"
|
|
59
|
+
default = ("latest",)
|
|
60
|
+
help = softwrap(
|
|
61
|
+
"""
|
|
62
|
+
The tags to apply to the pushed OCI artifact in every `oci_repositories` entry. An empty list pushes the
|
|
63
|
+
artifact untagged, addressable by its digest only.
|
|
64
|
+
"""
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OciMediaType(StringField):
|
|
69
|
+
"""What OCI `artifactType` to designate the `oras`-published artifact."""
|
|
70
|
+
|
|
71
|
+
alias = "oci_media_type"
|
|
72
|
+
help = softwrap(
|
|
73
|
+
"""
|
|
74
|
+
The media type identifying the kind of OCI artifact that wraps the package output of this target, passed
|
|
75
|
+
to `oras push` as `--artifact-type` and stored as the manifest's `artifactType`.
|
|
76
|
+
See also: https://oras.land/docs/concepts/artifact
|
|
77
|
+
"""
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class PexOciMediaType(OciMediaType):
|
|
82
|
+
"""The default OCI artifact type for a PEX published via `oras` to an OCI registry."""
|
|
83
|
+
|
|
84
|
+
default = "application/vnd.python.pex.v1"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class SkipOrasPublishField(BoolField):
|
|
88
|
+
"""A custom field to opt-out of `oras` publication to the configured OCI registry(s)."""
|
|
89
|
+
|
|
90
|
+
alias = "skip_oras"
|
|
91
|
+
default = False
|
|
92
|
+
help = "If true, don't publish this target's package output with `oras` during `pants publish`."
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
PEX_BINARY_PLUGIN_FIELDS: tuple[type[Field], ...] = (
|
|
96
|
+
OrasRepositoriesField,
|
|
97
|
+
OrasTagsField,
|
|
98
|
+
PexOciMediaType,
|
|
99
|
+
SkipOrasPublishField,
|
|
100
|
+
)
|
|
101
|
+
"""The fields registered on `pex_binary`, and moved from `pex_binaries` onto the targets it generates."""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def rules() -> CollectedRules:
|
|
105
|
+
"""Registers the plugin's fields on `pex_binary` and `pex_binaries`."""
|
|
106
|
+
return (
|
|
107
|
+
*(PexBinary.register_plugin_field(field) for field in PEX_BINARY_PLUGIN_FIELDS),
|
|
108
|
+
# `pex_binaries` moves its per-binary fields onto the generated `pex_binary` targets; so must these.
|
|
109
|
+
*(
|
|
110
|
+
PexBinariesGeneratorTarget.register_plugin_field(field, as_moved_field=True)
|
|
111
|
+
for field in PEX_BINARY_PLUGIN_FIELDS
|
|
112
|
+
),
|
|
113
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements a publisher for wrapping `BuiltPackage` instances as OCI artifacts and publishing those to an OCI
|
|
3
|
+
registry by using `oras`.
|
|
4
|
+
See also:
|
|
5
|
+
https://www.pantsbuild.org/stable/docs/writing-plugins/common-plugin-tasks/add-a-publisher
|
|
6
|
+
https://oras.land/
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING, Final
|
|
12
|
+
|
|
13
|
+
from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet
|
|
14
|
+
from pants.backend.python.target_types import PexLayoutField
|
|
15
|
+
from pants.core.goals.package import PackageFieldSet
|
|
16
|
+
from pants.core.goals.publish import (
|
|
17
|
+
CheckSkipRequest,
|
|
18
|
+
CheckSkipResult,
|
|
19
|
+
PublishFieldSet,
|
|
20
|
+
PublishOutputData,
|
|
21
|
+
PublishPackages,
|
|
22
|
+
PublishProcesses,
|
|
23
|
+
PublishRequest,
|
|
24
|
+
)
|
|
25
|
+
from pants.core.util_rules.env_vars import environment_vars_subset
|
|
26
|
+
from pants.core.util_rules.external_tool import download_external_tool
|
|
27
|
+
from pants.engine.env_vars import EnvironmentVarsRequest
|
|
28
|
+
from pants.engine.fs import Digest, MergeDigests
|
|
29
|
+
from pants.engine.intrinsics import merge_digests
|
|
30
|
+
from pants.engine.platform import Platform
|
|
31
|
+
from pants.engine.process import InteractiveProcess, Process, ProcessCacheScope
|
|
32
|
+
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
|
|
33
|
+
from pants.engine.unions import UnionRule
|
|
34
|
+
from pants.util.logging import LogLevel
|
|
35
|
+
from pants.util.strutil import pluralize
|
|
36
|
+
|
|
37
|
+
from oras_pants_plugin.fields import (
|
|
38
|
+
OciMediaType,
|
|
39
|
+
OrasRepositoriesField,
|
|
40
|
+
OrasTagsField,
|
|
41
|
+
PexOciMediaType,
|
|
42
|
+
SkipOrasPublishField,
|
|
43
|
+
)
|
|
44
|
+
from oras_pants_plugin.subsystem import ORAS_BASE_ENV_VAR_NAMES, Oras
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from pants_plugin_developer_utils import CollectedRules
|
|
48
|
+
|
|
49
|
+
ORAS_INPUT_DIR: Final[str] = "__oras__"
|
|
50
|
+
"""Sandbox directory the downloaded `oras` binary is mounted in, out of the way of the pushed files."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OrasPublishRequest(PublishRequest["OrasPublishFieldSet"]):
|
|
54
|
+
"""The general `PublishRequest` type which should be subtyped per `oras`-publishable target type."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class OrasPublishPexRequest(OrasPublishRequest):
|
|
58
|
+
"""The `PublishRequest` subtype specifically for publishing PEX artifacts to OCI registries."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class OrasPublishFieldSet(PublishFieldSet[OrasPublishRequest]):
|
|
63
|
+
"""
|
|
64
|
+
The base `PublishFieldSet` to subtype per `oras`-publishable target type.
|
|
65
|
+
|
|
66
|
+
A subtype narrows `required_fields` to the target type's `OciMediaType` subclass, so `oci_media_type` carries
|
|
67
|
+
that subclass's default.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
required_fields = (OrasRepositoriesField, OrasTagsField, OciMediaType, SkipOrasPublishField)
|
|
71
|
+
|
|
72
|
+
oci_repositories: OrasRepositoriesField
|
|
73
|
+
oci_tags: OrasTagsField
|
|
74
|
+
oci_media_type: OciMediaType
|
|
75
|
+
skip_oras: SkipOrasPublishField
|
|
76
|
+
|
|
77
|
+
def check_skip_request(self, package_fs: PackageFieldSet) -> OrasPublishSkipRequest | None:
|
|
78
|
+
return OrasPublishSkipRequest(package_fs=package_fs, publish_fs=self)
|
|
79
|
+
|
|
80
|
+
def get_output_data(self) -> PublishOutputData:
|
|
81
|
+
return PublishOutputData({"publisher": "oras", **super().get_output_data()})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class OrasPublishPexFieldSet(OrasPublishFieldSet):
|
|
86
|
+
"""The fields required for publishing a `pex_binary` target's package output artifact to an OCI registry."""
|
|
87
|
+
|
|
88
|
+
publish_request_type = OrasPublishPexRequest
|
|
89
|
+
required_fields = (*OrasPublishFieldSet.required_fields, PexOciMediaType, PexLayoutField)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class OrasPublishSkipRequest(CheckSkipRequest[OrasPublishFieldSet]):
|
|
93
|
+
"""The input to determining if an `oras`-compatible target should be skipped during a publish run."""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@rule(desc="Checks whether `oras push` is skipped for a target.", level=LogLevel.DEBUG)
|
|
97
|
+
async def check_if_skip_oras_push(request: OrasPublishSkipRequest, oras: Oras) -> CheckSkipResult:
|
|
98
|
+
"""Skips both packaging and publishing when nothing would be pushed, and says why."""
|
|
99
|
+
if oras.skip:
|
|
100
|
+
reason = f"(by `[{Oras.options_scope}].skip = True`)"
|
|
101
|
+
elif request.publish_fs.skip_oras.value:
|
|
102
|
+
reason = f"(by `{request.publish_fs.skip_oras.alias}` on {request.address})"
|
|
103
|
+
elif not request.publish_fs.oci_repositories.value:
|
|
104
|
+
reason = f"(no `{request.publish_fs.oci_repositories.alias}` specified for {request.address})"
|
|
105
|
+
else:
|
|
106
|
+
return CheckSkipResult.no_skip()
|
|
107
|
+
name = (
|
|
108
|
+
request.package_fs.output_pex_filename()
|
|
109
|
+
if isinstance(request.package_fs, PexBinaryFieldSet)
|
|
110
|
+
else request.address.spec
|
|
111
|
+
)
|
|
112
|
+
return CheckSkipResult.skip(names=[name], description=reason, data=request.publish_fs.get_output_data())
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(frozen=True)
|
|
116
|
+
class OrasPushRequest:
|
|
117
|
+
"""One `oras push`: every `files` entry becomes a layer of the artifact at `repository`, tagged with `tags`."""
|
|
118
|
+
|
|
119
|
+
repository: str
|
|
120
|
+
tags: tuple[str, ...]
|
|
121
|
+
files: tuple[str, ...]
|
|
122
|
+
input_digest: Digest
|
|
123
|
+
artifact_type: str | None = None
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def reference(self) -> str:
|
|
127
|
+
"""The `<repository>[:<tag>[,<tag>...]]` argument of `oras push`; no tags pushes the artifact untagged."""
|
|
128
|
+
return f"{self.repository}:{','.join(self.tags)}" if self.tags else self.repository
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def construct_oras_push_argv(oras: Oras, exe: str, request: OrasPushRequest) -> tuple[str, ...]:
|
|
132
|
+
"""Returns the full `oras push` command line for `request`, with `[oras].args` after the plugin's own flags."""
|
|
133
|
+
argv: list[str] = [exe, "push"]
|
|
134
|
+
if request.artifact_type:
|
|
135
|
+
argv.extend(["--artifact-type", request.artifact_type])
|
|
136
|
+
argv.extend(oras.args)
|
|
137
|
+
argv.append(request.reference)
|
|
138
|
+
# `oras` parses `<file>:<media type>`, so a path containing `:` needs a trailing `:` to keep the default type.
|
|
139
|
+
argv.extend(f"{file}:" if ":" in file else file for file in request.files)
|
|
140
|
+
return tuple(argv)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@rule(desc="Set up an `oras push` process", level=LogLevel.DEBUG)
|
|
144
|
+
async def setup_oras_push_process(request: OrasPushRequest, oras: Oras, platform: Platform) -> Process:
|
|
145
|
+
downloaded_oras, env = await concurrently(
|
|
146
|
+
download_external_tool(oras.get_request(platform)),
|
|
147
|
+
environment_vars_subset(
|
|
148
|
+
EnvironmentVarsRequest([*ORAS_BASE_ENV_VAR_NAMES, *oras.extra_env_vars]), **implicitly()
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
return Process(
|
|
152
|
+
argv=construct_oras_push_argv(
|
|
153
|
+
oras, os.path.normpath(os.path.join(ORAS_INPUT_DIR, downloaded_oras.exe)), request
|
|
154
|
+
),
|
|
155
|
+
input_digest=request.input_digest,
|
|
156
|
+
immutable_input_digests={ORAS_INPUT_DIR: downloaded_oras.digest},
|
|
157
|
+
env=env,
|
|
158
|
+
description=f"Push {pluralize(len(request.files), 'file')} to {request.reference} with `oras`",
|
|
159
|
+
level=LogLevel.DEBUG,
|
|
160
|
+
# Publishing must run every time: the engine asserts this scope for non-interactive publish processes.
|
|
161
|
+
cache_scope=ProcessCacheScope.PER_SESSION,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@rule(
|
|
166
|
+
desc="Uses `oras` to wrap PEX artifacts as an OCI artifact and push to the configured OCI registry.",
|
|
167
|
+
level=LogLevel.DEBUG,
|
|
168
|
+
)
|
|
169
|
+
async def publish_pex_as_oci_artifact(request: OrasPublishPexRequest) -> PublishProcesses:
|
|
170
|
+
"""One interactive `oras push` per repository, each pushing every artifact `pants package` built."""
|
|
171
|
+
files = tuple(artifact.relpath for pkg in request.packages for artifact in pkg.artifacts if artifact.relpath)
|
|
172
|
+
if not files:
|
|
173
|
+
return PublishProcesses()
|
|
174
|
+
|
|
175
|
+
input_digest = await merge_digests(MergeDigests(pkg.digest for pkg in request.packages))
|
|
176
|
+
field_set = request.field_set
|
|
177
|
+
push_requests = tuple(
|
|
178
|
+
OrasPushRequest(
|
|
179
|
+
repository=repository,
|
|
180
|
+
tags=field_set.oci_tags.value or (),
|
|
181
|
+
files=files,
|
|
182
|
+
input_digest=input_digest,
|
|
183
|
+
artifact_type=field_set.oci_media_type.value,
|
|
184
|
+
)
|
|
185
|
+
for repository in field_set.oci_repositories.value or ()
|
|
186
|
+
)
|
|
187
|
+
processes = await concurrently(
|
|
188
|
+
setup_oras_push_process(push_request, **implicitly()) for push_request in push_requests
|
|
189
|
+
)
|
|
190
|
+
return PublishProcesses(
|
|
191
|
+
PublishPackages(
|
|
192
|
+
names=files,
|
|
193
|
+
process=InteractiveProcess.from_process(process),
|
|
194
|
+
description=push_request.reference,
|
|
195
|
+
data=PublishOutputData({"repository": push_request.repository, "tags": push_request.tags}),
|
|
196
|
+
)
|
|
197
|
+
for push_request, process in zip(push_requests, processes, strict=True)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def rules() -> CollectedRules:
|
|
202
|
+
"""Returns the `publish`-goal-specific rules for the `oras` pants plugin."""
|
|
203
|
+
return (*collect_rules(), *OrasPublishPexFieldSet.rules(), UnionRule(CheckSkipRequest, OrasPublishSkipRequest))
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Backend entry point: Pants loads this module when `oras_pants_plugin` is in `backend_packages`."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from oras_pants_plugin.fields import rules as field_rules
|
|
6
|
+
from oras_pants_plugin.goals.publish import rules as publish_rules
|
|
7
|
+
from oras_pants_plugin.subsystem import rules as subsystem_rules
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pants_plugin_developer_utils import CollectedRules, CollectedTargetTypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def rules() -> CollectedRules:
|
|
14
|
+
"""Return every rule this backend registers with the Pants engine."""
|
|
15
|
+
return (*field_rules(), *publish_rules(), *subsystem_rules())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def target_types() -> CollectedTargetTypes:
|
|
19
|
+
"""The plugin adds fields to the builtin `pex_binary` targets and registers no target types of its own."""
|
|
20
|
+
return ()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""The `oras` subsystem: where the binary comes from and how `pants publish` runs it."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Final
|
|
4
|
+
|
|
5
|
+
from pants.core.goals.resolves import ExportableTool
|
|
6
|
+
from pants.core.util_rules.external_tool import TemplatedExternalTool
|
|
7
|
+
from pants.engine.platform import Platform
|
|
8
|
+
from pants.engine.rules import collect_rules
|
|
9
|
+
from pants.engine.unions import UnionRule
|
|
10
|
+
from pants.option.option_types import ArgsListOption, SkipOption, StrListOption
|
|
11
|
+
from pants.util.strutil import softwrap
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pants_plugin_developer_utils import CollectedRules
|
|
15
|
+
|
|
16
|
+
ORAS_BASE_ENV_VAR_NAMES: Final[tuple[str, ...]] = ("PATH", "HOME", "DOCKER_CONFIG")
|
|
17
|
+
"""
|
|
18
|
+
Always passed to `oras` processes. `oras` reads the credentials `docker login` and `oras login` store in
|
|
19
|
+
`$DOCKER_CONFIG/config.json` or `~/.docker/config.json`, and runs the credential helpers named there from `PATH`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Oras(TemplatedExternalTool):
|
|
24
|
+
"""
|
|
25
|
+
The configuration and tooling wrapper for the `oras` binary to publish arbitrary artifacts as OCI artifacts in
|
|
26
|
+
OCI-compliant registries. See also: https://oras.land/docs/
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
options_scope = "oras"
|
|
30
|
+
name = "oras"
|
|
31
|
+
help = "The `oras` CLI for pushing files to OCI registries as OCI artifacts (https://oras.land/)."
|
|
32
|
+
|
|
33
|
+
default_version = "1.3.4"
|
|
34
|
+
# Compute with: `curl -L $URL | tee >(wc -c) >(shasum -a 256) >/dev/null`
|
|
35
|
+
# Pants declares these class attributes without `ClassVar`, so `ty` rejects a `ClassVar` annotation here.
|
|
36
|
+
default_known_versions = [ # noqa: RUF012
|
|
37
|
+
"1.3.4|linux_arm64 |15702c6e3a4a56a8bd8ac5c17efdbcab56d9bada661ccbcf017f5b10c1d89399|4238235",
|
|
38
|
+
"1.3.4|linux_x86_64|f27adb935022d94df8dc77719c322dda592c78a0d57a6f7dcdd8d900b248c454|4679642",
|
|
39
|
+
"1.3.4|macos_arm64 |217761a9500242ff473de8656b5aca21136ff39e17e9e61fd8936bbfd902704c|4401355",
|
|
40
|
+
"1.3.4|macos_x86_64|5e964f3d5a36eb9499a9d3e252a86b09e7adf3e6f6447eec56fd249c6702af7e|4750901",
|
|
41
|
+
]
|
|
42
|
+
version_constraints = ">=1.3,<2"
|
|
43
|
+
default_url_template = (
|
|
44
|
+
"https://github.com/oras-project/oras/releases/download/v{version}/oras_{version}_{platform}.tar.gz"
|
|
45
|
+
)
|
|
46
|
+
# Go binaries are statically linked, so one Linux build runs on any distribution.
|
|
47
|
+
default_url_platform_mapping: dict[str, str] = { # noqa: RUF012
|
|
48
|
+
"linux_arm64": "linux_arm64",
|
|
49
|
+
"linux_x86_64": "linux_amd64",
|
|
50
|
+
"macos_arm64": "darwin_arm64",
|
|
51
|
+
"macos_x86_64": "darwin_amd64",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
skip = SkipOption("publish")
|
|
55
|
+
args = ArgsListOption(
|
|
56
|
+
example="--registry-config /path/to/auth.json --plain-http",
|
|
57
|
+
extra_help=softwrap(
|
|
58
|
+
"""
|
|
59
|
+
Added to every `oras push` run by `pants publish`, after the plugin's own flags
|
|
60
|
+
(https://oras.land/docs/commands/oras_push). A repeated flag overrides the plugin's value.
|
|
61
|
+
"""
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
extra_env_vars = StrListOption(
|
|
65
|
+
default=[],
|
|
66
|
+
advanced=True,
|
|
67
|
+
help=softwrap(
|
|
68
|
+
"""
|
|
69
|
+
Additional environment variables to make available to `oras` processes. Entries are either strings
|
|
70
|
+
in the form `ENV_VAR=value` to use explicitly, or just `ENV_VAR` to copy the value from Pants's own
|
|
71
|
+
environment. `PATH`, `HOME` and `DOCKER_CONFIG` are always passed through, so the credentials stored
|
|
72
|
+
by `docker login` or `oras login` and their credential helpers are found. Use this for proxy or TLS
|
|
73
|
+
settings such as `HTTPS_PROXY` or `SSL_CERT_FILE`.
|
|
74
|
+
"""
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def generate_exe(self, plat: Platform) -> str:
|
|
79
|
+
"""The release tarball extracts the binary to its root, next to the license."""
|
|
80
|
+
return "./oras"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def rules() -> CollectedRules:
|
|
84
|
+
return (*collect_rules(), UnionRule(ExportableTool, Oras))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oras-pants-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Pantsbuild plugin for publishing PEX binaries to OCI registries as OCI artifacts with oras.
|
|
5
|
+
Author: Zach Gottesman
|
|
6
|
+
Maintainer: zach-overflow
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/zach-overflow/pants-plugin-depot/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/zach-overflow/pants-plugin-depot/blob/main/provides/oras-pants-plugin/CHANGELOG.md
|
|
10
|
+
Project-URL: Repository, https://github.com/zach-overflow/pants-plugin-depot
|
|
11
|
+
Keywords: oci,oci artifact,oras,pants,pants plugin,pantsbuild,pex,publish,registry
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Classifier: Topic :: System :: Software Distribution
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.14
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pants-plugin-developer-utils>=0.1.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# oras-pants-plugin
|
|
26
|
+
|
|
27
|
+
A [Pantsbuild](https://www.pantsbuild.org/) plugin that publishes the output of `pex_binary` targets to OCI
|
|
28
|
+
registries as [OCI artifacts](https://oras.land/docs/concepts/artifact) with [`oras`](https://oras.land/). Any
|
|
29
|
+
registry that stores OCI artifacts works: GitHub Container Registry, Docker Hub, Artifact Registry, ECR, ACR,
|
|
30
|
+
Harbor, a local `registry:2`, and so on.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Add the plugin to `pants.toml` and enable its backend next to the Python backend:
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[GLOBAL]
|
|
38
|
+
plugins = ["oras-pants-plugin==<version>"]
|
|
39
|
+
backend_packages.add = ["pants.backend.python", "oras_pants_plugin"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Tell a `pex_binary` target where to push:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# src/app/BUILD
|
|
46
|
+
pex_binary(
|
|
47
|
+
name="app",
|
|
48
|
+
entry_point="main.py",
|
|
49
|
+
oci_repositories=["ghcr.io/my-org/app"],
|
|
50
|
+
oci_tags=["1.2.0", "latest"],
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then build and push it through the `publish` goal:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pants publish src/app:app
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Pull it back anywhere with `oras pull ghcr.io/my-org/app:1.2.0`, which restores `app.pex`.
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
`oras` is downloaded as a static binary; nothing is installed into a Pants resolve. For every `pex_binary`
|
|
65
|
+
target that lists `oci_repositories`, `pants publish`:
|
|
66
|
+
|
|
67
|
+
1. Packages the target as `pants package` would. A target without `oci_repositories`, with `skip_oras=True`,
|
|
68
|
+
or run with `[oras].skip = true` is skipped before it is packaged.
|
|
69
|
+
2. Runs one `oras push` per `oci_repositories` entry, in the foreground so `oras` can show progress. Every
|
|
70
|
+
artifact `pants package` produced for the target (the PEX, plus any `scie` executables) becomes a layer of
|
|
71
|
+
one OCI artifact, which is tagged with every `oci_tags` entry at once. The manifest's `artifactType` is the
|
|
72
|
+
target's `oci_media_type`.
|
|
73
|
+
3. Reports each push as published, failed or skipped, like every other publisher. `--publish-output` writes the
|
|
74
|
+
same as JSON, with the `repository` and `tags` of each push.
|
|
75
|
+
|
|
76
|
+
A `packed` or `loose` layout PEX is a directory, which `oras` pushes as a gzipped tar layer and `oras pull`
|
|
77
|
+
unpacks again. The layer media types are left to `oras`.
|
|
78
|
+
|
|
79
|
+
### Target fields
|
|
80
|
+
|
|
81
|
+
| Field | Target | Purpose |
|
|
82
|
+
| :----------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `oci_repositories` | `pex_binary`, `pex_binaries` | `<registry>/<repository>` references to push to, such as `ghcr.io/my-org/app` or `localhost:5000/app`. One push per entry. A reference MUST NOT carry a tag or digest. |
|
|
84
|
+
| `oci_tags` | `pex_binary`, `pex_binaries` | Tags applied to the artifact in every repository. Default: `["latest"]`. `[]` pushes the artifact untagged, addressable by digest only. |
|
|
85
|
+
| `oci_media_type` | `pex_binary`, `pex_binaries` | The `artifactType` of the pushed manifest, passed as `--artifact-type`. Default: `application/vnd.python.pex.v1`. |
|
|
86
|
+
| `skip_oras` | `pex_binary`, `pex_binaries` | If true, the target is skipped by `pants publish`. |
|
|
87
|
+
| `overrides` | `pex_binaries` | Per-binary values for the fields above, e.g. `overrides={"tool.py": {"skip_oras": True}}`. On `pex_binaries` the fields apply to every generated `pex_binary`. |
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
| Option | Default | Purpose |
|
|
92
|
+
| :------------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `[oras].skip` | `false` | Skip `oras` during `pants publish`. |
|
|
94
|
+
| `[oras].args` | `[]` | Extra arguments for every `oras push`, e.g. `--plain-http` or `--registry-config /path/to/auth.json`. They follow the plugin's own flags, so a repeated flag overrides it. |
|
|
95
|
+
| `[oras].extra_env_vars` | `[]` | Extra environment variables for `oras`, as `NAME=value` or `NAME` to copy from Pants' environment, e.g. `HTTPS_PROXY` or `SSL_CERT_FILE`. |
|
|
96
|
+
| `[oras].version` | bundled default | `oras` release to download. |
|
|
97
|
+
| `[oras].known_versions` | bundled default | Checksums of downloadable releases; extend to use another version. |
|
|
98
|
+
|
|
99
|
+
### Credentials
|
|
100
|
+
|
|
101
|
+
`oras` reads the credentials that `docker login` or `oras login` store in `$DOCKER_CONFIG/config.json` or
|
|
102
|
+
`~/.docker/config.json`, and runs the credential helpers configured there. `PATH`, `HOME` and `DOCKER_CONFIG`
|
|
103
|
+
are always passed to `oras`, so logging in once is enough:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
oras login ghcr.io # or: docker login ghcr.io
|
|
107
|
+
pants publish src/app:app
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
In CI, pass the token to a login step before `pants publish`, or point `[oras].args` at an auth file with
|
|
111
|
+
`--registry-config`. Avoid `--username`/`--password` in `pants.toml`: the file is committed.
|
|
112
|
+
|
|
113
|
+
### Pushing to a directory
|
|
114
|
+
|
|
115
|
+
`oras` can write to an [OCI image layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md)
|
|
116
|
+
directory instead of a registry, which is handy for inspecting what would be pushed:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pants --oras-args="['--oci-layout-path', '/tmp/app-layout']" publish src/app:app
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The reference in `oci_repositories` is kept as the tag name inside the layout.
|
|
123
|
+
|
|
124
|
+
### Using a different `oras` version
|
|
125
|
+
|
|
126
|
+
Add the release's checksums and sizes to `known_versions` and set `version`:
|
|
127
|
+
|
|
128
|
+
```toml
|
|
129
|
+
[oras]
|
|
130
|
+
version = "1.3.5"
|
|
131
|
+
known_versions.add = [
|
|
132
|
+
"1.3.5|linux_arm64 |<sha256>|<size>",
|
|
133
|
+
"1.3.5|linux_x86_64|<sha256>|<size>",
|
|
134
|
+
"1.3.5|macos_arm64 |<sha256>|<size>",
|
|
135
|
+
"1.3.5|macos_x86_64|<sha256>|<size>",
|
|
136
|
+
]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Compute each line with `curl -L $URL | tee >(wc -c) >(shasum -a 256) >/dev/null`, where `$URL` is the
|
|
140
|
+
`oras_<version>_<os>_<arch>.tar.gz` asset of the
|
|
141
|
+
[GitHub release](https://github.com/oras-project/oras/releases) (`linux`/`darwin`, `amd64`/`arm64`). The
|
|
142
|
+
release's `oras_<version>_checksums.txt` lists the same checksums.
|
|
143
|
+
|
|
144
|
+
## Supported versions
|
|
145
|
+
|
|
146
|
+
| Plugin | Pants | Python | `oras` |
|
|
147
|
+
| :----- | :----- | :----- | :--------- |
|
|
148
|
+
| `0.x` | `2.33` | `3.14` | `>=1.3,<2` |
|
|
149
|
+
|
|
150
|
+
Pants loads plugins into its own interpreter, so the plugin requires the Python version Pants runs on. The PEX
|
|
151
|
+
being published may target any Python version.
|
|
152
|
+
|
|
153
|
+
## Releases
|
|
154
|
+
|
|
155
|
+
Wheels are published to [PyPI](https://pypi.org/project/oras-pants-plugin/). Release notes live in the
|
|
156
|
+
[changelog](https://github.com/zach-overflow/pants-plugin-depot/blob/main/provides/oras-pants-plugin/CHANGELOG.md)
|
|
157
|
+
and on the [GitHub releases page](https://github.com/zach-overflow/pants-plugin-depot/releases).
|
|
158
|
+
|
|
159
|
+
## Contributing, Bug Reports and Feature Requests
|
|
160
|
+
|
|
161
|
+
This plugin lives in the [pants-plugin-depot](https://github.com/zach-overflow/pants-plugin-depot) repo.
|
|
162
|
+
See its [CONTRIBUTING.md](https://github.com/zach-overflow/pants-plugin-depot/blob/main/CONTRIBUTING.md), or
|
|
163
|
+
open an [issue](https://github.com/zach-overflow/pants-plugin-depot/issues/new/choose).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
oras_pants_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
oras_pants_plugin/fields.py,sha256=2aHICEvew7-AW1PZWmIcWOXifX98aJ9TdMfV_yAKhq4,4374
|
|
3
|
+
oras_pants_plugin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
oras_pants_plugin/register.py,sha256=YBxqBDYmCUldPCt7TuAGgYwDop6Bf4mkjRGqoa4CxvI,772
|
|
5
|
+
oras_pants_plugin/subsystem.py,sha256=OjWuS7D7rrnJpOGcMtQ-TnN76QcMckJBGCroD_NI1ug,3783
|
|
6
|
+
oras_pants_plugin/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
7
|
+
oras_pants_plugin/goals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
oras_pants_plugin/goals/publish.py,sha256=vtgzsa4xU_8NtWp-yZWF4QXUMxcxhEOlghmTtqxf-bo,8427
|
|
9
|
+
oras_pants_plugin-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
10
|
+
oras_pants_plugin-0.1.0.dist-info/METADATA,sha256=dATDjCLCDiYf8VHrJIhs8WOxfEo2P4m9oy5RcesWkM4,8855
|
|
11
|
+
oras_pants_plugin-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
12
|
+
oras_pants_plugin-0.1.0.dist-info/top_level.txt,sha256=OyVbti6SvqLNhscUwlw3jFiyXn6ZwXaShtubLVa4sFE,18
|
|
13
|
+
oras_pants_plugin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oras_pants_plugin
|