Skip to content

Commit

Permalink
engine: create KubernetesApplyStatus (#4681)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicks committed Jun 24, 2021
1 parent 6e213a5 commit bf2aee9
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 90 deletions.
75 changes: 75 additions & 0 deletions internal/controllers/core/kubernetesapply/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package kubernetesapply

import (
"crypto"
"encoding/base64"
"fmt"
"hash"

jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/types"

"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
)

var defaultJSONIterator = createDefaultJSONIterator()

func createDefaultJSONIterator() jsoniter.API {
return jsoniter.Config{
EscapeHTML: true,
SortMapKeys: true,
ValidateJsonRawMessage: true,
CaseSensitive: true,
}.Froze()
}

// Compute the hash of all the inputs we fed into this apply.
func ComputeInputHash(spec v1alpha1.KubernetesApplySpec, imageMaps map[types.NamespacedName]*v1alpha1.ImageMap) (string, error) {
w := newHashWriter()
err := w.append(spec)
if err != nil {
return "", err
}

for _, imageMapName := range spec.ImageMaps {
imageMap, ok := imageMaps[types.NamespacedName{Name: imageMapName}]
if !ok {
return "", fmt.Errorf("missing image map: %v", err)
}
err = w.append(imageMap.Spec)
if err != nil {
return "", fmt.Errorf("hashing %s spec: %v", imageMapName, err)
}
err = w.append(imageMap.Status)
if err != nil {
return "", fmt.Errorf("hashing %s status: %v", imageMapName, err)
}
}

return w.done(), nil
}

type hashWriter struct {
h hash.Hash
}

func newHashWriter() *hashWriter {
return &hashWriter{h: crypto.SHA1.New()}
}

func (w hashWriter) append(o interface{}) error {
data, err := defaultJSONIterator.Marshal(o)
if err != nil {
return errors.Wrap(err, "serializing object for hash")
}
_, err = w.h.Write(data)
if err != nil {
return errors.Wrap(err, "computing hash")
}
return nil
}

func (w hashWriter) done() string {
return base64.URLEncoding.EncodeToString(w.h.Sum(nil))
}
74 changes: 74 additions & 0 deletions internal/controllers/core/kubernetesapply/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package kubernetesapply

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"github.com/tilt-dev/tilt/internal/k8s/testyaml"
"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
)

// The hashes are hard-coded in this file to ensure we
// don't accidentally change them.
//
// When updating the hashes, make sure that you don't accidentally
// change two hashes to the same value
func MustComputeInputHash(t testing.TB, spec v1alpha1.KubernetesApplySpec, imageMaps map[types.NamespacedName]*v1alpha1.ImageMap) string {
hash, err := ComputeInputHash(spec, imageMaps)
require.NoError(t, err)
return hash
}

func TestComputeHashSancho(t *testing.T) {
spec := v1alpha1.KubernetesApplySpec{YAML: testyaml.SanchoYAML}
hash := MustComputeInputHash(t, spec, nil)
assert.Equal(t, hash, "NgLB5gs3oLNMTnK1W71r1pfqO44=")
}

func TestComputeHashSanchoSidecar(t *testing.T) {
spec := v1alpha1.KubernetesApplySpec{YAML: testyaml.SanchoSidecarYAML}
hash := MustComputeInputHash(t, spec, nil)
assert.Equal(t, hash, "1Cb6qJKoOTOJ4HFER755XZUJyk8=")
}

func TestComputeHashSanchoImageMap(t *testing.T) {
spec := v1alpha1.KubernetesApplySpec{YAML: testyaml.SanchoYAML, ImageMaps: []string{"sancho"}}
imageMaps := make(map[types.NamespacedName]*v1alpha1.ImageMap)
imageMaps[types.NamespacedName{Name: "sancho"}] = &v1alpha1.ImageMap{
ObjectMeta: metav1.ObjectMeta{Name: "sancho"},
Spec: v1alpha1.ImageMapSpec{Selector: "sancho"},
Status: v1alpha1.ImageMapStatus{Image: "sancho:1234"},
}

hash := MustComputeInputHash(t, spec, imageMaps)
assert.Equal(t, hash, "ap_IM4fXlSII4EfidE1ciYSLePg=")
}

func TestComputeHashSanchoIgnoresIrrelevantImageMap(t *testing.T) {
spec := v1alpha1.KubernetesApplySpec{YAML: testyaml.SanchoYAML}
imageMaps := make(map[types.NamespacedName]*v1alpha1.ImageMap)
imageMaps[types.NamespacedName{Name: "sancho"}] = &v1alpha1.ImageMap{
ObjectMeta: metav1.ObjectMeta{Name: "sancho"},
Spec: v1alpha1.ImageMapSpec{Selector: "sancho"},
Status: v1alpha1.ImageMapStatus{Image: "sancho:1234"},
}

assert.Equal(t, MustComputeInputHash(t, spec, nil), MustComputeInputHash(t, spec, imageMaps))
}

