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

Install multiple packages together as a meta package #45

Merged
merged 2 commits into from
Mar 11, 2022
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
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;
}
88 changes: 74 additions & 14 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import (

"github.com/hashicorp/go-version"
"github.com/vinyl-linux/vin/server"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)

const (
DefaultProfile = "default"
)

var (
errEmptyPackage = status.Error(codes.InvalidArgument, "package must not be empty")
)

type Server struct {
server.UnimplementedVinServer

Expand Down Expand Up @@ -55,8 +61,38 @@ func (s *Server) getOpsLock(oc chan string) {
}

func (s Server) Install(is *server.InstallSpec, vs server.Vin_InstallServer) (err error) {
if is.Pkg == "" {
return fmt.Errorf("package must not be empty")
var (
pkg string
ver version.Constraints
)

switch len(is.Pkg) {
case 0:
return errEmptyPackage

case 1:
pkg = is.Pkg[0]
if pkg == "" {
return errEmptyPackage
}

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 +101,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 +190,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 +225,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 errEmptyPackage
}

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