Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daemon): Add support for PEBBLE_COPY_ONCE. #352

Merged
merged 5 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ pebble run
...
```

To initialise the `$PEBBLE` directory with the contents of another, in a one time copy, set the `PEBBLE_COPY_ONCE` environment
variable to the source directory. This will only copy the contents if the target directory, `$PEBBLE`, is empty.

### Viewing, starting, and stopping services

You can view the status of one or more services by using `pebble services`:
Expand Down
4 changes: 4 additions & 0 deletions internals/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ func getEnvPaths() (pebbleDir string, socketPath string) {
return pebbleDir, socketPath
}

func getCopySource() string {
return os.Getenv("PEBBLE_COPY_ONCE")
}

type cliState struct {
NoticesLastListed time.Time `json:"notices-last-listed"`
NoticesLastOkayed time.Time `json:"notices-last-okayed"`
Expand Down
114 changes: 114 additions & 0 deletions internals/cli/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ package cli
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/signal"
"path/filepath"
"strconv"
"syscall"
"time"
Expand Down Expand Up @@ -157,6 +160,11 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
return err
}
}
err := maybeCopyPebbleDir(getCopySource(), pebbleDir)
if err != nil {
return err
}

dopts := daemon.Options{
Dir: pebbleDir,
SocketPath: socketPath,
Expand Down Expand Up @@ -275,3 +283,109 @@ func convertArgs(args [][]string) (map[string][]string, error) {

return mappedArgs, nil
}

func maybeCopyPebbleDir(srcDir, destDir string) error {
hpidcock marked this conversation as resolved.
Show resolved Hide resolved
if srcDir == "" {
return nil
}
dirEnts, err := os.ReadDir(destDir)
hpidcock marked this conversation as resolved.
Show resolved Hide resolved
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
} else if len(dirEnts) != 0 {
// Skip non-empty dir.
return nil
}
fsys := os.DirFS(srcDir)
// TODO: replace with os.CopyFS
hpidcock marked this conversation as resolved.
Show resolved Hide resolved
err = _go_os_CopyFS(destDir, fsys)
hpidcock marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("cannot copy %q to %q: %w", srcDir, destDir, err)
}
return nil
}

// Implementation from https://go-review.googlesource.com/c/go/+/558995 until accepted into go.
hpidcock marked this conversation as resolved.
Show resolved Hide resolved
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// - Redistributions of source code must retain the above copyright
//
// notice, this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above
//
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// - Neither the name of Google Inc. nor the names of its
//
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// _go_os_CopyFS copies the file system fsys into the directory dir,
// creating dir if necessary.
//
// Newly created directories and files have their default modes
// where any bits from the file in fsys that are not part of the
// standard read, write, and execute permissions will be zeroed
// out, and standard read and write permissions are set for owner,
// group, and others while retaining any existing execute bits from
// the file in fsys.
//
// Symbolic links in fsys are not supported, a *PathError with Err set
// to ErrInvalid is returned on symlink.
//
// Copying stops at and returns the first error encountered.
func _go_os_CopyFS(dir string, fsys fs.FS) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

newPath := filepath.Join(dir, path)
if d.IsDir() {
return os.MkdirAll(newPath, 0777)
}

// TODO(panjf2000): handle symlinks with the help of fs.ReadLinkFS
// once https://go.dev/issue/49580 is done.
// we also need safefilepath.IsLocal from https://go.dev/cl/564295.
if !d.Type().IsRegular() {
return &fs.PathError{Op: "CopyFS", Path: path, Err: fs.ErrInvalid}
}

r, err := fsys.Open(path)
if err != nil {
return err
}
defer r.Close()
info, err := r.Stat()
if err != nil {
return err
}
w, err := os.OpenFile(newPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666|info.Mode()&0777)
if err != nil {
return err
}

if _, err := io.Copy(w, r); err != nil {
w.Close()
return &fs.PathError{Op: "Copy", Path: newPath, Err: err}
}
return w.Close()
})
}
Loading