Skip to content

Commit

Permalink
Merge pull request #472 from fujiwara/init-unzip-file
Browse files Browse the repository at this point in the history
Add init --unzip
  • Loading branch information
fujiwara authored Feb 10, 2025
2 parents dee8679 + 31045a1 commit 782aced
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 4 deletions.
53 changes: 53 additions & 0 deletions archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package lambroll_test

import (
"archive/zip"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"sort"
"testing"

"time"

"github.com/fujiwara/lambroll"
Expand Down Expand Up @@ -115,3 +120,51 @@ func TestLoadNotZipArchive(t *testing.T) {
}
t.Log(err)
}

func TestUnzip(t *testing.T) {
ctx := context.TODO()
dest := t.TempDir()
if err := lambroll.Unzip(ctx, "test/src.zip", dest, false); err != nil {
t.Error("failed to Unzip", err)
}
unzipEntries := []string{}
err := filepath.WalkDir(dest, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
rel, _ := filepath.Rel(dest, path)
unzipEntries = append(unzipEntries, rel)
return nil
})
if err != nil {
t.Error("failed to walk", err)
}
sort.Strings(unzipEntries)
expected := []string{"dir.symlink", "dir/sub.txt", "hello.symlink", "hello.txt", "world"}
if diff := cmp.Diff(unzipEntries, expected); diff != "" {
t.Errorf("unexpected unzip entries %s", diff)
}

// debug
o, _ := exec.Command("ls", "-lR", dest).Output()
t.Log(string(o))

// check symlink
fi, err := os.Lstat(filepath.Join(dest, "hello.symlink"))
if err != nil {
t.Error("failed to stat hello.symlink", err)
}
if fi.Mode()&os.ModeSymlink == 0 {
t.Error("hello.symlink must be symlink", fi.Mode())
}
linkTarget, err := os.Readlink(filepath.Join(dest, "hello.symlink"))
if err != nil {
t.Error("failed to readlink hello.symlink", err)
}
if diff := cmp.Diff(linkTarget, "hello.txt"); diff != "" {
t.Errorf("unexpected symlink target %s", diff)
}
}
1 change: 1 addition & 0 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
MarshalJSON = marshalJSON
NewFunctionFrom = newFunctionFrom
NewCallerIdentity = newCallerIdentity
Unzip = unzip
)

