Add flag to remap registries for any registry mirror (#2935)

* Add flag to remap registries for any registry mirror

The purpose of this PR is to add an option to remap registries, a kind of generalized `--registry-mirror`.
This is helpful for air-gapped environments and/or when local registry mirrors are available (not limited to docker.io).
This allows user to reference any images without having to change their location.
It also permit to separate infra related configuration (the mirrors) from CI/CD pipeline definition by using an environment variable for example (the reason behind the early return if flag provided but empty).
Therefore you can have a pipeline calling kaniko with `--registry-map=$REGISTRY_MAP` and have the `REGISTRY_MAP` populated via the runner's env by another team, and the absence of env wouldn't trigger a failure, it makes the pipeline env independent.

I've also considered the option of environment variables directly but it doesn't seems to be in kaniko's philosophy.

This makes quite some duplicated code :/ One option to keep the mirror flag and behavior would be to use only one codebase and convert `--registry-mirror=VALUE` to `--registry-map=index.docker.io=VALUE` internally. Suggestions welcome!

* Configure logging config sooner to be able to use it in flag parsing

* Replace registry mirrors by maps logic and use env var

* Add env vars to README.md

* Fix test
This commit is contained in:
Damien Degois
2024-02-14 15:23:41 -08:00
committed by GitHub
parent da3878e16b
commit 1bf529e6d9
8 changed files with 178 additions and 20 deletions
+43
View File
@@ -82,3 +82,46 @@ func (a *keyValueArg) Set(value string) error {
func (a *keyValueArg) Type() string {
return "key-value-arg type"
}
type multiKeyMultiValueArg map[string][]string
func (c *multiKeyMultiValueArg) parseKV(value string) error {
valueSplit := strings.SplitN(value, "=", 2)
if len(valueSplit) < 2 {
return fmt.Errorf("invalid argument value. expect key=value, got %s", value)
}
(*c)[valueSplit[0]] = append((*c)[valueSplit[0]], valueSplit[1])
return nil
}
func (c *multiKeyMultiValueArg) String() string {
var result []string
for key := range *c {
for _, val := range (*c)[key] {
result = append(result, fmt.Sprintf("%s=%s", key, val))
}
}
return strings.Join(result, ";")
}
func (c *multiKeyMultiValueArg) Set(value string) error {
if value == "" {
return nil
}
if strings.Contains(value, ";") {
kvpairs := strings.Split(value, ";")
for _, kv := range kvpairs {
err := c.parseKV(kv)
if err != nil {
return err
}
}
return nil
}
return c.parseKV(value)
}
func (c *multiKeyMultiValueArg) Type() string {
return "key-multi-value-arg type"
}
+33
View File
@@ -45,3 +45,36 @@ func Test_KeyValueArg_Set_shouldAcceptEqualAsValue(t *testing.T) {
t.Error("Invalid split. key=value=something should be split to key=>value=something")
}
}
func Test_multiKeyMultiValueArg_Set_shouldSplitArgumentLikeKVA(t *testing.T) {
arg := make(multiKeyMultiValueArg)
arg.Set("key=value")
if arg["key"][0] != "value" {
t.Error("Invalid split. key=value should be split to key=>value")
}
}
func Test_multiKeyMultiValueArg_Set_ShouldAppendIfRepeated(t *testing.T) {
arg := make(multiKeyMultiValueArg)
arg.Set("key=v1")
arg.Set("key=v2")
if arg["key"][0] != "v1" || arg["key"][1] != "v2" {
t.Error("Invalid repeat behavior. Repeated keys should append values")
}
}
func Test_multiKeyMultiValueArg_Set_Composed(t *testing.T) {
arg := make(multiKeyMultiValueArg)
arg.Set("key1=value1;key2=value2")
if arg["key1"][0] != "value1" || arg["key2"][0] != "value2" {
t.Error("Invalid composed value parsing. key=value;key2=value2 should generate 2 keys")
}
}
func Test_multiKeyMultiValueArg_Set_WithEmptyValueShouldWork(t *testing.T) {
arg := make(multiKeyMultiValueArg)
err := arg.Set("")
if len(arg) != 0 || err != nil {
t.Error("multiKeyMultiValueArg must handle empty value")
}
}
+1
View File
@@ -32,6 +32,7 @@ type CacheOptions struct {
// RegistryOptions are all the options related to the registries, set by command line arguments.
type RegistryOptions struct {
RegistryMaps multiKeyMultiValueArg
RegistryMirrors multiArg
InsecureRegistries multiArg
SkipTLSVerifyRegistries multiArg
+11 -12
View File
@@ -17,7 +17,7 @@ limitations under the License.
package remote
import (
"errors"
"fmt"
"strings"
"github.com/GoogleContainerTools/kaniko/pkg/config"
@@ -51,33 +51,32 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo
return nil, err
}
if ref.Context().RegistryStr() == name.DefaultRegistry {
if newRegURLs, found := opts.RegistryMaps[ref.Context().RegistryStr()]; found {
ref, err := normalizeReference(ref, image)
if err != nil {
return nil, err
}
for _, registryMirror := range opts.RegistryMirrors {
for _, regToMapTo := range newRegURLs {
var newReg name.Registry
if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) {
newReg, err = name.NewRegistry(registryMirror, name.WeakValidation, name.Insecure)
if opts.InsecurePull || opts.InsecureRegistries.Contains(regToMapTo) {
newReg, err = name.NewRegistry(regToMapTo, name.WeakValidation, name.Insecure)
} else {
newReg, err = name.NewRegistry(registryMirror, name.StrictValidation)
newReg, err = name.NewRegistry(regToMapTo, name.StrictValidation)
}
if err != nil {
return nil, err
}
ref := setNewRegistry(ref, newReg)
logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror)
logrus.Infof("Retrieving image %s from mapped registry %s", ref, regToMapTo)
retryFunc := func() (v1.Image, error) {
return remoteImageFunc(ref, remoteOptions(registryMirror, opts, customPlatform)...)
return remoteImageFunc(ref, remoteOptions(regToMapTo, opts, customPlatform)...)
}
var remoteImage v1.Image
var err error
if remoteImage, err = util.RetryWithResult(retryFunc, opts.ImageDownloadRetry, 1000); err != nil {
logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err)
logrus.Warnf("Failed to retrieve image %s from remapped registry %s: %s. Will try with the next registry, or fallback to the original registry.", ref, regToMapTo, err)
continue
}
@@ -86,8 +85,8 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo
return remoteImage, nil
}
if len(opts.RegistryMirrors) > 0 && opts.SkipDefaultRegistryFallback {
return nil, errors.New("image not found on any configured mirror(s)")
if len(newRegURLs) > 0 && opts.SkipDefaultRegistryFallback {
return nil, fmt.Errorf("image not found on any configured mapped registries for %s", ref)
}
}
+1 -1
View File
@@ -117,7 +117,7 @@ func Test_RetrieveRemoteImage_skipFallback(t *testing.T) {
registryMirror := "some-registry"
opts := config.RegistryOptions{
RegistryMirrors: []string{registryMirror},
RegistryMaps: map[string][]string{name.DefaultRegistry: {registryMirror}},
SkipDefaultRegistryFallback: false,
}