Skip to content

Commit

Permalink
Merge pull request #41 from huchen2021/main-5917
Browse files Browse the repository at this point in the history
TKG-5917 bootstrap script should be executed on the host agent
  • Loading branch information
huchen2021 authored Jul 26, 2021
2 parents b1c2a7a + 07ded88 commit 3d8df56
Show file tree
Hide file tree
Showing 9 changed files with 414 additions and 100 deletions.
84 changes: 75 additions & 9 deletions agent/cloudinit/cloudinit.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package cloudinit

import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"path/filepath"
"strings"

"github.com/pkg/errors"
"sigs.k8s.io/yaml"
Expand All @@ -14,17 +19,17 @@ type ScriptExecutor struct {
}

type bootstrapConfig struct {
FilesToWrite []files `json:"write_files"`
FilesToWrite []Files `json:"write_files"`
CommandsToExecute []string `json:"runCmd"`
}

type files struct {
Path string `json:"path,"`
// Encoding string `json:"encoding,omitempty"`
// Owner string `json:"owner,omitempty"`
// Permissions string `json:"permissions,omitempty"`
Content string `json:"content"`
//Append bool `json:"append,"`
type Files struct {
Path string `json:"path,"`
Encoding string `json:"encoding,omitempty"`
Owner string `json:"owner,omitempty"`
Permissions string `json:"permissions,omitempty"`
Content string `json:"content"`
Append bool `json:"append,omitempty"`
}

func (se ScriptExecutor) Execute(bootstrapScript string) error {
Expand All @@ -40,7 +45,12 @@ func (se ScriptExecutor) Execute(bootstrapScript string) error {
return errors.Wrap(err, fmt.Sprintf("Error creating the directory %s", directoryToCreate))
}

err = se.WriteFilesExecutor.WriteToFile(file.Path, file.Content)
encodings := parseEncodingScheme(file.Encoding)
file.Content, err = decodeContent(file.Content, encodings)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error decoding content for %s", file.Path))
}
err = se.WriteFilesExecutor.WriteToFile(file)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Error writing the file %s", file.Path))
}
Expand All @@ -54,3 +64,59 @@ func (se ScriptExecutor) Execute(bootstrapScript string) error {
}
return nil
}

func parseEncodingScheme(e string) []string {
e = strings.ToLower(e)
e = strings.TrimSpace(e)

switch e {
case "gz+base64", "gzip+base64", "gz+b64", "gzip+b64":
return []string{"application/base64", "application/x-gzip"}
case "base64", "b64":
return []string{"application/base64"}
}

return []string{"text/plain"}
}

func decodeContent(content string, encodings []string) (string, error) {
for _, e := range encodings {
switch e {
case "application/base64":
rByte, err := base64.StdEncoding.DecodeString(content)
if err != nil {
return content, errors.WithStack(err)
}
content = string(rByte)
case "application/x-gzip":
rByte, err := gUnzipData([]byte(content))
if err != nil {
return content, err
}
content = string(rByte)
case "text/plain":
continue
default:
return content, errors.Errorf("Unknown bootstrap data encoding: %q", content)
}
}
return content, nil
}

func gUnzipData(data []byte) ([]byte, error) {
var r io.Reader
var err error
b := bytes.NewBuffer(data)
r, err = gzip.NewReader(b)
if err != nil {
return nil, errors.WithStack(err)
}

var resB bytes.Buffer
_, err = resB.ReadFrom(r)
if err != nil {
return nil, errors.WithStack(err)
}

return resB.Bytes(), nil
}
94 changes: 63 additions & 31 deletions agent/cloudinit/cloudinit_test.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
package cloudinit_test

import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"path"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/vmware-tanzu/cluster-api-provider-byoh/agent/cloudinit"
"github.com/vmware-tanzu/cluster-api-provider-byoh/agent/cloudinit/cloudinitfakes"
)

var someBootstrapSecret = `
write_files:
- path: /tmp/file1.txt
content: some-content
runCmd:
- echo 'some run command'
`

