diff --git a/README.md b/README.md index cc7fdd2..d4b58f9 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,23 @@ # Usage ``` - fromage list [--verbose] [--format=FORMAT] [--no-header] [--only-references] [--branch=BRANCH ...] URL + fromage list [--verbose] [--format=FORMAT] [--no-header] [--only-references] [--branch=BRANCH ...] URL fromage check [--verbose] [--format=FORMAT] [--no-header] [--only-references] [--branch=BRANCH ...] [--pin=LEVEL] URL - fromage bump [--verbose] [--dry-run] [--pin=LEVEL] --branch=BRANCH URL + fromage bump [--verbose] [--dry-run] [--pin=LEVEL] [--latest] --branch=BRANCH URL + fromage move [--verbose] [--dry-run] --from=FROM_REPOSITORY --to=TO_REPOSITORY --branch=BRANCH URL ``` -# Options +# Options ``` -Options: - --branch=BRANCH to inspect, defaults to all branches. - --format=FORMAT to print: text, json or yaml [default: text]. - --no-header do not print header if output type is text. - --only-references output only container image references. - --pin=LEVEL pins the MAJOR or MINOR version level - --latest bump to the latest version available +--branch=BRANCH to inspect, defaults to all branches. +--format=FORMAT to print: text, json or yaml [default: text]. +--no-header do not print header if output type is text. +--only-references output only container image references. +--pin=LEVEL pins the MAJOR or MINOR version level +--latest bump to the latest version available +--from=FROM_REPOSITORY from repository context +--to=TO_REPOSITORY to repository context + ``` # Description @@ -72,5 +75,20 @@ remote repository reference, the change will also be pushed. Read more at [How to keep your Dockerfile container image references up-to-date](https://binx.io/blog/2021/01/30/how-to-keep-your-dockerfile-container-image-references-up-to-date/) +## moving container registry + +If you need to move your container registry images from for instance docker hub to AWS Public ECR registry, type: + +``` +$ fromage move --verbose --from index.docker.io/library --to public.aws.ecr/docker/library --branch master git@github.com:binxio/kritis.git +2023/02/15 16:02:43 INFO: updating reference ubuntu:trusty to public.aws.ecr/docker/library/ubuntu:trusty in vendor/golang.org/x/net/http2/Dockerfile +2023/02/15 16:02:43 INFO: updating reference golang:1.13 to public.aws.ecr/docker/library/golang:1.13 in deploy/Dockerfile +2023/02/15 16:02:43 INFO: updating reference golang:1.13 to public.aws.ecr/docker/library/golang:1.13 in helm-hooks/Dockerfile +2023/02/15 16:02:43 INFO: updating reference golang:1.13 to public.aws.ecr/docker/library/golang:1.13 in helm-hooks/Dockerfile +2023/02/15 16:02:43 INFO: moved references from index.docker.io/library to public.aws.ecr/docker/library +2023/02/15 16:02:43 INFO: changes committed with 1234ef +2023/02/15 16:02:43 INFO: pushing changes to git@github.com:binxio/kritis.git +``` + # Caveats - The bump will update all container references it finds in all files diff --git a/main.go b/main.go index 4f7ce19..1737faf 100644 --- a/main.go +++ b/main.go @@ -251,42 +251,57 @@ func BumpReferences(f *Fromage) error { return nil } -func MoveImageReferences(f *Fromage) error { - updated := false - - content, err := ReadFile(f.workTree, f.dockerfile) - if err != nil { - return err - } +func moveImageReferences(content []byte, filename string, verbose bool, from, to string) ([]byte, bool, error) { + updated := false refs := ExtractFromStatements(content) for _, refString := range refs { ref, err := name.ParseReference(refString) fullRef := ref.Name() if err != nil { - log.Fatalf("failed to parse %s into a reference, %v", refString, err) + return nil, false, err } - if !strings.HasPrefix(fullRef, f.From) && len(fullRef) > len(f.From) { + if !strings.HasPrefix(fullRef, from) && len(fullRef) > len(from) { continue } - if delimiter := fullRef[len(f.From)]; delimiter != ':' && delimiter != '/' { + if delimiter := fullRef[len(from)]; delimiter != ':' && delimiter != '/' && delimiter != '@' { continue } - newRefString := f.To + fullRef[len(f.From):] + newRefString := to + fullRef[len(from):] + if to == "index.docker.io/library" { + newRefString = fullRef[len(from)+1:] + } newRef, err := name.ParseReference(newRefString) if err != nil { - log.Fatalf("failed to parse %s into a reference, %v", newRefString, err) + return nil, false, err + } + + if !RepositoryExists(newRef, verbose) { + return nil, false, fmt.Errorf("ERROR: %s is not a valid image reference", newRef) } - content, updated = UpdateFromStatements(content, ref, newRef, f.dockerfile, f.Verbose) - if updated { - f.updated = true + ok := false + if content, ok = UpdateFromStatements(content, ref, newRef, filename, verbose); ok { + updated = true } } + return content, updated, nil +} + +func MoveImageReferences(f *Fromage) error { + content, err := ReadFile(f.workTree, f.dockerfile) + if err != nil { + return err + } + + content, f.updated, err = moveImageReferences(content, f.dockerfile, f.Verbose, f.From, f.To) + if err != nil { + log.Fatalf("%s", err) + } if f.updated { if !f.DryRun { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..1ac755b --- /dev/null +++ b/main_test.go @@ -0,0 +1,76 @@ +package main + +import ( + "reflect" + "testing" +) + +func Test_moveImageReferences(t *testing.T) { + type args struct { + content []byte + filename string + verbose bool + from string + to string + } + tests := []struct { + name string + args args + want []byte + want1 bool + wantErr bool + }{ + { + "simple", + args{[]byte(`FROM python:3.7`), + "Dockerfile", + true, + "index.docker.io/library", + "public.ecr.aws/docker/library", + }, + []byte(`FROM public.ecr.aws/docker/library/python:3.7`), + true, + false, + }, + { + "and back again", + args{[]byte(`FROM public.ecr.aws/docker/library/python:3.7`), + "Dockerfile", + true, + "public.ecr.aws/docker/library", + "index.docker.io/library", + }, + []byte(`FROM python:3.7`), + true, + false, + }, + + { + "error", + args{[]byte(`FROM python:3.7`), + "Dockerfile", + true, + "index.docker.io/library", + "public.ecr.aws/dorker/library", + }, + nil, + false, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := moveImageReferences(tt.args.content, tt.args.filename, tt.args.verbose, tt.args.from, tt.args.to) + if (err != nil) != tt.wantErr { + t.Errorf("moveImageReferences() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("moveImageReferences() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("moveImageReferences() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/repository_exists.go b/repository_exists.go new file mode 100644 index 0000000..54a2a78 --- /dev/null +++ b/repository_exists.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + "log" +) + +var cache = map[string]bool{} + +func RepositoryExists(reference name.Reference, verbose bool) bool { + name := reference.Context().String() + if result, ok := cache[name]; ok { + return result + } + _, err := crane.Head(reference.Name()) + // _, err := crane.ListTags(reference.Context().String()) + if err != nil && verbose { + log.Printf("DEBUG: no manifest found for %s, %s", name, err) + } + + cache[name] = err == nil + return err == nil +} diff --git a/tag/tag.go b/tag/tag.go index 0e20a2e..5f2ef29 100644 --- a/tag/tag.go +++ b/tag/tag.go @@ -53,6 +53,7 @@ var ( tagCategoryCache = map[string]TagCategories{} gitDescribeSuffixRegExp = regexp.MustCompile(`(?m)^-((?P[0-9]+)-g)?(?P[0-9a-f]{6,})(?P-dirty)?$`) gitDescribeOrderSubExprIndex = findStringIndex(gitDescribeSuffixRegExp.SubexpNames(), "order") + tagListCache = map[string][]string{} ) func findStringIndex(a []string, item string) int {