mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-27 10:52:52 +00:00
245 lines
6.7 KiB
Go
245 lines
6.7 KiB
Go
/*
|
|
Copyright 2022 The KubeSphere 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 cloudinit
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/kubesphere/kubekey/pkg/clients/ssh"
|
|
"github.com/kubesphere/kubekey/pkg/service/operation"
|
|
"github.com/kubesphere/kubekey/pkg/service/operation/directory"
|
|
"github.com/kubesphere/kubekey/pkg/service/provisioning/commands"
|
|
"github.com/kubesphere/kubekey/pkg/util/filesystem"
|
|
)
|
|
|
|
const (
|
|
kubeadmInitPath = "/run/kubeadm/kubeadm.yaml"
|
|
kubeproxyComponentConfig = `
|
|
---
|
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
|
kind: KubeProxyConfiguration
|
|
conntrack:
|
|
# Skip setting sysctl value "net.netfilter.nf_conntrack_max"
|
|
# It is a global variable that affects other namespaces
|
|
maxPerCore: 0
|
|
`
|
|
)
|
|
|
|
// writeFilesAction defines a list of files that should be written to a node.
|
|
type writeFilesAction struct {
|
|
sshClient ssh.Interface
|
|
directoryFactory func(sshClient ssh.Interface, path string, mode os.FileMode) operation.Directory
|
|
|
|
Files []files `json:"write_files,"`
|
|
}
|
|
|
|
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,"`
|
|
}
|
|
|
|
func newWriteFilesAction(sshClient ssh.Interface) action {
|
|
return &writeFilesAction{
|
|
sshClient: sshClient,
|
|
}
|
|
}
|
|
|
|
func (a *writeFilesAction) getDirectoryService(path string, mode os.FileMode) operation.Directory {
|
|
if a.directoryFactory != nil {
|
|
return a.directoryFactory(a.sshClient, path, mode)
|
|
}
|
|
return directory.NewService(a.sshClient, path, mode)
|
|
}
|
|
|
|
// Unmarshal unmarshals the given content into the given encoding.
|
|
func (a *writeFilesAction) Unmarshal(userData []byte) error {
|
|
if err := yaml.Unmarshal(userData, a); err != nil {
|
|
return errors.Wrapf(err, "error parsing write_files action: %s", userData)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Commands return a list of commands to run on the node.
|
|
// Each command defines the parameters of a shell command necessary to generate a file replicating the cloud-init write_files module.
|
|
func (a *writeFilesAction) Commands() ([]commands.Cmd, error) {
|
|
cmds := make([]commands.Cmd, 0)
|
|
for _, f := range a.Files {
|
|
// Fix attributes and apply defaults
|
|
path := fixPath(f.Path) // NB. the real cloud init module for writes files converts path into absolute paths; this is not possible here...
|
|
encodings := fixEncoding(f.Encoding)
|
|
owner := fixOwner(f.Owner)
|
|
permissions := fixPermissions(f.Permissions)
|
|
content, err := fixContent(f.Content, encodings)
|
|
if path == kubeadmInitPath {
|
|
content += kubeproxyComponentConfig
|
|
}
|
|
if err != nil {
|
|
return cmds, errors.Wrapf(err, "error decoding content for %s", path)
|
|
}
|
|
|
|
// Make the directory so cat + redirection will work
|
|
directory := filepath.Dir(path)
|
|
cmds = append(cmds, commands.Cmd{Cmd: "mkdir", Args: []string{"-p", directory}})
|
|
|
|
redirects := ">"
|
|
if f.Append {
|
|
redirects = ">>"
|
|
}
|
|
|
|
// generate a command that will create a file with the expected contents.
|
|
cmds = append(cmds, commands.Cmd{Cmd: "/bin/sh", Args: []string{"-c", fmt.Sprintf("echo '%s' %s %s", content, redirects, path)}})
|
|
|
|
// if permissions are different than default ownership, add a command to modify the permissions.
|
|
if permissions != "0644" {
|
|
cmds = append(cmds, commands.Cmd{Cmd: "chmod", Args: []string{permissions, path}})
|
|
}
|
|
|
|
// if ownership is different than default ownership, add a command to modify file ownerhsip.
|
|
if owner != "root:root" {
|
|
cmds = append(cmds, commands.Cmd{Cmd: "chown", Args: []string{owner, path}})
|
|
}
|
|
}
|
|
return cmds, nil
|
|
}
|
|
|
|
// Run runs the action.
|
|
func (a *writeFilesAction) Run() error {
|
|
for _, f := range a.Files {
|
|
// Fix attributes and apply defaults
|
|
path := fixPath(f.Path) // NB. the real cloud init module for writes files converts path into absolute paths; this is not possible here...
|
|
encodings := fixEncoding(f.Encoding)
|
|
content, err := fixContent(f.Content, encodings)
|
|
if path == kubeadmInitPath {
|
|
content += kubeproxyComponentConfig
|
|
}
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error decoding content for %s", path)
|
|
}
|
|
|
|
// Make the directory so cat + redirection will work
|
|
dir := filepath.Dir(path)
|
|
svc := a.getDirectoryService(dir, os.FileMode(filesystem.FileMode0755))
|
|
if err := svc.Make(); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.Chown("root"); err != nil {
|
|
return err
|
|
}
|
|
|
|
redirects := ">"
|
|
if f.Append {
|
|
redirects = ">>"
|
|
}
|
|
if _, err := a.sshClient.SudoCmdf("echo '%s' %s %s", content, redirects, path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fixPath(p string) string {
|
|
return strings.TrimSpace(p)
|
|
}
|
|
|
|
func fixOwner(o string) string {
|
|
o = strings.TrimSpace(o)
|
|
if o != "" {
|
|
return o
|
|
}
|
|
return "root:root"
|
|
}
|
|
|
|
func fixPermissions(p string) string {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
return p
|
|
}
|
|
return "0644"
|
|
}
|
|
|
|
func fixEncoding(e string) []string {
|
|
e = strings.ToLower(e)
|
|
e = strings.TrimSpace(e)
|
|
|
|
switch e {
|
|
case "gz", "gzip":
|
|
return []string{"application/x-gzip"}
|
|
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 fixContent(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)
|
|
}
|
|
return string(rByte), nil
|
|
case "application/x-gzip":
|
|
rByte, err := gUnzipData([]byte(content))
|
|
if err != nil {
|
|
return content, err
|
|
}
|
|
return string(rByte), nil
|
|
case "text/plain":
|
|
return content, nil
|
|
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
|
|
}
|