Skip to content

Commit

Permalink
Support for install_check_path and install_check_script
Browse files Browse the repository at this point in the history
  • Loading branch information
1dustindavis committed Aug 15, 2018
1 parent a5878d9 commit 810bb03
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 26 deletions.
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ A manifest can include managed_installs, managed_uninstalls, managed_updates, or
---
name: example
managed_installs:
- googlechrome
- slack
- GoogleChrome
- Slack
managed_uninstalls:
- firefox
- Firefox
managed_upgrades:
- jre8
- Jre8
included_manifests:
- printers
- internal
Expand All @@ -55,27 +55,53 @@ A catalog contains details on all available packages. Catalogs are in yaml forma

```yaml
---
googlechrome:
GoogleChrome:
display_name: Google Chrome
installer_item_hash: c1ed04713c5a8b4ff8bc7d77036644dac505784818b91850f180e08da786fbca
installer_item_location: packages/GoogleChrome.65.0.3325.18100.msi
version: 65.0.3325.18100
colorprinter:
ColorPrinter:
display_name: Color Printer
installer_item_hash: a8b4ff8bc7d77036644c1ed04713c550550f180e08da786fbca784818b918dac
installer_item_location: packages/colorprinter.1.0.nupkg
version: 1.0
dependencies: Canon-Drivers
Canon-Drivers:
CanonDrivers:
display_name: Canon Printer Drivers
installer_item_hash: ca784818b91850f180e08da786ac1ed04713c5a8b4ff8bc7d77036644dac505aec
installer_item_location: packages/Canon-Drivers.1.0.nupkg
version: 1.0
Chocolatey:
display_name: Chocolatey
install_check_path: C:\ProgramData\chocolatey\bin\choco.exe
installer_item_location: packages/apps/chocolatey/chocolateyInstall.ps1
installer_item_hash: 38cf17a230dbe53efc49f63bbc9931296b5cea84f45ac6528ce60767fe370230
version: 1.0
ChefClient:
display_name: Chef Client
install_check_script: |
$latest = "13.8.5"
$current = chef-client --version
$current = $current.Split(" ")[1]
$upToDate = [System.Version]$current -gt [System.Version]$latest
If ($upToDate) {
exit 1
} Else {
exit 0
}
installer_item_location: packages/apps/chef-client/chef-client-13.8.5-x64.msi
installer_item_hash: c1ed04719c5a8b4ff9bc7d77036644dec505984808b91850f180e08da786fbca
version: 13.8.5
```

