Move bus implementation to a separate repo, hook to events in luet

This commit is contained in:
Ettore Di Giacinto
2020-11-13 18:25:44 +01:00
parent a793b44e83
commit 0e46e763d5
16 changed files with 248 additions and 333 deletions

46
pkg/bus/events.go Normal file
View File

@@ -0,0 +1,46 @@
package bus
import (
"github.com/mudler/go-pluggable"
)
var (
// Package events
// EventPackageInstall is the event fired when a new package is being installed
EventPackageInstall pluggable.EventType = "package.install"
// EventPackageUnInstall is the event fired when a new package is being uninstalled
EventPackageUnInstall pluggable.EventType = "package.uninstall"
// Package build
// EventPackagePreBuild is the event fired before a package is being built
EventPackagePreBuild pluggable.EventType = "package.pre.build"
// EventPackagePreBuildArtifact is the event fired before a package artifact is being built
EventPackagePreBuildArtifact pluggable.EventType = "package.pre.build_artifact"
// EventPackagePostBuildArtifact is the event fired after a package artifact was built
EventPackagePostBuildArtifact pluggable.EventType = "package.post.build_artifact"
// EventPackagePostBuild is the event fired after a package was built
EventPackagePostBuild pluggable.EventType = "package.post.build"
// Repository events
// EventRepositoryPreBuild is the event fired before a repository is being built
EventRepositoryPreBuild pluggable.EventType = "repository.pre.build"
// EventRepositoryPostBuild is the event fired after a repository was built
EventRepositoryPostBuild pluggable.EventType = "repository.post.build"
)
// Manager is the bus instance manager, which subscribes plugins to events emitted by Luet
var Manager *pluggable.Manager = pluggable.NewManager(
[]pluggable.EventType{
EventPackageInstall,
EventPackageUnInstall,
EventPackagePreBuild,
EventPackagePreBuildArtifact,
EventPackagePostBuildArtifact,
EventPackagePostBuild,
EventRepositoryPreBuild,
EventRepositoryPostBuild,
},
)

View File