func TestComputeHashSanchoImageMapChange(t *testing.T) {
spec := v1alpha1.KubernetesApplySpec{YAML: testyaml.SanchoYAML, ImageMaps: []string{"sancho"}}
imageMaps := make(map[types.NamespacedName]*v1alpha1.ImageMap)
imageMaps[types.NamespacedName{Name: "sancho"}] = &v1alpha1.ImageMap{
ObjectMeta: metav1.ObjectMeta{Name: "sancho"},
Spec: v1alpha1.ImageMapSpec{Selector: "sancho"},
Status: v1alpha1.ImageMapStatus{Image: "sancho:45678"},
}

hash := MustComputeInputHash(t, spec, imageMaps)
assert.Equal(t, hash, "6CHjHc7hicdn0U6UkGqo0qL3uO8=")
}
55 changes: 43 additions & 12 deletions internal/engine/buildcontrol/image_build_and_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import (
"github.com/tilt-dev/tilt/internal/analytics"
"github.com/tilt-dev/tilt/internal/build"
"github.com/tilt-dev/tilt/internal/container"
"github.com/tilt-dev/tilt/internal/controllers/core/kubernetesapply"
"github.com/tilt-dev/tilt/internal/dockerfile"
"github.com/tilt-dev/tilt/internal/k8s"
"github.com/tilt-dev/tilt/internal/store"
"github.com/tilt-dev/tilt/pkg/apis"
"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
"github.com/tilt-dev/tilt/pkg/logger"
"github.com/tilt-dev/tilt/pkg/model"
Expand Down Expand Up @@ -208,7 +210,7 @@ func (ibd *ImageBuildAndDeployer) BuildAndDeploy(ctx context.Context, st store.R

// (If we pass an empty list of refs here (as we will do if only deploying
// yaml), we just don't inject any image refs into the yaml, nbd.
k8sResult, err := ibd.deploy(ctx, st, ps, imageMapSet, kTarget, q.AllResults())
k8sResult, err := ibd.deploy(ctx, st, ps, kTarget.ID(), kTarget.KubernetesApplySpec, imageMapSet)
reportK8sDeployMetrics(ctx, kTarget, time.Since(startDeployTime), err != nil)
if err != nil {
return newResults, WrapDontFallBackError(err)
Expand Down Expand Up @@ -280,52 +282,81 @@ func (ibd *ImageBuildAndDeployer) shouldUseKINDLoad(ctx context.Context, iTarg m
}

// Returns: the entities deployed and the namespace of the pod with the given image name/tag.
func (ibd *ImageBuildAndDeployer) deploy(ctx context.Context, st store.RStore, ps *build.PipelineState,
imageMaps map[types.NamespacedName]*v1alpha1.ImageMap,
kTarget model.K8sTarget, results store.ImageBuildResultSet) (store.BuildResult, error) {
func (ibd *ImageBuildAndDeployer) deploy(
ctx context.Context,
st store.RStore,
ps *build.PipelineState,
kTargetID model.TargetID,
spec v1alpha1.KubernetesApplySpec,
imageMaps map[types.NamespacedName]*v1alpha1.ImageMap) (store.K8sBuildResult, error) {
ps.StartPipelineStep(ctx, "Deploying")
defer ps.EndPipelineStep(ctx)

ps.StartBuildStep(ctx, "Injecting images into Kubernetes YAML")

inputHash, err := kubernetesapply.ComputeInputHash(spec, imageMaps)
if err != nil {
return store.K8sBuildResult{}, err
}

// Create API objects.
spec := kTarget.KubernetesApplySpec
newK8sEntities, err := ibd.createEntitiesToDeploy(ctx, imageMaps, spec)
if err != nil {
return nil, err
return store.K8sBuildResult{}, err
}

ctx = ibd.indentLogger(ctx)
l := logger.Get(ctx)

l.Infof("Applying via kubectl:")
for _, displayName := range kTarget.DisplayNames {

// Use a min component count of 2 for computing names,
// so that the resource type appears
displayNames := k8s.UniqueNames(newK8sEntities, 2)
for _, displayName := range displayNames {
l.Infof("→ %s", displayName)
}

timeout := kTarget.Timeout.Duration
timeout := spec.Timeout.Duration
if timeout == 0 {
timeout = v1alpha1.KubernetesApplyTimeoutDefault
}

deployed, err := ibd.k8sClient.Upsert(ctx, newK8sEntities, timeout)
if err != nil {
return nil, err
return store.K8sBuildResult{}, err
}

status := v1alpha1.KubernetesApplyStatus{
LastApplyTime: apis.NowMicro(),
AppliedInputHash: inputHash,
}

for _, d := range deployed {
d.Clean()
}

var resultYAML string
resultYAML, err = k8s.SerializeSpecYAML(deployed)
if err != nil {
return store.K8sBuildResult{}, err
}

status.ResultYAML = resultYAML

podTemplateSpecHashes := []k8s.PodTemplateSpecHash{}
for _, entity := range deployed {
if entity.UID() == "" {
return nil, fmt.Errorf("Entity not deployed correctly: %v", entity)
return store.K8sBuildResult{}, fmt.Errorf("Entity not deployed correctly: %v", entity)
}
hs, err := k8s.ReadPodTemplateSpecHashes(entity)
if err != nil {
return nil, errors.Wrap(err, "reading pod template spec hashes")
return store.K8sBuildResult{}, errors.Wrap(err, "reading pod template spec hashes")
}
podTemplateSpecHashes = append(podTemplateSpecHashes, hs...)
}

return store.NewK8sDeployResult(kTarget.ID(), podTemplateSpecHashes, deployed), nil
return store.NewK8sDeployResult(kTargetID, status, k8s.ToRefList(deployed), podTemplateSpecHashes), nil
}

func (ibd *ImageBuildAndDeployer) indentLogger(ctx context.Context) context.Context {
Expand Down

0 comments on commit bf2aee9

Please sign in to comment.