func (cmd *DestroyCommand) Execute(args []string) error { Logger.Info("Destroying... using information from dir:", cmd.Options.Dir) // Check Config and Buildfiles configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile) dc.ShowInfo() // Register channel so we can watch for events as they happen eventsChannel := make(ApiChannel) go watchForEventsOn(eventsChannel) dc.RegisterChannel(eventsChannel) fqImageName := cmd.App.AppState.BuildFile.ImageName + ":" + cmd.App.AppState.BuildFile.Tag if _, err := dc.DeleteContainer(fqImageName); err != nil { Logger.Error("Could not delete container,", err) return err } if _, err := dc.DeleteImage(fqImageName); err != nil { Logger.Error("Could not delete image,", err) return err } msg := "Destroyed all known containers and images associated with: " + fqImageName Logger.Console(msg) // Nothing to do return nil }
func (cmd *GroundCommand) Execute(args []string) error { Logger.Info("Grounding Tests... in dir:", cmd.Options.Dir) // Check Config and Buildfiles configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile) dc.ShowInfo() if err := dc.createTestTemplates(*cmd.Options); err != nil { return err } // Register channel so we can watch for events as they happen eventsChannel := make(ApiChannel) go watchForEventsOn(eventsChannel) dc.RegisterChannel(eventsChannel) fqImageName := cmd.App.AppState.BuildFile.ImageName + ":" + cmd.App.AppState.BuildFile.Tag if running, err := dc.ListContainers(fqImageName); err != nil { Logger.Error("Error while trying to get a list of containers for ", fqImageName) return err } else { for _, container := range running { dc.StopContainer(container) } } Logger.Console("Grounded.") return nil }
func (fc *FlightControls) CheckConfigs(app *TestFlight, options *CommandOptions) (*ConfigFile, *BuildFile, error) { // Setup Logging // TODO: Replace with only Load once app.Init() is gone Logger.Load(len(options.Verbose)) Logger.Setup() // Set vars if options.Dir == "" { options.Dir = "./" // set to local working dir as default } Logger.Info("Using directory:", options.Dir) // Prereqs app.SetDir(options.Dir) configFile, err := ReadConfigFile(options.Configfile, options.Dir) if err != nil { return nil, nil, err } app.SetConfigFile(configFile) Logger.Info("Using configfile:", configFile.Location) // Get the buildfile // TODO: as more Control funcs get created refactor this below buildFile, err := fc.CheckBuild(options.Dir) if err != nil { return nil, nil, err } app.SetBuildFile(buildFile) Logger.Info("Using buildfile:", buildFile.Location) Logger.Debug("buildfile - contents:", *buildFile) if err := fc.CheckRequiredFiles(options.Dir, options.SingleFileMode, buildFile); err != nil { return nil, nil, err } return configFile, buildFile, nil }
func (cmd *CheckCommand) Execute(args []string) error { Logger.Info("Running Pre-Flight Check... in dir:", cmd.Options.Dir) // Check Config and Buildfiles _, b, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { Logger.Error("Could not verify config files. " + err.Error()) } else { generateRequiredFilesFrom(b) Logger.Console("All checks passed! Files found!") } return nil }
func (api *DockerApi) ShowImages() error { images, _ := api.client.ListImages(true) if len(images) <= 0 { Logger.Info("No docker images found.") return nil } for _, img := range images { Logger.Info("ID: ", img.ID) Logger.Info("RepoTags: ", img.RepoTags) Logger.Info("Created: ", img.Created) Logger.Info("Size: ", img.Size) Logger.Info("VirtualSize: ", img.VirtualSize) Logger.Info("ParentId: ", img.ParentId) Logger.Info("Repository: ", img.Repository) } return nil }
func (api *DockerApi) StartContainer(id string) (*string, error) { endpoint := api.configFile.DockerEndpoint postBody := DockerHostConfig{} url := strings.Join( []string{ endpoint, "containers", id, "start", }, "/", ) Logger.Trace("StartContainer() - Api call to:", url) var jsonResult string bytesReader, _ := postBody.Bytes() resp, _ := http.Post(url, "text/json", bytes.NewReader(bytesReader)) defer resp.Body.Close() if _, err := ioutil.ReadAll(resp.Body); err != nil { Logger.Error("Could not contact docker endpoint:", endpoint) return nil, err } else { switch resp.StatusCode { case 204: Logger.Info("Started container:", id) return &jsonResult, nil case 404: Logger.Warn("No such container") case 500: Logger.Error("Error while trying to communicate to docker endpoint:", endpoint) } msg := "Unexpected response code: " + string(resp.StatusCode) Logger.Error(msg) return nil, errors.New(msg) } }
func (cmd *TemplateCommand) Execute(args []string) error { Logger.Info("Creating Templates... in dir:", cmd.Options.Dir) _, _, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } cmd.App.AppState.Meta.Dir = cmd.Options.Dir dc := NewDockerApi(cmd.App.AppState.Meta, cmd.App.AppState.ConfigFile, cmd.App.AppState.BuildFile) err = dc.createTestTemplates(*cmd.Options) if err != nil { Logger.Console("Could not create Test-Flight templates.") return err } Logger.Console("Test-Flight templates created.") return nil }
func (api *DockerApi) StopContainer(containerId string) (string, error) { endpoint := api.configFile.DockerEndpoint url := strings.Join( []string{ endpoint, "containers", containerId, "stop", }, "/", ) Logger.Trace("StopContainer() - Api call to:", url) resp, _ := http.Post(url, "text/json", nil) defer resp.Body.Close() if _, err := ioutil.ReadAll(resp.Body); err != nil { Logger.Error("Could not contact docker endpoint:", endpoint) return "", err } else { switch resp.StatusCode { case 204: Logger.Info("Stopped container:", containerId) return containerId, nil case 404: msg := "No such container" Logger.Warn(msg) return "", nil case 500: msg := "Error while trying to communicate to docker endpoint: " + endpoint Logger.Error(msg) return "", nil } msg := "Unexpected response code: " + string(resp.StatusCode) Logger.Error(msg) return "", errors.New(msg) } }
func (cmd *ImagesCommand) Execute(args []string) error { Logger.Info("Listing images...") // Check Config and Buildfiles configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } // Api interaction here dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile) dc.ShowInfo() fqImageName := buildFile.ImageName + ":" + buildFile.Tag if imageDetails, err := dc.GetImageDetails(fqImageName); err != nil { return err } else if imageDetails != nil { imageDetails.Print() } return nil }
// == Build Command == // Should build a docker image func (cmd *BuildCommand) Execute(args []string) error { // Set vars Logger.Info("Building... using information from dir:", cmd.Options.Dir) // Check Config and Buildfiles configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } // Api interaction here dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile) dc.ShowInfo() // Generate Templates // TODO: fails here with filemode if err := dc.createTestTemplates(*cmd.Options); err != nil { return err } // Register channel so we can watch for events as they happen eventsChannel := make(ApiChannel) go watchForEventsOn(eventsChannel) dc.RegisterChannel(eventsChannel) fqImageName := buildFile.ImageName + ":" + buildFile.Tag image, err := dc.CreateDockerImage(fqImageName, cmd.Options) if err != nil { return err } msg := "Created Docker Image: " + image Logger.Console(msg) return nil }
func (cmd *LaunchCommand) Execute(args []string) error { Logger.Info("Launching Tests... in dir:", cmd.Options.Dir) Logger.Debug("Force:", cmd.Options.Force) var wg sync.WaitGroup // used for channels // Check Config and Buildfiles configFile, buildFile, err := cmd.Controls.CheckConfigs(cmd.App, cmd.Options) if err != nil { return err } dc := NewDockerApi(cmd.App.AppState.Meta, configFile, buildFile) dc.ShowInfo() if err := dc.createTestTemplates(*cmd.Options); err != nil { return err } Logger.Trace("Created test-flight templates.") // Register channel so we can watch for events as they happen eventsChannel := make(ApiChannel) go watchForEventsOn(eventsChannel) dc.RegisterChannel(eventsChannel) fqImageName := cmd.App.AppState.BuildFile.ImageName + ":" + buildFile.Tag getImageId := func() (string, error) { if image, err := dc.GetImageDetails(fqImageName); err != nil { return "", err } else { // response from endpoint (but could be 404) if image == nil { // doesn't exist if newImage, err := dc.CreateDockerImage(fqImageName, cmd.Options); err != nil { return "", err } else { return newImage, nil } } else { // exists if cmd.Options.Force { // recreate anyway if newImage, err := dc.CreateDockerImage(fqImageName, cmd.Options); err != nil { return "", err } else { return newImage, nil } } else { // warn user Logger.Warn("Will be starting a container from an image which already exists.") return image.Id, nil } } return image.Id, nil } } createAndStartContainerFrom := func(imageId string) error { if container, err := dc.CreateContainer(imageId); err != nil { return err } else { Logger.Trace("Docker Container to start:", container.Id) if _, err := dc.StartContainer(container.Id); err != nil { return err } else { wg.Add(1) containerChannel := dc.Attach(container.Id) go watchContainerOn(containerChannel, &wg) wg.Wait() return nil } } } if imageId, err := getImageId(); err != nil { return err } else { msg := "Launching docker container: " + imageId Logger.Console(msg) return createAndStartContainerFrom(imageId) } return nil }
func (api *DockerApi) CreateContainer(fqImageName string) (*ApiPostResponse, error) { if _, err := api.DeleteContainer(fqImageName); err != nil { msg := "Error occured while trying to delete a container. " + " This attempt failed for the following reason:" + err.Error() Logger.Error(msg, err) return nil, err } endpoint := api.configFile.DockerEndpoint postBody := ApiPostRequest{ Image: fqImageName, OpenStdin: true, AttachStdin: false, AttachStdout: true, AttachStderr: true, Cmd: api.buildFile.LaunchCmd, } url := FilePath(endpoint, "containers", "create") Logger.Trace("CreateContainer() - Api call to:", url) jsonResult := ApiPostResponse{} bytesReader, _ := postBody.Bytes() resp, _ := http.Post(url, "application/json", bytes.NewReader(bytesReader)) defer resp.Body.Close() if body, err := ioutil.ReadAll(resp.Body); err != nil { Logger.Error("Could not contact docker endpoint:", endpoint) return nil, err } else { msg := "Unexpected response code: " + string(resp.StatusCode) switch resp.StatusCode { case 201: if err := json.Unmarshal(body, &jsonResult); err != nil { Logger.Error(err) return nil, err } Logger.Info("Created container:", fqImageName) return &jsonResult, nil case 404: msg = "No such container" Logger.Warn(msg) case 406: msg = "Impossible to attach (container not running)" Logger.Warn(msg) case 500: // noticed that when docker endpoint fails, it fails with just // status 500, no message back. Logs do show error though somewhat // slim details. i.e No command specified where command is really CMD :-( msg = "Server and/or API error while trying to communicate with docker " + "endpoint: " + endpoint + ". Could be malformed Dockerfile (template). " + "Check your remote docker endpoint logs." Logger.Error(msg) } return nil, errors.New(msg) } }
// CreateDockerImage creates a docker image by first creating the Dockerfile // in memory and then tars it, prior to sending it along to the docker // endpoint. // Note: once the image creation starts the only way to know if it succeeded // is to query for it again. func (api *DockerApi) CreateDockerImage(fqImageName string, options *CommandOptions) (string, error) { Logger.Debug("Creating Docker image by attempting to build Dockerfile: " + fqImageName) tmplName := "Dockerfile" // file ext of `.tmpl` is implicit, see below var requiredDockerFile = RequiredFile{ Name: "Test-Flight Dockerfile", FileName: tmplName, FileType: "f", } modeDir := func() string { if options.SingleFileMode { return "filemode" } else { return "dirmode" } }() dockerfileBuffer := bytes.NewBuffer(nil) tarbuf := bytes.NewBuffer(nil) // Dockerfile mem dockerfile := bufio.NewWriter(dockerfileBuffer) // Or create file templateOutputDir := getTemplateDir(api.configFile) Logger.Trace(templateOutputDir, requiredDockerFile.FileName) fileToCreate := FilePath(templateOutputDir.FileName, requiredDockerFile.FileName) Logger.Debug("Trying to build:", fileToCreate) dockerfileOut, err := os.Create(fileToCreate) if err != nil { Logger.Error("Could not write Dockerfile", err) return "", err } defer dockerfileOut.Close() // The directory where the templates used to create inventory and playbook templates := func() string { // could have been simpler but I want to log path at this level var templatePath string if api.configFile.UseSystemDockerTemplates { templatePath = FilePath(api.configFile.TestFlightAssets, "templates", "system") } else { templatePath = FilePath(api.configFile.TestFlightAssets, "templates", "user") } Logger.Debug("Using template dir:", templatePath) return templatePath }() // This is used below in `ExecuteTemplate()` templateInputDir := FilePath(templates, modeDir) pattern := filepath.Join(templateInputDir, requiredDockerFile.FileName+"*.tmpl") tmpl := template.Must(template.ParseGlob(pattern)) // In Mem if err := tmpl.ExecuteTemplate(dockerfile, requiredDockerFile.FileName, *api.getTemplateVar()); err != nil { Logger.Error("template execution: %s", err) return "", err } // File if err := tmpl.ExecuteTemplate(dockerfileOut, requiredDockerFile.FileName, *api.getTemplateVar()); err != nil { Logger.Error("template execution: %s", err) return "", err } // For in-mem flush buffer here dockerfile.Flush() // Logger.Trace("Dockerfile buffer len", dockerfileBuffer.Len()) Logger.Trace("Dockerfile:", dockerfileBuffer.String()) // Logger.Info("Created Dockerfile: " + fqImageName) currTime := time.Now() // Add Dockerfile to archive, break out tr := tar.NewWriter(tarbuf) tr.WriteHeader(&tar.Header{ Name: "Dockerfile", Size: int64(dockerfileBuffer.Len()), ModTime: currTime, AccessTime: currTime, ChangeTime: currTime, }) tr.Write(dockerfileBuffer.Bytes()) // Add Context to archive // tar test directory TarDirectory(tr, api.meta.Dir, api.buildFile.Ignore) tr.Close() var wg sync.WaitGroup // used for channels wg.Add(1) Logger.Trace("Building image...") buildChannel := make(ContainerChannel) // go watchContainerOn(buildChannel, &wg) api.BuildImage(tarbuf, fqImageName, &buildChannel, &wg) // go CaptureUserCancel(&buildChannel) select { case value := <-buildChannel: switch value { case "ok": failMsg := "Docker Image [" + fqImageName + "] failed to build." // We got an ok but don't know if it is legit. Possible to get no error // but image will be nil (successful api call but image doesn't exist) if image, err := api.GetImageDetails(fqImageName); err != nil || image == nil { return "", errors.New(failMsg) } else { Logger.Info("Successfully built Docker image: " + fqImageName) return image.Id, nil } case "canceled": msg := "User Canceled docker creation." Logger.Warn(msg, "User must manually stop last running container.") return fqImageName, errors.New(msg) } } wg.Wait() Logger.Info("Successfully built Docker image: " + fqImageName) return fqImageName, nil }
// Returns container name deleted, empty string if none, and an // error (if any) func (api *DockerApi) DeleteContainer(name string) ([]DeletedContainer, error) { var deletedContainers []DeletedContainer var returnError error endpoint := api.configFile.DockerEndpoint delete := func(id string) (*string, error) { baseUrl := strings.Join( []string{ endpoint, "containers", id, }, "/", ) params := strings.Join( []string{ "v=true", "force=true", }, "&", ) url := baseUrl + "?" + params Logger.Trace("DeleteContainer() Api call:", url) req, _ := http.NewRequest("DELETE", url, nil) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() if _, err := ioutil.ReadAll(resp.Body); err != nil { Logger.Error("Could not contact docker endpoint:", endpoint) return nil, err } else { switch resp.StatusCode { case 204: Logger.Info("Container Deleted:", name, ", with ID:", id) return &name, nil case 400: msg := "Bad Api param supplied while trying to delete a container, notify developers." Logger.Error(msg) return nil, errors.New(msg) case 404: Logger.Warn("Container not found, nothing to delete.") return nil, nil case 406: msg := "Container:" + name + " - (" + id + "), is running, cannot delete." Logger.Warn(msg) return nil, errors.New(msg) case 500: msg := "Error while trying to communicate to docker endpoint:" + endpoint // Logger.Error(msg) return nil, errors.New(msg) } statusCode := strconv.Itoa(resp.StatusCode) msg := "API out of sync, contact developers! Status Code:" + statusCode return nil, errors.New(msg) } } if images, err := api.ListContainers(name); err != nil { Logger.Error("Could not get image details:", err) return nil, err } else if images != nil { Logger.Info("Found", len(images), "to delete...") for _, container := range images { Logger.Debug("Trying to delete", container) if deleted, err := delete(container); err != nil { returnError = err msg := "Could not delete container: " + err.Error() Logger.Error(msg) } else if *deleted != "" { deletedContainers = append(deletedContainers, DeletedContainer{*deleted, container}) } } return deletedContainers, returnError } else { Logger.Debug("No containers found for", name) return deletedContainers, returnError } }