// Run Parity - the main application entrypoint func (p *Parity) Run() { log.Banner(banner) log.Debug("Loading plugins...") p.LoadPlugins() // Execute all plugins in parallel? // TODO: Initial Sync may need to be blocking so that Run // can work? for _, pl := range p.SyncPlugins { p.runAsync(pl.Sync) } // Run all Runners for _, pl := range p.RunPlugins { p.runAsync(pl.Run) } // Interrupt handler sigChan := make(chan os.Signal, 1) p.errorChan = make(chan error) signal.Notify(sigChan, os.Interrupt, os.Kill) select { case e := <-p.errorChan: log.Error(e.Error()) case <-sigChan: log.Debug("Received interrupt, shutting down.") p.Teardown() } }
// Shell creates an interactive Docker session to the specified service // starting it if not currently running func (c *DockerCompose) Shell(config parity.ShellConfig) (err error) { log.Stage("Interactive Shell") log.Debug("Compose - starting docker compose services") mergedConfig := *parity.DEFAULT_INTERACTIVE_SHELL_OPTIONS mergo.MergeWithOverwrite(&mergedConfig, &config) // Check if services running // TODO: if running, attach to running container // if NOT running, start services and attach if c.project != nil { log.Step("Starting compose services") injectDisplayEnvironmentVariables(c.project) } container := fmt.Sprintf("parity-%s_%s_1", c.pluginConfig.ProjectNameSafe, mergedConfig.Service) client := utils.DockerClient() createExecOptions := dockerclient.CreateExecOptions{ AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: mergedConfig.Command, User: mergedConfig.User, Container: container, } startExecOptions := dockerclient.StartExecOptions{ Detach: false, Tty: true, InputStream: os.Stdin, OutputStream: os.Stdout, ErrorStream: os.Stderr, RawTerminal: true, } log.Step("Attaching to container '%s'", container) if id, err := client.CreateExec(createExecOptions); err == nil { client.StartExec(id.ID, startExecOptions) } else { log.Error("error: %v", err.Error()) } log.Debug("Docker Compose Run() finished") return err }
// XServerProxy creates a TCP proxy on port 6000 to a the Unix // socket that XQuartz is listening on. // // NOTE: this function does not start/install the XQuartz service func XServerProxy(port int) { if runtime.GOOS != "darwin" { log.Debug("Not running an OSX environment, skip run X Server Proxy") return } l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { log.Fatal(err) } defer l.Close() // Send all traffic back to unix $DISPLAY socket on a running XQuartz server addr, err := net.ResolveUnixAddr("unix", os.Getenv("DISPLAY")) if err != nil { log.Error("Error: ", err.Error()) } log.Info("X Service Proxy available on all network interfaces on port %d", port) if host, err := utils.DockerVMHost(); err == nil { log.Info("Parity has detected your Docker environment and recommends running 'export DISPLAY=%s:0' in your container to forward the X display", host) } for { xServerClient, err := net.DialUnix("unix", nil, addr) if err != nil { log.Error("Error: ", err.Error()) } defer xServerClient.Close() conn, err := l.Accept() log.Debug("X Service Proxy connected to client on: %s (remote: %s)", conn.LocalAddr(), conn.RemoteAddr()) if err != nil { log.Fatal(err) } go func(c net.Conn, s *net.UnixConn) { buf := make([]byte, 8092) io.CopyBuffer(s, c, buf) s.CloseWrite() }(conn, xServerClient) go func(c net.Conn, s *net.UnixConn) { buf := make([]byte, 8092) io.CopyBuffer(c, s, buf) c.Close() }(conn, xServerClient) } }
// Teardown stops any running projects before Parity exits func (c *DockerCompose) Teardown() error { log.Debug("Tearing down 'Docker Machine' 'Run' plugin") if c.project != nil { c.project.Down() } return nil }
// Configure sets up this plugin with initial state func (c *DockerCompose) Configure(pc *parity.PluginConfig) { log.Debug("Configuring 'Docker Machine' 'Run\\Build\\Shell' plugin") c.pluginConfig = pc var err error if c.project, err = c.GetProject(); err != nil { log.Fatalf("Unable to create Compose Project: %s", err.Error()) } }
// Attach attaches to the specified service in the running container func (c *DockerCompose) Attach(config parity.ShellConfig) (err error) { log.Stage("Interactive Shell") log.Debug("Compose - starting docker compose services") mergedConfig := *parity.DEFAULT_INTERACTIVE_SHELL_OPTIONS mergo.MergeWithOverwrite(&mergedConfig, &config) client := utils.DockerClient() container := fmt.Sprintf("parity-%s_%s_1", c.pluginConfig.ProjectNameSafe, mergedConfig.Service) opts := dockerclient.AttachToContainerOptions{ Stdin: true, Stdout: true, Stderr: true, InputStream: os.Stdin, OutputStream: os.Stdout, ErrorStream: os.Stderr, RawTerminal: true, Container: container, Stream: true, Logs: true, } if c.project.Configs[config.Service] == nil { return fmt.Errorf("Service %s does not exist", config.Service) } log.Step("Attaching to container '%s'", container) if err := client.AttachToContainer(opts); err == nil { err = c.project.Up(config.Service) if err != nil { log.Error("error: %s", err.Error()) return err } } else { return err } _, err = client.WaitContainer(container) log.Error("wc error: %s", err.Error()) log.Debug("Docker Compose Run() finished") return err }
// Build runs all builders on the project, e.g. Docker build func (p *Parity) Build() error { log.Debug("Loading plugins...") p.LoadPlugins() for _, pl := range p.BuildPlugins { if err := pl.Build(); err != nil { return err } } return nil }
// Run the Docker Compose Run Plugin // // Detects docker-compose.yml files, builds and runs. func (c *DockerCompose) Run() (err error) { log.Stage("Run Docker") log.Step("Building base image") c.Build() log.Step("Building compose project") if c.project != nil { log.Debug("Compose - starting docker compose services") go c.runXServerProxy() c.project.Delete() c.project.Build() c.project.Up() } log.Debug("Docker Compose Run() finished") return err }
// generateContainerVersion creates a unique hash for a given Dockerfile. // It uses the contents of the Dockerfile and any package lock file (package.json, Gemfile etc.) // Replaces this shell: `echo $(md5Files $(find -L $1 -maxdepth 1 | egrep "(Gemfile.lock|package\.json|Dockerfile)"))` func (c *DockerCompose) generateContainerVersion(dirName string, dockerfile string) string { log.Debug("Looking for %s and related package files in: %s", dockerfile, dirName) dir, _ := os.Open(dirName) files, _ := dir.Readdir(-1) var data []byte regex, _ := regexp.CompilePOSIX(fmt.Sprintf("(Gemfile.lock|package\\.json|^%s$)", dockerfile)) for _, f := range files { if regex.MatchString(f.Name()) { log.Debug("Found file: %s", f.Name()) if d, err := ioutil.ReadFile(filepath.Join(dirName, f.Name())); err == nil { data = append(data, d...) } } } if len(data) == 0 { return "" } return fmt.Sprintf("%x", md5.Sum(data)) }
// LoadPlugins loads all plugins referenced in the parity.yml file // from those registered at runtime func (p *Parity) LoadPlugins() { log.Debug("loading plugins") var err error var confLoader *plugo.ConfigLoader c := &config.RootConfig{} if p.config.ConfigFile != "" { confLoader = &plugo.ConfigLoader{} err = confLoader.LoadFromFile(p.config.ConfigFile, &c) if err != nil { log.Fatalf("Unable to read configuration file: %s", err.Error()) } } else { log.Fatalf("No configuration file provided. Please create a 'parity.yml' file.") } log.SetLevel(log.LogLevel(c.LogLevel)) // Load all plugins p.pluginConfig = &PluginConfig{Ui: p.config.Ui} // Set project name p.pluginConfig.ProjectName = c.Name p.pluginConfig.ProjectNameSafe = strings.Replace(strings.ToLower(c.Name), " ", "", -1) // Sync plugins p.SyncPlugins = make([]Sync, len(c.Sync)) syncPlugins := plugo.LoadPluginsWithConfig(confLoader, c.Sync) for i, pl := range syncPlugins { log.Debug("Loading Sync Plugin\t" + log.Colorize(log.YELLOW, c.Sync[i].Name)) p.SyncPlugins[i] = pl.(Sync) p.SyncPlugins[i].Configure(p.pluginConfig) p.plugins = append(p.plugins, p.SyncPlugins[i]) } // Run plugins p.RunPlugins = make([]Run, len(c.Run)) runPlugins := plugo.LoadPluginsWithConfig(confLoader, c.Run) for i, pl := range runPlugins { log.Debug("Loading Run Plugin\t" + log.Colorize(log.YELLOW, c.Run[i].Name)) p.RunPlugins[i] = pl.(Run) p.RunPlugins[i].Configure(p.pluginConfig) p.plugins = append(p.plugins, p.RunPlugins[i]) } // Build plugins p.BuildPlugins = make([]Builder, len(c.Build)) buildPlugins := plugo.LoadPluginsWithConfig(confLoader, c.Build) for i, pl := range buildPlugins { log.Debug("Loading Build Plugin\t" + log.Colorize(log.YELLOW, c.Build[i].Name)) p.BuildPlugins[i] = pl.(Builder) p.BuildPlugins[i].Configure(p.pluginConfig) p.plugins = append(p.plugins, p.BuildPlugins[i]) } // Shell plugins p.ShellPlugins = make([]Shell, len(c.Shell)) shellPlugins := plugo.LoadPluginsWithConfig(confLoader, c.Shell) for i, pl := range shellPlugins { log.Debug("Loading Shell Plugin\t" + log.Colorize(log.YELLOW, c.Shell[i].Name)) p.ShellPlugins[i] = pl.(Shell) p.ShellPlugins[i].Configure(p.pluginConfig) p.plugins = append(p.plugins, p.ShellPlugins[i]) } }
func (p *Mirror) Sync() error { log.Stage("Synchronising source/dest folders") pkiMgr, err := pki.New() pkiMgr.Config.Insecure = true if err != nil { p.pluginConfig.Ui.Error(fmt.Sprintf("Unable to setup public key infrastructure: %s", err.Error())) } Config, err := pkiMgr.GetClientTLSConfig() if err != nil { p.pluginConfig.Ui.Error(fmt.Sprintf("%v", err)) } // Removing shared folders if utils.CheckSharedFolders() { utils.UnmountSharedFolders() } // Read volumes for share/watching var volumes []string // Exclude non-local volumes (e.g. might want to mount a dir on the VM guest) for _, v := range utils.ReadComposeVolumes() { if _, err := os.Stat(v); err == nil { volumes = append(volumes, v) } } // Add PWD if nothing in compose dir, _ := os.Getwd() if len(volumes) == 0 { volumes = append(volumes, mutils.LinuxPath(dir)) } pki.MirrorConfig.ClientTlsConfig = Config excludes := make([]regexp.Regexp, len(p.Exclude)) for i, v := range p.Exclude { r, err := regexp.CompilePOSIX(v) if err == nil { excludes[i] = *r } else { log.Error("Error parsing Regex:", err.Error()) } } options := &sync.Options{Exclude: excludes, Verbose: p.Verbose} // Sync and watch all volumes for _, v := range volumes { log.Step("Syncing contents of '%s' -> '%s'", v, fmt.Sprintf("mirror://%s%s", utils.MirrorHost(), v)) err = sync.Sync(v, fmt.Sprintf("mirror://%s%s", utils.MirrorHost(), v), options) if err != nil { log.Error("Error during initial file sync: %v", err) } log.Step("Monitoring '%s' for changes", v) go sync.Watch(v, fmt.Sprintf("mirror://%s%s", utils.MirrorHost(), v), options) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, os.Kill) <-sigChan log.Debug("Interrupt received, shutting down") return nil }
func (m *Mirror) Teardown() error { log.Debug("Tearing down mirror sync plugin") return nil }
func (m *Mirror) Configure(c *parity.PluginConfig) { log.Debug("Configuring mirror sync plugin") m.pluginConfig = c }
func SetupParityProject(config *Config) error { log.Stage("Setup Parity Project") // 1. Merge Config with Defaults -> need to create Base, Ci and Production image names if err := expandAndValidateConfig(config); err != nil { return err } var parityTemplate []string // Scan template index file if config.templateIndex != "" { log.Step("Downloading template index: %s", config.templateIndex) resp, err := http.Get(config.templateIndex) if err != nil { return err } scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { parityTemplate = append(parityTemplate, scanner.Text()) } } // Make .parity dir if not exists if _, err := os.Stat(".parity"); err == nil { os.Mkdir(".parity", 0755) } errors := make(chan error) done := make(chan bool) defer close(done) // Download and install templates in parallel go func() { wg := sync.WaitGroup{} wg.Add(len(parityTemplate)) dir, _ := os.Getwd() for _, f := range parityTemplate { go func(f string, errorChan chan error) { // 1. Check presence of local File - overwrite? TODO: config targetFile := filepath.Join(dir, f) if _, err := os.Stat(targetFile); err == nil { if !config.Overwrite { errorChan <- fmt.Errorf("File '%s' already exists. Please specify --force to overwrite files.", targetFile) wg.Done() return } } // 2. Download resources // 2a. TODO: Check/store local cache? // 2b. Pull from remote url := fmt.Sprintf(`%s/%s`, config.TemplateSourceURL, f) log.Step("Downloading template file: %s", url) resp, err := http.Get(url) var file *os.File if err == nil { // 3. Interpolate template with Setup data if file, err = tempFile(resp.Body, config); err != nil { errorChan <- err wg.Done() return } } else { log.Error("Error downloading template: %s", err.Error()) errorChan <- fmt.Errorf("Error downloading template: %s", err.Error()) wg.Done() return } // 4. Move to local folder. log.Debug("Moving %s -> %s", file.Name(), targetFile) log.Debug("Ensuring parent dir exists: %s", filepath.Dir(targetFile)) os.MkdirAll(filepath.Dir(targetFile), 0755) os.Rename(file.Name(), targetFile) wg.Done() }(f, errors) } wg.Wait() done <- true }() select { case e := <-errors: log.Error(e.Error()) return e case <-done: log.Debug("Finished installing template") } log.Stage("Setup Parity Project : Complete") return nil }