github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/generic-autobumper/imagebumper/imagebumper_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package imagebumper
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"testing"
    23  )
    24  
    25  func TestDeconstructCommit(t *testing.T) {
    26  	cases := []struct {
    27  		name           string
    28  		commit         string
    29  		tag            string
    30  		num            int
    31  		expectedCommit string
    32  	}{
    33  		{
    34  			name: "basically works",
    35  		},
    36  		{
    37  			name:           "just commit works",
    38  			commit:         "deadbeef",
    39  			expectedCommit: "deadbeef",
    40  		},
    41  		{
    42  			name:           "commit drops leading g",
    43  			commit:         "gdeadbeef",
    44  			expectedCommit: "deadbeef",
    45  		},
    46  		{
    47  			name:   "just tag works",
    48  			commit: "v0.0.30",
    49  			tag:    "v0.0.30",
    50  		},
    51  		{
    52  			name:           "commits past tags work",
    53  			commit:         "v0.0.30-14-gdeadbeef",
    54  			tag:            "v0.0.30",
    55  			num:            14,
    56  			expectedCommit: "deadbeef",
    57  		},
    58  	}
    59  
    60  	for _, tc := range cases {
    61  		t.Run(tc.name, func(t *testing.T) {
    62  			tag, num, commit := DeconstructCommit(tc.commit)
    63  			if tag != tc.tag {
    64  				t.Errorf("DeconstructCommit(%s) got tag %q, want %q", tc.commit, tag, tc.tag)
    65  			}
    66  			if num != tc.num {
    67  				t.Errorf("DeconstructCommit(%s) got tag %d, want %d", tc.commit, num, tc.num)
    68  			}
    69  			if commit != tc.expectedCommit {
    70  				t.Errorf("DeconstructCommit(%s) got commit %q, want %q", tc.commit, commit, tc.expectedCommit)
    71  			}
    72  
    73  		})
    74  	}
    75  }
    76  
    77  func TestDeconstructTag(t *testing.T) {
    78  	cases := []struct {
    79  		tag     string
    80  		date    string
    81  		commit  string
    82  		variant string
    83  	}{
    84  		{
    85  			tag: "deadbeef",
    86  			// TODO(fejta): commit: "deadbeef",
    87  		},
    88  		{
    89  			tag: "v0.0.30",
    90  			// TODO(fejta): commit: "v0.0.30",
    91  		},
    92  		{
    93  			tag:    "v20190404-65af07d",
    94  			date:   "20190404",
    95  			commit: "65af07d",
    96  		},
    97  		{
    98  			tag:     "v20190330-811f79999-experimental",
    99  			date:    "20190330",
   100  			commit:  "811f79999",
   101  			variant: "-experimental",
   102  		},
   103  		{
   104  			tag:    "latest",
   105  			date:   "atest", // TODO(fejta): empty
   106  			commit: "latest",
   107  		},
   108  		{
   109  			tag:     "latest-experimental",
   110  			date:    "atest", // TODO(fejta): empty
   111  			commit:  "latest",
   112  			variant: "-experimental", // TODO(fejta): no -
   113  		},
   114  		{
   115  			tag:    "v20210125-v0.0.41-8-gcb960c8",
   116  			date:   "20210125",
   117  			commit: "gcb960c8", // TODO(fejta): "v0.0.41-8-gcb960c8",
   118  		},
   119  		{
   120  			tag:     "v20210125-v0.0.41-8-gcb960c8-fancy",
   121  			date:    "20210125",
   122  			commit:  "gcb960c8", // TODO(fejta): "v0.0.41-8-gcb960c8",
   123  			variant: "-fancy",   // TODO(fejta): no -
   124  		},
   125  	}
   126  
   127  	for _, tc := range cases {
   128  		t.Run(tc.tag, func(t *testing.T) {
   129  			date, commit, variant := DeconstructTag(tc.tag)
   130  			if date != tc.date {
   131  				t.Errorf("DeconstructTag(%q) got date %s, want %s", tc.tag, date, tc.date)
   132  			}
   133  			if commit != tc.commit {
   134  				t.Errorf("DeconstructTag(%q) got commit %s, want %s", tc.tag, commit, tc.commit)
   135  			}
   136  			if variant != tc.variant {
   137  				t.Errorf("DeconstructTag(%q) got variant %s, want %s", tc.tag, variant, tc.variant)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestPickBestTag(t *testing.T) {
   144  	tests := []struct {
   145  		name      string
   146  		tag       string
   147  		manifest  manifest
   148  		bestTag   string
   149  		expectErr bool
   150  	}{
   151  		{
   152  			name: "simple lookup",
   153  			tag:  "v20190329-811f7954b",
   154  			manifest: manifest{
   155  				"image1": {
   156  					TimeCreatedMs: "2000",
   157  					Tags:          []string{"v20190404-65af07d"},
   158  				},
   159  				"image2": {
   160  					TimeCreatedMs: "1000",
   161  					Tags:          []string{"v20190329-811f7954b"},
   162  				},
   163  			},
   164  			bestTag: "v20190404-65af07d",
   165  		},
   166  		{
   167  			name: "'latest' overrides date",
   168  			tag:  "v20190329-811f7954b",
   169  			manifest: manifest{
   170  				"image1": {
   171  					TimeCreatedMs: "2000",
   172  					Tags:          []string{"v20190404-65af07d"},
   173  				},
   174  				"image2": {
   175  					TimeCreatedMs: "1000",
   176  					Tags:          []string{"v20190330-811f79999", "latest"},
   177  				},
   178  			},
   179  			bestTag: "v20190330-811f79999",
   180  		},
   181  		{
   182  			name: "tags with suffixes only match other tags with the same suffix",
   183  			tag:  "v20190329-811f7954b-experimental",
   184  			manifest: manifest{
   185  				"image1": {
   186  					TimeCreatedMs: "2000",
   187  					Tags:          []string{"v20190404-65af07d"},
   188  				},
   189  				"image2": {
   190  					TimeCreatedMs: "1000",
   191  					Tags:          []string{"v20190330-811f79999-experimental"},
   192  				},
   193  			},
   194  			bestTag: "v20190330-811f79999-experimental",
   195  		},
   196  		{
   197  			name: "unsuffixed 'latest' has no effect on suffixed tags",
   198  			tag:  "v20190329-811f7954b-experimental",
   199  			manifest: manifest{
   200  				"image1": {
   201  					TimeCreatedMs: "2000",
   202  					Tags:          []string{"v20190404-65af07d", "latest"},
   203  				},
   204  				"image2": {
   205  					TimeCreatedMs: "1000",
   206  					Tags:          []string{"v20190330-811f79999-experimental"},
   207  				},
   208  			},
   209  			bestTag: "v20190330-811f79999-experimental",
   210  		},
   211  		{
   212  			name: "suffixed 'latest' has no effect on unsuffixed tags",
   213  			tag:  "v20190329-811f7954b",
   214  			manifest: manifest{
   215  				"image1": {
   216  					TimeCreatedMs: "2000",
   217  					Tags:          []string{"v20190404-65af07d"},
   218  				},
   219  				"image2": {
   220  					TimeCreatedMs: "1000",
   221  					Tags:          []string{"v20190330-811f79999-experimental", "latest-experimental"},
   222  				},
   223  			},
   224  			bestTag: "v20190404-65af07d",
   225  		},
   226  		{
   227  			name: "'latest' with the correct suffix overrides date",
   228  			tag:  "v20190329-811f7954b-experimental",
   229  			manifest: manifest{
   230  				"image1": {
   231  					TimeCreatedMs: "2000",
   232  					Tags:          []string{"v20190404-65af07d-experimental"},
   233  				},
   234  				"image2": {
   235  					TimeCreatedMs: "1000",
   236  					Tags:          []string{"v20190330-811f79999-experimental", "latest-experimental"},
   237  				},
   238  			},
   239  			bestTag: "v20190330-811f79999-experimental",
   240  		},
   241  		{
   242  			name: "it is an error when no tags are found",
   243  			tag:  "v20190329-811f7954b-master",
   244  			manifest: manifest{
   245  				"image1": {
   246  					TimeCreatedMs: "2000",
   247  					Tags:          []string{"v20190404-65af07d-experimental"},
   248  				},
   249  				"image2": {
   250  					TimeCreatedMs: "1000",
   251  					Tags:          []string{"v20190330-811f79999-experimental", "latest-experimental"},
   252  				},
   253  			},
   254  			expectErr: true,
   255  		},
   256  	}
   257  
   258  	for _, test := range tests {
   259  		t.Run(test.name, func(t *testing.T) {
   260  			tagParts := tagRegexp.FindStringSubmatch(test.tag)
   261  			bestTag, err := pickBestTag(tagParts, test.manifest)
   262  			if err != nil {
   263  				if !test.expectErr {
   264  					t.Fatalf("Unexpected error: %v", err)
   265  				}
   266  				return
   267  			}
   268  			if test.expectErr {
   269  				t.Fatalf("Expected an error, but got result %q", bestTag)
   270  			}
   271  			if bestTag != test.bestTag {
   272  				t.Fatalf("Expected tag %q, but got %q instead", test.bestTag, bestTag)
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  func TestUpdateAllTags(t *testing.T) {
   279  	tests := []struct {
   280  		name           string
   281  		content        string
   282  		expectedResult string
   283  		imageFilter    *regexp.Regexp
   284  		newTags        map[string]string
   285  	}{
   286  		{
   287  			name:           "file with no images does nothing",
   288  			content:        "this is just a normal file",
   289  			expectedResult: "this is just a normal file",
   290  		},
   291  		{
   292  			name:           "file that has only an image replaces the image",
   293  			content:        "gcr.io/k8s-testimages/some-image:v20190404-12345678",
   294  			expectedResult: "gcr.io/k8s-testimages/some-image:v20190405-123456789",
   295  			newTags: map[string]string{
   296  				"gcr.io/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   297  			},
   298  		},
   299  		{
   300  			name:           "file that has content before and after an image still has it later",
   301  			content:        `{"image": "gcr.io/k8s-testimages/some-image:v20190404-12345678"}`,
   302  			expectedResult: `{"image": "gcr.io/k8s-testimages/some-image:v20190405-123456789"}`,
   303  			newTags: map[string]string{
   304  				"gcr.io/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   305  			},
   306  		},
   307  		{
   308  			name:           "file that has multiple different images replaces both of them",
   309  			content:        `{"images": ["gcr.io/k8s-testimages/some-image:v20190404-12345678-master", "gcr.io/k8s-testimages/some-image:v20190404-12345678-experimental"]}`,
   310  			expectedResult: `{"images": ["gcr.io/k8s-testimages/some-image:v20190405-123456789-master", "gcr.io/k8s-testimages/some-image:v20190405-123456789-experimental"]}`,
   311  			newTags: map[string]string{
   312  				"gcr.io/k8s-testimages/some-image:v20190404-12345678-master":       "v20190405-123456789-master",
   313  				"gcr.io/k8s-testimages/some-image:v20190404-12345678-experimental": "v20190405-123456789-experimental",
   314  			},
   315  		},
   316  		{
   317  			name:           "file with an error image is still otherwise updated",
   318  			content:        `{"images": ["gcr.io/k8s-testimages/some-image:0.2", "gcr.io/k8s-testimages/some-image:v20190404-12345678"]}`,
   319  			expectedResult: `{"images": ["gcr.io/k8s-testimages/some-image:0.2", "gcr.io/k8s-testimages/some-image:v20190405-123456789"]}`,
   320  			newTags: map[string]string{
   321  				"gcr.io/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   322  			},
   323  		},
   324  		{
   325  			name:           "gcr subdomains are supported",
   326  			content:        `{"images": ["eu.gcr.io/k8s-testimages/some-image:v20190404-12345678"]}`,
   327  			expectedResult: `{"images": ["eu.gcr.io/k8s-testimages/some-image:v20190405-123456789"]}`,
   328  			newTags: map[string]string{
   329  				"eu.gcr.io/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   330  			},
   331  		},
   332  		{
   333  			name:           "AR multi-regional subdomains are supported",
   334  			content:        `{"images": ["us-docker.pkg.dev/k8s-testimages/some-image:v20190404-12345678"]}`,
   335  			expectedResult: `{"images": ["us-docker.pkg.dev/k8s-testimages/some-image:v20190405-123456789"]}`,
   336  			newTags: map[string]string{
   337  				"us-docker.pkg.dev/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   338  			},
   339  		},
   340  		{
   341  			name:           "AR regional subdomains are supported",
   342  			content:        `{"images": ["us-central1-docker.pkg.dev/k8s-testimages/some-image:v20190404-12345678"]}`,
   343  			expectedResult: `{"images": ["us-central1-docker.pkg.dev/k8s-testimages/some-image:v20190405-123456789"]}`,
   344  			newTags: map[string]string{
   345  				"us-central1-docker.pkg.dev/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   346  			},
   347  		},
   348  		{
   349  			name:           "images not matching the filter regex are not updated",
   350  			content:        `{"images": ["gcr.io/k8s-prow/pkg-thing:v20190404-12345678", "gcr.io/k8s-testimages/some-image:v20190404-12345678"]}`,
   351  			expectedResult: `{"images": ["gcr.io/k8s-prow/pkg-thing:v20190404-12345678", "gcr.io/k8s-testimages/some-image:v20190405-123456789"]}`,
   352  			newTags: map[string]string{
   353  				"gcr.io/k8s-prow/pkg-thing:v20190404-12345678":        "v20190405-123456789",
   354  				"gcr.io/k8s-testimages/some-image:v20190404-12345678": "v20190405-123456789",
   355  			},
   356  			imageFilter: regexp.MustCompile("gcr.io/k8s-testimages"),
   357  		},
   358  	}
   359  
   360  	for _, test := range tests {
   361  		t.Run(test.name, func(t *testing.T) {
   362  			tagPicker := func(imageHost string, imageName string, imageTag string) (string, error) {
   363  				result, ok := test.newTags[imageHost+"/"+imageName+":"+imageTag]
   364  				if !ok {
   365  					return "", fmt.Errorf("unknown image %s/%s:%s", imageHost, imageName, imageTag)
   366  				}
   367  				return result, nil
   368  			}
   369  
   370  			newContent := updateAllTags(tagPicker, []byte(test.content), test.imageFilter)
   371  			if test.expectedResult != string(newContent) {
   372  				t.Fatalf("Expected content:\n%s\n\nActual content:\n%s\n\n", test.expectedResult, string(newContent))
   373  			}
   374  		})
   375  	}
   376  }