var _ = Describe("Cloudinit", func() {
var (
workDir string
err error
)

Context("Testing write_files and runCmd directives of cloudinit", func() {
var (
fakeFileWriter *cloudinitfakes.FakeIFileWriter
fakeCmdExecutor *cloudinitfakes.FakeICmdRunner
scriptExecutor cloudinit.ScriptExecutor
err error
fakeFileWriter *cloudinitfakes.FakeIFileWriter
fakeCmdExecutor *cloudinitfakes.FakeICmdRunner
scriptExecutor cloudinit.ScriptExecutor
defaultBootstrapSecret string
)

BeforeEach(func() {
Expand All @@ -34,31 +35,63 @@ var _ = Describe("Cloudinit", func() {
WriteFilesExecutor: fakeFileWriter,
RunCmdExecutor: fakeCmdExecutor,
}

defaultBootstrapSecret = fmt.Sprintf(`write_files:
- path: %s/defaultFile.txt
content: some-content
runCmd:
- echo 'some run command'`, workDir)

workDir, err = ioutil.TempDir("", "cloudinit_ut")
Expect(err).ToNot(HaveOccurred())
})

AfterEach(func() {
err := os.RemoveAll(workDir)
Expect(err).ToNot(HaveOccurred())
})

It("should write files successfully", func() {
bootstrapSecretUnencoded := `write_files:
- path: /tmp/a/file1.txt
content: some-content
- path: /tmp/b/file2.txt
content: whatever`
fileDir1 := path.Join(workDir, "dir1")
fileName1 := path.Join(fileDir1, "file1.txt")
fileContent1 := "some-content-1"

fileDir2 := path.Join(workDir, "dir2")
fileName2 := path.Join(fileDir2, "file2.txt")
fileContent2 := "some-content-2"
fileBase64Content := base64.StdEncoding.EncodeToString([]byte(fileContent2))
permissions := "0777"
encoding := "base64"

bootstrapSecretUnencoded := fmt.Sprintf(`write_files:
- path: %s
content: %s
- path: %s
content: %s
permissions: '%s'
append: true
encoding: %s`, fileName1, fileContent1, fileName2, fileBase64Content, permissions, encoding)

err = scriptExecutor.Execute(bootstrapSecretUnencoded)
Expect(err).ToNot(HaveOccurred())

Expect(fakeFileWriter.MkdirIfNotExistsCallCount()).To(Equal(2))
Expect(fakeFileWriter.WriteToFileCallCount()).To(Equal(2))

dirNameForFirstFile := fakeFileWriter.MkdirIfNotExistsArgsForCall(0)
Expect(dirNameForFirstFile).To(Equal("/tmp/a"))
firstFileName, firstFileContents := fakeFileWriter.WriteToFileArgsForCall(0)
Expect(firstFileName).To(Equal("/tmp/a/file1.txt"))
Expect(firstFileContents).To(Equal("some-content"))
Expect(dirNameForFirstFile).To(Equal(fileDir1))
firstFile := fakeFileWriter.WriteToFileArgsForCall(0)
Expect(firstFile.Path).To(Equal(fileName1))
Expect(firstFile.Content).To(Equal(fileContent1))

dirNameForSecondFile := fakeFileWriter.MkdirIfNotExistsArgsForCall(1)
Expect(dirNameForSecondFile).To(Equal("/tmp/b"))
secondFileName, secondFileContents := fakeFileWriter.WriteToFileArgsForCall(1)
Expect(secondFileName).To(Equal("/tmp/b/file2.txt"))
Expect(secondFileContents).To(Equal("whatever"))
Expect(dirNameForSecondFile).To(Equal(fileDir2))
secondFile := fakeFileWriter.WriteToFileArgsForCall(1)
Expect(secondFile.Path).To(Equal(fileName2))
Expect(secondFile.Content).To(Equal(fileContent2))
Expect(secondFile.Permissions).To(Equal(permissions))
Expect(secondFile.Append).To(BeTrue())

})

It("should error out when an invalid yaml is passed", func() {
Expand All @@ -71,7 +104,7 @@ var _ = Describe("Cloudinit", func() {
It("should error out when there is not enough permission to mkdir", func() {
fakeFileWriter.MkdirIfNotExistsReturns(errors.New("not enough permissions"))

err := scriptExecutor.Execute(someBootstrapSecret)
err := scriptExecutor.Execute(defaultBootstrapSecret)

Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("not enough permissions"))
Expand All @@ -82,14 +115,14 @@ var _ = Describe("Cloudinit", func() {
It("should error out write to file failes", func() {
fakeFileWriter.WriteToFileReturns(errors.New("cannot write to file"))

err := scriptExecutor.Execute(someBootstrapSecret)
err := scriptExecutor.Execute(defaultBootstrapSecret)

Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("cannot write to file"))
})

It("run the command given in the runCmd directive", func() {
err := scriptExecutor.Execute(someBootstrapSecret)
err := scriptExecutor.Execute(defaultBootstrapSecret)
Expect(err).ToNot(HaveOccurred())

Expect(fakeCmdExecutor.RunCmdCallCount()).To(Equal(1))
Expand All @@ -108,9 +141,8 @@ var _ = Describe("Cloudinit", func() {
})

It("should error out when command execution fails", func() {

fakeCmdExecutor.RunCmdReturns(errors.New("command execution failed"))
err := scriptExecutor.Execute(someBootstrapSecret)
err := scriptExecutor.Execute(defaultBootstrapSecret)
Expect(err).To(HaveOccurred())

Expect(fakeCmdExecutor.RunCmdCallCount()).To(Equal(1))
Expand Down
22 changes: 10 additions & 12 deletions agent/cloudinit/cloudinitfakes/fake_ifile_writer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions agent/cloudinit/cmd_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ type CmdRunner struct {
}

func (r CmdRunner) RunCmd(cmd string) error {
subStrs := []string{"kubeadm init", "kubeadm join"}

if strings.Contains(cmd, "kubeadm") {
cmd = "kubeadm join --config /run/kubeadm/kubeadm-join-config.yaml --ignore-preflight-errors=all && echo success > /run/cluster-api/bootstrap-success.complete"
for _, subStr := range subStrs {
if strings.Contains(cmd, subStr) {
index := strings.Index(cmd, subStr)
index += len(subStr)
newCmd := cmd[:index] + " --ignore-preflight-errors=all " + cmd[index:]
cmd = newCmd
}
}
command := exec.Command("/bin/sh", "-c", cmd)
output, err := command.Output()
fmt.Println(string(output))
return err

}
Loading

0 comments on commit 3d8df56

Please sign in to comment.