* `display_name` is currently unused, but optionally includes a human-readable name.
* `install_check_path` is a path to a file that must exist for the item to be considered installed. If this option is not provided, Gorilla will default to using the registry.
* `install_check_script` is a code block that will be executed to determine is the packanyge should be installed. Any non-zero exit code will be considered installed. If this option is not provided, Gorilla will default to using the registry.
* `installer_item_hash` is required and should be a sha256 hash of the file located at `installer_item_location`.
* `installer_item_location` is required and should be the path to the package, relative to the `url` provided in the configuration file.
* `version` is compared to the currently installed version to determine if it needs to be installed.
Expand Down
2 changes: 2 additions & 0 deletions pkg/catalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
type Item struct {
Dependencies []string `yaml:"dependencies"`
DisplayName string `yaml:"display_name"`
InstallCheckPath string `yaml:"install_check_path"`
InstallCheckScript string `yaml:"install_check_script"`
InstallerItemHash string `yaml:"installer_item_hash"`
InstallerItemLocation string `yaml:"installer_item_location"`
InstallerType string `yaml:"installer_type"`
Expand Down
19 changes: 19 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ import (
"gopkg.in/yaml.v2"
)

var (
// URL of the web server with all of our files
URL string
// Manifest is a yaml file with the packages to manage on this node
Manifest string
// Catalog is a yaml file with details on the available packages
Catalog string
// CachePath is a directory we will use for temporary storage
CachePath string
// Verbose is true is we should output more detail than normal
Verbose bool
)

// Object to store our configuration
type Object struct {
URL string `yaml:"url"`
Expand Down Expand Up @@ -69,5 +82,11 @@ func Get() Object {
configuration.Verbose = true
}

URL = configuration.URL
Manifest = configuration.Manifest
Catalog = configuration.Catalog
CachePath = configuration.CachePath
Verbose = configuration.Verbose

return configuration
}
35 changes: 18 additions & 17 deletions pkg/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,45 @@ import (
"github.com/1dustindavis/gorilla/pkg/status"
)

func runCommand(installCmd string, installArgs []string, verbose bool) {
cmd := exec.Command(installCmd, installArgs...)
// runCommand executes a command and it's argurments in the CMD enviroment
func runCommand(command string, arguments []string, verbose bool) {
cmd := exec.Command(command, arguments...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
fmt.Println("command:", installCmd, installArgs)
fmt.Println("command:", command, arguments)
fmt.Fprintln(os.Stderr, "Error creating pipe to stdout", err)
os.Exit(1)
}

scanner := bufio.NewScanner(cmdReader)
if verbose {
fmt.Println("command:", installCmd, installArgs)
fmt.Println("command:", command, arguments)
go func() {
for scanner.Scan() {
fmt.Printf("Installer output | %s\n", scanner.Text())
fmt.Printf("Command output | %s\n", scanner.Text())
}
}()
}

err = cmd.Start()
if err != nil {
fmt.Println("command:", installCmd, installArgs)
fmt.Println("command:", command, arguments)
fmt.Println(os.Stderr, "Error running command:", err)
os.Exit(1)
}

err = cmd.Wait()
if err != nil {
fmt.Println("command:", installCmd, installArgs)
fmt.Println(os.Stderr, "Installer error:", err)
fmt.Println("command:", command, arguments)
fmt.Println(os.Stderr, "Command error:", err)
os.Exit(1)
}
return
}

// Returns true if the item is already installed AND up-to-date.
func alreadyUpToDate(catalogItem catalog.Item) bool {
installed, versionMatch, err := status.CheckRegistry(catalogItem)
installed, versionMatch, err := status.CheckStatus(catalogItem)
if err != nil {
fmt.Println("Unable to check status of item:", catalogItem.DisplayName)
return false
Expand All @@ -65,7 +66,7 @@ func alreadyUpToDate(catalogItem catalog.Item) bool {

// Returns true if the item is installed, but not up-to-date
func upgradeNeeded(catalogItem catalog.Item) bool {
installed, versionMatch, err := status.CheckRegistry(catalogItem)
installed, versionMatch, err := status.CheckStatus(catalogItem)
if err != nil {
fmt.Println("Unable to check status of item:", catalogItem.DisplayName)
return false
Expand Down Expand Up @@ -133,7 +134,7 @@ func Install(item catalog.Item, cachePath string, verbose bool, repoURL string)
} else if fileExt == ".msi" {
fmt.Println("Installing MSI for", fileName)
installCmd = filepath.Join(os.Getenv("WINDIR"), "system32/", "msiexec.exe")
installArgs = []string{"/I", absFile, "/quiet"}
installArgs = []string{"/i", absFile, "/qn", "/norestart"}

} else if fileExt == ".exe" {
fmt.Println("EXE support not added yet:", fileName)
Expand Down Expand Up @@ -210,7 +211,7 @@ func Uninstall(item catalog.Item, cachePath string, verbose bool, repoURL string
} else if item.UninstallMethod == "msi" {
fmt.Println("unnstalling MSI for", item.DisplayName)
uninstallCmd = filepath.Join(os.Getenv("WINDIR"), "system32/", "msiexec.exe")
uninstallArgs = []string{"/X", absFile, "/quiet"}
uninstallArgs = []string{"/x", absFile, "/qn", "/norestart"}
} else {
fmt.Println("Unable to uninstall", item.DisplayName)
fmt.Println("Installer type unsupported:", item.UninstallMethod)
Expand Down Expand Up @@ -279,20 +280,20 @@ func Upgrade(item catalog.Item, cachePath string, verbose bool, repoURL string)
} else if fileExt == ".msi" {
fmt.Println("Installing MSI for", fileName)
installCmd = filepath.Join(os.Getenv("WINDIR"), "system32/", "msiexec.exe")
installArgs = []string{"/I", absFile, "/quiet"}
installArgs = []string{"/i", absFile, "/qn", "/norestart"}

} else if fileExt == ".exe" {
fmt.Println("EXE support not added yet:", fileName)
return
} else if fileExt == ".ps1" {
fmt.Println("Powershell support not added yet:", fileName)
return
} else {
fmt.Println("Unable to install", fileName)
fmt.Println("Installer type unsupported:", fileExt)
return
}

fmt.Println("Unable to install", fileName)
fmt.Println("Installer type unsupported:", fileExt)
return

runCommand(installCmd, installArgs, verbose)

return
Expand Down
77 changes: 75 additions & 2 deletions pkg/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package status

import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/1dustindavis/gorilla/pkg/catalog"
"github.com/1dustindavis/gorilla/pkg/config"
"github.com/hashicorp/go-version"
"golang.org/x/sys/windows/registry"
)
Expand Down Expand Up @@ -96,8 +101,58 @@ func UninstallReg(itemName string) string {
return uninstallString
}

// CheckRegistry iterates through the local registry and compiles all installed software
func CheckRegistry(catalogItem catalog.Item) (installed bool, versionMatch bool, checkErr error) {
func checkScript(catalogItem catalog.Item) (installed bool, versionMatch bool, checkErr error) {

// Write InstallCheckScript to disk as a Powershell file
tmpScript := filepath.Join(config.CachePath, "tmpCheckScript.ps1")
ioutil.WriteFile(tmpScript, []byte(catalogItem.InstallCheckScript), 0755)

// Build the command to execute the script
psCmd := filepath.Join(os.Getenv("WINDIR"), "system32/", "WindowsPowershell", "v1.0", "powershell.exe")
psArgs := []string{"-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Normal", "-ExecutionPolicy", "Bypass", "-File", tmpScript}

// Execute the script
cmd := exec.Command(psCmd, psArgs...)
stdOut, stdErr := cmd.CombinedOutput()

// Delete the temporary sctip
os.Remove(tmpScript)

if config.Verbose {
fmt.Println("stdout:")
fmt.Println(stdOut)
fmt.Println("stderr:")
fmt.Println(stdErr)
}
if stdErr != nil {
installed = true
versionMatch = true
} else {
installed = true
versionMatch = false
}

return installed, versionMatch, checkErr
}

func checkPath(catalogItem catalog.Item) (installed bool, versionMatch bool, checkErr error) {
path := filepath.Clean(catalogItem.InstallCheckPath)
if config.Verbose {
fmt.Println(path)
}
if _, checkErr := os.Stat(path); checkErr == nil {
installed = true
versionMatch = true
}

installed = true
versionMatch = false

return installed, versionMatch, checkErr
}

// checkRegistry iterates through the local registry and compiles all installed software
func checkRegistry(catalogItem catalog.Item) (installed bool, versionMatch bool, checkErr error) {
// If we don't have version information, we cant compare
if catalogItem.Version == "" {
return false, false, checkErr
Expand Down Expand Up @@ -132,3 +187,21 @@ func CheckRegistry(catalogItem catalog.Item) (installed bool, versionMatch bool,
return installed, versionMatch, checkErr

}

// CheckStatus determines the method for checking status
func CheckStatus(catalogItem catalog.Item) (installed bool, versionMatch bool, checkErr error) {

if catalogItem.InstallCheckScript != "" {
fmt.Printf("Checking status of %s via Script...\n", catalogItem.DisplayName)
return checkScript(catalogItem)

} else if catalogItem.InstallCheckPath != "" {
fmt.Printf("Checking status of %s via Path...\n", catalogItem.DisplayName)
return checkPath(catalogItem)

}

fmt.Printf("Checking status of %s via Registry...\n", catalogItem.DisplayName)
return checkRegistry(catalogItem)

}

0 comments on commit 810bb03

Please sign in to comment.