Skip to content

Commit

Permalink
feat: Add kubectl renderer for render v2
Browse files Browse the repository at this point in the history
  • Loading branch information
tejal29 committed Feb 16, 2022
1 parent 5a671f5 commit aa09298
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 189 deletions.
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,9 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
Expand Down Expand Up @@ -1700,6 +1703,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 h1:Q3C9yzW
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel/exporters/stdout v0.20.0 h1:NXKkOWV7Np9myYrQE0wqRS3SbwzbupHu07rDONKubMo=
go.opentelemetry.io/otel/exporters/stdout v0.20.0/go.mod h1:t9LUU3JvYlmoPA61abhvsXxKh58xdyi3nMtI6JiR8v0=
go.opentelemetry.io/otel/exporters/trace/jaeger v0.20.0 h1:FoclOadJNul1vUiKnZU0sKFWOZtZQq3jUzSbrX2jwNM=
Expand All @@ -1716,6 +1721,8 @@ go.opentelemetry.io/otel/sdk/metric v0.20.0 h1:7ao1wpzHRVKf0OQ7GIxiQJA6X7DLX9o14
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
Expand Down
173 changes: 173 additions & 0 deletions pkg/skaffold/render/renderer/kpt/kpt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
Copyright 2022 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kpt

import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"

sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/errors"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/generate"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/kptfile"
rUtil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/renderer/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/transform"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/validate"
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
"github.com/GoogleContainerTools/skaffold/proto/v1"
)

type Kpt struct {
generate.Generator
validate.Validator
transform.Transformer
hydrationDir string
labels map[string]string
}

func New(config *latestV2.RenderConfig, workingDir, hydrationDir string,
labels map[string]string) (*Kpt, error) {
generator := generate.NewGenerator(workingDir, config.Generate, hydrationDir)
var validator validate.Validator
if config.Validate != nil {
var err error
validator, err = validate.NewValidator(*config.Validate)
if err != nil {
return nil, err
}
} else {
validator, _ = validate.NewValidator([]latestV2.Validator{})
}

var transformer transform.Transformer
if config.Transform != nil {
var err error
transformer, err = transform.NewTransformer(*config.Transform)
if err != nil {
return nil, err
}
} else {
transformer, _ = transform.NewTransformer([]latestV2.Transformer{})
}
return &Kpt{Generator: generator, Validator: validator, Transformer: transformer,
hydrationDir: hydrationDir, labels: labels}, nil
}

func (r *Kpt) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, _ bool, output string) error {

kptfilePath := filepath.Join(r.hydrationDir, kptfile.KptFileName)
kfConfig := &kptfile.KptFile{}

// Initialize the kpt hydration directory.
// This directory is used to cache DRY config and hydrates the DRY config to WET config in-place.
// This is needed because kpt v1 only supports in-place config while users may not want to have their config be
// hydrated in place.
// Once Kptfile is initialized, its "pipeline" field will be updated in each skaffold render, and its "inventory"
// will keep the same to guarantee accurate `kpt live apply`.
_, endTrace := instrumentation.StartTrace(ctx, "Render_initKptfile")
if _, err := os.Stat(kptfilePath); os.IsNotExist(err) {
if err := os.MkdirAll(r.hydrationDir, os.ModePerm); err != nil {
endTrace(instrumentation.TraceEndError(fmt.Errorf("create hydration dir %v:%w", r.hydrationDir, err)))
return err
}
cmd := exec.CommandContext(ctx, "kpt", "pkg", "init", r.hydrationDir)
if _, err := util.RunCmdOut(ctx, cmd); err != nil {
return sErrors.NewError(err,
&proto.ActionableErr{
Message: fmt.Sprintf("unable to initialize Kptfile in %v", r.hydrationDir),
ErrCode: proto.StatusCode_RENDER_KPTFILE_INIT_ERR,
Suggestions: []*proto.Suggestion{
{
SuggestionCode: proto.SuggestionCode_KPTFILE_MANUAL_INIT,
Action: fmt.Sprintf("please manually run `kpt pkg init %v`", r.hydrationDir),
},
},
})
}
}
endTrace()
_, endTrace = instrumentation.StartTrace(ctx, "Render_readKptfile")
kptfileBytes, err := ioutil.ReadFile(kptfilePath)
if err != nil {
endTrace(instrumentation.TraceEndError(fmt.Errorf("read Kptfile from %v: %w",
filepath.Dir(kptfilePath), err)))
return err
}
if err := yaml.UnmarshalStrict(kptfileBytes, &kfConfig); err != nil {
return errors.ParseKptfileError(err, r.hydrationDir)
}
if err := os.RemoveAll(r.hydrationDir); err != nil {
return errors.DeleteKptfileError(err, r.hydrationDir)
}
endTrace()
rUtil.GenerateHydratedManifests(ctx, out, builds, r.Generator, r.hydrationDir, r.labels)

if kfConfig.Pipeline == nil {
kfConfig.Pipeline = &kptfile.Pipeline{}
}
kfConfig.Pipeline.Validators = r.GetDeclarativeValidators()
kfConfig.Pipeline.Mutators, err = r.GetDeclarativeTransformers()
if err != nil {
return err
}
configByte, err := yaml.Marshal(kfConfig)
if err != nil {
return err
}
if err = ioutil.WriteFile(kptfilePath, configByte, 0644); err != nil {
return err
}
endTrace()

rCtx, endTrace := instrumentation.StartTrace(ctx, "Render_kptRenderCommand")
cmd := exec.CommandContext(rCtx, "kpt", "fn", "render", r.hydrationDir)
cmd.Stdout = out
cmd.Stderr = out
if err := util.RunCmd(ctx, cmd); err != nil {
endTrace(instrumentation.TraceEndError(err))
// TODO(yuwenma): How to guide users when they face kpt error (may due to bad user config)?
return err
}

if output != "" {
r.writeManifestsToFile(ctx, out, output)
}
return nil
}

