Skip to content

Commit ff6ed75

Browse files
committed
pull: airgapped mode
Adds a new "airgapped" attribute to the pull rule. If set, it can be used to ensure that any requests are provided by predownloaded files from "blob_files".
1 parent 39464a7 commit ff6ed75

File tree

4 files changed

+21
-4
lines changed

4 files changed

+21
-4
lines changed

docs/pull.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Public API for pulling base container images.
99
<pre>
1010
load("@rules_img//img:pull.bzl", "pull")
1111

12-
pull(<a href="#pull-name">name</a>, <a href="#pull-blob_files">blob_files</a>, <a href="#pull-digest">digest</a>, <a href="#pull-downloader">downloader</a>, <a href="#pull-layer_handling">layer_handling</a>, <a href="#pull-registries">registries</a>, <a href="#pull-registry">registry</a>, <a href="#pull-repo_mapping">repo_mapping</a>,
13-
<a href="#pull-repository">repository</a>, <a href="#pull-tag">tag</a>)
12+
pull(<a href="#pull-name">name</a>, <a href="#pull-airgapped">airgapped</a>, <a href="#pull-blob_files">blob_files</a>, <a href="#pull-digest">digest</a>, <a href="#pull-downloader">downloader</a>, <a href="#pull-layer_handling">layer_handling</a>, <a href="#pull-registries">registries</a>, <a href="#pull-registry">registry</a>,
13+
<a href="#pull-repo_mapping">repo_mapping</a>, <a href="#pull-repository">repository</a>, <a href="#pull-tag">tag</a>)
1414
</pre>
1515

1616
Pulls a container image from a registry using shallow pulling.
@@ -42,6 +42,7 @@ will resolve the tag to a digest at fetch time and print a warning.
4242
| Name | Description | Type | Mandatory | Default |
4343
| :------------- | :------------- | :------------- | :------------- | :------------- |
4444
| <a id="pull-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
45+
| <a id="pull-airgapped"></a>airgapped | Enable airgapped mode.<br><br>When enabled, the pull tool will only use locally cached blobs and will not attempt any network requests. This is useful for completely offline/air-gapped environments where all required blobs must be provided via blob_files.<br><br>If a required blob is not available locally, the pull will fail rather than attempting to download it. | Boolean | optional | `False` |
4546
| <a id="pull-blob_files"></a>blob_files | Pre-downloaded blob files to use.<br><br>A dictionary mapping blob digests (e.g., "sha256:abc123...") to file labels. These blobs will be verified and used instead of downloading from the registry when available. This is useful for air-gapped environments or to avoid redundant downloads of common base layers. | Dictionary: String -> Label | optional | `{}` |
4647
| <a id="pull-digest"></a>digest | The image digest for reproducible pulls (e.g., "sha256:abc123...").<br><br>When specified, the image is pulled by digest instead of tag, ensuring reproducible builds. The digest must be a full SHA256 digest starting with "sha256:". | String | optional | `""` |
4748
| <a id="pull-downloader"></a>downloader | The tool to use for downloading manifests and blobs.<br><br>**Available options:**<br><br>* **`img_tool`** (default): Uses the `img` tool for all downloads.<br><br>* **`bazel`**: Uses Bazel's native HTTP capabilities for downloading manifests and blobs. | String | optional | `"img_tool"` |

img/private/repository_rules/download.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,15 @@ def get_layers(rctx, digests):
181181
for digest in digests
182182
]
183183

184-
def download_with_tool(rctx, *, tool_path, reference, blob_files = {}):
184+
def download_with_tool(rctx, *, tool_path, reference, blob_files = {}, airgapped = False):
185185
"""Download an image using the img tool.
186186
187187
Args:
188188
rctx: Repository context.
189189
tool_path: The path to the img tool to use for downloading.
190190
reference: The image reference to download.
191191
blob_files: Dictionary mapping blob digests to file labels.
192+
airgapped: Enable airgapped mode (no network access).
192193
193194
Returns:
194195
A struct containing manifest and layers of the downloaded image.
@@ -223,6 +224,8 @@ def download_with_tool(rctx, *, tool_path, reference, blob_files = {}):
223224
"--repository=" + rctx.attr.repository,
224225
"--layer-handling=" + rctx.attr.layer_handling,
225226
] + ["--registry=" + r for r in registries]
227+
if airgapped:
228+
args.append("--airgapped")
226229
result = rctx.execute(args, quiet = False)
227230
if result.return_code != 0:
228231
fail("img tool failed with exit code {} and message {}".format(result.return_code, result.stderr))

img/private/repository_rules/pull.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def _pull_impl(rctx):
6161
tool_path = tool_path,
6262
reference = reference,
6363
blob_files = rctx.attr.blob_files,
64+
airgapped = rctx.attr.airgapped,
6465
)
6566

6667
manifest_kwargs = dict(
@@ -359,6 +360,16 @@ These blobs will be verified and used instead of downloading from the registry w
359360
This is useful for air-gapped environments or to avoid redundant downloads of common base layers.""",
360361
allow_files = True,
361362
),
363+
"airgapped": attr.bool(
364+
default = False,
365+
doc = """Enable airgapped mode.
366+
367+
When enabled, the pull tool will only use locally cached blobs and will not attempt any network
368+
requests. This is useful for completely offline/air-gapped environments where all required blobs
369+
must be provided via blob_files.
370+
371+
If a required blob is not available locally, the pull will fail rather than attempting to download it.""",
372+
),
362373
},
363374
)
364375

pull_tool/cmd/internal/pull/pull.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func PullProcess(ctx context.Context, args []string) {
2626
var registries stringSliceFlag
2727
var layerHandling string
2828
var concurrency int
29+
var airgapped bool
2930

3031
flagSet := flag.NewFlagSet("pull", flag.ExitOnError)
3132
flagSet.Usage = func() {
@@ -48,6 +49,7 @@ func PullProcess(ctx context.Context, args []string) {
4849
flagSet.Var(&registries, "registry", "Registry to use (can be specified multiple times, defaults to docker.io)")
4950
flagSet.StringVar(&layerHandling, "layer-handling", "shallow", "Method used for handling layer data. \"eager\" causes layer data to be materialized.")
5051
flagSet.IntVar(&concurrency, "j", 10, "Number of concurrent download workers")
52+
flagSet.BoolVar(&airgapped, "airgapped", false, "Enable airgapped mode (only use local cached blobs, no network access)")
5153

5254
if err := flagSet.Parse(args); err != nil {
5355
flagSet.Usage()
@@ -77,7 +79,7 @@ func PullProcess(ctx context.Context, args []string) {
7779
os.Exit(1)
7880
}
7981
// Create a custom HTTP client with cached blob transport
80-
transport := cachedblob.NewTransport(outputDir, http.DefaultTransport)
82+
transport := cachedblob.NewTransport(outputDir, http.DefaultTransport, cachedblob.WithAirgapped(airgapped))
8183

8284
// Default to docker.io if no registries specified
8385
if len(registries) == 0 {

0 commit comments

Comments
 (0)