Skip to content

Commit

Permalink
Implemented pmesh create command, improved hashing for consistency.
Browse files Browse the repository at this point in the history
  • Loading branch information
can1357 committed Mar 6, 2024
1 parent e5dc04b commit c5a7071
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 138 deletions.
163 changes: 163 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"strings"

"get.pme.sh/pmesh/config"
"get.pme.sh/pmesh/service"
"get.pme.sh/pmesh/ui"
"get.pme.sh/pmesh/util"
"github.com/alecthomas/chroma/v2/quick"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

type GeneratedManifest struct {
ServiceRoot string `yaml:"service_root,omitempty"` // Service root directory
Services util.OrderedMap[string, any] `yaml:"services,omitempty"` // Services
Server map[string]any `yaml:"server,omitempty"` // Virtual hosts
Hosts []string `yaml:"hosts,omitempty"` // Hostname to IP mapping
}

func exists(path ...string) bool {
_, err := os.Stat(filepath.Join(path...))
return err == nil
}

func autoGenerateManifest(cwd string) []byte {
gm := GeneratedManifest{
ServiceRoot: ".",
}
for _, delimiter := range []string{"packages", "services", "apps", "pkg"} {
if exists(cwd, delimiter) {
gm.ServiceRoot = delimiter
break
}
}

validator := func(path string) error {
res, err := os.ReadDir(filepath.Join(cwd, path))
if err == nil {
for _, file := range res {
if file.IsDir() {
return nil
}
}
return fmt.Errorf("no directories found in %q", path)
}
return err
}

gm.ServiceRoot = ui.PromptString("Where do your services live?", gm.ServiceRoot, "", validator)
gm.ServiceRoot = filepath.Clean(gm.ServiceRoot)
if gm.ServiceRoot == "." {
gm.ServiceRoot = ""
}

sroot := filepath.Join(cwd, gm.ServiceRoot)
files, err := os.ReadDir(sroot)
if err != nil {
ui.ExitWithError(err)
}

svcNameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000"))
for _, file := range files {
if file.IsDir() {
var options []yaml.Node
for tag, value := range service.Registry.Tags {
if advisor, ok := value.Instance.(service.Advisor); ok {
if advice := advisor.Advise(filepath.Join(sroot, file.Name())); advice != nil {
node := yaml.Node{}
if err := node.Encode(advice); err == nil {
node.Tag = "!" + tag
options = append(options, node)
}
}
}
}

{
node := yaml.Node{}
node.Encode(struct{}{})
node.Tag = "!FS"
options = append(options, node)
node = yaml.Node{}
node.Encode(map[string]any{
"run": "start --arg1 --arg2",
"build": []string{
"build --arg1 --arg2",
"then --another",
},
})
node.Tag = "!App"
options = append(options, node)
}

var valueSelect []string
for _, node := range options {
valueSelect = append(valueSelect, node.Tag[1:])
}
valueSelect = append(valueSelect, "Skip")

query := fmt.Sprintf("What type of service is %s?", svcNameStyle.Render(file.Name()))
result := ui.PromptSelect(query, valueSelect)

for _, node := range options {
if node.Tag[1:] == result {
gm.Services.Set(file.Name(), node)
break
}
}
}
}

domain := ui.PromptString("What is your domain?", "example.com", "", nil)
devDomain := ui.PromptString("What is your development domain?", "example.local", "", nil)

router := []map[string]string{}
gm.Services.ForEach(func(k string, _ any) {
route := map[string]string{}
route[k+"."+domain+"/"] = k
router = append(router, route)
gm.Hosts = append(gm.Hosts, k+"."+devDomain)
})

gm.Server = map[string]any{
domain + ", " + devDomain: map[string]any{
"router": router,
},
}

buf := &strings.Builder{}
enc := yaml.NewEncoder(buf)
enc.SetIndent(2)
err = enc.Encode(gm)
if err != nil {
ui.ExitWithError(err)
}

if err := quick.Highlight(os.Stdout, buf.String(), "yaml", "terminal256", "monokai"); err != nil {
fmt.Println(buf.String())
}

return []byte(buf.String())
}

func init() {
config.RootCommand.AddCommand(&cobra.Command{
Use: "create",
Short: "Create a new manifest",
Run: func(cmd *cobra.Command, args []string) {
manifest := autoGenerateManifest(".")
manifestPath := filepath.Join(".", "pm3.yml")

if ui.PromptSelect("Save to "+manifestPath+"?", []string{"Yes", "No"}) == "Yes" {
_ = os.WriteFile(manifestPath, manifest, 0644)
}
},
})
}
17 changes: 8 additions & 9 deletions enats/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Client struct {
}

