refactor: first pass at rewriting apps plugin in go
This commit is contained in:
@@ -2,6 +2,8 @@ package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -96,6 +98,83 @@ func AskForDestructiveConfirmation(name string, objectType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DockerCleanup cleans up all exited/dead containers and removes all dangling images
|
||||
func DockerCleanup(appName string, forceCleanup bool) error {
|
||||
if !forceCleanup {
|
||||
skipCleanup := false
|
||||
if appName != "" {
|
||||
b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SKIP_CLEANUP"}...)
|
||||
output := string(b[:])
|
||||
if output == "true" {
|
||||
skipCleanup = true
|
||||
}
|
||||
}
|
||||
|
||||
if skipCleanup || os.Getenv("DOKKU_SKIP_CLEANUP") == "true" {
|
||||
LogInfo1("DOKKU_SKIP_CLEANUP set. Skipping dokku cleanup")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
LogInfo1("Cleaning up...")
|
||||
scheduler := GetAppScheduler(appName)
|
||||
if appName == "--global" {
|
||||
appName = ""
|
||||
}
|
||||
|
||||
forceCleanupArg := "false"
|
||||
if forceCleanup {
|
||||
forceCleanupArg = "true"
|
||||
}
|
||||
|
||||
if err := PlugnTrigger("scheduler-docker-cleanup", []string{scheduler, appName, forceCleanupArg}...); err != nil {
|
||||
return fmt.Errorf("Failure while cleaning up app: %s", err)
|
||||
}
|
||||
|
||||
// delete all non-running and dead containers
|
||||
exitedContainerIDs, _ := listContainers("exited", appName)
|
||||
deadContainerIDs, _ := listContainers("dead", appName)
|
||||
containerIDs := append(exitedContainerIDs, deadContainerIDs...)
|
||||
|
||||
if len(containerIDs) > 0 {
|
||||
removeContainers(containerIDs)
|
||||
}
|
||||
|
||||
// delete dangling images
|
||||
imageIDs, _ := listDanglingImages(appName)
|
||||
if len(imageIDs) > 0 {
|
||||
RemoveImages(imageIDs)
|
||||
}
|
||||
|
||||
if appName != "" {
|
||||
// delete unused images
|
||||
pruneUnusedImages(appName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAppScheduler fetches the scheduler for a given application
|
||||
func GetAppScheduler(appName string) string {
|
||||
if appName == "--global" {
|
||||
appName = ""
|
||||
}
|
||||
|
||||
b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...)
|
||||
value := string(b[:])
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
|
||||
b, _ = PlugnTriggerOutput("config-get-global", []string{"DOKKU_SCHEDULER"}...)
|
||||
value = string(b[:])
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
|
||||
return "docker-local"
|
||||
}
|
||||
|
||||
// GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented
|
||||
func GetDeployingAppImageName(appName, imageTag, imageRepo string) (imageName string) {
|
||||
if appName == "" {
|
||||
@@ -152,8 +231,7 @@ func GetAppContainerIDs(appName string, containerType string) ([]string, error)
|
||||
return containerIDs, err
|
||||
}
|
||||
|
||||
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
||||
appRoot := fmt.Sprintf("%v/%v", dokkuRoot, appName)
|
||||
appRoot := AppRoot(appName)
|
||||
containerFilePath := fmt.Sprintf("%v/CONTAINER", appRoot)
|
||||
_, err := os.Stat(containerFilePath)
|
||||
if !os.IsNotExist(err) {
|
||||
@@ -200,6 +278,26 @@ func GetAppRunningContainerIDs(appName string, containerType string) ([]string,
|
||||
return runningContainerIDs, nil
|
||||
}
|
||||
|
||||
// GetRunningImageTag retrieves current image tag for a given app and returns empty string if no deployed containers are found
|
||||
func GetRunningImageTag(appName string) (string, error) {
|
||||
if err := VerifyAppName(appName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
containerIDs, err := GetAppContainerIDs(appName, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, containerID := range containerIDs {
|
||||
if image, err := DockerInspect(containerID, "{{ .Config.Image }}"); err == nil {
|
||||
return strings.Split(image, ":")[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("No image tag found")
|
||||
}
|
||||
|
||||
// ContainerIsRunning checks to see if a container is running
|
||||
func ContainerIsRunning(containerID string) bool {
|
||||
b, err := DockerInspect(containerID, "'{{.State.Running}}'")
|
||||
@@ -242,7 +340,7 @@ func DokkuApps() (apps []string, err error) {
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
appRoot := strings.Join([]string{dokkuRoot, f.Name()}, "/")
|
||||
appRoot := AppRoot(f.Name())
|
||||
if !DirectoryExists(appRoot) {
|
||||
continue
|
||||
}
|
||||
@@ -314,9 +412,7 @@ func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
|
||||
|
||||
// IsDeployed returns true if given app has a running container
|
||||
func IsDeployed(appName string) bool {
|
||||
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
||||
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
|
||||
files, err := ioutil.ReadDir(appRoot)
|
||||
files, err := ioutil.ReadDir(AppRoot(appName))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -450,21 +546,37 @@ func UcFirst(str string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// VerifyAppName verifies app name format and app existence"
|
||||
func VerifyAppName(appName string) (err error) {
|
||||
// IsValidAppName verifies app name format
|
||||
func IsValidAppName(appName string) error {
|
||||
if appName == "" {
|
||||
return fmt.Errorf("App name must not be null")
|
||||
}
|
||||
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
||||
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
|
||||
if !DirectoryExists(appRoot) {
|
||||
return fmt.Errorf("app %s does not exist", appName)
|
||||
return fmt.Errorf("APP must not be null")
|
||||
}
|
||||
|
||||
r, _ := regexp.Compile("^[a-z0-9].*")
|
||||
if !r.MatchString(appName) {
|
||||
return fmt.Errorf("app name (%s) must begin with lowercase alphanumeric character", appName)
|
||||
if r.MatchString(appName) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
||||
appRoot := AppRoot(appName)
|
||||
if DirectoryExists(appRoot) {
|
||||
os.RemoveAll(appRoot)
|
||||
}
|
||||
|
||||
return errors.New("App name must begin with lowercase alphanumeric character")
|
||||
}
|
||||
|
||||
// VerifyAppName verifies app name format and app existence"
|
||||
func VerifyAppName(appName string) error {
|
||||
if err := IsValidAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appRoot := AppRoot(appName)
|
||||
if !DirectoryExists(appRoot) {
|
||||
return fmt.Errorf("App %s does not exist", appName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyImage returns true if docker image exists in local repo
|
||||
@@ -486,24 +598,23 @@ func DockerBin() string {
|
||||
|
||||
// PlugnTrigger fire the given plugn trigger with the given args
|
||||
func PlugnTrigger(triggerName string, args ...string) error {
|
||||
shellArgs := make([]interface{}, len(args)+2)
|
||||
shellArgs[0] = "trigger"
|
||||
shellArgs[1] = triggerName
|
||||
for i, arg := range args {
|
||||
shellArgs[i+2] = arg
|
||||
}
|
||||
return sh.Command("plugn", shellArgs...).Run()
|
||||
return PlugnTriggerSetup(triggerName, args...).Run()
|
||||
}
|
||||
|
||||
// PlugnTriggerOutput fire the given plugn trigger with the given args
|
||||
func PlugnTriggerOutput(triggerName string, args ...string) ([]byte, error) {
|
||||
return PlugnTriggerSetup(triggerName, args...).Output()
|
||||
}
|
||||
|
||||
// PlugnTriggerSetup sets up a plugn trigger call
|
||||
func PlugnTriggerSetup(triggerName string, args ...string) *sh.Session {
|
||||
shellArgs := make([]interface{}, len(args)+2)
|
||||
shellArgs[0] = "trigger"
|
||||
shellArgs[1] = triggerName
|
||||
for i, arg := range args {
|
||||
shellArgs[i+2] = arg
|
||||
}
|
||||
return sh.Command("plugn", shellArgs...).Output()
|
||||
return sh.Command("plugn", shellArgs...)
|
||||
}
|
||||
|
||||
func times(str string, n int) (out string) {
|
||||
@@ -512,3 +623,112 @@ func times(str string, n int) (out string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func listContainers(status string, appName string) ([]string, error) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"container",
|
||||
"list",
|
||||
"--quiet",
|
||||
"--all",
|
||||
"--filter",
|
||||
fmt.Sprintf("status=%v", status),
|
||||
"--filter",
|
||||
fmt.Sprintf("label=%v", os.Getenv("DOKKU_CONTAINER_LABEL")),
|
||||
}
|
||||
|
||||
if appName != "" {
|
||||
command = append(command, []string{"--filter", fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...)
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
listCmd := NewShellCmd(strings.Join(command, " "))
|
||||
listCmd.ShowOutput = false
|
||||
listCmd.Command.Stderr = &stderr
|
||||
b, err := listCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
return []string{}, errors.New(strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
output := strings.Split(strings.TrimSpace(string(b[:])), "\n")
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func listDanglingImages(appName string) ([]string, error) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"image",
|
||||
"list",
|
||||
"--quiet",
|
||||
"--filter",
|
||||
"dangling=true",
|
||||
}
|
||||
|
||||
if appName != "" {
|
||||
command = append(command, []string{"--filter", fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...)
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
listCmd := NewShellCmd(strings.Join(command, " "))
|
||||
listCmd.ShowOutput = false
|
||||
listCmd.Command.Stderr = &stderr
|
||||
b, err := listCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
return []string{}, errors.New(strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
output := strings.Split(strings.TrimSpace(string(b[:])), "\n")
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func removeContainers(containerIDs []string) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"container",
|
||||
"rm",
|
||||
}
|
||||
|
||||
command = append(command, containerIDs...)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
rmCmd := NewShellCmd(strings.Join(command, " "))
|
||||
rmCmd.ShowOutput = false
|
||||
rmCmd.Command.Stderr = &stderr
|
||||
rmCmd.Execute()
|
||||
}
|
||||
|
||||
func RemoveImages(imageIDs []string) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"image",
|
||||
"rm",
|
||||
}
|
||||
|
||||
command = append(command, imageIDs...)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
rmCmd := NewShellCmd(strings.Join(command, " "))
|
||||
rmCmd.ShowOutput = false
|
||||
rmCmd.Command.Stderr = &stderr
|
||||
rmCmd.Execute()
|
||||
}
|
||||
|
||||
func pruneUnusedImages(appName string) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"image",
|
||||
"prune",
|
||||
"--all",
|
||||
"--force",
|
||||
"--filter",
|
||||
fmt.Sprintf("label=com.dokku.app-name=%v", appName),
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
pruneCmd := NewShellCmd(strings.Join(command, " "))
|
||||
pruneCmd.ShowOutput = false
|
||||
pruneCmd.Command.Stderr = &stderr
|
||||
pruneCmd.Execute()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,27 @@ func LogFail(text string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// LogFailQuiet is the failure log formatter (with quiet option)
|
||||
// prints text to stderr and exits with status 1
|
||||
func LogFailQuiet(text string) {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf("FAILED: %s", text))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Log is the log formatter
|
||||
func Log(text string) {
|
||||
fmt.Println(text)
|
||||
}
|
||||
|
||||
// LogQuiet is the log formatter (with quiet option)
|
||||
func LogQuiet(text string) {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
|
||||
fmt.Println(text)
|
||||
}
|
||||
}
|
||||
|
||||
// LogInfo1 is the info1 header formatter
|
||||
func LogInfo1(text string) {
|
||||
fmt.Println(fmt.Sprintf("-----> %s", text))
|
||||
|
||||
Reference in New Issue
Block a user