// writeManifestsToFile converts the structured manifest to a flatten format and store them in the given `output` file.
func (r *Kpt) writeManifestsToFile(ctx context.Context, out io.Writer, output string) error {
rCtx, endTrace := instrumentation.StartTrace(ctx, "Render_outputManifests")
cmd := exec.CommandContext(rCtx, "kpt", "fn", "source", r.hydrationDir, "-o", "unwrap")
var buf []byte
cmd.Stderr = out
buf, err := util.RunCmdOut(ctx, cmd)
if err != nil {
endTrace(instrumentation.TraceEndError(err))
return err
}
return ioutil.WriteFile(output, buf, os.ModePerm)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package renderer
package kpt

import (
"bytes"
Expand All @@ -25,6 +25,7 @@ import (
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/kptfile"
rUtil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/renderer/util"
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/testutil"
Expand Down Expand Up @@ -168,7 +169,7 @@ pipeline:
Write(filepath.Join(constants.DefaultHydrationDir, kptfile.KptFileName), test.originalKptfile).
Touch("empty.ignored").
Chdir()
r, err := NewSkaffoldRenderer(test.renderConfig, tmpDirObj.Root(),
r, err := New(test.renderConfig, tmpDirObj.Root(),
filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir), map[string]string{})
t.CheckNoError(err)
t.Override(&util.DefaultExecCommand,
Expand All @@ -178,7 +179,7 @@ pipeline:
err = r.Render(context.Background(), &b, []graph.Artifact{{ImageName: "leeroy-web", Tag: "leeroy-web:v1"}},
true, "")
t.CheckNoError(err)
t.CheckFileExistAndContent(filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir, dryFileName), []byte(labeledPodYaml))
t.CheckFileExistAndContent(filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir, rUtil.DryFileName), []byte(labeledPodYaml))
t.CheckFileExistAndContent(filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir, kptfile.KptFileName), []byte(test.updatedKptfile))
})
}
Expand Down Expand Up @@ -231,7 +232,7 @@ inventory:
tmpDirObj.Write("pod.yaml", podYaml).
Write(filepath.Join(constants.DefaultHydrationDir, kptfile.KptFileName), test.originalKptfile).
Chdir()
r, err := NewSkaffoldRenderer(&latestV2.RenderConfig{
r, err := New(&latestV2.RenderConfig{
Generate: latestV2.Generate{RawK8s: []string{"pod.yaml"}},
Validate: &[]latestV2.Validator{{Name: "kubeval"}}}, tmpDirObj.Root(),
filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir), map[string]string{})
Expand Down
43 changes: 43 additions & 0 deletions pkg/skaffold/render/renderer/kubectl/kubectl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2022 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubectl

import (
"context"
"io"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/generate"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/renderer/util"
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
)

type Kubectl struct {
generate.Generator
hydrationDir string
labels map[string]string
}

func New(config *latestV2.RenderConfig, workingDir, hydrationDir string,
labels map[string]string) (Kubectl, error) {
generator := generate.NewGenerator(workingDir, config.Generate, hydrationDir)
return Kubectl{Generator: generator, hydrationDir: hydrationDir, labels: labels}, nil
}

func (r Kubectl) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, _ bool, _ string) error {
return util.GenerateHydratedManifests(ctx, out, builds, r.Generator, r.hydrationDir, r.labels)
}
105 changes: 105 additions & 0 deletions pkg/skaffold/render/renderer/kubectl/kubectl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright 2021 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl

import (
"bytes"
"context"
"path/filepath"
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
rUtil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/renderer/util"
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
"github.com/GoogleContainerTools/skaffold/testutil"
)

const (
// Raw manifests
podYaml = `apiVersion: v1
kind: Pod
metadata:
name: leeroy-web
spec:
containers:
- image: leeroy-web
name: leeroy-web
`
// manifests with image tags and label
labeledPodYaml = `apiVersion: v1
kind: Pod
metadata:
labels:
run.id: test
name: leeroy-web
spec:
containers:
- image: leeroy-web:v1
name: leeroy-web
`
// manifests with image tags
taggedPodYaml = `apiVersion: v1
kind: Pod
metadata:
name: leeroy-web
spec:
containers:
- image: leeroy-web:v1
name: leeroy-web
`
)

func TestRender(t *testing.T) {
tests := []struct {
description string
renderConfig *latestV2.RenderConfig
labels map[string]string
expected string
}{
{
description: "single manifest with no labels",
renderConfig: &latestV2.RenderConfig{
Generate: latestV2.Generate{RawK8s: []string{"pod.yaml"}},
},
expected: taggedPodYaml,
},
{
description: "single manifest with labels",
renderConfig: &latestV2.RenderConfig{
Generate: latestV2.Generate{RawK8s: []string{"pod.yaml"}},
},
labels: map[string]string{"run.id": "test"},
expected: labeledPodYaml,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
tmpDirObj := t.NewTempDir()
tmpDirObj.Write("pod.yaml", podYaml).
Touch("empty.ignored").
Chdir()
r, err := New(test.renderConfig, tmpDirObj.Root(),
filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir), test.labels)
t.CheckNoError(err)
var b bytes.Buffer
err = r.Render(context.Background(), &b, []graph.Artifact{{ImageName: "leeroy-web", Tag: "leeroy-web:v1"}},
true, "")
t.CheckNoError(err)
t.CheckFileExistAndContent(filepath.Join(tmpDirObj.Root(), constants.DefaultHydrationDir, rUtil.DryFileName), []byte(test.expected))
})
}
}
Loading

0 comments on commit aa09298

Please sign in to comment.