Skip to content

Commit

Permalink
Install multiple packages together as a meta package
Browse files Browse the repository at this point in the history
Because we now support triggers, and because triggers need to run at the absolute end, and because the potential install loop is a pain, we can install build a meta package when many packages are set. So long as that package stays out of the state db, so much the better.

See: #44
  • Loading branch information
jspc committed Mar 11, 2022
1 parent c4f9e4a commit e554374
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 55 deletions.
13 changes: 1 addition & 12 deletions client/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ var installCmd = &cobra.Command{
return fmt.Errorf("missing package(s)")
}

if argCount > 1 && (version != "" && version != "latest") {
cmd.Usage()

return fmt.Errorf("setting version with multiple packages makes no sense")
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
Expand All @@ -72,12 +66,7 @@ var installCmd = &cobra.Command{
return err
}

for _, pkg := range args {
err = client.install(pkg, version, force)
if err != nil {
break
}
}
err = client.install(args, version, force)

return errWrap("installation", err)
},
Expand Down
4 changes: 2 additions & 2 deletions client/cmd/vin.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func parseAddr(addr string) (s string, err error) {
return u.String(), nil
}

func (c client) install(pkg, version string, force bool) (err error) {
func (c client) install(pkgs []string, version string, force bool) (err error) {
is := &vin.InstallSpec{
Pkg: pkg,
Pkg: pkgs,
Force: force,
}

Expand Down
14 changes: 7 additions & 7 deletions client/cmd/vin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,20 @@ func (c *dummyInstallClient) getOutput() []string { return c.output }
func TestClient_Install(t *testing.T) {
for _, test := range []struct {
name string
pkg string
pkg []string
ver string
client DummyVinClient
expectSpec *vin.InstallSpec
expectOutput []string
expectError bool
}{
{"valid package, 'latest' version", "foo", "latest", &dummyInstallClient{}, &vin.InstallSpec{Pkg: "foo"}, []string{}, false},
{"valid package, empty version", "foo", "", &dummyInstallClient{}, &vin.InstallSpec{Pkg: "foo"}, []string{}, false},
{"valid package, set version", "foo", "1.0.0", &dummyInstallClient{}, &vin.InstallSpec{Pkg: "foo", Version: "1.0.0"}, []string{}, false},
{"valid package, 'latest' version, output", "foo", "latest", &dummyInstallClient{output: []string{"line-1", "line-2"}}, &vin.InstallSpec{Pkg: "foo"}, []string{"line-1", "line-2"}, false},
{"valid package, 'latest' version", []string{"foo"}, "latest", &dummyInstallClient{}, &vin.InstallSpec{Pkg: []string{"foo"}}, []string{}, false},
{"valid package, empty version", []string{"foo"}, "", &dummyInstallClient{}, &vin.InstallSpec{Pkg: []string{"foo"}}, []string{}, false},
{"valid package, set version", []string{"foo"}, "1.0.0", &dummyInstallClient{}, &vin.InstallSpec{Pkg: []string{"foo"}, Version: "1.0.0"}, []string{}, false},
{"valid package, 'latest' version, output", []string{"foo"}, "latest", &dummyInstallClient{output: []string{"line-1", "line-2"}}, &vin.InstallSpec{Pkg: []string{"foo"}}, []string{"line-1", "line-2"}, false},

{"vind throws error", "foo", "1.0.0", &dummyInstallClient{err: true}, &vin.InstallSpec{Pkg: "foo", Version: "1.0.0"}, []string{}, true},
{"vind stream error", "foo", "1.0.0", &dummyInstallClient{recvErr: true}, &vin.InstallSpec{Pkg: "foo", Version: "1.0.0"}, []string{}, true},
{"vind throws error", []string{"foo"}, "1.0.0", &dummyInstallClient{err: true}, &vin.InstallSpec{Pkg: []string{"foo"}, Version: "1.0.0"}, []string{}, true},
{"vind stream error", []string{"foo"}, "1.0.0", &dummyInstallClient{recvErr: true}, &vin.InstallSpec{Pkg: []string{"foo"}, Version: "1.0.0"}, []string{}, true},
} {
t.Run(test.name, func(t *testing.T) {
c := client{c: test.client}
Expand Down
4 changes: 4 additions & 0 deletions manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const (
// DefaultInstall is the command used to configure packages
// where a configure command is not provided
DefaultInstall = "make install {{ .MakeOpts }}"

// MetaManifestName is the name of the dummy manifest we use
// when installing multiple package at once
MetaManifestName = "packages"
)

// Dep represents a dependency tuple.
Expand Down
18 changes: 17 additions & 1 deletion manifest_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (d *ManifestDB) loadManifests() (err error) {
}

for _, manifest := range manifests {
err = tx.Insert("package", manifest)
err = d.addManifest(tx, manifest)
if err != nil {
return
}
Expand All @@ -132,3 +132,19 @@ func (d *ManifestDB) loadManifests() (err error) {

return
}

func (d *ManifestDB) addManifest(tx *memdb.Txn, manifest *Manifest) error {
return tx.Insert("package", manifest)
}

func (d *ManifestDB) deleteManifest(name string) (err error) {
m, err := d.Latest(name, latest)
if err != nil {
return
}

tx := d.db.Txn(true)
defer tx.Commit()

return tx.Delete("package", m)
}
2 changes: 1 addition & 1 deletion proto/install.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package server;
//
// InstallSpec messages are sent via `vin install package [-v 1.0.0]`
message InstallSpec {
string pkg = 1;
repeated string pkg = 1;
string version = 2;
bool force = 3;
}
80 changes: 67 additions & 13 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,38 @@ func (s *Server) getOpsLock(oc chan string) {
}

func (s Server) Install(is *server.InstallSpec, vs server.Vin_InstallServer) (err error) {
if is.Pkg == "" {
var (
pkg string
ver version.Constraints
)

switch len(is.Pkg) {
case 0:
return fmt.Errorf("package must not be empty")

case 1:
pkg = is.Pkg[0]
if pkg == "" {
return fmt.Errorf("package must not be empty")
}

if is.Version != "" {
ver, err = version.NewConstraint(is.Version)
if err != nil {
return
}
}

default:
err = s.createMetaPackage(is.Pkg)
if err != nil {
return err
}

pkg = MetaManifestName
ver = latest

defer s.mdb.deleteManifest(pkg)
}

output := NewOutputter(vs)
Expand All @@ -65,22 +95,13 @@ func (s Server) Install(is *server.InstallSpec, vs server.Vin_InstallServer) (er
defer close(output.C)
go output.Dispatch()

output.C <- fmt.Sprintf("installing %s", is.Pkg)
output.C <- fmt.Sprintf("installing %s", pkg)

s.getOpsLock(output.C)
defer s.operationLock.Unlock()

var ver version.Constraints

if is.Version != "" {
ver, err = version.NewConstraint(is.Version)
if err != nil {
return
}
}

g := NewGraph(&s.mdb, &s.sdb, output.C)
tasks, err := g.Solve(DefaultProfile, is.Pkg, ver)
tasks, err := g.Solve(DefaultProfile, pkg, ver)
if err != nil {
return
}
Expand Down Expand Up @@ -163,7 +184,9 @@ func (s Server) Install(is *server.InstallSpec, vs server.Vin_InstallServer) (er
}
}

s.sdb.AddWorld(is.Pkg, is.Version)
if pkg != MetaManifestName {
s.sdb.AddWorld(pkg, is.Version)
}

return
}
Expand Down Expand Up @@ -196,6 +219,37 @@ func (s Server) Version(ctx context.Context, _ *emptypb.Empty) (v *server.Versio
}, nil
}

func (s Server) createMetaPackage(packages []string) (err error) {
// create a new 'meta package'
deps := make([]Dep, len(packages))
for i, p := range packages {
if p == "" {
return fmt.Errorf("package must not be empty")
}

deps[i] = [2]string{p, ">=0"}
}

metaManifest := &Manifest{
Provides: MetaManifestName,
Version: new(version.Version),
Meta: true,
Profiles: map[string]Profile{
"default": Profile{
Deps: deps,
},
},
}

metaManifest.ID = metaManifest.String()

// add metaManifest to database
tx := s.mdb.db.Txn(true)
defer tx.Commit()

return s.mdb.addManifest(tx, metaManifest)
}

func installingLine(tasks []*Manifest) string {
sb := strings.Builder{}

Expand Down
12 changes: 6 additions & 6 deletions server/install.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 16 additions & 13 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,24 @@ func TestServer_Install(t *testing.T) {

for _, test := range []struct {
name string
pkg string
pkg []string
ver string
expectError bool
}{
{"valid package, explicit version", "standalone", "1.0.0", false},
{"valid package, empty version", "standalone", "", false},
{"valid package, missing version", "standalone", "> 2.0.0", true},
{"invalid package", "foo", "", true},
{"valid package, invalid version", "standalone", "zzzzz", true},
{"valid package, bad checksum", "standalone", "0.1.1", true},
{"valid package, bad command template", "standalone", "0.1.2", true},
{"valid package, 404 archive", "standalone", "0.1.3", true},
{"erroring commands", "standalone", "0.1.0", true},
{"missing package", "", "", true},
{"meta package", "metaz", "", false},
{"valid package, explicit version", []string{"standalone"}, "1.0.0", false},
{"valid package, empty version", []string{"standalone"}, "", false},
{"valid package, missing version", []string{"standalone"}, "> 2.0.0", true},
{"invalid package", []string{"foo"}, "", true},
{"valid package, invalid version", []string{"standalone"}, "zzzzz", true},
{"valid package, bad checksum", []string{"standalone"}, "0.1.1", true},
{"valid package, bad command template", []string{"standalone"}, "0.1.2", true},
{"valid package, 404 archive", []string{"standalone"}, "0.1.3", true},
{"erroring commands", []string{"standalone"}, "0.1.0", true},
{"missing package", []string{}, "", true},
{"empty package", []string{""}, "", true},
{"multiple empty packages", []string{"", ""}, "", true},
{"multiple valid packages", []string{"standalone", "metaz"}, "", false},
{"meta package", []string{"metaz"}, "", false},
} {
t.Run(test.name, func(t *testing.T) {
// create an empty statedb
Expand Down Expand Up @@ -111,7 +114,7 @@ func TestServer_Install_WithService(t *testing.T) {
s.sdb, _ = LoadStateDB()

is := &server.InstallSpec{
Pkg: "another-sample-app",
Pkg: []string{"another-sample-app"},
Version: "",
}

Expand Down

0 comments on commit e554374

Please sign in to comment.