<commit_before>// Package kitchen implements a driver based on Test Kitchen.
package kitchen

import (
	"errors"
	"fmt"
	"io/ioutil"
	"path"
	"strconv"

	"github.com/mlafeldt/chef-runner/log"
	"github.com/mlafeldt/chef-runner/openssh"
	"github.com/mlafeldt/chef-runner/rsync"
	"gopkg.in/yaml.v1"
)

// Driver is a driver based on Test Kitchen.
type Driver struct {
	instance    string
	sshClient   *openssh.Client
	rsyncClient *rsync.Client
}

type instanceConfig struct {
	Hostname string `yaml:"hostname"`
	Username string `yaml:"username"`
	SSHKey   string `yaml:"ssh_key"`
	Port     string `yaml:"port"`
}

func readInstanceConfig(instance string) (*instanceConfig, error) {
	configFile := path.Join(".kitchen", instance+".yml")
	log.Debugf("Kitchen config file = %s\n", configFile)

	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	var config instanceConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, err
	}
	log.Debugf("Kitchen config = %+v\n", config)

	if config.Hostname == "" {
		return nil, errors.New(configFile + ": invalid `hostname`")
	}
	if config.Username == "" {
		return nil, errors.New(configFile + ": invalid `username`")
	}
	if config.SSHKey == "" {
		return nil, errors.New(configFile + ": invalid `ssh_key`")
	}
	if _, err := strconv.Atoi(config.Port); err != nil {
		return nil, errors.New(configFile + ": invalid `port`")
	}

	return &config, nil
}

// NewDriver creates a new Test Kitchen driver that communicates with the given
// Test Kitchen instance. Under the hood the instance's YAML configuration is
// parsed to get a working SSH configuration.
func NewDriver(instance string) (*Driver, error) {
	config, err := readInstanceConfig(instance)
	if err != nil {
		return nil, err
	}

	// Test Kitchen stores the port as an string
	port, _ := strconv.Atoi(config.Port)

	// This is what `vagrant ssh` uses
	sshOpts := map[string]string{
		"UserKnownHostsFile":     "/dev/null",
		"StrictHostKeyChecking":  "no",
		"PasswordAuthentication": "no",
		"IdentitiesOnly":         "yes",
		"LogLevel":               "FATAL",<commit_msg>Check that Kitchen YAML file exists<commit_after>// Package kitchen implements a driver based on Test Kitchen.
package kitchen

import (
	"errors"
	"fmt"
	"io/ioutil"
	"path"
	"strconv"

	"github.com/mlafeldt/chef-runner/log"
	"github.com/mlafeldt/chef-runner/openssh"
	"github.com/mlafeldt/chef-runner/rsync"
	"github.com/mlafeldt/chef-runner/util"
	"gopkg.in/yaml.v1"
)

// Driver is a driver based on Test Kitchen.
type Driver struct {
	instance    string
	sshClient   *openssh.Client
	rsyncClient *rsync.Client
}

type instanceConfig struct {
	Hostname string `yaml:"hostname"`
	Username string `yaml:"username"`
	SSHKey   string `yaml:"ssh_key"`
	Port     string `yaml:"port"`
}

func readInstanceConfig(instance string) (*instanceConfig, error) {
	configFile := path.Join(".kitchen", instance+".yml")
	log.Debugf("Kitchen config file = %s\n", configFile)

	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	var config instanceConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, err
	}
	log.Debugf("Kitchen config = %+v\n", config)

	if config.Hostname == "" {
		return nil, errors.New(configFile + ": invalid `hostname`")
	}
	if config.Username == "" {
		return nil, errors.New(configFile + ": invalid `username`")
	}
	if config.SSHKey == "" {
		return nil, errors.New(configFile + ": invalid `ssh_key`")
	}
	if _, err := strconv.Atoi(config.Port); err != nil {
		return nil, errors.New(configFile + ": invalid `port`")
	}

	return &config, nil
}

// NewDriver creates a new Test Kitchen driver that communicates with the given
// Test Kitchen instance. Under the hood the instance's YAML configuration is
// parsed to get a working SSH configuration.
func NewDriver(instance string) (*Driver, error) {
	if !util.FileExist(".kitchen.yml") {
		return nil, errors.New("Kitchen YAML file not found")
	}

	config, err := readInstanceConfig(instance)
	if err != nil {
		return nil, err
	}

	// Test Kitchen stores the port as an string
	port, _ := strconv.Atoi(config.Port)

	// This is what `vagrant ssh` uses
	sshOpts := map[string]string{
		"UserKnownHostsFile":     "/dev/null",
		"StrictHostKeyChecking":  "no",
		"PasswordAuthentication": "no",
		"IdentitiesOnly":         "yes",
		"LogLevel":               "FATAL",
{'fraction_non_alphanumeric': 0.1376251137397634, 'fraction_numerical': 0.00045495905368516835, 'mean_word_length': 3.9128491620111734, 'pattern_counts': {'":': 18, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5075820', 'n_tokens_mistral': 1547, 'n_tokens_neox': 1485, 'n_words': 410}