Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ golint := $(shell go env GOPATH)/bin/golangci-lint
endif

$(golint):
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest

.PHONY: lint
lint: $(golint)
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,19 @@ Example:
perm: 0600
```

Inline data example:

```yaml
- name: motd
data: |
Powered by k0s
dst: /etc/motd
perm: 0644
```

* `name`: name of the file "bundle", used only for logging purposes (optional)
* `src`: File path, an URL or [Glob pattern](https://golang.org/pkg/path/filepath/#Match) to match files to be uploaded. URL sources will be directly downloaded using the target host. If the value is a URL, '%'-prefixed tokens can be used, see [tokens](#tokens). (required)
* `src`: File path, an URL or [Glob pattern](https://golang.org/pkg/path/filepath/#Match) to match files to be uploaded. URL sources will be directly downloaded using the target host. If the value is a URL, '%'-prefixed tokens can be used, see [tokens](#tokens). (required when `data` is not set)
* `data`: Inline file data to write to the destination. Use together with `dst` or `dst` + `dstDir`. (required when `src` is not set)
* `dstDir`: Destination directory for the file(s). `k0sctl` will create full directory structure if it does not already exist on the host (default: user home)
* `dst`: Destination filename for the file. Only usable for single file uploads (default: basename of file)
* `perm`: File permission mode for uploaded file(s) (default: same as local)
Expand Down
56 changes: 55 additions & 1 deletion phase/uploadfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"strconv"

"al.essio.dev/pkg/shellescape"
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1"
Expand Down Expand Up @@ -54,8 +55,10 @@ func (p *UploadFiles) uploadFiles(ctx context.Context, h *cluster.Host) error {
var err error
if f.IsURL() {
err = p.uploadURL(h, f)
} else {
} else if len(f.Sources) > 0 {
err = p.uploadFile(h, f)
} else if f.HasData() {
err = p.uploadData(h, f)
}
if err != nil {
return err
Expand Down Expand Up @@ -168,6 +171,57 @@ func (p *UploadFiles) uploadFile(h *cluster.Host, f *cluster.UploadFile) error {
return nil
}

func (p *UploadFiles) uploadData(h *cluster.Host, f *cluster.UploadFile) error {
log.Infof("%s: uploading inline data", h)
dest := f.DestinationFile
if dest == "" {
if f.DestinationDir != "" {
dest = path.Join(f.DestinationDir, f.Name)
} else {
dest = f.Name
}
}

owner := f.Owner()

if err := p.ensureDir(h, path.Dir(dest), f.DirPermString, owner); err != nil {
return err
}

err := p.Wet(h, fmt.Sprintf("upload inline data => %s", dest), func() error {
fileMode, _ := strconv.ParseUint(f.PermString, 8, 32)
remoteFile, err := h.SudoFsys().OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(fileMode))
if err != nil {
return err
}

defer func() {
if err := remoteFile.Close(); err != nil {
log.Warnf("failed to close remote file %s: %v", dest, err)
}
}()

_, err = fmt.Fprint(remoteFile, f.Data)

return err
})
if err != nil {
return err
}

if owner != "" {
err := p.Wet(h, fmt.Sprintf("set owner for %s to %s", dest, owner), func() error {
log.Debugf("%s: setting owner %s for %s", h, owner, dest)
return h.Execf(`chown %s %s`, shellescape.Quote(owner), shellescape.Quote(dest), exec.Sudo(h))
})
if err != nil {
return err
}
}

return nil
}

func (p *UploadFiles) uploadURL(h *cluster.Host, f *cluster.UploadFile) error {
log.Infof("%s: downloading %s to host %s", h, f, f.DestinationFile)
owner := f.Owner()
Expand Down
27 changes: 19 additions & 8 deletions pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ type LocalFile struct {
// UploadFile describes a file to be uploaded for the host
type UploadFile struct {
Name string `yaml:"name,omitempty"`
Source string `yaml:"src"`
DestinationDir string `yaml:"dstDir"`
DestinationFile string `yaml:"dst"`
PermMode interface{} `yaml:"perm"`
DirPermMode interface{} `yaml:"dirPerm"`
User string `yaml:"user"`
Group string `yaml:"group"`
Source string `yaml:"src,omitempty"`
Data string `yaml:"data,omitempty"`
DestinationDir string `yaml:"dstDir,omitempty"`
DestinationFile string `yaml:"dst,omitempty"`
PermMode interface{} `yaml:"perm,omitempty"`
DirPermMode interface{} `yaml:"dirPerm,omitempty"`
User string `yaml:"user,omitempty"`
Group string `yaml:"group,omitempty"`
PermString string `yaml:"-"`
DirPermString string `yaml:"-"`
Sources []*LocalFile `yaml:"-"`
Expand All @@ -35,7 +36,9 @@ type UploadFile struct {

func (u UploadFile) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.Source, validation.Required),
validation.Field(&u.Name, validation.Required.When(u.HasData() && u.DestinationFile == "").Error("name or dst required for data")),
validation.Field(&u.Source, validation.Required.When(!u.HasData()).Error("src or data required")),
validation.Field(&u.Data, validation.Required.When(u.Source == "").Error("src or data required")),
validation.Field(&u.DestinationFile, validation.Required.When(u.DestinationDir == "").Error("dst or dstdir required")),
validation.Field(&u.DestinationDir, validation.Required.When(u.DestinationFile == "").Error("dst or dstdir required")),
)
Expand Down Expand Up @@ -139,6 +142,10 @@ func (u *UploadFile) resolve() error {
return nil
}

if u.HasData() {
return nil
}

if isGlob(u.Source) {
return u.glob(u.Source)
}
Expand Down Expand Up @@ -211,3 +218,7 @@ func (u *UploadFile) glob(src string) error {
func (u *UploadFile) IsURL() bool {
return strings.Contains(u.Source, "://")
}

func (u *UploadFile) HasData() bool {
return strings.TrimSpace(u.Data) != ""
}
30 changes: 30 additions & 0 deletions pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster/uploadfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,33 @@ perm: 0

require.Error(t, yaml.Unmarshal(yml, &u))
}

func TestUploadFileValidateRequiresDestinationFileForData(t *testing.T) {
u := UploadFile{Data: "hello", DestinationDir: "/tmp"}

err := u.Validate()
require.Error(t, err)
require.Contains(t, err.Error(), "name or dst required for data")
}

func TestUploadFileValidateDataWithDestinationFile(t *testing.T) {
u := UploadFile{Data: "hello", DestinationFile: "/tmp/inline.txt"}

require.NoError(t, u.Validate())
}

func TestUploadFileValidateRequiresSourceOrData(t *testing.T) {
u := UploadFile{Data: " "}

err := u.Validate()
require.Error(t, err)
require.Contains(t, err.Error(), "src or data required")
}

func TestUploadFileValidateRequiresDestinationFileOrName(t *testing.T) {
u := UploadFile{Data: "hello", DestinationDir: "/tmp/"}

err := u.Validate()
require.Error(t, err)
require.Contains(t, err.Error(), "name or dst required for data")
}
8 changes: 8 additions & 0 deletions smoke-test/k0sctl-files.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ spec:
dst: /root/singlefile/renamed.txt
user: test
group: test
- name: inline-data
dst: /root/content/hello.sh
user: test
group: test
perm: 0755
data: |-
#!/bin/sh
echo hello
- name: dest_dir
src: ./upload/toplevel.txt
dstDir: /root/destdir
Expand Down
10 changes: 9 additions & 1 deletion smoke-test/smoke-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ remoteCommand root@manager0 stat -c '%U:%G' /root/singlefile/renamed.txt | grep
printf %s "[stat]"
echo "OK"

printf %s " - File from inline data .. "
remoteFileExist root@manager0 /root/content/hello.sh
printf %s "[exist]"
remoteCommand root@manager0 stat -c '%U:%G' /root/content/hello.sh | grep -q test:test
printf %s "[stat]"
remoteCommand root@manager0 /root/content/hello.sh | grep -q hello
printf %s "[run] "
echo "OK"

printf %s " - Single file using destination dir .. "
remoteFileExist root@manager0 /root/destdir/toplevel.txt
echo "OK"
Expand Down Expand Up @@ -117,4 +126,3 @@ printf %s "[content] "
echo "OK"

echo "* Done"

Loading