/** * This is a fallback client builder, which builds the default * coach client. The default coach client is currently the * FSouza Docker client, configured to use ENV settings, or * a local socket. */ func (clientFactories *ClientFactories) from_Default(logger log.Log, project *conf.Project) { clientFactorySettings := &FSouza_ClientFactorySettings{} clientType := "fsouza" if DockerHost := os.Getenv("DOCKER_HOST"); DockerHost == "" { logger.Debug(log.VERBOSITY_DEBUG, "No local environment DOCKER settings found, assuming a locally running docker client will be found.") clientFactorySettings.Host = "unix:///var/run/docker.sock" } else { clientFactorySettings.Host = DockerHost } // if we have no cert path, and we are going to use a TCP socket, test for a default cert path. if DockerCertPath := os.Getenv("DOCKER_CERT_PATH"); DockerCertPath != "" { clientFactorySettings.CertPath = DockerCertPath } factory := FSouza_ClientFactory{} if !factory.Init(logger, project, ClientFactorySettings(clientFactorySettings)) { logger.Error("Failed to initialize FSouza factory from client factory configuration") } // Add this factory to the factory list logger.Debug(log.VERBOSITY_DEBUG_LOTS, "Client Factory Created [Client_DockerFSouzaFactory]", factory) clientFactories.AddClientFactory(clientType, ClientFactory(&factory)) }
func Init_Generate(logger log.Log, handler string, path string, skip []string, sizeLimit int64, output io.Writer) bool { logger.Message("GENERATING INIT") var generator Generator switch handler { case "test": generator = Generator(&TestInitGenerator{logger: logger, output: output}) case "yaml": generator = Generator(&YMLInitGenerator{logger: logger, output: output}) default: logger.Error("Unknown init generator (handler) " + handler) return false } iterator := GenerateIterator{ logger: logger, output: output, skip: skip, sizeLimit: sizeLimit, generator: generator, } if iterator.Generate(path) { logger.Message("FINISHED GENERATING YML INIT") return true } else { logger.Error("ERROR OCCURRED GENERATING YML INIT") return false } }
func (client *FSouza_NodeClient) Destroy(logger log.Log, force bool) bool { // Get the image name image, tag := client.GetImageName() if tag != "" { image += ":" + tag } if !client.HasImage() { logger.Warning("Node has no image to destroy [" + image + "]") return false } options := docker.RemoveImageOptions{ Force: force, } // ask the docker client to remove the image err := client.backend.RemoveImageExtended(image, options) if err != nil { logger.Error("Node image removal failed [" + image + "] => " + err.Error()) return false } else { client.backend.Refresh(true, false) logger.Message("Node image was removed [" + image + "]") return true } }
func (operation *InitGenerateOperation) Run(logger log.Log) bool { logger.Info("running init operation:" + operation.output) var writer io.Writer switch operation.output { case "logger": fallthrough case "": writer = logger default: if strings.HasSuffix(operation.output, ".") { operation.output = operation.output + operation.handler } if fileWriter, err := os.Create(operation.output); err == nil { operation.skip = append(operation.skip, operation.output) writer = io.Writer(fileWriter) defer fileWriter.Close() logger.Message("Opening file for init generation output: " + operation.output) } else { logger.Error("Could not open output file to write init to:" + operation.output) } } initialize.Init_Generate(logger.MakeChild("init-generate"), operation.handler, operation.root, operation.skip, operation.sizeLimit, writer) return true }
func (clientFactory *FSouza_ClientFactory) Init(logger log.Log, project *conf.Project, settings ClientFactorySettings) bool { clientFactory.log = logger clientFactory.conf = project // make sure that the settings that were given, where the proper "FSouza_ClientFactory" type typedSettings := settings.Settings() switch asserted := typedSettings.(type) { case *FSouza_ClientFactorySettings: clientFactory.settings = *asserted default: logger.Error("Invalid settings type passed to Fsouza Factory") logger.Debug(log.VERBOSITY_DEBUG, "Settings passed:", asserted) } // if we haven't made an actual fsouza docker client, then do it now if clientFactory.client == nil { if client, pk := clientFactory.makeFsouzaClientWrapper(logger.MakeChild("fsouza")); pk { clientFactory.client = client return true } else { logger.Error("Failed to create actual FSouza Docker client from client factory configuration") return false } } return true }
// Try to configure factories by parsing yaml from a byte stream func (clientFactories *ClientFactories) from_ClientFactoriesYamlBytes(logger log.Log, project *conf.Project, yamlBytes []byte) bool { if project != nil { // token replace tokens := &project.Tokens yamlBytes = []byte(tokens.TokenReplace(string(yamlBytes))) } var yaml_clients map[string]map[string]interface{} err := yaml.Unmarshal(yamlBytes, &yaml_clients) if err != nil { logger.Warning("YAML parsing error : " + err.Error()) return false } logger.Debug(log.VERBOSITY_DEBUG_STAAAP, "YAML source:", yaml_clients) for name, client_struct := range yaml_clients { clientType := "" client_json, _ := json.Marshal(client_struct) logger.Debug(log.VERBOSITY_DEBUG_STAAAP, "Single client JSON:", string(client_json)) if clientType_struct, ok := client_struct["Type"]; ok { clientType, _ = clientType_struct.(string) } else { clientType = name } switch strings.ToLower(clientType) { case "docker": fallthrough case "fsouza": clientFactorySettings := &FSouza_ClientFactorySettings{} err := json.Unmarshal(client_json, clientFactorySettings) if err != nil { logger.Warning("Factory definition failed to configure client factory :" + err.Error()) logger.Debug(log.VERBOSITY_DEBUG, "Factory configuration json: ", string(client_json), clientFactorySettings) continue } factory := FSouza_ClientFactory{} if !factory.Init(logger.MakeChild(clientType), project, ClientFactorySettings(clientFactorySettings)) { logger.Error("Failed to initialize FSouza factory from client factory configuration: " + err.Error()) continue } // Add this factory to the factory list logger.Debug(log.VERBOSITY_DEBUG_LOTS, "Client Factory Created [Client_DockerFSouzaFactory]", factory) clientFactories.AddClientFactory(clientType, ClientFactory(&factory)) case "": logger.Warning("Client registration failure, client has a bad value for 'Type'") default: logger.Warning("Client registration failure, client has an unknown value for 'Type' :" + clientType) } } return true }
func (client *FSouza_InstanceClient) Attach(logger log.Log) bool { id := client.instance.MachineName() // build options for the docker attach operation options := docker.AttachToContainerOptions{ Container: id, InputStream: os.Stdin, OutputStream: os.Stdout, ErrorStream: logger, Logs: true, // Get container logs, sending it to OutputStream. Stream: true, // Stream the response? Stdin: true, // Attach to stdin, and use InputStream. Stdout: true, // Attach to stdout, and use OutputStream. Stderr: true, //Success chan struct{} RawTerminal: client.settings.Config.Tty, // Use raw terminal? Usually true when the container contains a TTY. } logger.Message("Attaching to instance container [" + id + "]") err := client.backend.AttachToContainer(options) if err != nil { logger.Error("Failed to attach to instance container [" + id + "] =>" + err.Error()) return false } else { logger.Message("Disconnected from instance container [" + id + "]") return true } }
func (operation *UnknownOperation) Run(logger log.Log) bool { if operation.id == DEFAULT_OPERATION { logger.Error("No operation specified") } else { logger.Error("Unknown operation: " + operation.id) } return false }
func (tasks *InitTasks) Init_Demo_Run(logger log.Log, demo string) bool { if demoPath, ok := COACH_DEMO_URLS[demo]; ok { return tasks.Init_Yaml_Run(logger, demoPath) } else { logger.Error("Unknown demo key : " + demo) return false } }
// Run all of the prepared operations func (operations *Operations) Run(logger log.Log) { if len(operations.operationsList) == 0 { logger.Error("No operation created") } else { for _, operation := range operations.operationsList { operation.Run(logger.MakeChild(operation.Id())) } } }
func (client *FSouza_NodeClient) Pull(logger log.Log, force bool) bool { image, tag := client.GetImageName() actionCacheTag := "pull:" + image + ":" + tag if _, ok := actionCache[actionCacheTag]; ok { logger.Message("Node image [" + image + ":" + tag + "] was just pulled, so not pulling it again.") return true } if !force && client.HasImage() { logger.Info("Node already has an image [" + image + ":" + tag + "], so not pulling it again. You can force this operation if you want to pull this image.") return false } options := docker.PullImageOptions{ Repository: image, OutputStream: logger, RawJSONStream: false, } if tag != "" { options.Tag = tag } var auth docker.AuthConfiguration // var ok bool //options.Registry = "https://index.docker.io/v1/" // auths, _ := docker.NewAuthConfigurationsFromDockerCfg() // if auth, ok = auths.Configs[registry]; ok { // options.Registry = registry // } else { // node.log.Warning("You have no local login credentials for any repo. Defaulting to no login.") auth = docker.AuthConfiguration{} options.Registry = "https://index.docker.io/v1/" // } logger.Message("Pulling node image [" + image + ":" + tag + "] from server [" + options.Registry + "] using auth [" + auth.Username + "] : " + image + ":" + tag) logger.Debug(log.VERBOSITY_DEBUG_LOTS, "AUTH USED: ", map[string]string{"Username": auth.Username, "Password": auth.Password, "Email": auth.Email, "ServerAdddress": auth.ServerAddress}) // ask the docker client to build the image err := client.backend.PullImage(options, auth) if err != nil { logger.Error("Node image not pulled : " + image + " => " + err.Error()) actionCache[actionCacheTag] = false return false } else { client.backend.Refresh(true, false) logger.Message("Node image pulled: " + image + ":" + tag) actionCache[actionCacheTag] = false return true } }
func (client *FSouza_InstanceClient) Unpause(logger log.Log) bool { id := client.instance.MachineName() err := client.backend.UnpauseContainer(id) if err != nil { logger.Error("Failed to unpause Instance [" + client.instance.Id() + "] Container [" + id + "] =>" + err.Error()) return false } else { client.backend.Refresh(false, true) logger.Message("Unpaused Instance [" + client.instance.Id() + "] Container [" + id + "]") return true } }
func (client *FSouza_InstanceClient) Stop(logger log.Log, force bool, timeout uint) bool { id := client.instance.MachineName() err := client.backend.StopContainer(id, timeout) if err != nil { logger.Error("Failed to stop node container [" + id + "] => " + err.Error()) return false } else { client.backend.Refresh(false, true) logger.Message("Node instance stopped [" + id + "]") return true } }
func (client *FSouza_NodeClient) Build(logger log.Log, force bool) bool { image, tag := client.GetImageName() if client.settings.BuildPath == "" { logger.Warning("Node image [" + image + ":" + tag + "] not built as an empty path was provided. You must point Build: to a path inside .coach") return false } if !force && client.HasImage() { logger.Info("Node image [" + image + ":" + tag + "] not built as an image already exists. You can force this operation to build this image") return false } // determine an absolute buildPath to the build, for Docker to use. buildPath := "" for _, confBuildPath := range client.conf.Paths.GetConfSubPaths(client.settings.BuildPath) { logger.Debug(log.VERBOSITY_DEBUG_STAAAP, "Looking for Build: "+confBuildPath) if _, err := os.Stat(confBuildPath); !os.IsNotExist(err) { buildPath = confBuildPath break } } if buildPath == "" { logger.Error("No matching build path could be found [" + client.settings.BuildPath + "]") } options := docker.BuildImageOptions{ Name: image + ":" + tag, ContextDir: buildPath, RmTmpContainer: true, OutputStream: logger, } logger.Info("Building node image [" + image + ":" + tag + "] From build path [" + buildPath + "]") // ask the docker client to build the image err := client.backend.BuildImage(options) if err != nil { logger.Error("Node build failed [" + client.node.MachineName() + "] in build path [" + buildPath + "] => " + err.Error()) return false } else { client.backend.Refresh(true, false) logger.Message("Node succesfully built image [" + image + ":" + tag + "] From path [" + buildPath + "]") return true } }
func (client *FSouza_Client) Init(logger log.Log, project *conf.Project, settings ClientSettings) bool { client.log = logger client.conf = project // make sure that the settings that were given, where the proper "FSouza_Client" type settingsTyped := settings.Settings() switch asserted := settingsTyped.(type) { case *FSouza_ClientSettings: client.settings = *asserted client.settings.Init(logger, project) return true default: logger.Error("Invalid settings type passed to Fsouza Client") return false } }
func (client *FSouza_InstanceClient) Start(logger log.Log, force bool) bool { // Convert the node data into docker data (transform node keys to container IDs for things like Links & VolumesFrom) id := client.instance.MachineName() Host := client.settings.Host // ask the docker client to start the instance container err := client.backend.StartContainer(id, &Host) if err != nil { logger.Error("Failed to start node container [" + id + "] => " + err.Error()) return false } else { logger.Message("Node instance started [" + id + "]") client.backend.Refresh(false, true) return true } }
func (client *FSouza_InstanceClient) Remove(logger log.Log, force bool) bool { name := client.instance.MachineName() options := docker.RemoveContainerOptions{ ID: name, } // ask the docker client to remove the instance container err := client.backend.RemoveContainer(options) if err != nil { logger.Error("Failed to remove instance container [" + name + "] =>" + err.Error()) return false } else { client.backend.Refresh(false, true) logger.Message("Removed instance container [" + name + "] ") return true } return false }
// perform a string replace on file contents func (task *InitTaskFileBase) FileStringReplace(logger log.Log, targetPath string, oldString string, newString string, replaceCount int) bool { targetPath, ok := task.absolutePath(targetPath, false) if !ok { logger.Warning("Invalid string replace path: " + targetPath) return false } contents, err := ioutil.ReadFile(targetPath) if err != nil { logger.Error(err.Error()) } contents = []byte(strings.Replace(string(contents), oldString, newString, replaceCount)) err = ioutil.WriteFile(targetPath, contents, 0644) if err != nil { logger.Error(err.Error()) } return true }
func (tasks *InitTasks) Init_Git_Run(logger log.Log, source string) bool { if source == "" { logger.Error("You have not provided a git target $/> coach init git https://github.com/aleksijohansson/docker-drupal-coach") return false } url := source path := tasks.root cmd := exec.Command("git", "clone", "--progress", url, path) cmd.Stdin = os.Stdin cmd.Stdout = logger cmd.Stderr = logger err := cmd.Start() if err != nil { logger.Error("Failed to clone the remote repository [" + url + "] => " + err.Error()) return false } logger.Message("Clone remote repository to local project folder [" + url + "]") err = cmd.Wait() if err != nil { logger.Error("Failed to clone the remote repository [" + url + "] => " + err.Error()) return false } tasks.AddMessage("Cloned remote repository [" + url + "] to local project folder") tasks.AddFile(".coach/CREATEDFROM.md", `THIS PROJECT WAS CREATED FROM GIT`) return true }
func (tasks *InitTasks) Init_User_Run(logger log.Log, template string) bool { if template == "" { logger.Error("You have not provided a template name $/> coach init user {template}") return false } templatePath, ok := tasks.conf.Path("user-templates") if !ok { logger.Error("COACH has no user template path for the current user") return false } sourcePath := path.Join(templatePath, template) if _, err := os.Stat(sourcePath); err != nil { logger.Error("Invalid template path suggested for new project init : [" + template + "] expected path [" + sourcePath + "] => " + err.Error()) return false } logger.Message("Perfoming init operation from user template [" + template + "] : " + sourcePath) tasks.AddFileCopy(tasks.root, sourcePath) tasks.AddMessage("Copied coach template [" + template + "] to init project") tasks.AddFile(".coach/CREATEDFROM.md", `THIS PROJECT WAS CREATED FROM A User Template :`+template) return true }
// Init constructor for the client wrapper func (wrapper *FSouza_Wrapper) Init(logger log.Log, settings FSouza_ClientFactorySettings) bool { var client *docker.Client var err error logger.Debug(log.VERBOSITY_DEBUG_WOAH, "Docker client conf: ", settings) if strings.HasPrefix(settings.Host, "tcp://") { if _, err := os.Stat(settings.CertPath); err == nil { // TCP DOCKER CLIENT WITH CERTS client, err = docker.NewTLSClient( settings.Host, path.Join(settings.CertPath, "cert.pem"), path.Join(settings.CertPath, "key.pem"), path.Join(settings.CertPath, "ca.pem"), ) } else { // TCP DOCKER CLIENT WITHOUT CERTS client, err = docker.NewClient(settings.Host) } } else if strings.HasPrefix(settings.Host, "unix://") { // TCP DOCKER CLIENT WITHOUT CERTS client, err = docker.NewClient(settings.Host) } else { err = errors.New("Unknown client host :" + settings.Host) } if err == nil { logger.Debug(log.VERBOSITY_DEBUG_WOAH, "FSouza Docker client created:", client) wrapper.Client = client return true } else { logger.Error(err.Error()) return false } }
// Get tasks from remote YAML corresponding to a remote yaml file func (tasks *InitTasks) Init_Yaml_Run(logger log.Log, path string) bool { var yamlSourceBytes []byte var err error if strings.Contains(path, "://") { resp, err := http.Get(path) if err != nil { logger.Error("Could not retrieve remote yaml init instructions [" + path + "] : " + err.Error()) return false } defer resp.Body.Close() yamlSourceBytes, err = ioutil.ReadAll(resp.Body) } else { // read the config file yamlSourceBytes, err = ioutil.ReadFile(path) if err != nil { logger.Error("Could not read the local YAML file [" + path + "]: " + err.Error()) return false } if len(yamlSourceBytes) == 0 { logger.Error("Yaml file [" + path + "] was empty") return false } } tasks.AddMessage("Initializing using YAML Source [" + path + "] to local project folder") // get tasks from yaml tasks.AddTasksFromYaml(logger, yamlSourceBytes) // Add some message items tasks.AddFile(".coach/CREATEDFROM.md", "THIS PROJECT WAS CREATED A COACH YAML INSTALLER :"+path) return true }
func (task *InitTaskGitClone) RunTask(logger log.Log) bool { if task.root == "" || task.url == "" { logger.Error("EMPTY ROOT PASSED TO GIT: " + task.root) return false } destinationPath := task.path url := task.url if !task.MakeDir(logger, destinationPath, false) { return false } destinationAbsPath, ok := task.absolutePath(destinationPath, true) if !ok { logger.Warning("Invalid copy destination path: " + destinationPath) return false } cmd := exec.Command("git", "clone", "--progress", url, destinationAbsPath) cmd.Stderr = logger err := cmd.Start() if err != nil { logger.Error("Failed to clone the remote repository [" + url + "] => " + err.Error()) return false } err = cmd.Wait() if err != nil { logger.Error("Failed to clone the remote repository [" + url + "] => " + err.Error()) return false } logger.Message("Cloned remote repository [" + url + "] to local path " + destinationPath) return true }
func (client *FSouza_InstanceClient) Run(logger log.Log, persistant bool, cmd []string) bool { hushedLogger := logger.MakeChild("RunSupport") hushedLogger.Hush() instance := client.instance // Set up some additional settings for TTY commands if client.settings.Config.Tty == true { // set a default hostname to make a prettier prompt if client.settings.Config.Hostname == "" { client.settings.Config.Hostname = instance.Id() } // make sure that all tty runs have openstdin client.settings.Config.OpenStdin = true } client.settings.Config.AttachStdin = true client.settings.Config.AttachStdout = true client.settings.Config.AttachStderr = true // 1. get the container for the instance (create it if needed) hasContainer := client.HasContainer() if !hasContainer { logger.Info("Creating new disposable RUN container") if hasContainer = client.Create(hushedLogger, cmd, false); hasContainer { logger.Debug(log.VERBOSITY_DEBUG, "Created disposable run container") if !persistant { // 5. [DEFERED] remove the container (if not instructed to keep it) defer func(client *FSouza_InstanceClient, hushedLogger log.Log) { client.backend.Refresh(false, true) if client.IsRunning() { client.Stop(hushedLogger, true, 0) } client.Remove(hushedLogger, true) }(client, hushedLogger) } } else { logger.Error("Failed to create disposable run container") } } else { logger.Info("Run container already exists") } if hasContainer { // 3. start the container (set up a remove) logger.Info("Starting RUN container") ok := client.Start(hushedLogger, false) // 4. attach to the container if ok { logger.Info("Attaching to disposable RUN container") client.Attach(logger) return true } else { logger.Error("Could not start RUN container") return false } } else { logger.Error("Could not create RUN container") } return false }
func (operation *InitOperation) Run(logger log.Log) bool { logger.Info("running init operation") var err error var ok bool var targetPath, coachPath string targetPath = operation.root if targetPath == "" { targetPath, ok = operation.conf.Path("project-root") if !ok || targetPath == "" { targetPath, err = os.Getwd() if err != nil { logger.Error("No path suggested for new project init") return false } } } _, err = os.Stat(targetPath) if err != nil { logger.Error("Invalid path suggested for new project init : [" + targetPath + "] => " + err.Error()) return false } coachPath, _ = operation.conf.Paths.Path("coach-root") logger.Message("Preparing INIT operation [" + operation.handler + ":" + operation.source + "] in path : " + targetPath) _, err = os.Stat(coachPath) if !operation.force && err == nil { logger.Error("cannot create new project folder, as one already exists") return false } logger = logger.MakeChild(strings.ToUpper(operation.handler)) tasks := initialize.InitTasks{} tasks.Init(logger.MakeChild("TASKS"), operation.conf, targetPath) ok = true switch operation.handler { case "user": ok = tasks.Init_User_Run(logger, operation.source) case "demo": ok = tasks.Init_Demo_Run(logger, operation.source) case "git": ok = tasks.Init_Git_Run(logger, operation.source) case "yaml": ok = tasks.Init_Yaml_Run(logger, operation.source) case "default": ok = tasks.Init_Default_Run(logger, operation.source) default: logger.Error("Unknown init handler " + operation.handler) ok = false } if ok { logger.Info("Running init tasks") tasks.RunTasks(logger) return true } else { logger.Warning("No init tasks were defined.") return false } }
func (task *InitTaskError) RunTask(logger log.Log) bool { logger.Error(task.error) return true }
func (client *FSouza_InstanceClient) Create(logger log.Log, overrideCmd []string, force bool) bool { instance := client.instance if !force && client.HasContainer() { logger.Info("[" + instance.MachineName() + "]: Skipping node instance, which already has a container") return false } /** * Transform node data, into a format that can be used * for the actual Docker call. This involves transforming * the node keys into docker container ids, for things like * the name, Links, VolumesFrom etc */ name := instance.MachineName() Config := client.settings.Config Host := client.settings.Host image, tag := client.GetImageName() if tag != "" && tag != "latest" { image += ":" + tag } Config.Image = image if len(overrideCmd) > 0 { Config.Cmd = overrideCmd } // ask the docker client to create a container for this instance options := docker.CreateContainerOptions{ Name: name, Config: &Config, HostConfig: &Host, } container, err := client.backend.CreateContainer(options) client.backend.Refresh(false, true) if err != nil { logger.Debug(log.VERBOSITY_DEBUG, "CREATE FAIL CONTAINERS: ", err) /** * There is a weird bug with the library, where sometimes it * reports a missing image error, and yet it still creates the * container. It is not clear if this failure occurs in the * remote API, or in the dockerclient library. */ client.backend.Refresh(false, true) if err.Error() == "no such image" && client.HasContainer() { logger.Message("Created instance container [" + name + " FROM " + Config.Image + "] => " + container.ID[:12]) logger.Warning("Docker created the container, but reported an error due to a 'missing image'. This is a known bug, that can be ignored") return true } logger.Error("Failed to create instance container [" + name + " FROM " + Config.Image + "] => " + err.Error()) return false } else { client.backend.Refresh(false, true) logger.Message("Created instance container [" + name + "] => " + container.ID[:12]) return true } }