type Gateway struct {
*Client
Client
Server *autonats.Server
url string

Expand Down Expand Up @@ -58,12 +58,6 @@ func New() (r *Gateway) {
return
}
func (r *Gateway) Open(ctx context.Context) (err error) {
select {
case <-ctx.Done():
return ctx.Err()
case <-r.Server.Ready():
}
r.Client = &Client{}
if r.Server == nil {
if strings.HasPrefix(r.url, "nats://") {
r.Client.Conn, err = nats.Connect(r.url)
Expand All @@ -77,6 +71,11 @@ func (r *Gateway) Open(ctx context.Context) (err error) {
)
}
} else {
select {
case <-ctx.Done():
return ctx.Err()
case <-r.Server.Ready():
}
r.Client.Conn, err = r.Server.Connect()
}
if err != nil {
Expand Down Expand Up @@ -153,8 +152,8 @@ func (r *Gateway) Open(ctx context.Context) (err error) {

}
func (r *Gateway) Close(ctx context.Context) (err error) {
if cli := r.Client; cli != nil {
r.Client = nil
if cli := r.Client; cli.Conn != nil {
r.Client.Conn = nil
select {
case <-ctx.Done():
case err = <-lo.Async(cli.Drain):
Expand Down
71 changes: 20 additions & 51 deletions glob/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package glob
import (
"context"
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"hash"
"hash/crc32"
Expand All @@ -26,16 +25,6 @@ func hasher() hash.Hash {
return sha1.New()
}
}
func checksum(data []byte) (r Checksum) {
if hashSize == crc32.Size {
u32 := crc32.Checksum(data, tbl)
binary.LittleEndian.PutUint32(r[:], u32)
} else {
h := sha1.Sum(data)
copy(r[:], h[:])
}
return
}

type Checksum [hashSize]byte

Expand All @@ -46,34 +35,12 @@ func (d Checksum) String() string {
return hex.EncodeToString(d[:])
}

type HashKind uint8

const (
HashContent HashKind = iota
HashStat HashKind = iota
)

func hashAppend(h hash.Hash, file *File, kind HashKind) {
stat, err := os.Stat(file.Location)
func hashAppend(h hash.Hash, file *File) {
h.Write([]byte(file.Location))
f, err := os.Open(file.Location)
if err == nil {
u1 := uint64(stat.Size())
u2 := uint64(stat.Mode())
u2 <<= 32
u2 ^= uint64(stat.ModTime().UnixNano())

buf := [16]byte{}
binary.LittleEndian.PutUint64(buf[:8], u1)
binary.LittleEndian.PutUint64(buf[8:], u2)
h.Write(buf[:])

if kind == HashContent {
f, err := os.Open(file.Location)
if err == nil {
defer f.Close()
io.Copy(h, f)
}
}
defer f.Close()
io.Copy(h, f)
}
}
func normalLoc(location string) string {
Expand All @@ -99,22 +66,24 @@ func (l *HashList) Dir(prefix string) Checksum {
}

i, _ := slices.BinarySearch(l.StableList, prefix)
subist := l.StableList[i:]

buf := make([]byte, hashSize*len(subist))
written := 0
h := hasher()
for _, loc := range l.StableList[i:] {
if !strings.HasPrefix(loc, prefix) {
break
}
h := [hashSize]byte(l.HashMap[loc])
copy(buf[written:], h[:])
written += hashSize
h.Write(l.HashMap[loc].Slice())
}
return Checksum(h.Sum(nil))
}
func (l *HashList) All() Checksum {
h := hasher()
for _, hash := range l.HashMap {
h.Write(hash.Slice())
}
return checksum(buf[:written])
return Checksum(h.Sum(nil))
}

func ReduceToHash(ch <-chan *File, kind HashKind) (l *HashList) {
func ReduceToHash(ch <-chan *File) (l *HashList) {
l = &HashList{
HashMap: make(map[string]Checksum),
}
Expand All @@ -126,7 +95,7 @@ func ReduceToHash(ch <-chan *File, kind HashKind) (l *HashList) {
defer wg.Done()
var dig Checksum
h := hasher()
hashAppend(h, file, kind)
hashAppend(h, file)
copy(dig[:], h.Sum(nil))
loc := normalLoc(file.Location)

Expand All @@ -142,9 +111,9 @@ func ReduceToHash(ch <-chan *File, kind HashKind) (l *HashList) {
return
}

func HashContext(ctx context.Context, dir string, kind HashKind, opts ...Option) *HashList {
return ReduceToHash(WalkContext(ctx, dir, opts...), kind)
func HashContext(ctx context.Context, dir string, opts ...Option) *HashList {
return ReduceToHash(WalkContext(ctx, dir, opts...))
}
func Hash(dir string, kind HashKind, opts ...Option) *HashList {
return HashContext(bgCtx, dir, kind, opts...)
func Hash(dir string, opts ...Option) *HashList {
return HashContext(bgCtx, dir, opts...)
}
6 changes: 1 addition & 5 deletions lb/upstream.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package lb

import (
"bytes"
"io"
"net/http"
"net/http/httputil"
"strings"
Expand All @@ -11,7 +9,6 @@ import (
"unsafe"

"get.pme.sh/pmesh/netx"
"get.pme.sh/pmesh/util"
)

type Upstream struct {
Expand Down Expand Up @@ -134,8 +131,7 @@ func NewHttpUpstreamTransport(address string, director func(r *http.Request), tr

// Ask load balancer to handle the error.
if hnd := ctx.LoadBalancer.OnErrorResponse(ctx, r); hnd != nil {
go util.DrainClose(r.Body)
r.Body = io.NopCloser(bytes.NewReader(nil))
r.Body.Close()
return SuppressedHttpError{hnd}
}
return nil
Expand Down
1 change: 0 additions & 1 deletion lyml/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

"get.pme.sh/pmesh/luae"

"github.com/samber/lo"
lua "github.com/yuin/gopher-lua"
"gopkg.in/yaml.v3"
Expand Down
2 changes: 1 addition & 1 deletion service/svc_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (app *AppService) BuildApp(c context.Context, force bool) (chk glob.Checksu

// Create a checksum.
//
chk = glob.Hash(app.Root, glob.HashStat, glob.IgnoreArtifacts(), glob.AddGitIgnores(app.Root)).Dir(app.Root)
chk = glob.Hash(app.Root, glob.IgnoreArtifacts(), glob.AddGitIgnores(app.Root)).All()

// Check the cache.
//
Expand Down
Loading

0 comments on commit c5a7071

Please sign in to comment.