Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ jobs:
username: ${{ secrets.QUAY_STACKROX_IO_RW_USERNAME }}
password: ${{ secrets.QUAY_STACKROX_IO_RW_PASSWORD }}

# Distro-less "FROM scratch" images.

- name: Prepare manifest OCI metadata for amd64
id: meta-amd64
uses: docker/metadata-action@v6
Expand Down Expand Up @@ -204,6 +206,149 @@ jobs:
docker manifest push "${tag}"
done

# Distro-full "FROM debian:stable-slim" images.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why not ubi?


- name: Prepare manifest OCI metadata for amd64 - distro
id: meta-amd64-distro
uses: docker/metadata-action@v6
with:
images: "quay.io/stackrox-io/image-prefetcher"
# generate Docker tags based on the following events/attributes
# See https://github.com/docker/metadata-action
flavor: suffix=-distro-amd64,onlatest=true
tags: |
type=ref,event=branch,prefix=branch-
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha

- name: Build and push OCI amd64 image - distro
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-amd64-distro.outputs.tags }}
labels: ${{ steps.meta-amd64-distro.outputs.labels }}
platforms: linux/amd64
build-args: |
ARCH=amd64
BASE=debian:stable-slim

- name: Prepare manifest OCI metadata for arm64 - distro
id: meta-arm64-distro
uses: docker/metadata-action@v6
with:
images: "quay.io/stackrox-io/image-prefetcher"
# generate Docker tags based on the following events/attributes
# See https://github.com/docker/metadata-action
flavor: suffix=-distro-arm64,onlatest=true
tags: |
type=ref,event=branch,prefix=branch-
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha

- name: Build and push OCI arm64 image - distro
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-arm64-distro.outputs.tags }}
labels: ${{ steps.meta-arm64-distro.outputs.labels }}
platforms: linux/arm64
build-args: |
ARCH=arm64
BASE=debian:stable-slim

- name: Prepare manifest OCI metadata for ppc64le - distro
id: meta-ppc64le-distro
uses: docker/metadata-action@v6
with:
images: "quay.io/stackrox-io/image-prefetcher"
# generate Docker tags based on the following events/attributes
# See https://github.com/docker/metadata-action
flavor: suffix=-distro-ppc64le,onlatest=true
tags: |
type=ref,event=branch,prefix=branch-
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha

- name: Build and push OCI ppc64le image - distro
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-ppc64le-distro.outputs.tags }}
labels: ${{ steps.meta-ppc64le-distro.outputs.labels }}
platforms: linux/ppc64le
build-args: |
ARCH=ppc64le
BASE=debian:stable-slim

- name: Prepare manifest OCI metadata for s390x - distro
id: meta-s390x-distro
uses: docker/metadata-action@v6
with:
images: "quay.io/stackrox-io/image-prefetcher"
# generate Docker tags based on the following events/attributes
# See https://github.com/docker/metadata-action
flavor: suffix=-distro-s390x,onlatest=true
tags: |
type=ref,event=branch,prefix=branch-
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha

- name: Build and push OCI s390x image - distro
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-s390x-distro.outputs.tags }}
labels: ${{ steps.meta-s390x-distro.outputs.labels }}
platforms: linux/s390x
build-args: |
ARCH=s390x
BASE=debian:stable-slim

- name: Prepare manifest OCI metadata - distro
id: meta-distro
uses: docker/metadata-action@v6
with:
images: "quay.io/stackrox-io/image-prefetcher"
# generate Docker tags based on the following events/attributes
# See https://github.com/docker/metadata-action
flavor: suffix=-distro
tags: |
type=ref,event=branch,prefix=branch-
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha

- name: Create and push multi-arch manifest - distro
if: github.event_name != 'pull_request'
env:
IMAGE_TAGS: ${{ steps.meta-distro.outputs.tags }}
run: |
for tag in ${IMAGE_TAGS};
do
docker manifest create "${tag}" \
--amend "${tag}-amd64" \
--amend "${tag}-arm64" \
--amend "${tag}-ppc64le" \
--amend "${tag}-s390x"
docker manifest push "${tag}"
done

e2e:
if: github.event_name != 'pull_request'
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM scratch
ARG BASE=scratch
FROM $BASE
ARG ARCH=amd64
COPY ./image-prefetcher-${ARCH} /image-prefetcher
ENTRYPOINT ["/image-prefetcher"]
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ It also optionally collects each pull attempt's duration and result.
Plugin credentials fetched dynamically and tried for the images configured in the `CredentialProviderConfig` before pull secrets.
Currently only supports mode `GKE`, which uses `/etc/srv/kubernetes/cri_auth_config.yaml` and `/home/kubernetes/bin` mounted from the host.

Example:
Note that in this case, the tool uses distro-based prefetcher images, to provide the dynamic
linker and shared libraries that a credential plugin binary might need.

Example:

