Пример #1
0
func getConfig(file string) (*ConfigFile, error) {
	Logger.Debug("Checking for config file in location:", file)

	var unmarshal = func(jsonBlob []byte) (*ConfigFile, error) {
		configFile := NewConfigFile()

		if err := json.Unmarshal(jsonBlob, &configFile); err != nil {
			msg := "Can't find or having trouble reading " + file +
				". Please create the file or address syntax issues. System error:" + err.Error()
			// return nil, ReadFileError.New(msg)
			return nil, errors.New(msg)
		} else {
			// Augment configfile with it's location
			configFile.Location = file
			configFile.LocationDir = file[:strings.LastIndex(file, "/")]

			return configFile, nil
		}
	}

	if jsonBlob, err := ioutil.ReadFile(file); err != nil {
		return nil, errors.New("Can't find " + file)
	} else {
		if jsonBlob == nil {
			msg := file + " not found."
			return nil, errors.New(msg)
		} else {
			return unmarshal(jsonBlob)
		}
	}
}
Пример #2
0
func (api *DockerApi) GetImageDetails(fqImageName string) (*ApiDockerImage, error) {
	url := strings.Join(
		[]string{
			api.configFile.DockerEndpoint,
			"images",
			fqImageName,
			"json",
		},
		"/",
	)

	// For testing, also need to `import "encoding/json"`
	// result := map[string]json.RawMessage{}
	result := ApiDockerImage{}

	Logger.Trace("GetImageDetails Api call to:", url)
	if resp, err := napping.Get(url, nil, &result, nil); err != nil {
		Logger.Error("Error while getting Image information from docker,", err)
		return nil, err
	} else {
		switch resp.Status() {
		case 200:
			Logger.Trace(result)
			return &result, nil
		case 404:
			Logger.Debug("Image not found")
			return nil, nil
		}

		return nil, nil
	}
}
Пример #3
0
// ListContainers that are running
func (api *DockerApi) ListContainers(imageName string) ([]string, error) {
	endpoint := api.configFile.DockerEndpoint
	baseUrl := strings.Join(
		[]string{
			endpoint,
			"containers",
			"json",
		},
		"/",
	)
	params := strings.Join(
		[]string{
			"all=true",
		},
		"&",
	)
	url := baseUrl + "?" + params
	Logger.Trace("ListContainers Api call:", url)

	resp, _ := http.Get(url)
	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 {
		switch resp.StatusCode {
		case 200:
			var jsonResult []ApiContainer
			if err := json.Unmarshal(body, &jsonResult); err != nil {
				Logger.Error("Error while trying to marshall result, body:", jsonResult, " - Error:", err)
				return nil, err
			} else {
				var ids []string
				for i := range jsonResult {
					if jsonResult[i].Image == imageName {
						ids = append(ids, jsonResult[i].Id)
					}
				}

				Logger.Debug("Containers found for", imageName, ids)
				return ids, nil
			}
		case 404:
			Logger.Warn("Bad param supplied to API")
		case 500:
			Logger.Error("Error while trying to communicate to docker endpoint:", endpoint)
		}
	}

	return nil, nil
}
Пример #4
0
// tries to find config file in user home, then if it cannot find one there
// will try to find a config file in the local running directory
func findConfig(dir string) (*ConfigFile, error) {
	configFileName := Constants().configFileName
	configFile := NewConfigFile()
	logConfigFile := func(configFile *ConfigFile) {
		Logger.Debug("Found config file.")
		Logger.Trace("Config file contents:", *configFile)
	}

	// try dir specified
	localConfigPath := FilePath(dir, configFileName)
	configFile, err := getConfig(localConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	// try running directory next
	pwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	Logger.Debug("Checking for config file in local pwd: " + pwd + "/" + configFileName)
	pwdConfigPath := FilePath(pwd, ".test-flight", configFileName)
	configFile, err = getConfig(pwdConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	// try home
	usr, err := user.Current() // to get user home, get user first
	if err != nil {
		Logger.Error("Can't read user home.")
		// return nil, ReadFileError.New("Can't read user home.")
		return nil, errors.New("Can't read user home.")
	}

	homeConfigPath := FilePath(usr.HomeDir, ".test-flight", configFileName)
	Logger.Debug("Checking for config file in user HOME: ", homeConfigPath)

	configFile, err = getConfig(homeConfigPath)
	if err != nil {
		msg := "Config: " + localConfigPath + " may not exist or cannot be read. " + err.Error()
		Logger.Debug(msg)
	} else {
		logConfigFile(configFile)
		return configFile, nil
	}

	return nil, errors.New("Cannot find config file, Please supply the config file.")
}
Пример #5
0
// listens for ctrl-c from the user
func CaptureUserCancel(containerChannel *ContainerChannel, wg *sync.WaitGroup) {
	syschan := make(chan os.Signal, 1)
	signal.Notify(syschan, os.Interrupt)
	signal.Notify(syschan, syscall.SIGTERM)
	go func() {
		<-syschan
		Logger.Debug("Canceling user operation...")
		if wg != nil {
			*containerChannel <- "canceled"
			wg.Done()
		}
		defer close(*containerChannel)
		return
	}()
}
Пример #6
0
func NewDockerApi(meta *ApplicationMeta, configFile *ConfigFile, buildFile *BuildFile) *DockerApi {
	api := DockerApi{
		meta:       meta,
		configFile: configFile,
		buildFile:  buildFile,
	}

	client, err := docker.NewClient(configFile.DockerEndpoint)
	if err != nil {
		Logger.Error("Docker API Client Error:", err)
		os.Exit(ExitCodes["docker_error"])
	}

	Logger.Debug("Docker client created using endpoint:", configFile.DockerEndpoint)
	api.client = client

	return &api
}
Пример #7
0
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
}
Пример #8
0
// http get using golang packaged lib
func (api *DockerApi) ShowImageGo() (*ApiDockerImage, error) {
	endpoint := api.configFile.DockerEndpoint

	url := strings.Join(
		[]string{
			endpoint,
			"images",
			api.buildFile.ImageName + ":" + api.buildFile.Tag,
			"json",
		},
		"/",
	)

	resp, _ := http.Get(url)
	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 {
		switch resp.StatusCode {
		case 200:
			var jsonResult ApiDockerImage
			if err := json.Unmarshal(body, &jsonResult); err != nil {
				Logger.Error(err)
				return nil, err
			}
			return &jsonResult, nil
		case 404:
			Logger.Debug("Image not found")
		case 500:
			Logger.Error("Error while trying to communicate to docker endpoint:", endpoint)
		}

		return nil, nil
	}
}
Пример #9
0
func TarDirectory(tw *tar.Writer, dir string, ignoreList []string) error {
	Logger.Trace("Taring: ", dir)

	// Add Dockerfile to the ignore list
	// This can also help prevent existing Dockerfiles from being used
	// and accidently shipped in the context.
	ignoreList = append(ignoreList, "Dockerfile")

	shouldIgnore := func(file string) bool {
		for _, entry := range ignoreList {
			if entry == file {
				return true
			}
		}
		return false
	}

	var archive = func(files []os.FileInfo) error {
		Logger.Trace("Found files to archive into context: ", len(files))

		for _, file := range files {
			if shouldIgnore(file.Name()) {
				Logger.Debug("Ignoring:", file.Name())
				continue
			}

			fullFilePath := strings.Join([]string{dir, file.Name()}, "/")

			if file.IsDir() {
				TarDirectory(tw, fullFilePath, ignoreList)
				continue
			}

			hdr := &tar.Header{
				Name: fullFilePath,
				Size: file.Size(),
			}
			if err := tw.WriteHeader(hdr); err != nil {
				Logger.Error("Could not write context archive header", err)
				return err
			}

			if bytes, err := ioutil.ReadFile(fullFilePath); err != nil {
				Logger.Error("Could not read context file: ["+fullFilePath+"]", err)
				return err
			} else {
				if _, err := tw.Write(bytes); err != nil {
					Logger.Error("Could not archive context file: ["+fullFilePath+"]", err)
					return err
				}
			}

			Logger.Trace("Archived into context the file: [" + fullFilePath + "]")
		}

		Logger.Trace("Successfully archived context", dir)
		return nil
	}

	if filesFromDisk, err := ioutil.ReadDir(dir); err != nil {
		Logger.Error("Error while trying to tar ["+dir+"]", err)
		return err
	} else {
		return archive(filesFromDisk)
	}
}
Пример #10
0
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
}
Пример #11
0
// 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
}
Пример #12
0
// 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
	}
}
Пример #13
0
func (api *DockerApi) ShowInfo() {
	Logger.Debug("---------- Test-Flight Docker Info ----------")
	Logger.Debug("Docker Endpoint:", api.configFile.DockerEndpoint)
	Logger.Debug("---------------------------------------------")
}
Пример #14
0
func (api *DockerApi) createTestTemplates(options CommandOptions) error {
	var templateDir = getTemplateDir(api.configFile)

	var inventory = RequiredFile{
		Name: "Test-Flight Test Inventory file", FileName: "inventory", FileType: "f",
	}

	var playbook = RequiredFile{
		Name: "Test-Flight Test Playbook file", FileName: "playbook.yml", FileType: "f",
	}

	// TODO: better to have a type specified that is calculated
	//       much earlier in the process
	var modeDir = func() string {
		if options.SingleFileMode {
			return "filemode"
		} else {
			return "dirmode"
		}
	}()

	// --HERE MUST KNOW ABOUT SUB DIR dirmode/filemode of templates
	// -- needs to work for input and output dir

	// 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("Template dir used for ansible tests:", templatePath)
		return templatePath
	}()

	// This is used below in `ExecuteTemplate()`
	templateInputDir := FilePath(templates, modeDir)
	Logger.Debug("Template dir used for generation of ansible files:", templateInputDir)

	// The directory where to put the generated files
	templateOutputDir := FilePath(api.meta.Pwd /*api.meta.Dir,*/, templateDir.FileName)
	Logger.Debug("Template dir used to put generated ansible test files:", templateOutputDir)

	createFilesFromTemplate := func(
		templateInputDir string,
		templateOutputDir string,
		requiredFile RequiredFile) error {

		hasFiles, _ := HasRequiredFile(templateOutputDir, requiredFile)

		if hasFiles && api.configFile.OverwriteTemplates || !hasFiles {
			fileToCreate := FilePath(templateOutputDir, requiredFile.FileName)
			var err error
			file, err := os.Create(fileToCreate)
			if err != nil {
				Logger.Error("Error:", err)
				return err
			}

			pattern := filepath.Join(templateInputDir, requiredFile.FileName+"*.tmpl")
			tmpl := template.Must(template.ParseGlob(pattern))

			if err = tmpl.ExecuteTemplate(file, requiredFile.FileName, *api.getTemplateVar()); err != nil {
				Logger.Error("template execution: %s", err)
				return err
			}

			Logger.Debug("Created file from template:", fileToCreate)
		} else if hasFiles && !api.configFile.OverwriteTemplates {
			Logger.Debug(requiredFile.Name, "exists, and system configured to not overwrite.")
		}

		return nil
	}

	// If the test-flight templates dir doesn't exist, create it.
	hasFiles, err := HasRequiredFile(api.meta.Dir, templateDir)

	if !hasFiles { // create if it doesn't exist
		if _, err = CreateFile(&api.meta.Dir, templateDir); err != nil {
			return err
		}
	} else {
		Logger.Debug("Required template files already exist. Overwrite set to:", api.configFile.OverwriteTemplates)
	}

	err = createFilesFromTemplate(templateInputDir, templateOutputDir, inventory)
	if err != nil {
		return err
	}

	err = createFilesFromTemplate(templateInputDir, templateOutputDir, playbook)
	if err != nil {
		return err
	}

	return nil
}