Skip to content

Commit

Permalink
Merge branch 'master' of github.com:GoogleCloudPlatform/skaffold into…
Browse files Browse the repository at this point in the history
… watch
  • Loading branch information
r2d4 committed Feb 7, 2018
2 parents fd40286 + 22c4c4b commit 2677194
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 21 deletions.
9 changes: 8 additions & 1 deletion pkg/skaffold/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,14 @@ type DeployConfig struct {

// DeployType contains the specific implementation and parameters needed
// for the deploy step. Only one field should be populated.
type DeployType struct{}
type DeployType struct {
KubectlDeploy *KubectlDeploy `yaml:"kubectl"`
}

// KubectlDeploy contains the configuration needed for deploying with `kubectl apply`
type KubectlDeploy struct {
Manifests []string `yaml:"manifests"`
}

// Arifact represents items that need should be built, along with the context in which
// they should be built.
Expand Down
23 changes: 23 additions & 0 deletions pkg/skaffold/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ limitations under the License.
package deploy

import (
"fmt"

"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/build"
"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/config"
"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/util"
)

var execCommand = util.DefaultExecCommand

// Result is currently unused, but a stub for results that might be returned
// from a Deployer.Run()
type Result struct{}
Expand All @@ -31,3 +37,20 @@ type Deployer interface {
// cluster.
Run(*build.BuildResult) (*Result, error)
}

func JoinTagsToBuildResult(b *build.BuildResult, cfg *config.DeployConfig) (map[string]build.Build, error) {
imageToBuildResult := map[string]build.Build{}
for _, build := range b.Builds {
imageToBuildResult[build.ImageName] = build
}

paramToBuildResult := map[string]build.Build{}
for param, imageName := range cfg.Parameters {
build, ok := imageToBuildResult[imageName]
if !ok {
return nil, fmt.Errorf("No build present for %s", imageName)
}
paramToBuildResult[param] = build
}
return paramToBuildResult, nil
}
81 changes: 81 additions & 0 deletions pkg/skaffold/deploy/kubectl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2018 Google LLC
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 deploy

import (
"bytes"
"io"
"os/exec"
"strings"

"github.com/spf13/afero"

"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/build"
"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/config"
"github.com/pkg/errors"
)

var fs = afero.NewOsFs()

type KubectlDeployer struct {
*config.DeployConfig
}

// NewKubectlDeployer returns a new KubectlDeployer for a DeployConfig filled
// with the needed configuration for `kubectl apply`
func NewKubectlDeployer(cfg *config.DeployConfig) (*KubectlDeployer, error) {
return &KubectlDeployer{cfg}, nil
}

// Run templates the provided manifests with a simple `find and replace` and
// runs `kubectl apply` on those manifests
func (k *KubectlDeployer) Run(b *build.BuildResult) (*Result, error) {
params, err := JoinTagsToBuildResult(b, k.DeployConfig)
if err != nil {
return nil, errors.Wrap(err, "joining template keys to image tag")
}

for _, m := range k.DeployConfig.KubectlDeploy.Manifests {
f, err := fs.Open(m)
if err != nil {
return nil, errors.Wrap(err, "opening manifest")
}
if err := deployManifest(f, params); err != nil {
return nil, errors.Wrapf(err, "deploying manifest %s", m)
}
}

return &Result{}, nil
}

func deployManifest(r io.Reader, params map[string]build.Build) error {
var manifestContents bytes.Buffer
if _, err := manifestContents.ReadFrom(r); err != nil {
return errors.Wrap(err, "reading manifest")
}
manifest := manifestContents.String()
for old, new := range params {
manifest = strings.Replace(manifest, old, new.Tag, -1)
}
cmd := exec.Command("kubectl", "apply", "-f", "-")
stdin := strings.NewReader(manifest)
out, outerr, err := execCommand.RunCommand(cmd, stdin)
if err != nil {
return errors.Wrapf(err, "running kubectl apply: stdout: %s stderr: %s err: %s", out, outerr, err)
}
return nil
}
224 changes: 224 additions & 0 deletions pkg/skaffold/deploy/kubectl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
Copyright 2018 Google LLC
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 deploy

import (
"fmt"
"io"
"testing"

"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/build"
"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/config"
"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/util"
testutil "github.com/GoogleCloudPlatform/skaffold/test"
"github.com/spf13/afero"
)

const deploymentYAML = `apiVersion: apps/v1
kind: Deployment
metadata:
name: leeroy-web
labels:
app: leeroy-web
spec:
replicas: 1
selector:
matchLabels:
app: leeroy-web
template:
metadata:
labels:
app: leeroy-web
spec:
containers:
- name: leeroy-web
image: IMAGE_NAME
ports:
- containerPort: 8080
`

func TestKubectlRun(t *testing.T) {
var tests = []struct {
description string
cfg *config.DeployConfig
b *build.BuildResult
command util.Command

expected *Result
shouldErr bool
}{
{
description: "parameter mismatch",
shouldErr: true,
cfg: &config.DeployConfig{
Parameters: map[string]string{
"IMAGE_NAME": "abc",
},
DeployType: config.DeployType{
KubectlDeploy: &config.KubectlDeploy{
Manifests: []string{
"test/deployment.yaml",
},
},
},
},
b: &build.BuildResult{
Builds: []build.Build{
{
ImageName: "not_abc",
Tag: "not_abc:123",
},
},
},
},
{
description: "missing manifest file",
shouldErr: true,
cfg: &config.DeployConfig{
Parameters: map[string]string{
"IMAGE_NAME": "abc",
},
DeployType: config.DeployType{
KubectlDeploy: &config.KubectlDeploy{
Manifests: []string{
"test/not_deployment.yaml",
},
},
},
},
b: &build.BuildResult{
Builds: []build.Build{
{
ImageName: "abc",
Tag: "abc:123",
},
},
},
},
{
description: "deploy success",
cfg: &config.DeployConfig{
Parameters: map[string]string{
"IMAGE_NAME": "abc",
},
DeployType: config.DeployType{
KubectlDeploy: &config.KubectlDeploy{
Manifests: []string{
"test/deployment.yaml",
},
},
},
},
command: testutil.NewFakeRunCommand("", "", nil),
b: &build.BuildResult{
Builds: []build.Build{
{
ImageName: "abc",
Tag: "abc:123",
},
},
},
expected: &Result{},
},
{
description: "deploy command error",
shouldErr: true,
cfg: &config.DeployConfig{
Parameters: map[string]string{
"IMAGE_NAME": "abc",
},
DeployType: config.DeployType{
KubectlDeploy: &config.KubectlDeploy{
Manifests: []string{
"test/deployment.yaml",
},
},
},
},
command: testutil.NewFakeRunCommand("", "", fmt.Errorf("")),
b: &build.BuildResult{
Builds: []build.Build{
{
ImageName: "abc",
Tag: "abc:123",
},
},
},
},
}

pkgFS := fs
defer func() {
fs = pkgFS
}()
fs = afero.NewMemMapFs()
fs.MkdirAll("test", 0750)
files := map[string]string{
"test/deployment.yaml": deploymentYAML,
}
for path, contents := range files {
afero.WriteFile(fs, path, []byte(contents), 0644)
}

pkgCommand := execCommand

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
if test.command != nil {
execCommand = test.command
}
k, err := NewKubectlDeployer(test.cfg)
if err != nil {
t.Errorf("Error getting kubectl deployer: %s", err)
return
}
res, err := k.Run(test.b)
execCommand = pkgCommand
testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, res)
})

}
}

func TestDeployManifest(t *testing.T) {
var tests = []struct {
description string
r io.Reader
params map[string]build.Build

shouldErr bool
}{
{
description: "bad reader",
r: testutil.BadReader{},
params: map[string]build.Build{
"IMAGE_NAME": {
ImageName: "leeroy-web",
Tag: "leeroy-web:a1b2c3",
},
},
shouldErr: true,
},
}

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
err := deployManifest(test.r, test.params)
testutil.CheckError(t, test.shouldErr, err)
})
}
}
Loading

0 comments on commit 2677194

Please sign in to comment.