Skip to content

Instantly share code, notes, and snippets.

@clarkmcc
Last active March 5, 2020 17:53
Show Gist options
  • Save clarkmcc/7b2db9c5529f8812070db2560adcdb54 to your computer and use it in GitHub Desktop.
Save clarkmcc/7b2db9c5529f8812070db2560adcdb54 to your computer and use it in GitHub Desktop.
This package is a dependency installer for Golang. Dependencies can be registered with the startup.Engine, checked, and installed.
package startup
import (
"fmt"
"log"
"sync"
)
const BufferedChan int = 1
const FailedToInstallDependency = "failed to install dependency"
const FailedToDownloadFile = "failed to download file"
type Dependency interface {
Check() bool
Install(chan error, interface{})
IsCritical() bool
GetFilename() string
GetFilePath() string
}
type File struct {
Filename string
URL string
Critical bool
}
type Zip struct {
Filename string
Destination string
URL string
Critical bool
}
type Dca struct {
Filename string
URL string
Arch string
Overwrite bool
}
type EngineOpts struct {
Dependencies []Dependency
}
// The engine holds a slice of dependencies that it has the ability to check and install when needed
type Engine struct {
Opts *EngineOpts
Dependencies []Dependency
}
// Returns a new instance of a startup engine
func NewStartupEngine(opts *EngineOpts) *Engine {
e := &Engine{
Opts: opts,
}
if opts == nil {
e.Dependencies = []Dependency{}
} else {
e.Dependencies = opts.Dependencies
}
return e
}
// Adds a dependency to the engine struct
func (e *Engine) RegisterDependency(dep Dependency) *Engine {
e.Dependencies = append(e.Dependencies, dep)
return e
}
// Adds a slice of dependencies to the engine struct
func (e *Engine) RegisterDependencies(deps []Dependency) *Engine {
for _, dep := range deps {
e.Dependencies = append(e.Dependencies, dep)
}
return e
}
// Checks for the existence of an individual dependency and installs it if necessary
func (e *Engine) CheckDependency(dep Dependency) error {
log.Printf("checking that dependency %s exists", dep.GetFilename())
exists := dep.Check()
if !exists {
// if its not a critical dependency, just download and install it in the background
if !dep.IsCritical() {
wg := &sync.WaitGroup{}
wg.Add(1)
go dep.Install(make(chan error, BufferedChan), wg)
wg.Wait()
} else {
ec := make(chan error, BufferedChan)
dep.Install(ec, nil)
select {
case e := <-ec:
return fmt.Errorf("%v: %v", FailedToInstallDependency, e)
default:
log.Printf("successfully installed dependency: %s", dep.GetFilename())
return nil
}
}
}
return nil
}
// Checks for the existence of an individual dependency and installs it if necessary
func (e *Engine) CheckDependencyChan(dep Dependency, msgChan chan string) {
msgChan <- fmt.Sprintf("checking that dependency %s exists", dep.GetFilename())
exists := dep.Check()
if !exists {
msgChan <- fmt.Sprintf("dependency %s not existing or should be overwritten", dep.GetFilename())
if !dep.IsCritical() {
wg := &sync.WaitGroup{}
wg.Add(1)
go dep.Install(make(chan error, BufferedChan), wg)
wg.Wait()
} else {
ec := make(chan error, BufferedChan)
dep.Install(ec, nil)
select {
case e := <-ec:
msgChan <- fmt.Sprintf("%v: %v", FailedToInstallDependency, e)
default:
msgChan <- fmt.Sprintf("successfully installed dependency: %s", dep.GetFilename())
}
}
}
}
// Checks for the existence all of the dependencies registered to a startup engine
func (e *Engine) CheckAllDependencies() []error {
var errors []error
for _, dep := range e.Dependencies {
err := e.CheckDependency(dep)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
// Checks for the existence all of the dependencies registered to a startup engine and sends logs via the channel
func (e *Engine) CheckAllDependenciesChan(msgChan chan string) {
for _, dep := range e.Dependencies {
e.CheckDependencyChan(dep, msgChan)
}
close(msgChan)
}
func (d *File) Check() bool {
return util.Exists(d.Filename)
}
func (d *File) Install(ec chan error, wgs interface{}) {
if wgs != nil {
defer wgs.(*sync.WaitGroup).Done()
}
err := util.DownloadFile(d.Filename, d.URL)
if err != nil {
ec <- fmt.Errorf(fmt.Sprintf("%v: %v", FailedToDownloadFile, err))
}
}
func (d *File) IsCritical() bool {
return d.Critical
}
func (d *File) GetFilename() string {
return d.Filename
}
func (d *File) GetFilePath() string {
return d.Filename
}
func (d *Zip) Check() bool {
return util.Exists(d.Destination)
}
func (d *Zip) Install(ec chan error, wgs interface{}) {
if wgs != nil {
defer wgs.(*sync.WaitGroup).Done()
}
err := util.DownloadFile(d.Filename, d.URL)
if err != nil {
ec <- fmt.Errorf(fmt.Sprintf("%v: %v", FailedToDownloadFile, err))
return
}
defer os.Remove(d.Filename)
_, err = util.Unzip(d.Filename, d.Destination)
if err != nil {
ec <- fmt.Errorf(fmt.Sprintf("%v: %v", FailedToInstallDependency, err))
return
}
}
func (d *Zip) IsCritical() bool {
return d.Critical
}
func (d *Zip) GetFilename() string {
return d.Filename
}
func (d *Zip) GetFilePath() string {
return d.Destination
}
func (d *Dca) Check() bool {
if d.Overwrite {
return false
}
return util.Exists(d.Filename)
}
func (d *Dca) Install(ec chan error, wgs interface{}) {
if wgs != nil {
defer wgs.(*sync.WaitGroup).Done()
}
err := util.DownloadFile(d.Filename, d.URL)
if err != nil {
ec <- fmt.Errorf(fmt.Sprintf("%v: %v", FailedToDownloadFile, err))
}
}
func (d *Dca) IsCritical() bool {
return true
}
func (d *Dca) GetFilename() string {
return d.Filename
}
func (d *Dca) GetFilePath() string {
return d.Filename
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment