github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/image_test.go (about)

     1  package k8s
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/docker/distribution/reference"
     9  	"github.com/opencontainers/go-digest"
    10  	"github.com/stretchr/testify/assert"
    11  	appsv1 "k8s.io/api/apps/v1"
    12  	v1 "k8s.io/api/core/v1"
    13  
    14  	"github.com/windmilleng/tilt/internal/container"
    15  	"github.com/windmilleng/tilt/internal/k8s/testyaml"
    16  )
    17  
    18  func TestInjectDigestSanchoYAML(t *testing.T) {
    19  	entities, err := ParseYAMLFromString(testyaml.SanchoYAML)
    20  	if err != nil {
    21  		t.Fatal(err)
    22  	}
    23  
    24  	if len(entities) != 1 {
    25  		t.Fatalf("Unexpected entities: %+v", entities)
    26  	}
    27  
    28  	entity := entities[0]
    29  	name := "gcr.io/some-project-162817/sancho"
    30  	digest := "sha256:2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
    31  	newEntity, replaced, err := InjectImageDigestWithStrings(entity, name, digest, v1.PullIfNotPresent)
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  
    36  	if !replaced {
    37  		t.Errorf("Expected replaced: true. Actual: %v", replaced)
    38  	}
    39  
    40  	result, err := SerializeSpecYAML([]K8sEntity{newEntity})
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	if !strings.Contains(result, fmt.Sprintf("image: %s@%s", name, digest)) {
    46  		t.Errorf("image name did not appear in serialized yaml: %s", result)
    47  	}
    48  }
    49  
    50  func TestInjectDigestDoesNotMutateOriginal(t *testing.T) {
    51  	entities, err := ParseYAMLFromString(testyaml.SanchoYAML)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	if len(entities) != 1 {
    57  		t.Fatalf("Unexpected entities: %+v", entities)
    58  	}
    59  
    60  	entity := entities[0]
    61  	name := "gcr.io/some-project-162817/sancho"
    62  	digest := "sha256:2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
    63  	_, replaced, err := InjectImageDigestWithStrings(entity, name, digest, v1.PullIfNotPresent)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  
    68  	if !replaced {
    69  		t.Errorf("Expected replaced: true. Actual: %v", replaced)
    70  	}
    71  
    72  	result, err := SerializeSpecYAML([]K8sEntity{entity})
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	if strings.Contains(result, fmt.Sprintf("image: %s@%s", name, digest)) {
    78  		t.Errorf("oops! accidentally mutated original entity: %s", result)
    79  	}
    80  }
    81  
    82  func TestInjectImagePullPolicy(t *testing.T) {
    83  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	entity := entities[1]
    89  	newEntity, err := InjectImagePullPolicy(entity, v1.PullNever)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	result, err := SerializeSpecYAML([]K8sEntity{newEntity})
    95  	if err != nil {
    96  		t.Fatal(err)
    97  	}
    98  
    99  	if !strings.Contains(result, "imagePullPolicy: Never") {
   100  		t.Errorf("image does not have correct pull policy: %s", result)
   101  	}
   102  
   103  	serializedOrigEntity, err := SerializeSpecYAML([]K8sEntity{entity})
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	if strings.Contains(serializedOrigEntity, "imagePullPolicy: Never") {
   109  		t.Errorf("oops! accidentally mutated original entity: %+v", entity)
   110  	}
   111  }
   112  
   113  func TestInjectImagePullPolicyDoesNotMutateOriginal(t *testing.T) {
   114  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  
   119  	entity := entities[1]
   120  	_, err = InjectImagePullPolicy(entity, v1.PullNever)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	result, err := SerializeSpecYAML([]K8sEntity{entity})
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	if strings.Contains(result, "imagePullPolicy: Never") {
   131  		t.Errorf("oops! accidentally mutated original entity: %+v", entity)
   132  	}
   133  }
   134  
   135  func TestErrorInjectDigestBlorgBackendYAML(t *testing.T) {
   136  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  
   141  	if len(entities) != 2 {
   142  		t.Fatalf("Unexpected entities: %+v", entities)
   143  	}
   144  
   145  	entity := entities[1]
   146  	name := "gcr.io/blorg-dev/blorg-backend"
   147  	digest := "sha256:2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
   148  	_, _, err = InjectImageDigestWithStrings(entity, name, digest, v1.PullNever)
   149  	if err == nil || !strings.Contains(err.Error(), "INTERNAL TILT ERROR") {
   150  		t.Errorf("Expected internal tilt error, actual: %v", err)
   151  	}
   152  }
   153  
   154  func TestInjectDigestBlorgBackendYAML(t *testing.T) {
   155  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	if len(entities) != 2 {
   161  		t.Fatalf("Unexpected entities: %+v", entities)
   162  	}
   163  
   164  	entity := entities[1]
   165  	name := "gcr.io/blorg-dev/blorg-backend"
   166  	namedTagged, _ := reference.ParseNamed(fmt.Sprintf("%s:wm-tilt", name))
   167  	newEntity, replaced, err := InjectImageDigest(entity, container.NameSelector(namedTagged), namedTagged, false, v1.PullNever)
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  
   172  	if !replaced {
   173  		t.Errorf("Expected replaced: true. Actual: %v", replaced)
   174  	}
   175  
   176  	result, err := SerializeSpecYAML([]K8sEntity{newEntity})
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	if !strings.Contains(result, fmt.Sprintf("image: %s", namedTagged)) {
   182  		t.Errorf("image name did not appear in serialized yaml: %s", result)
   183  	}
   184  
   185  	if !strings.Contains(result, "imagePullPolicy: Never") {
   186  		t.Errorf("image does not have correct pull policy: %s", result)
   187  	}
   188  }
   189  
   190  // Returns: the new entity, whether anything was replaced, and an error.
   191  func InjectImageDigestWithStrings(entity K8sEntity, original string, newDigest string, policy v1.PullPolicy) (K8sEntity, bool, error) {
   192  	originalRef, err := reference.ParseNamed(original)
   193  	if err != nil {
   194  		return K8sEntity{}, false, err
   195  	}
   196  
   197  	d, err := digest.Parse(newDigest)
   198  	if err != nil {
   199  		return K8sEntity{}, false, err
   200  	}
   201  
   202  	canonicalRef, err := reference.WithDigest(originalRef, d)
   203  	if err != nil {
   204  		return K8sEntity{}, false, err
   205  	}
   206  
   207  	return InjectImageDigest(entity, container.NameSelector(originalRef), canonicalRef, false, policy)
   208  }
   209  
   210  func TestInjectSyncletImage(t *testing.T) {
   211  	entities, err := ParseYAMLFromString(testyaml.SyncletYAML)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	assert.Equal(t, 1, len(entities))
   217  	entity := entities[0]
   218  	name := "gcr.io/windmill-public-containers/synclet"
   219  	namedTagged, _ := container.ParseNamedTagged(fmt.Sprintf("%s:tilt-deadbeef", name))
   220  	newEntity, replaced, err := InjectImageDigest(entity, container.NameSelector(namedTagged), namedTagged, false, v1.PullNever)
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	} else if !replaced {
   224  		t.Errorf("Expected replacement in:\n%s", testyaml.SyncletYAML)
   225  	}
   226  
   227  	result, err := SerializeSpecYAML([]K8sEntity{newEntity})
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  
   232  	if !strings.Contains(result, namedTagged.String()) {
   233  		t.Errorf("could not find image in yaml (%s):\n%s", namedTagged, result)
   234  	}
   235  }
   236  
   237  func TestEntityHasImage(t *testing.T) {
   238  	entities, err := ParseYAMLFromString(testyaml.BlorgBackendYAML)
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	img := container.MustParseSelector("gcr.io/blorg-dev/blorg-backend:devel-nick")
   244  	wrongImg := container.MustParseSelector("gcr.io/blorg-dev/wrong-app-whoops:devel-nick")
   245  
   246  	match, err := entities[0].HasImage(img, nil, false)
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	assert.False(t, match, "service yaml should not match (does not contain image)")
   251  
   252  	match, err = entities[1].HasImage(img, nil, false)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	assert.True(t, match, "deployment yaml should match image %s", img.String())
   257  
   258  	match, err = entities[1].HasImage(wrongImg, nil, false)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	assert.False(t, match, "deployment yaml should not match image %s", img.String())
   263  
   264  	entities, err = ParseYAMLFromString(testyaml.CRDYAML)
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  
   269  	img = container.MustParseTaggedSelector("docker.io/bitnami/minideb:latest")
   270  	e := entities[0]
   271  	jp, err := NewJSONPath("{.spec.validation.openAPIV3Schema.properties.spec.properties.image}")
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	imageJSONPaths := []JSONPath{jp}
   276  	match, err = e.HasImage(img, imageJSONPaths, false)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	assert.True(t, match, "CRD yaml should match image %s", img.String())
   281  
   282  	entities, err = ParseYAMLFromString(testyaml.SanchoImageInEnvYAML)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	img = container.MustParseSelector("gcr.io/some-project-162817/sancho")
   287  	e = entities[0]
   288  	match, err = e.HasImage(img, nil, false)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	assert.True(t, match, "deployment yaml should match image %s", img.String())
   293  	img2 := container.MustParseSelector("gcr.io/some-project-162817/sancho2")
   294  	e = entities[0]
   295  	match, err = e.HasImage(img2, nil, true)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	assert.True(t, match, "CRD yaml should match image %s", img2.String())
   300  
   301  }
   302  
   303  func testInjectDigestCRD(t *testing.T, yaml string, expectedDigestPrefix string) {
   304  	entities, err := ParseYAMLFromString(yaml)
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	if len(entities) != 1 {
   310  		t.Fatalf("Unexpected entities: %+v", entities)
   311  	}
   312  
   313  	entity := entities[0]
   314  	name := "gcr.io/foo"
   315  	digest := "sha256:2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
   316  	newEntity, replaced, err := InjectImageDigestWithStrings(entity, name, digest, v1.PullIfNotPresent)
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  
   321  	if !replaced {
   322  		t.Errorf("Expected replaced: true. Actual: %v", replaced)
   323  	}
   324  
   325  	result, err := SerializeSpecYAML([]K8sEntity{newEntity})
   326  	if err != nil {
   327  		t.Fatal(err)
   328  	}
   329  
   330  	if !strings.Contains(result, fmt.Sprintf("%s%s@%s", expectedDigestPrefix, name, digest)) {
   331  		t.Errorf("image name did not appear in serialized yaml: %s", result)
   332  	}
   333  }
   334  
   335  func TestInjectDigestCRDMapValue(t *testing.T) {
   336  	testInjectDigestCRD(t, `
   337  apiversion: foo/v1
   338  kind: Foo
   339  spec:
   340      image: gcr.io/foo:stable
   341  `, "image: ")
   342  }
   343  
   344  func TestInjectDigestCRDListElement(t *testing.T) {
   345  	testInjectDigestCRD(t, `
   346  apiversion: foo/v1
   347  kind: Foo
   348  spec:
   349      images:
   350        - gcr.io/foo:stable
   351  `, "- ")
   352  }
   353  
   354  func TestInjectDigestCRDListOfMaps(t *testing.T) {
   355  	testInjectDigestCRD(t, `
   356  apiversion: foo/v1
   357  kind: Foo
   358  spec:
   359      args:
   360          image: gcr.io/foo:stable
   361  `, "image: ")
   362  }
   363  
   364  func TestMatchInEnvVarsFalse(t *testing.T) {
   365  	entity := parseOneEntity(t, testyaml.SanchoImageInEnvYAML)
   366  	name := "gcr.io/some-project-162817/sancho"
   367  	digest := "2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
   368  	namedTagged, err := reference.ParseNamed(fmt.Sprintf("%s:%s", name, digest))
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	newEntity, replaced, err := InjectImageDigest(entity, container.NameSelector(namedTagged), namedTagged, false, v1.PullNever)
   373  	if err != nil {
   374  		t.Fatal(err)
   375  	}
   376  	assert.True(t, replaced)
   377  	d := newEntity.Obj.(*appsv1.Deployment)
   378  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   379  		return
   380  	}
   381  	c := d.Spec.Template.Spec.Containers[0]
   382  	// make sure we didn't inject to the env var
   383  	assert.Equal(t, namedTagged.String(), c.Image)
   384  	assert.Contains(t, c.Env, v1.EnvVar{Name: "bar", Value: name})
   385  }
   386  
   387  func TestMatchInEnvVarsTrue(t *testing.T) {
   388  	entity := parseOneEntity(t, testyaml.SanchoImageInEnvYAML)
   389  	name := "gcr.io/some-project-162817/sancho"
   390  	digest := "2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9"
   391  	namedTagged, err := reference.ParseNamed(fmt.Sprintf("%s:%s", name, digest))
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  	newEntity, replaced, err := InjectImageDigest(entity, container.NameSelector(namedTagged), namedTagged, true, v1.PullNever)
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	assert.True(t, replaced)
   400  	d := newEntity.Obj.(*appsv1.Deployment)
   401  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   402  		return
   403  	}
   404  	c := d.Spec.Template.Spec.Containers[0]
   405  	// make sure we didn't inject to the env var
   406  	assert.Equal(t, namedTagged.String(), c.Image)
   407  	assert.Contains(t, c.Env, v1.EnvVar{Name: "bar", Value: namedTagged.String()})
   408  }