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
82 changes: 41 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,32 @@ places they need to be for Rancher and its associated projects to use them.

## Overview

`regsync` is used for mirroring images from one repository to another. `regsync`
is configured via `regsync.yaml`. `regsync.yaml` is generated by running the
`generate-regsync` subcommand of the Go code in `tools/`, using `config.yaml` as
input. You can build the latest version of this code to `bin/image-mirror-tools`
using `scripts/build-tools.sh`. Thus, your workflow might look like this:
```
scripts/build-tools.sh
bin/image-mirror-tools generate-regsync
```
Once `regsync.yaml` has been updated, you may run `regsync` via the command
```
regsync once --verbosity error --config regsync.yaml --missing
```
`autoupdate.yaml` is used to configure automatic updates for images. When
an update is found, the automation creates a pull request that a human user
can then review and merge. See [`autoupdate.yaml`](#autoupdateyaml) for
more information.
- `regsync` does the actual mirroring. `regsync` is configured by `regsync.yaml`,
and it runs in a workflow upon merges to `master`.
- `regsync.yaml` is generated from `config.yaml` using Go code in `tools/`.
`config.yaml` is what a user typically interacts directly with.
- `autoupdate.yaml` may be used to configure autoupdates for your images in
`config.yaml`. The autoupdate workflow runs daily, and creates pull requests
that update `config.yaml`, and by extension, `regsync.yaml`.

A typical workflow looks like this:
1. Modify `config.yaml` with your desired changes. See below for documentation
on its fields.
2. Run `scripts/build-tools.sh` to build `image-mirror-tools`.
3. Run `bin/image-mirror-tools generate-regsync` to update `regsync.yaml` from
the new `config.yaml`.
4. Run `bin/image-mirror-tools format` to format any files that might need it.
5. Run `bin/image-mirror-tools validate` to catch certain errors you may have
made.
6. Make a pull request to get your changes merged.

### Adding New Images

When adding new images to the repo, please indicate so in the pull request.
When mirroring new images via this repo, please indicate so in the pull request.
You will need to submit a request to EIO, who will create the repo in
DockerHub. If this is not done, mirroring the image will fail. Nothing
special needs to be done for mirroring a new image to the Rancher Prime
registry.
DockerHub. If this is not done, the mirroring workflow will fail, potentially
inconveniencing other users. This only applies to DockerHub; nothing special
needs to be done for mirroring a new image to the Rancher Prime registry.

### Image Prefixes

Expand Down Expand Up @@ -67,32 +68,31 @@ information to be available to users.

### `config.yaml`

#### `Repositories`
#### `Registries`

`Repositories` describes the repositories that image-mirror interfaces with.
`Registries` describes the registries that image-mirror interfaces with.
This section roughly correlates to the `creds` section of `regsync.yaml`.

| Field | Required | Description |
| ------------- | ------------- |------------- |
| `BaseUrl` | yes | The base URL for the repository. Appending `/` plus an image name should be a valid image reference.
| `BaseUrl` | yes | The base URL for the registry. Appending `/` plus an image name should be a valid image reference.
Comment on lines -77 to +78
Copy link
Member

Choose a reason for hiding this comment

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

If you're here to correct terminology, this is NOT a BaseURL. If you were actually specifying the BaseURL for the registry API here, it would look something like https://dp.apps.rancher.io/v2.

What you instead seem to be keeping here is the target registry hostname plus optional namespace prefix - for example dp.apps.rancher.io/containers which is NOT a URL, and is actually comprised of both a hostname, and a namespace that is prefixed to the image name when syncing.

Copy link
Contributor Author

@adamkpickering adamkpickering Nov 27, 2025

Choose a reason for hiding this comment

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

Hmm, you're right that it isn't a URL. Though I'm not sure what to call it - BaseName? BaseRef? BaseImageRef? RepositoryPrefix? What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

"BaseURL" could have a few interpretations - is it a "full URL used as the base value", or is it "the base string that URLs that will be constructed from". I think we mean the second one and I could see how the current term could imply the first meaning too.

It makes me wonder does "BaseURL == URLBase"? Potentially flipping it changes the implied meaning - where one will be more fitting for our use?


If we consider how this value is used, we construct a string like:

docker.io/rancher/mirrored-tonistiigi-xx:1.6.1

If we think about URI vs URL - it cannot be a URL as it has not protocol, however it can be considered a URI. If we included the protocol - like some tools require skopeo - then I think it would be a URL.

That in mind - maybe a term like URIBase or BaseURI is more accurate than using URL.

| `Password` | yes | The password to use when authenticating against the registry. See [the regsync documentation](https://regclient.org/usage/regsync/) for more details.
| `Registry` | yes | The registry URL. See [the regsync documentation](https://regclient.org/usage/regsync/) for more details.
| `ReqConcurrent` | no | The number of concurrent requests that are made to this registry. See [the regsync documentation](https://regclient.org/usage/regsync/) for more details.
| `DefaultTarget` | no | Whether the Repository is used as a target repository for a given Image when the `TargetRepositories` field of the Image is not set.
| `DefaultTarget` | no | Whether the Registry is used as a target registry for a given Image when the `TargetRegistries` field of the Image is not set.
| `Username` | yes | The username to use when authenticating against the registry. See [the regsync documentation](https://regclient.org/usage/regsync/) for more details.

#### `Images`

`Images` describes the images that we want to mirror to each target
repository.
`Images` describes the images that we want to mirror to each target registry.

| Field | Required | Description |
| ------------- | ------------- |------------- |
| `DoNotMirror` | no | Set to `true` to exclude the entire image from regsync.yaml. Alternatively, set to an array of strings to specify tags to exclude from regsync.yaml.
| `SourceImage` | yes | The source image. If there is no host, the image is assumed to be from Docker Hub.
| `Tags` | yes | The tags to mirror.
| `Tags` | yes | The tags to mirror.
| `TargetImageName` | no | By default, the target image name is derived from the source image, and is of the format `mirrored-<org>-<name>`. For example, `banzaicloud/logging-operator` becomes `mirrored-banzaicloud-logging-operator`. However, there are some images that do not follow this convention - this field exists for these cases. New images should not set this field.
| `TargetRepositories` | no | Repositories to mirror the image to. Repositories are specified via their `BaseUrl` field. If not specified, the Image is mirrored to all Repositories that have `DefaultTarget` set to true.
| `TargetRegistries` | no | Registries to mirror the image to. Registries are specified via their `BaseUrl` field. If not specified, the Image is mirrored to all Registries that have `DefaultTarget` set to true.

### `autoupdate.yaml`

Expand All @@ -111,9 +111,9 @@ entry specifies a strategy for finding tags of images to potentially add to

#### `GithubRelease`

The `GithubRelease` strategy fetches all release tags that matches the VersionConstraint from a GitHub
The `GithubRelease` strategy fetches all release tags that matches the `VersionConstraint` from a GitHub
repository and applies it to the specified images.
If LatestOnly is true, it only fetches from the latest release and does not consider the VersionConstraint.
If `LatestOnly` is true, it only fetches from the latest release and does not consider the `VersionConstraint`.

| Field | Required | Description |
|---------------------|----------|------------- |
Expand All @@ -129,9 +129,9 @@ If LatestOnly is true, it only fetches from the latest release and does not cons
A list of images to be updated with the latest release tag. Each image will get the same tag as the GitHub release.

| Field | Required | Description |
| ------------- | ------------- |------------- |
| `SourceImage` | yes | The GitHub repository name.
| `TargetImageName` | no | The TargetImageName of the image in `config.yaml` that you want to update.
| ----------------- | ------------- |------------- |
| `SourceImage` | yes | The GitHub repository name.
| `TargetImageName` | no | The TargetImageName of the image in `config.yaml` that you want to update.

#### `HelmLatest`

Expand All @@ -140,16 +140,16 @@ Helm charts and extracts image references from the rendered manifests. It
recursively searches for fields with an "image" key in the templated YAML
output.

| Field | Required | Description |
| ------------- | ------------- |------------- |
| `HelmRepo` | yes | The URL of the Helm chart repository.
| `Charts` | yes | A map where keys are the charts to template, and values are another map from environment name to lists of helm values to `--set` in that environment. `helm template` is run once for each environment.
| `Images` | no | Used to map a given update image to an entry in `config.yaml`. There may be multiple entries that have the same `SourceImage`, but different `TargetImageName`s, so we need to choose which one receives the update image.
| `ImageDenylist` | no | A list of images to exclude from the results.
| Field | Required | Description |
| --------------- | ------------- |------------- |
| `HelmRepo` | yes | The URL of the Helm chart repository.
| `Charts` | yes | A map where keys are the charts to template, and values are another map from environment name to lists of helm values to `--set` in that environment. `helm template` is run once for each environment.
| `Images` | no | Used to map a given update image to an entry in `config.yaml`. There may be multiple entries that have the same `SourceImage`, but different `TargetImageName`s, so we need to choose which one receives the update image.
| `ImageDenylist` | no | A list of images to exclude from the results.

#### `Registry`

The `Registry` strategy fetches all image tags that matches the `VersionFilter` from a registry defined in the Images provided.
The `Registry` strategy fetches all image tags that match the `VersionFilter` from a registry defined in the Images provided.
Supported registries are:
* Suse Container Registry (registry.suse.com)
* Docker Hub
Expand Down
12 changes: 6 additions & 6 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ Images:
- SourceImage: dp.apps.rancher.io/charts/kubernetes-cluster-autoscaler
Tags:
- 9.50.1
TargetRepositories:
TargetRegistries:
- registry.suse.com/rancher/charts
- stgregistry.suse.com/rancher/charts
- SourceImage: dp.apps.rancher.io/charts/suse-virtual-cluster-engine
Tags:
- 1.0.0
TargetRepositories:
TargetRegistries:
- registry.suse.com/rancher/charts
- stgregistry.suse.com/rancher/charts
- SourceImage: dp.apps.rancher.io/containers/alertmanager
Expand All @@ -114,13 +114,13 @@ Images:
- SourceImage: dp.apps.rancher.io/containers/k3k
Tags:
- 1.0.0-1.1
TargetRepositories:
TargetRegistries:
- registry.suse.com/rancher
- stgregistry.suse.com/rancher
- SourceImage: dp.apps.rancher.io/containers/k3k-kubelet
Tags:
- 1.0.0-1.1
TargetRepositories:
TargetRegistries:
- registry.suse.com/rancher
- stgregistry.suse.com/rancher
- SourceImage: dp.apps.rancher.io/containers/k8s-sidecar
Expand All @@ -137,7 +137,7 @@ Images:
- 1.32.3-1.5
- 1.33.0-3.3
- 1.34.0-3.4
TargetRepositories:
TargetRegistries:
- registry.suse.com/rancher
- stgregistry.suse.com/rancher
- SourceImage: dp.apps.rancher.io/containers/node-exporter
Expand Down Expand Up @@ -3150,7 +3150,7 @@ Images:
Tags:
- v0.7.3
TargetImageName: mirrored-kubernetes-external-dns
Repositories:
Registries:
- BaseUrl: docker.io/rancher
DefaultTarget: true
Password: '{{ env "DOCKER_PASSWORD" }}'
Expand Down
2 changes: 1 addition & 1 deletion tools/internal/config/accumulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (ia *ImageAccumulator) TagDifference(image *Image) (*Image, error) {
return image, nil
}

imageToReturn, err := NewImage(image.SourceImage, make([]string, 0, len(image.Tags)), image.TargetImageName(), image.DoNotMirror, image.TargetRepositories)
imageToReturn, err := NewImage(image.SourceImage, make([]string, 0, len(image.Tags)), image.TargetImageName(), image.DoNotMirror, image.TargetRegistries)
if err != nil {
return nil, fmt.Errorf("failed to construct new image from passed image: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion tools/internal/config/accumulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestImageAccumulator(t *testing.T) {
assert.Equal(t, image.SourceImage, diffImage.SourceImage)
assert.Equal(t, image.TargetImageName(), diffImage.TargetImageName())
assert.Equal(t, image.Tags, diffImage.Tags)
assert.Equal(t, image.TargetRepositories, diffImage.TargetRepositories)
assert.Equal(t, image.TargetRegistries, diffImage.TargetRegistries)
})

t.Run("should return the tags that are not already present in the accumulator", func(t *testing.T) {
Expand Down
48 changes: 24 additions & 24 deletions tools/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,38 @@ import (
)

type Config struct {
Images []*Image
Repositories []Repository
Images []*Image
Registries []Registry
}

type Repository struct {
// BaseUrl is used exclusively for referring to the Repository
type Registry struct {
// BaseUrl is used exclusively for referring to the Registry
// in general, and for building the target image ref for a given
// image for a repository. For example, a target image name of
// image for a registry. For example, a target image name of
// "mirrored-rancher-cis-operator" and a BaseUrl of "docker.io/rancher"
// produce a target image ref of "docker.io/rancher/mirrored-rancher-cis-operator".
BaseUrl string
// Whether the Repository is used as a target repository for a given
// Image when the TargetRepositories field of the Image is not set.
// Whether the Registry is used as a target registry for a given
// Image when the TargetRegistries field of the Image is not set.
DefaultTarget bool
// Password is what goes into the "pass" field of regsync.yaml
// for this repository. For more information please see
// for this registry. For more information please see
// https://github.com/regclient/regclient/blob/main/docs/regsync.md
Password string
// Registry is what goes into the "registry" field of regsync.yaml
// for this repository. For more information please see
// for this registry. For more information please see
// https://github.com/regclient/regclient/blob/main/docs/regsync.md
Registry string
// RepoAuth goes into the "repoAuth" field of regsync.yaml in this
// repository. For more information please see
// registry. For more information please see
// https://github.com/regclient/regclient/blob/main/docs/regsync.md
RepoAuth bool `json:",omitempty"`
// ReqConcurrent is what goes into the "reqConcurrent" field of
// regsync.yaml for this repository. For more information please see
// regsync.yaml for this registry. For more information please see
// https://github.com/regclient/regclient/blob/main/docs/regsync.md
ReqConcurrent int `json:",omitempty"`
// Username is what goes into the "user" field of regsync.yaml
// for this repository. For more information please see
// for this registry. For more information please see
// https://github.com/regclient/regclient/blob/main/docs/regsync.md
Username string
}
Expand Down Expand Up @@ -93,26 +93,26 @@ func (config *Config) Sort() {
image.Sort()
}
slices.SortStableFunc(config.Images, CompareImages)
slices.SortStableFunc(config.Repositories, compareRepositories)
slices.SortStableFunc(config.Registries, compareRegistries)
}

func (config *Config) ToRegsyncConfig() (regsync.Config, error) {
regsyncYaml := regsync.Config{
Creds: make([]regsync.ConfigCred, 0, len(config.Repositories)),
Creds: make([]regsync.ConfigCred, 0, len(config.Registries)),
Defaults: regsync.ConfigDefaults{
UserAgent: "rancher-image-mirror",
},
Sync: make([]regsync.ConfigSync, 0),
}

credsMap := map[regsync.ConfigCred]struct{}{}
for _, targetRepository := range config.Repositories {
for _, targetRegistry := range config.Registries {
credEntry := regsync.ConfigCred{
Pass: targetRepository.Password,
Registry: targetRepository.Registry,
RepoAuth: targetRepository.RepoAuth,
ReqConcurrent: targetRepository.ReqConcurrent,
User: targetRepository.Username,
Pass: targetRegistry.Password,
Registry: targetRegistry.Registry,
RepoAuth: targetRegistry.RepoAuth,
ReqConcurrent: targetRegistry.ReqConcurrent,
User: targetRegistry.Username,
}
if _, ok := credsMap[credEntry]; ok {
continue
Expand All @@ -124,7 +124,7 @@ func (config *Config) ToRegsyncConfig() (regsync.Config, error) {
})

for _, image := range config.Images {
syncEntries, err := image.ToRegsyncImages(config.Repositories)
syncEntries, err := image.ToRegsyncImages(config.Registries)
if err != nil {
return regsync.Config{}, fmt.Errorf("failed to convert Image with SourceImage %q: %w", image.SourceImage, err)
}
Expand All @@ -136,15 +136,15 @@ func (config *Config) ToRegsyncConfig() (regsync.Config, error) {

func (config *Config) DeepCopy() *Config {
copiedConfig := &Config{
Images: make([]*Image, 0, len(config.Images)),
Repositories: slices.Clone(config.Repositories),
Images: make([]*Image, 0, len(config.Images)),
Registries: slices.Clone(config.Registries),
}
for _, image := range config.Images {
copiedConfig.Images = append(copiedConfig.Images, image.DeepCopy())
}
return copiedConfig
}

func compareRepositories(a, b Repository) int {
func compareRegistries(a, b Registry) int {
return strings.Compare(a.BaseUrl, b.BaseUrl)
}
10 changes: 5 additions & 5 deletions tools/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import (

func TestConfig(t *testing.T) {
t.Run("ToRegsyncConfig", func(t *testing.T) {
t.Run("should always include Repositories in regsync config even if DefaultTarget field is false", func(t *testing.T) {
// Non-target repos should still be included in regsync.yaml, since
t.Run("should always include Registries in regsync config even if DefaultTarget field is false", func(t *testing.T) {
// Non-target registries should still be included in regsync.yaml, since
// they may be the source of some images.
config := &Config{
Images: []*Image{},
Repositories: []Repository{
Registries: []Registry{
{
BaseUrl: "docker.io/target-repo",
BaseUrl: "docker.io/target-registry",
DefaultTarget: true,
Username: "target-user",
Password: "target-pass",
Registry: "docker.io",
},
{
BaseUrl: "docker.io/non-target-repo",
BaseUrl: "docker.io/non-target-registry",
DefaultTarget: false,
Username: "non-target-user",
Password: "non-target-pass",
Expand Down
Loading
Loading