```
go run github.com/stackrox/image-prefetcher/deploy@v0.3.0 --version v0.3.0 --namespace prefetch-images my-images > manifest.yaml
```
Expand Down
20 changes: 12 additions & 8 deletions deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,22 @@ func init() {
// processVersion processes the version string and returns the appropriate format.
// For versions with dashes containing SHA (like v0.4.2-0.20251126115717-559dd9fd402f),
// extracts short SHA and returns "sha-<shortSHA>" as long as the sha is at least 7 chars long.
// Otherwise, returns as is
func processVersion(version string) string {
// Otherwise, returns as is.
// If distro is true, appends "-distro" to the result.
func processVersion(version string, distro bool) string {
Comment on lines +60 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If distro is true, appends "-distro" to the result.
func processVersion(version string, distro bool) string {
// If useDistroImage is true, appends "-distro" to the result.
func processVersion(version string, useDistroImage bool) string {

nit: thought that distro is a bit cryptic

result := version
// Pattern: vX.Y.Z-0.timestamp-SHA produced by `go mod tidy`.
dashRegex := regexp.MustCompile(`^v\d+\.\d+\.\d+-\d+\.\d+-([a-f0-9]+)$`)
matches := dashRegex.FindStringSubmatch(version)
if len(matches) != 2 {
return version
if len(matches) == 2 {
if fullSHA := matches[1]; len(fullSHA) >= 7 {
result = fmt.Sprintf("sha-%s", fullSHA[:7])
}
}
if fullSHA := matches[1]; len(fullSHA) >= 7 {
return fmt.Sprintf("sha-%s", fullSHA[:7])
if distro {
result += "-distro"
}
return version
return result
}

func main() {
Expand All @@ -84,7 +88,7 @@ func main() {
Name: name,
Namespace: namespace,
Image: imageRepo,
Version: processVersion(version),
Version: processVersion(version, useKubeletImageCredentialIntegration != ""),
Secret: secret,
IsCRIO: isOcp,
NeedsPrivileged: isOcp,
Expand Down
38 changes: 15 additions & 23 deletions internal/credentialprovider/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

const (
supportedResponseAPIVersion = "credentialprovider.kubelet.k8s.io/v1"
supportedConfigApiVersion = "kubelet.config.k8s.io/v1"
supportedConfigAPIVersion = "kubelet.config.k8s.io/v1"
)

// Minimal mirrors of k8s.io/kubelet/config/v1.CredentialProviderConfig to avoid depending on k8s.io/kubernetes.
Expand Down Expand Up @@ -101,8 +101,8 @@ func readCredentialProviderConfig(configPath string) (*credentialProviderConfig,
return nil, fmt.Errorf("unexpected kind %q, expected CredentialProviderConfig", config.Kind)
}

if config.APIVersion != supportedConfigApiVersion {
return nil, fmt.Errorf("unexpected API version %q, expected %q", config.APIVersion, supportedConfigApiVersion)
if config.APIVersion != supportedConfigAPIVersion {
return nil, fmt.Errorf("unexpected API version %q, expected %q", config.APIVersion, supportedConfigAPIVersion)
}

return &config, nil
Expand All @@ -119,7 +119,7 @@ func (kr *PluginKeyring) LookupWithCtx(ctx context.Context, image string) ([]Aut
return nil, false
}

var allCreds []AuthConfig
dk := &BasicDockerKeyring{}
for _, provider := range kr.providers {
if !kr.matchesImage(provider.matchImages, image) {
continue
Expand All @@ -131,15 +131,10 @@ func (kr *PluginKeyring) LookupWithCtx(ctx context.Context, image string) ([]Aut
kr.logger.Warn("credential provider plugin failed", "plugin", provider.name, "image", image, "error", err)
continue
}

allCreds = append(allCreds, creds...)
}

if len(allCreds) > 0 {
return allCreds, true
dk.Add(creds)
}

return nil, false
return dk.Lookup(image)
}

// matchesImage checks if any of the match patterns match the given image.
Expand All @@ -154,7 +149,7 @@ func (kr *PluginKeyring) matchesImage(patterns []string, image string) bool {
}

// execPlugin executes the credential provider plugin and parses the response.
func (kr *PluginKeyring) execPlugin(ctx context.Context, provider pluginProviderWrapper, image string) ([]AuthConfig, error) {
func (kr *PluginKeyring) execPlugin(ctx context.Context, provider pluginProviderWrapper, image string) (DockerConfig, error) {
// Prepare the request
request := credentialproviderv1.CredentialProviderRequest{
Image: image,
Expand Down Expand Up @@ -188,16 +183,13 @@ func (kr *PluginKeyring) execPlugin(ctx context.Context, provider pluginProvider
return nil, fmt.Errorf("apiVersion from credential plugin response did not match expected apiVersion:%s, actual apiVersion:%s", supportedResponseAPIVersion, response.APIVersion)
}

// Convert to AuthConfig
var creds []AuthConfig
for registry, authConfig := range response.Auth {
creds = append(creds, AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: registry,
})
kr.logger.Debug("received credentials from plugin", "plugin", provider.name, "count", len(response.Auth))
dockerConfig := make(DockerConfig, len(response.Auth))
for matchImage, authConfig := range response.Auth {
dockerConfig[matchImage] = DockerConfigEntry{
Username: authConfig.Username,
Password: authConfig.Password,
}
}

kr.logger.Debug("received credentials from plugin", "plugin", provider.name, "count", len(creds))
return creds, nil
return dockerConfig, nil
}
7 changes: 3 additions & 4 deletions internal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func Run(logger *slog.Logger, criSocketPath string, dockerConfigJSONPath string,
},
Auth: auth,
}
go pullImageWithRetries(ctx, logger.With("image", imageName, "authNum", i), &wg, criClient, metricsSink.Chan(), imageName, request, timing, &results)
go pullImageWithRetries(ctx, logger.With("image", imageName, "authNum", i, "authServer", auth.ServerAddress, "authUsername", auth.Username), &wg, criClient, metricsSink.Chan(), imageName, request, timing, &results)
}
}
wg.Wait()
Expand Down Expand Up @@ -141,9 +141,8 @@ func getAuthsForImage(ctx context.Context, logger *slog.Logger, pluginKr *creden
logger.DebugContext(ctx, "got credentials from plugin", "image", imageName, "count", len(pluginCreds))
for _, creds := range pluginCreds {
auth := &criV1.AuthConfig{
Username: creds.Username,
Password: creds.Password,
ServerAddress: creds.ServerAddress,
Username: creds.Username,
Password: creds.Password,
}
auths = append(auths, auth)
}
Expand Down
Loading