@@ -34,6 +34,7 @@ import (
"strings"
"sync"
bus "github.com/mudler/luet/pkg/bus"
. "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
@@ -170,6 +171,8 @@ func (a *PackageArtifact) WriteYaml(dst string) error {
return errors.Wrap(err, "While marshalling for PackageArtifact YAML")
}
bus.Manager.Publish(bus.EventPackagePreBuildArtifact, a)
mangle, err := NewPackageArtifactFromYaml(data)
if err != nil {
return errors.Wrap(err, "Generated invalid artifact")
@@ -191,6 +194,7 @@ func (a *PackageArtifact) WriteYaml(dst string) error {
return errors.Wrap(err, "While writing PackageArtifact YAML")
}
//a.CompileSpec.GetPackage().SetPath(p)
bus.Manager.Publish(bus.EventPackagePostBuildArtifact, a)
return nil
}

View File

@@ -26,6 +26,8 @@ import (
"sync"
"time"
bus "github.com/mudler/luet/pkg/bus"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
@@ -596,6 +598,14 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
targetAssertion := p.GetSourceAssertion().Search(p.GetPackage().GetFingerPrint())
targetPackageHash := cs.ImageRepository + ":" + targetAssertion.Hash.PackageHash
bus.Manager.Publish(bus.EventPackagePreBuild, struct {
CompileSpec CompilationSpec
Assert solver.PackageAssert
}{
CompileSpec: p,
Assert: *targetAssertion,
})
// - If image is set we just generate a plain dockerfile
// Treat last case (easier) first. The image is provided and we just compute a plain dockerfile with the images listed as above
if p.GetImage() != "" {
@@ -636,6 +646,14 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
Debug(pkgTag, " :arrow_right_hook: :whale: Builder image from", buildImageHash)
Debug(pkgTag, " :arrow_right_hook: :whale: Package image name", currentPackageImageHash)
bus.Manager.Publish(bus.EventPackagePreBuild, struct {
CompileSpec CompilationSpec
Assert solver.PackageAssert
}{
CompileSpec: compileSpec,
Assert: assertion,
})
lastHash = currentPackageImageHash
if compileSpec.GetImage() != "" {
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().HumanReadableString()+" from image")
@@ -655,6 +673,15 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
// deperrs = append(deperrs, err)
// break // stop at first error
}
bus.Manager.Publish(bus.EventPackagePostBuild, struct {
CompileSpec CompilationSpec
Artifact Artifact
}{
CompileSpec: compileSpec,
Artifact: artifact,
})
departifacts = append(departifacts, artifact)
Info(pkgTag, ":white_check_mark: Done")
}
@@ -673,6 +700,14 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
artifact.SetDependencies(departifacts)
artifact.SetSourceAssertion(p.GetSourceAssertion())
bus.Manager.Publish(bus.EventPackagePostBuild, struct {
CompileSpec CompilationSpec
Artifact Artifact
}{
CompileSpec: p,
Artifact: artifact,
})
return artifact, err
} else {
return departifacts[len(departifacts)-1], nil

View File

@@ -24,6 +24,7 @@ import (
"strings"
"sync"
"github.com/mudler/luet/pkg/bus"
compiler "github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
@@ -451,6 +452,7 @@ func (l *LuetInstaller) install(syncedRepos Repositories, cp pkg.Packages, s *Sy
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Failed creating package")
}
bus.Manager.Publish(bus.EventPackageInstall, c)
}
var toFinalize []pkg.Package
if !l.Options.NoDeps {
@@ -655,6 +657,8 @@ func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error {
return errors.Wrap(err, "Failed removing package from database")
}
bus.Manager.Publish(bus.EventPackageUnInstall, p)
Info(":recycle:", p.GetFingerPrint(), "Removed :heavy_check_mark:")
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"strings"
"time"
"github.com/mudler/luet/pkg/bus"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
@@ -426,6 +427,14 @@ func (r *LuetSystemRepository) Write(dst string, resetRevision bool) error {
r.Name, r.Revision, r.LastUpdate,
))
bus.Manager.Publish(bus.EventRepositoryPreBuild, struct {
Repo LuetSystemRepository
Path string
}{
Repo: *r,
Path: dst,
})
// Create tree and repository file
archive, err := config.LuetCfg.GetSystem().TempDir("archive")
if err != nil {
@@ -506,6 +515,14 @@ func (r *LuetSystemRepository) Write(dst string, resetRevision bool) error {
return err
}
bus.Manager.Publish(bus.EventRepositoryPostBuild, struct {
Repo LuetSystemRepository
Path string
}{
Repo: *r,
Path: dst,
})
return nil
}

View File

@@ -1,70 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin
import (
"github.com/chuckpreslar/emission"
"github.com/codegangsta/inject"
)
type Bus struct {
inject.Injector
emission.Emitter
}
func NewBus() *Bus {
return &Bus{
Injector: inject.New(),
Emitter: *emission.NewEmitter(),
}
}
// On Binds a callback to an event, mapping the arguments on a global level
func (a *Bus) Listen(event EventType, listener interface{}) *Bus {
a.On(string(event), func() { a.Invoke(listener) })
return a
}
// Emit Emits an event, it does accept only the event as argument, since
// the callback will have access to the service mapped by the injector
func (a *Bus) Publish(t EventType, e *Event) *Bus {
a.Map(e)
a.Emit(string(t))
return a
}
// Once Binds a callback to an event, mapping the arguments on a global level
// It is fired only once.
func (a *Bus) OnlyOnce(event EventType, listener interface{}) *Bus {
a.Once(string(event), func() { a.Invoke(listener) })
return a
}
// EmitSync Emits an event in a syncronized manner,
// it does accept only the event as argument, since
// the callback will have access to the service mapped by the injector
func (a *Bus) EmitSync(event interface{}) *Bus {
a.EmitSync(event)
return a
}
func (b *Bus) PropagateEvent(p Plugin) func(e *Event) {
return func(e *Event) {
resp, _ := p.Run(*e)
b.Map(&resp)
b.Emit(p.Name)
}
}

View File

@@ -1,53 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin
import "encoding/json"
var (
PackageInstalled EventType = "package.install"
)
type EventType string
type Event struct {
Name EventType `json:"name"`
Data string `json:"data"`
}
type EventResponse struct {
State string `json:"state"`
Data string `json:"data"`
Error string `json:"error"`
}
func (e Event) JSON() (string, error) {
dat, err := json.Marshal(e)
return string(dat), err
}
func (r EventResponse) Unmarshal(i interface{}) error {
return json.Unmarshal([]byte(r.Data), i)
}
func (r EventResponse) Errored() bool {
return len(r.Error) != 0
}
func NewEvent(name EventType, obj interface{}) (*Event, error) {
dat, err := json.Marshal(obj)
return &Event{Name: name, Data: string(dat)}, err
}

View File

@@ -1,29 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin
type Manager struct {
Plugins []Plugin
Events []EventType
}
func (m Manager) Subscribe(b *Bus) {
for _, p := range m.Plugins {
for _, e := range m.Events {
b.Listen(e, b.PropagateEvent(p))
}
}
}

View File

@@ -1,49 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin
import (
"encoding/json"
"os/exec"
"github.com/pkg/errors"
)
// External binaries to be hooked on events, with common js input, and common js output
type Plugin struct {
Name string
Executable string
}
func (p Plugin) Run(e Event) (EventResponse, error) {
r := EventResponse{}
k, err := e.JSON()
if err != nil {
return r, errors.Wrap(err, "while marshalling evet")
}
cmd := exec.Command(p.Executable, string(e.Name), k)
out, err := cmd.Output()
if err != nil {
r.Error = err.Error()
return r, errors.Wrap(err, "while executing plugin")
}
if err := json.Unmarshal(out, &r); err != nil {
r.Error = err.Error()
return r, errors.Wrap(err, "while unmarshalling response")
}
return r, nil
}

View File

@@ -1,32 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestPlugin(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Plugin Suite")
}

View File

@@ -1,92 +0,0 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@mocaccino.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package plugin_test
import (
"io/ioutil"
"os"
. "github.com/mudler/luet/pkg/plugin"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Plugin", func() {
Context("event subscription", func() {
var pluginFile *os.File
var err error
var b *Bus
var m *Manager
BeforeEach(func() {
pluginFile, err = ioutil.TempFile(os.TempDir(), "tests")
Expect(err).Should(BeNil())
defer os.Remove(pluginFile.Name()) // clean up
b = NewBus()
m = &Manager{}
})
It("gets the json event name", func() {
d1 := []byte("#!/bin/bash\necho \"{ \\\"state\\\": \\\"$1\\\" }\"\n")
err := ioutil.WriteFile(pluginFile.Name(), d1, 0550)
Expect(err).Should(BeNil())
m.Plugins = []Plugin{{Name: "test", Executable: pluginFile.Name()}}
m.Events = []EventType{PackageInstalled}
m.Subscribe(b)
ev, err := NewEvent(PackageInstalled, map[string]string{"foo": "bar"})
Expect(err).Should(BeNil())
var received map[string]string
var resp *EventResponse
b.Listen("test", func(r *EventResponse) {
resp = r
r.Unmarshal(&received)
})
b.Publish(PackageInstalled, ev)
Expect(resp.Errored()).ToNot(BeTrue())
Expect(resp.State).Should(Equal(string(PackageInstalled)))
})
It("gets the json event payload", func() {
d1 := []byte("#!/bin/bash\necho $2\n")
err := ioutil.WriteFile(pluginFile.Name(), d1, 0550)
Expect(err).Should(BeNil())
m.Plugins = []Plugin{{Name: "test", Executable: pluginFile.Name()}}
m.Events = []EventType{PackageInstalled}
m.Subscribe(b)
foo := map[string]string{"foo": "bar"}
ev, err := NewEvent(PackageInstalled, foo)
Expect(err).Should(BeNil())
var received map[string]string
var resp *EventResponse
b.Listen("test", func(r *EventResponse) {
resp = r
r.Unmarshal(&received)
})
b.Publish(PackageInstalled, ev)
Expect(resp.Errored()).ToNot(BeTrue())
Expect(received).Should(Equal(foo))
})
})
})