Skip to content

Commit

Permalink
Prefactoring before adding metrics support.
Browse files Browse the repository at this point in the history
- change deploy tool to use the flag package where appropriate rather
  than positional arguments for everything, as they would become a mess
  when another is added,
- rename daemonSetTemplate to deploymentTemplate as it already contains
  more than the daemonset, and will continue to grow soon,
- rename some variables to mention CRI to make room for an upcoming
  non-cri client/connection,
- extract a logging package, to be used in upcoming subcommand,
- in README and GHA workflow, correct deploy tool usage
- in README, mention daemonset in individual commands,
  • Loading branch information
porridge committed May 8, 2024
1 parent 4da0963 commit 2b62c53
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 38 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ jobs:
- name: Prepare manifests for linting
run: |
mkdir manifests
go run deploy/main.go my-images v0.0.8 vanilla > manifests/vanilla.yaml
go run deploy/main.go my-images v0.0.8 ocp > manifests/ocp.yaml
go run deploy/main.go my-images v0.0.8 vanilla my-secret > manifests/vanilla-with-secret.yaml
go run deploy/main.go my-images v0.0.8 ocp my-secret > manifests/ocp-with-secret.yaml
go run deploy/*.go --k8s-flavor vanilla my-images > manifests/vanilla.yaml
go run deploy/*.go --k8s-flavor ocp my-images > manifests/ocp.yaml
go run deploy/*.go --k8s-flavor vanilla --secret my-secret my-images > manifests/vanilla-with-secret.yaml
go run deploy/*.go --k8s-flavor ocp --secret my-secret my-images > manifests/ocp-with-secret.yaml
- name: kube-linter
uses: stackrox/[email protected]
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ Talks directly to Container Runtime Interface ([CRI](https://kubernetes.io/docs/
### `image-prefetcher`

- main binary,
- meant to be run in pods of a DaemonSet,
- shipped as an OCI image,
- provides two subcommands:
- `fetch`: runs the actual image pulls via CRI, meant to run as an init container,
- `fetch`: runs the actual image pulls via CRI, meant to run as an init container
of DaemonSet pods.
Requires access to the CRI UNIX domain socket from the host.
- `sleep`: just sleeps forever, meant to run as the main container,
- `sleep`: just sleeps forever, meant to run as the main container of DaemonSet pods.

### `deploy`

Expand All @@ -29,21 +29,22 @@ Talks directly to Container Runtime Interface ([CRI](https://kubernetes.io/docs/

You can run many instances independently.

It requires a few arguments:
- **name** of the instance.
This also determines the name of a `ConfigMap` supplying names of images to fetch.
- `image-prefetcher` OCI image **version**. See [list of existing tags](https://quay.io/repository/mowsiany/image-prefetcher?tab=tags).
- **cluster flavor**. Currently one of:
It requires a single positional argument for the **name** of the instance.
This also determines the name of a `ConfigMap` supplying names of images to fetch.

It also accepts a few optional flags:
- `--version`: `image-prefetcher` OCI image tag. See [list of existing tags](https://quay.io/repository/mowsiany/image-prefetcher?tab=tags).
- `--k8s-flavor` depending on the cluster. Currently one of:
- `vanilla`: a generic Kubernetes distribution without additional restrictions.
- `ocp`: OpenShift, which requires explicitly granting special privileges.
- optional **image pull `Secret` name**. Required if the images are not pullable anonymously.
- `--secret`: image pull `Secret` name. Required if the images are not pullable anonymously.
This image pull secret should be usable for all images fetched by the given instance.
If provided, it must be of type `kubernetes.io/dockerconfigjson` and exist in the same namespace.

Example:

```
go run github.com/stackrox/image-prefetcher/deploy@main my-images v0.0.8 vanilla > manifest.yaml
go run github.com/stackrox/image-prefetcher/deploy@master my-images v0.0.8 vanilla > manifest.yaml
```

2. Prepare an image list. This should be a plain text file with one image name per line.
Expand Down
11 changes: 3 additions & 8 deletions cmd/fetch.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package cmd

import (
"log/slog"
"os"
"strings"
"time"

"github.com/stackrox/image-prefetcher/internal"
"github.com/stackrox/image-prefetcher/internal/logging"

"github.com/spf13/cobra"
)
Expand All @@ -19,11 +19,7 @@ var fetchCmd = &cobra.Command{
It talks to Container Runtime Interface API to pull images in parallel, with retries.`,
RunE: func(cmd *cobra.Command, args []string) error {
opts := &slog.HandlerOptions{AddSource: true}
if debug {
opts.Level = slog.LevelDebug
}
logger := slog.New(slog.NewTextHandler(os.Stderr, opts))
logger := logging.GetLogger()
timing := internal.TimingConfig{
ImageListTimeout: imageListTimeout,
InitialPullAttemptTimeout: initialPullAttemptTimeout,
Expand All @@ -45,7 +41,6 @@ var (
criSocket string
dockerConfigJSONPath string
imageListFile string
debug bool
imageListTimeout = time.Minute
initialPullAttemptTimeout = 30 * time.Second
maxPullAttemptTimeout = 5 * time.Minute
Expand All @@ -56,11 +51,11 @@ var (

func init() {
rootCmd.AddCommand(fetchCmd)
logging.AddFlags(fetchCmd.Flags())

fetchCmd.Flags().StringVar(&criSocket, "cri-socket", "/run/containerd/containerd.sock", "Path to CRI UNIX socket.")
fetchCmd.Flags().StringVar(&dockerConfigJSONPath, "docker-config", "", "Path to docker config json file.")
fetchCmd.Flags().StringVar(&imageListFile, "image-list-file", "", "Path to text file containing images to pull (one per line).")
fetchCmd.Flags().BoolVar(&debug, "debug", false, "Whether to enable debug logging.")

fetchCmd.Flags().DurationVar(&imageListTimeout, "image-list-timeout", imageListTimeout, "Timeout for image list calls (for debugging).")
fetchCmd.Flags().DurationVar(&initialPullAttemptTimeout, "initial-pull-attempt-timeout", initialPullAttemptTimeout, "Timeout for initial image pull call. Each subsequent attempt doubles it until max.")
Expand Down
29 changes: 29 additions & 0 deletions deploy/flavor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"fmt"
"log"
"slices"
)

type k8sFlavorType string

func (k *k8sFlavorType) UnmarshalText(text []byte) error {
if slices.Contains(allFlavors, string(text)) {
*k = k8sFlavorType(text)
return nil
}
return fmt.Errorf("unknown k8s flavor %q", text)
}

func (k *k8sFlavorType) MarshalText() (text []byte, err error) {
return []byte(*k), nil
}

func flavor(flavor string) *k8sFlavorType {
var f k8sFlavorType
if err := f.UnmarshalText([]byte(flavor)); err != nil {
log.Fatal(err)
}
return &f
}
40 changes: 29 additions & 11 deletions deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package main

import (
_ "embed"
"flag"
"fmt"
"log"
"os"
"strings"
"text/template"
)

Expand All @@ -16,23 +19,38 @@ type settings struct {
NeedsPrivileged bool
}

const (
vanillaFlavor = "vanilla"
ocpFlavor = "ocp"
)

var allFlavors = []string{vanillaFlavor, ocpFlavor}

const imageRepo = "quay.io/stackrox-io/image-prefetcher"

//go:embed deployment.yaml.gotpl
var daemonSetTemplate string
var deploymentTemplate string

var (
version string
k8sFlavor k8sFlavorType
secret string
)

func init() {
flag.StringVar(&version, "version", "v0.1.0", "Version of image prefetcher OCI image.")
flag.TextVar(&k8sFlavor, "k8s-flavor", flavor(vanillaFlavor), fmt.Sprintf("Kubernetes flavor. Accepted values: %s", strings.Join(allFlavors, ",")))
flag.StringVar(&secret, "secret", "", "Kubernetes image pull Secret to use when pulling.")
}

func main() {
if len(os.Args) < 4 {
println("Usage:", os.Args[0], "<name> <version> vanilla|ocp [secret]")
flag.Parse()
if len(flag.Args()) < 1 {
println("Usage:", os.Args[0], "[ FLAGS ] <name>")
os.Exit(1)
}
name := os.Args[1]
version := os.Args[2]
isOcp := os.Args[3] == "ocp"
secret := ""
if len(os.Args) > 4 {
secret = os.Args[4]
}
name := flag.Arg(0)
isOcp := k8sFlavor == ocpFlavor

s := settings{
Name: name,
Expand All @@ -42,7 +60,7 @@ func main() {
IsCRIO: isOcp,
NeedsPrivileged: isOcp,
}
tmpl := template.Must(template.New("deployment").Parse(daemonSetTemplate))
tmpl := template.Must(template.New("deployment").Parse(deploymentTemplate))
if err := tmpl.Execute(os.Stdout, s); err != nil {
log.Fatal(err)
}
Expand Down
22 changes: 22 additions & 0 deletions internal/logging/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package logging

import (
"log/slog"
"os"

"github.com/spf13/pflag"
)

var debug bool

func GetLogger() *slog.Logger {
opts := &slog.HandlerOptions{AddSource: true}
if debug {
opts.Level = slog.LevelDebug
}
return slog.New(slog.NewTextHandler(os.Stderr, opts))
}

func AddFlags(flags *pflag.FlagSet) {
flags.BoolVar(&debug, "debug", false, "Whether to enable debug logging.")
}
10 changes: 5 additions & 5 deletions internal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ func Run(logger *slog.Logger, criSocketPath string, dockerConfigJSONPath string,
ctx, cancel := context.WithTimeout(context.Background(), timing.OverallTimeout)
defer cancel()

clientConn, err := grpc.DialContext(ctx, "unix://"+criSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials()))
criConn, err := grpc.DialContext(ctx, "unix://"+criSocketPath, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to dial CRI socket %q: %w", criSocketPath, err)
}
client := criV1.NewImageServiceClient(clientConn)
criClient := criV1.NewImageServiceClient(criConn)

if err := listImagesForDebugging(ctx, logger, client, timing.ImageListTimeout, "before"); err != nil {
if err := listImagesForDebugging(ctx, logger, criClient, timing.ImageListTimeout, "before"); err != nil {
return fmt.Errorf("failed to list images for debugging before pulling: %w", err)
}

Expand All @@ -54,12 +54,12 @@ func Run(logger *slog.Logger, criSocketPath string, dockerConfigJSONPath string,
},
Auth: auth,
}
go pullImageWithRetries(ctx, logger.With("image", imageName, "authNum", i), &wg, client, request, timing)
go pullImageWithRetries(ctx, logger.With("image", imageName, "authNum", i), &wg, criClient, request, timing)
}
}
wg.Wait()
logger.Info("pulling images finished")
if err := listImagesForDebugging(ctx, logger, client, timing.ImageListTimeout, "after"); err != nil {
if err := listImagesForDebugging(ctx, logger, criClient, timing.ImageListTimeout, "after"); err != nil {
return fmt.Errorf("failed to list images for debugging after pulling: %w", err)
}
return nil
Expand Down

0 comments on commit 2b62c53

Please sign in to comment.