type VersionsOutput = versionsOutput
Expand Down
2 changes: 1 addition & 1 deletion functionurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ func (app *App) initFunctionURL(ctx context.Context, fn *Function, exists bool,
return err
}
}
if err := app.saveFile(name, b, os.FileMode(0644), opt.ForceOverwrite); err != nil {
if err := app.saveFile(ctx, name, b, os.FileMode(0644), opt.ForceOverwrite); err != nil {
return err
}

Expand Down
95 changes: 93 additions & 2 deletions init.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package lambroll

import (
"archive/zip"
"context"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
Expand All @@ -19,6 +21,8 @@ import (
type InitOption struct {
FunctionName *string `help:"Function name for init" required:"true" default:""`
DownloadZip bool `name:"download" help:"Download function.zip" default:"false"`
Unzip bool `help:"Unzip function.zip and delete it" default:"false"`
Src string `help:"Source directory for unzipping function.zip" default:"."`
Jsonnet bool `help:"render function.json as jsonnet" default:"false"`
Qualifier *string `help:"function version or alias"`
FunctionURL bool `help:"create function url definition file" default:"false"`
Expand Down Expand Up @@ -79,15 +83,21 @@ func (app *App) Init(ctx context.Context, opt *InitOption) error {
}
fn := newFunctionFrom(c, code, tags)

if opt.DownloadZip && res.Code != nil && *res.Code.RepositoryType == "S3" {
if (opt.DownloadZip || opt.Unzip) && res.Code != nil && *res.Code.RepositoryType == "S3" {
log.Printf("[info] downloading %s", FunctionZipFilename)
if err := download(*res.Code.Location, FunctionZipFilename); err != nil {
return err
}
if opt.Unzip {
if err := unzipAfterInit(ctx, FunctionZipFilename, opt.Src, opt.ForceOverwrite); err != nil {
return err
}
}
}

log.Printf("[info] creating %s", IgnoreFilename)
err = app.saveFile(
ctx,
IgnoreFilename,
[]byte(strings.Join(DefaultExcludes, "\n")+"\n"),
os.FileMode(0644),
Expand All @@ -111,7 +121,7 @@ func (app *App) Init(ctx context.Context, opt *InitOption) error {
return err
}
}
if err := app.saveFile(name, b, os.FileMode(0644), opt.ForceOverwrite); err != nil {
if err := app.saveFile(ctx, name, b, os.FileMode(0644), opt.ForceOverwrite); err != nil {
return err
}

Expand All @@ -137,3 +147,84 @@ func download(url, path string) error {
_, err = io.Copy(f, resp.Body)
return err
}

func unzipAfterInit(ctx context.Context, path, dest string, force bool) error {
log.Printf("[info] unzipping %s to %s", path, dest)
if err := unzip(ctx, path, dest, force); err != nil {
return fmt.Errorf("failed to unzip %s: %w", path, err)
}
log.Printf("[info] removing %s", path)
if err := os.Remove(path); err != nil {
return fmt.Errorf("failed to remove %s: %w", path, err)
}
return nil
}

func unzip(ctx context.Context, src, dest string, force bool) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()

if err := os.MkdirAll(dest, 0755); err != nil {
return err
}

for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
fi := f.FileInfo()
if fi.IsDir() {
log.Printf("[debug] creating directory %s", fpath)
if err := os.MkdirAll(fpath, f.Mode()); err != nil {
return err
}
continue
}

log.Printf("[debug] extracting %s", fpath)
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}

fc, err := f.Open()
if err != nil {
return err
}
if fi.Mode()&os.ModeSymlink != 0 {
// supports for symbolic link
if err := saveSymlinkIO(ctx, fpath, fc); err != nil {
return err
}
} else {
// normal file
if err := saveFileIO(ctx, fpath, fc, f.Mode(), force); err != nil {
return err
}
}
}

return nil
}

func saveSymlinkIO(_ context.Context, fpath string, r io.ReadCloser) error {
defer r.Close()
l, err := io.ReadAll(r)
if err != nil {
return err
}
linkTo := string(l)
log.Printf("[debug] writing symlink %s -> %s", fpath, linkTo)

cwd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(cwd)
if err := os.Chdir(filepath.Dir(fpath)); err != nil {
return err
}
name := filepath.Base(fpath)
log.Printf("[debug] creating symlink %s -> %s", name, linkTo)
return os.Symlink(linkTo, name)
}
Binary file modified test/src.zip
Binary file not shown.
29 changes: 28 additions & 1 deletion utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package lambroll

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
Expand All @@ -13,16 +15,41 @@ import (
"github.com/google/go-jsonnet/formatter"
)

func (app *App) saveFile(path string, b []byte, mode os.FileMode, force bool) error {
func (app *App) saveFile(ctx context.Context, path string, b []byte, mode os.FileMode, force bool) error {
log.Printf("[debug] writing file to %s mode %s", path, mode)
if _, err := os.Stat(path); err == nil {
ok := force || prompter.YN(fmt.Sprintf("Overwrite existing file %s?", path), false)
if !ok {
if ctx.Err() != nil {
return ctx.Err()
}
return nil
}
}
return os.WriteFile(path, b, mode)
}

func saveFileIO(ctx context.Context, path string, r io.ReadCloser, mode os.FileMode, force bool) error {
log.Printf("[debug] writing file to %s mode %s", path, mode)
defer r.Close()
if _, err := os.Stat(path); err == nil {
ok := force || prompter.YN(fmt.Sprintf("Overwrite existing file %s?", path), false)
if !ok {
if ctx.Err() != nil {
return ctx.Err()
}
return nil
}
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, mode)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}

func toGeneralMap(s any, omitEmpty bool) (any, error) {
b, err := json.Marshal(s)
if err != nil {
Expand Down

0 comments on commit 782aced

Please sign in to comment.