gopkg.in/docker/docker.v20@v20.10.27/reference/store_test.go (about)

     1  package reference // import "github.com/docker/docker/reference"
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/distribution/reference"
    11  	digest "github.com/opencontainers/go-digest"
    12  	"gotest.tools/v3/assert"
    13  	is "gotest.tools/v3/assert/cmp"
    14  )
    15  
    16  var (
    17  	saveLoadTestCases = map[string]digest.Digest{
    18  		"registry:5000/foobar:HEAD":      "sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6",
    19  		"registry:5000/foobar:alternate": "sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793",
    20  		"registry:5000/foobar:latest":    "sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b",
    21  		"registry:5000/foobar:master":    "sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc",
    22  		"jess/hollywood:latest":          "sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe",
    23  		"registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6": "sha256:24126a56805beb9711be5f4590cc2eb55ab8d4a85ebd618eed72bb19fc50631c",
    24  		"busybox:latest": "sha256:91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c",
    25  	}
    26  
    27  	marshalledSaveLoadTestCases = []byte(`{"Repositories":{"busybox":{"busybox:latest":"sha256:91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c"},"jess/hollywood":{"jess/hollywood:latest":"sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe"},"registry":{"registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6":"sha256:24126a56805beb9711be5f4590cc2eb55ab8d4a85ebd618eed72bb19fc50631c"},"registry:5000/foobar":{"registry:5000/foobar:HEAD":"sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6","registry:5000/foobar:alternate":"sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793","registry:5000/foobar:latest":"sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b","registry:5000/foobar:master":"sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc"}}}`)
    28  )
    29  
    30  func TestLoad(t *testing.T) {
    31  	jsonFile, err := os.CreateTemp("", "tag-store-test")
    32  	if err != nil {
    33  		t.Fatalf("error creating temp file: %v", err)
    34  	}
    35  	defer os.RemoveAll(jsonFile.Name())
    36  
    37  	// Write canned json to the temp file
    38  	_, err = jsonFile.Write(marshalledSaveLoadTestCases)
    39  	if err != nil {
    40  		t.Fatalf("error writing to temp file: %v", err)
    41  	}
    42  	jsonFile.Close()
    43  
    44  	store, err := NewReferenceStore(jsonFile.Name())
    45  	if err != nil {
    46  		t.Fatalf("error creating tag store: %v", err)
    47  	}
    48  
    49  	for refStr, expectedID := range saveLoadTestCases {
    50  		ref, err := reference.ParseNormalizedNamed(refStr)
    51  		if err != nil {
    52  			t.Fatalf("failed to parse reference: %v", err)
    53  		}
    54  		id, err := store.Get(ref)
    55  		if err != nil {
    56  			t.Fatalf("could not find reference %s: %v", refStr, err)
    57  		}
    58  		if id != expectedID {
    59  			t.Fatalf("expected %s - got %s", expectedID, id)
    60  		}
    61  	}
    62  }
    63  
    64  func TestSave(t *testing.T) {
    65  	jsonFile, err := os.CreateTemp("", "tag-store-test")
    66  	assert.NilError(t, err)
    67  
    68  	_, err = jsonFile.Write([]byte(`{}`))
    69  	assert.NilError(t, err)
    70  	jsonFile.Close()
    71  	defer os.RemoveAll(jsonFile.Name())
    72  
    73  	store, err := NewReferenceStore(jsonFile.Name())
    74  	if err != nil {
    75  		t.Fatalf("error creating tag store: %v", err)
    76  	}
    77  
    78  	for refStr, id := range saveLoadTestCases {
    79  		ref, err := reference.ParseNormalizedNamed(refStr)
    80  		if err != nil {
    81  			t.Fatalf("failed to parse reference: %v", err)
    82  		}
    83  		if canonical, ok := ref.(reference.Canonical); ok {
    84  			err = store.AddDigest(canonical, id, false)
    85  			if err != nil {
    86  				t.Fatalf("could not add digest reference %s: %v", refStr, err)
    87  			}
    88  		} else {
    89  			err = store.AddTag(ref, id, false)
    90  			if err != nil {
    91  				t.Fatalf("could not add reference %s: %v", refStr, err)
    92  			}
    93  		}
    94  	}
    95  
    96  	jsonBytes, err := os.ReadFile(jsonFile.Name())
    97  	if err != nil {
    98  		t.Fatalf("could not read json file: %v", err)
    99  	}
   100  
   101  	if !bytes.Equal(jsonBytes, marshalledSaveLoadTestCases) {
   102  		t.Fatalf("save output did not match expectations\nexpected:\n%s\ngot:\n%s", marshalledSaveLoadTestCases, jsonBytes)
   103  	}
   104  }
   105  
   106  func TestAddDeleteGet(t *testing.T) {
   107  	jsonFile, err := os.CreateTemp("", "tag-store-test")
   108  	if err != nil {
   109  		t.Fatalf("error creating temp file: %v", err)
   110  	}
   111  	_, err = jsonFile.Write([]byte(`{}`))
   112  	assert.NilError(t, err)
   113  	_ = jsonFile.Close()
   114  	defer func() { _ = os.RemoveAll(jsonFile.Name()) }()
   115  
   116  	store, err := NewReferenceStore(jsonFile.Name())
   117  	if err != nil {
   118  		t.Fatalf("error creating tag store: %v", err)
   119  	}
   120  
   121  	testImageID1 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9c")
   122  	testImageID2 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9d")
   123  	testImageID3 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9e")
   124  
   125  	// Try adding a reference with no tag or digest
   126  	nameOnly, err := reference.ParseNormalizedNamed("username/repo")
   127  	if err != nil {
   128  		t.Fatalf("could not parse reference: %v", err)
   129  	}
   130  	if err = store.AddTag(nameOnly, testImageID1, false); err != nil {
   131  		t.Fatalf("error adding to store: %v", err)
   132  	}
   133  
   134  	// Add a few references
   135  	ref1, err := reference.ParseNormalizedNamed("username/repo1:latest")
   136  	if err != nil {
   137  		t.Fatalf("could not parse reference: %v", err)
   138  	}
   139  	if err = store.AddTag(ref1, testImageID1, false); err != nil {
   140  		t.Fatalf("error adding to store: %v", err)
   141  	}
   142  
   143  	ref2, err := reference.ParseNormalizedNamed("username/repo1:old")
   144  	if err != nil {
   145  		t.Fatalf("could not parse reference: %v", err)
   146  	}
   147  	if err = store.AddTag(ref2, testImageID2, false); err != nil {
   148  		t.Fatalf("error adding to store: %v", err)
   149  	}
   150  
   151  	ref3, err := reference.ParseNormalizedNamed("username/repo1:alias")
   152  	if err != nil {
   153  		t.Fatalf("could not parse reference: %v", err)
   154  	}
   155  	if err = store.AddTag(ref3, testImageID1, false); err != nil {
   156  		t.Fatalf("error adding to store: %v", err)
   157  	}
   158  
   159  	ref4, err := reference.ParseNormalizedNamed("username/repo2:latest")
   160  	if err != nil {
   161  		t.Fatalf("could not parse reference: %v", err)
   162  	}
   163  	if err = store.AddTag(ref4, testImageID2, false); err != nil {
   164  		t.Fatalf("error adding to store: %v", err)
   165  	}
   166  	// Write the same values again; should silently succeed
   167  	if err = store.AddTag(ref4, testImageID2, false); err != nil {
   168  		t.Fatalf("error redundantly adding to store: %v", err)
   169  	}
   170  
   171  	ref5, err := reference.ParseNormalizedNamed("username/repo3@sha256:58153dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c")
   172  	if err != nil {
   173  		t.Fatalf("could not parse reference: %v", err)
   174  	}
   175  	if err = store.AddDigest(ref5.(reference.Canonical), testImageID2, false); err != nil {
   176  		t.Fatalf("error adding to store: %v", err)
   177  	}
   178  	// Write the same values again; should silently succeed
   179  	if err = store.AddDigest(ref5.(reference.Canonical), testImageID2, false); err != nil {
   180  		t.Fatalf("error redundantly adding to store: %v", err)
   181  	}
   182  
   183  	// Attempt to overwrite with force == false
   184  	if err = store.AddTag(ref4, testImageID3, false); err == nil || !strings.HasPrefix(err.Error(), "Conflict:") {
   185  		t.Fatalf("did not get expected error on overwrite attempt - got %v", err)
   186  	}
   187  	// Repeat to overwrite with force == true
   188  	if err = store.AddTag(ref4, testImageID3, true); err != nil {
   189  		t.Fatalf("failed to force tag overwrite: %v", err)
   190  	}
   191  
   192  	// Check references so far
   193  	id, err := store.Get(nameOnly)
   194  	if err != nil {
   195  		t.Fatalf("Get returned error: %v", err)
   196  	}
   197  	if id != testImageID1 {
   198  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
   199  	}
   200  
   201  	id, err = store.Get(ref1)
   202  	if err != nil {
   203  		t.Fatalf("Get returned error: %v", err)
   204  	}
   205  	if id != testImageID1 {
   206  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
   207  	}
   208  
   209  	id, err = store.Get(ref2)
   210  	if err != nil {
   211  		t.Fatalf("Get returned error: %v", err)
   212  	}
   213  	if id != testImageID2 {
   214  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID2.String())
   215  	}
   216  
   217  	id, err = store.Get(ref3)
   218  	if err != nil {
   219  		t.Fatalf("Get returned error: %v", err)
   220  	}
   221  	if id != testImageID1 {
   222  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
   223  	}
   224  
   225  	id, err = store.Get(ref4)
   226  	if err != nil {
   227  		t.Fatalf("Get returned error: %v", err)
   228  	}
   229  	if id != testImageID3 {
   230  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID3.String())
   231  	}
   232  
   233  	id, err = store.Get(ref5)
   234  	if err != nil {
   235  		t.Fatalf("Get returned error: %v", err)
   236  	}
   237  	if id != testImageID2 {
   238  		t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID3.String())
   239  	}
   240  
   241  	// Get should return ErrDoesNotExist for a nonexistent repo
   242  	nonExistRepo, err := reference.ParseNormalizedNamed("username/nonexistrepo:latest")
   243  	if err != nil {
   244  		t.Fatalf("could not parse reference: %v", err)
   245  	}
   246  	if _, err = store.Get(nonExistRepo); err != ErrDoesNotExist {
   247  		t.Fatal("Expected ErrDoesNotExist from Get")
   248  	}
   249  
   250  	// Get should return ErrDoesNotExist for a nonexistent tag
   251  	nonExistTag, err := reference.ParseNormalizedNamed("username/repo1:nonexist")
   252  	if err != nil {
   253  		t.Fatalf("could not parse reference: %v", err)
   254  	}
   255  	if _, err = store.Get(nonExistTag); err != ErrDoesNotExist {
   256  		t.Fatal("Expected ErrDoesNotExist from Get")
   257  	}
   258  
   259  	// Check References
   260  	refs := store.References(testImageID1)
   261  	if len(refs) != 3 {
   262  		t.Fatal("unexpected number of references")
   263  	}
   264  	// Looking for the references in this order verifies that they are
   265  	// returned lexically sorted.
   266  	if refs[0].String() != ref3.String() {
   267  		t.Fatalf("unexpected reference: %v", refs[0].String())
   268  	}
   269  	if refs[1].String() != ref1.String() {
   270  		t.Fatalf("unexpected reference: %v", refs[1].String())
   271  	}
   272  	if refs[2].String() != nameOnly.String()+":latest" {
   273  		t.Fatalf("unexpected reference: %v", refs[2].String())
   274  	}
   275  
   276  	// Check ReferencesByName
   277  	repoName, err := reference.ParseNormalizedNamed("username/repo1")
   278  	if err != nil {
   279  		t.Fatalf("could not parse reference: %v", err)
   280  	}
   281  	associations := store.ReferencesByName(repoName)
   282  	if len(associations) != 3 {
   283  		t.Fatal("unexpected number of associations")
   284  	}
   285  	// Looking for the associations in this order verifies that they are
   286  	// returned lexically sorted.
   287  	if associations[0].Ref.String() != ref3.String() {
   288  		t.Fatalf("unexpected reference: %v", associations[0].Ref.String())
   289  	}
   290  	if associations[0].ID != testImageID1 {
   291  		t.Fatalf("unexpected reference: %v", associations[0].Ref.String())
   292  	}
   293  	if associations[1].Ref.String() != ref1.String() {
   294  		t.Fatalf("unexpected reference: %v", associations[1].Ref.String())
   295  	}
   296  	if associations[1].ID != testImageID1 {
   297  		t.Fatalf("unexpected reference: %v", associations[1].Ref.String())
   298  	}
   299  	if associations[2].Ref.String() != ref2.String() {
   300  		t.Fatalf("unexpected reference: %v", associations[2].Ref.String())
   301  	}
   302  	if associations[2].ID != testImageID2 {
   303  		t.Fatalf("unexpected reference: %v", associations[2].Ref.String())
   304  	}
   305  
   306  	// Delete should return ErrDoesNotExist for a nonexistent repo
   307  	if _, err = store.Delete(nonExistRepo); err != ErrDoesNotExist {
   308  		t.Fatal("Expected ErrDoesNotExist from Delete")
   309  	}
   310  
   311  	// Delete should return ErrDoesNotExist for a nonexistent tag
   312  	if _, err = store.Delete(nonExistTag); err != ErrDoesNotExist {
   313  		t.Fatal("Expected ErrDoesNotExist from Delete")
   314  	}
   315  
   316  	// Delete a few references
   317  	if deleted, err := store.Delete(ref1); err != nil || !deleted {
   318  		t.Fatal("Delete failed")
   319  	}
   320  	if _, err := store.Get(ref1); err != ErrDoesNotExist {
   321  		t.Fatal("Expected ErrDoesNotExist from Get")
   322  	}
   323  	if deleted, err := store.Delete(ref5); err != nil || !deleted {
   324  		t.Fatal("Delete failed")
   325  	}
   326  	if _, err := store.Get(ref5); err != ErrDoesNotExist {
   327  		t.Fatal("Expected ErrDoesNotExist from Get")
   328  	}
   329  	if deleted, err := store.Delete(nameOnly); err != nil || !deleted {
   330  		t.Fatal("Delete failed")
   331  	}
   332  	if _, err := store.Get(nameOnly); err != ErrDoesNotExist {
   333  		t.Fatal("Expected ErrDoesNotExist from Get")
   334  	}
   335  }
   336  
   337  func TestInvalidTags(t *testing.T) {
   338  	tmpDir, err := os.MkdirTemp("", "tag-store-test")
   339  	assert.NilError(t, err)
   340  	defer os.RemoveAll(tmpDir)
   341  
   342  	store, err := NewReferenceStore(filepath.Join(tmpDir, "repositories.json"))
   343  	assert.NilError(t, err)
   344  	id := digest.Digest("sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6")
   345  
   346  	// sha256 as repo name
   347  	ref, err := reference.ParseNormalizedNamed("sha256:abc")
   348  	assert.NilError(t, err)
   349  	err = store.AddTag(ref, id, true)
   350  	assert.Check(t, is.ErrorContains(err, ""))
   351  
   352  	// setting digest as a tag
   353  	ref, err = reference.ParseNormalizedNamed("registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6")
   354  	assert.NilError(t, err)
   355  
   356  	err = store.AddTag(ref, id, true)
   357  	assert.Check(t, is.ErrorContains(err, ""))
   358  }