<commit_before>package cli

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	"github.com/bitrise-io/bitrise-cli/models"
	"github.com/codegangsta/cli"
)

func getWorkFlowPathInCurrentFolder() (string, error) {
	fileInfos, err := ioutil.ReadDir("./")
	if err != nil {
		return "", err
	}

	matches := 0
	workFlowName := ""
	for _, fileInfo := range fileInfos {
		if match, err := regexp.MatchString("([a-z]+).json", fileInfo.Name()); err != nil {
			return "", err
		} else if match {
			matches = matches + 1
			workFlowName = fileInfo.Name()
		}
	}

	if matches == 0 {
		return "", errors.New("No workflow json found")
	} else if matches > 1 {
		return "", errors.New("More then one possible workflow json found")
	}
	return workFlowName, nil
}

func activateAndRunSteps(workFlow models.WorkFlowModel) error {
	for _, step := range workFlow.Steps {
		stepDir := "./steps/" + step.Id + "/" + step.VersionTag + "/"

		if err := bitrise.RunStepmanActivate(step.Id, step.VersionTag, stepDir); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run stepman activate")
			return err
		}

		log.Infof("[BITRISE_CLI] - Step activated: %s (%s)", step.Id, step.VersionTag)

		if err := runStep(step); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run step")
			return err
		}
	}
	return nil
}

func runStep(step models.StepModel) error {
	// Add step envs
	for _, input := range step.Inputs {
		if input.Value != nil {
			if err := bitrise.RunEnvmanAdd(*input.MappedTo, *input.Value); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := "./steps/" + step.Id + "/" + step.VersionTag + "/"
	stepCmd := fmt.Sprintf("%sstep.sh", stepDir)
	cmd := []string{"bash", stepCmd}

	if err := bitrise.RunEnvmanRun(cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Infof("[BITRISE_CLI] - Step executed: %s (%s)", step.Id, step.VersionTag)
	return nil
}

func doRun(c *cli.Context) {
	log.Info("[BITRISE_CLI] - Run")

	// Input validation
	workFlowJsonPath := c.String(PATH_KEY)
	if workFlowJsonPath == "" {
		log.Infoln("[BITRISE_CLI] - Workflow json path not defined, try search in current folder")

		if workFlowName, err := getWorkFlowPathInCurrentFolder(); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to find workflow json:", err)
			return
		} else {
			workFlowJsonPath = "./" + workFlowName
		}
	}

	// Envman setup
	if err := os.Setenv(ENVSTORE_PATH_ENV_KEY, ENVSTORE_PATH); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to add env:", err)
		return
	}

	if err := os.Setenv(FORMATTED_OUTPUT_PATH_ENV_KEY, FORMATTED_OUTPUT_PATH); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to add env:", err)
		return
	}

	if err := bitrise.RunEnvmanInit(); err != nil {
		log.Error("[BITRISE_CLI] - Failed to run envman init")
		return
	}

	// Run work flow
	if workFlow, err := bitrise.ReadWorkFlowJson(workFlowJsonPath); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to read work flow:", err)
		return
	} else {
		if err := activateAndRunSteps(workFlow); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to activate steps:", err)
			return
		}
	}
}
<commit_msg>run fixes<commit_after>package cli

import (
	"fmt"
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	"github.com/bitrise-io/bitrise-cli/models"
	"github.com/bitrise-io/go-pathutil"
	"github.com/codegangsta/cli"
)

func activateAndRunSteps(workFlow models.WorkFlowModel) error {
	for _, step := range workFlow.Steps {
		stepDir := "./steps/" + step.Id + "/" + step.VersionTag + "/"

		if err := bitrise.RunStepmanActivate(step.Id, step.VersionTag, stepDir); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run stepman activate")
			return err
		}

		log.Infof("[BITRISE_CLI] - Step activated: %s (%s)", step.Id, step.VersionTag)

		if err := runStep(step); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run step")
			return err
		}
	}
	return nil
}

func runStep(step models.StepModel) error {
	// Add step envs
	for _, input := range step.Inputs {
		if input.Value != nil {
			if err := bitrise.RunEnvmanAdd(*input.MappedTo, *input.Value); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := "./steps/" + step.Id + "/" + step.VersionTag + "/"
	stepCmd := fmt.Sprintf("%sstep.sh", stepDir)
	cmd := []string{"bash", stepCmd}

	if err := bitrise.RunEnvmanRun(cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Infof("[BITRISE_CLI] - Step executed: %s (%s)", step.Id, step.VersionTag)
	return nil
}

func doRun(c *cli.Context) {
	log.Info("[BITRISE_CLI] - Run")

	// Input validation
	workFlowJsonPath := c.String(PATH_KEY)
	if workFlowJsonPath == "" {
		log.Infoln("[BITRISE_CLI] - Workflow json path not defined, try search in current folder")

		if exist, err := pathutil.IsPathExists("./bitrise.json"); err != nil {
			log.Fatalln("Failed to check path:", err)
		} else if !exist {
			log.Fatalln("No workflow json found")
		}
		workFlowJsonPath = "./bitrise.json"
	}

	// Envman setup
	if err := os.Setenv(ENVSTORE_PATH_ENV_KEY, ENVSTORE_PATH); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if err := os.Setenv(FORMATTED_OUTPUT_PATH_ENV_KEY, FORMATTED_OUTPUT_PATH); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if err := bitrise.RunEnvmanInit(); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to run envman init")
	}

	// Run work flow
	if workFlow, err := bitrise.ReadWorkFlowJson(workFlowJsonPath); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to read work flow:", err)
	} else {
		if err := activateAndRunSteps(workFlow); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to activate steps:", err)
		}
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	failedSteps   []string
	inventoryPath string
)

// StepIDData ...
type StepIDData struct {
	ID            string
	Version       string
	SteplibSource string
}

func isBuildFailed() bool {
	if len(failedSteps) > 0 {
		return true
	}
	return false
}

func createStepIDDataFromString(s string) (StepIDData, error) {
	libsourceStepSplits := strings.Split(s, "::")
	if len(libsourceStepSplits) != 2 {
		return StepIDData{}, errors.New("Steplib should be separated with a '::' separator from the step ID (" + s + ")")
	}
	stepidVersionSplits := strings.Split(libsourceStepSplits[1], "@")
	if len(stepidVersionSplits) != 2 {
		return StepIDData{}, errors.New("Step ID and version should be separated with a '@' separator (" + libsourceStepSplits[1] + ")")
	}

	return StepIDData{
		SteplibSource: libsourceStepSplits[0],
		ID:            stepidVersionSplits[0],
		Version:       stepidVersionSplits[1],
	}, nil
}

func exportEnvironmentsList(envsList []models.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		if env.Value != "" {
			if err := bitrise.RunEnvmanAdd(env.Key, env.Value, env.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel) error {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	for idx, stepListItm := range workflow.Steps {
		// TODO: first arg should be 'stepCompositeID'
		//  which can contain the step-collection, step-id, version, etc.
		//  in one string!
		compositeStepIDStr, workflowStep, err := stepListItm.GetStepIDStepDataPair()
		if err != nil {
			return err
		}
		stepIDData, err := createStepIDDataFromString(compositeStepIDStr)
		if err != nil {
			return err
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		fmt.Println()
		log.Infof("========== (%d) %s ==========", idx, workflowStep.Name)
		fmt.Println()
		stepDir := bitrise.BitriseWorkStepsDirPath + "/" + stepIDData.ID + "/" + stepIDData.Version + "/"

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			log.Error("Failed to setup stepman:", err)
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RemoveFile(stepYMLPth); err != nil {
			log.Fatal("Failed to remove step yml:", err)
		}

		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run stepman activate")
			failedSteps = append(failedSteps, compositeStepIDStr)
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			if specStep, err := bitrise.ReadSpecStep(stepYMLPth); err != nil {
				log.Fatal("Failed to read spec step:", err)
			} else {
				specStep.MergeWith(workflowStep)

				if err := runStep(specStep, stepIDData); err != nil {
					log.Errorln("[BITRISE_CLI] - Failed to run step:", err)
					failedSteps = append(failedSteps, compositeStepIDStr)
				}
			}
		}
	}
	return nil
}

func runStep(step models.StepModel, stepIDData StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		if input.Value != "" {
			log.Info("Input:", input)
			if err := bitrise.RunEnvmanAdd(input.Key, input.Value, input.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath + "/" + stepIDData.ID + "/" + stepIDData.Version + "/"
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	log.Debugln("[BITRISE_CLI] - Run")

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		log.Fatal("Failed to cleanup bitrise work dir:", err)
	}
	failedSteps = []string{}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No inventory yml found")
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			log.Fatal("Invalid invetory format:", err)
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			log.Fatal("Failed to copy inventory:", err)
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to run envman init")
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to read Workflow:", err)
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		log.Fatalln("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!")
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to export App environments:", err)
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to export Workflow environments:", err)
	}

	// Run the Workflow
	if err := activateAndRunSteps(workflowToRun); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to activate steps:", err)
	}

	log.Debugln("Failed steps:", failedSteps)
	log.Infoln("")
	log.Infoln("DONE - Congrats!!")
}
<commit_msg>log info->debug<commit_after>package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	failedSteps   []string
	inventoryPath string
)

// StepIDData ...
type StepIDData struct {
	ID            string
	Version       string
	SteplibSource string
}

func isBuildFailed() bool {
	if len(failedSteps) > 0 {
		return true
	}
	return false
}

func createStepIDDataFromString(s string) (StepIDData, error) {
	libsourceStepSplits := strings.Split(s, "::")
	if len(libsourceStepSplits) != 2 {
		return StepIDData{}, errors.New("Steplib should be separated with a '::' separator from the step ID (" + s + ")")
	}
	stepidVersionSplits := strings.Split(libsourceStepSplits[1], "@")
	if len(stepidVersionSplits) != 2 {
		return StepIDData{}, errors.New("Step ID and version should be separated with a '@' separator (" + libsourceStepSplits[1] + ")")
	}

	return StepIDData{
		SteplibSource: libsourceStepSplits[0],
		ID:            stepidVersionSplits[0],
		Version:       stepidVersionSplits[1],
	}, nil
}

func exportEnvironmentsList(envsList []models.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		if env.Value != "" {
			if err := bitrise.RunEnvmanAdd(env.Key, env.Value, env.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel) error {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	for idx, stepListItm := range workflow.Steps {
		// TODO: first arg should be 'stepCompositeID'
		//  which can contain the step-collection, step-id, version, etc.
		//  in one string!
		compositeStepIDStr, workflowStep, err := stepListItm.GetStepIDStepDataPair()
		if err != nil {
			return err
		}
		stepIDData, err := createStepIDDataFromString(compositeStepIDStr)
		if err != nil {
			return err
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		fmt.Println()
		log.Infof("========== (%d) %s ==========", idx, workflowStep.Name)
		fmt.Println()
		stepDir := bitrise.BitriseWorkStepsDirPath + "/" + stepIDData.ID + "/" + stepIDData.Version + "/"

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			log.Error("Failed to setup stepman:", err)
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RemoveFile(stepYMLPth); err != nil {
			log.Fatal("Failed to remove step yml:", err)
		}

		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			log.Errorln("[BITRISE_CLI] - Failed to run stepman activate")
			failedSteps = append(failedSteps, compositeStepIDStr)
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			if specStep, err := bitrise.ReadSpecStep(stepYMLPth); err != nil {
				log.Fatal("Failed to read spec step:", err)
			} else {
				specStep.MergeWith(workflowStep)

				if err := runStep(specStep, stepIDData); err != nil {
					log.Errorln("[BITRISE_CLI] - Failed to run step:", err)
					failedSteps = append(failedSteps, compositeStepIDStr)
				}
			}
		}
	}
	return nil
}

func runStep(step models.StepModel, stepIDData StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		if input.Value != "" {
			log.Debugf("Input: %#v\n", input)
			if err := bitrise.RunEnvmanAdd(input.Key, input.Value, input.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath + "/" + stepIDData.ID + "/" + stepIDData.Version + "/"
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd); err != nil {
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	log.Debugln("[BITRISE_CLI] - Run")

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		log.Fatal("Failed to cleanup bitrise work dir:", err)
	}
	failedSteps = []string{}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to check path:", err)
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No inventory yml found")
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			log.Fatal("Invalid invetory format:", err)
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			log.Fatal("Failed to copy inventory:", err)
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to add env:", err)
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			log.Fatalln("[BITRISE_CLI] - Failed to run envman init")
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to read Workflow:", err)
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		log.Fatalln("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!")
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to export App environments:", err)
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to export Workflow environments:", err)
	}

	// Run the Workflow
	if err := activateAndRunSteps(workflowToRun); err != nil {
		log.Fatalln("[BITRISE_CLI] - Failed to activate steps:", err)
	}

	log.Debugln("Failed steps:", failedSteps)
	log.Infoln("")
	log.Infoln("DONE - Congrats!!")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/datawire/teleproxy/pkg/k8s"
	"github.com/datawire/teleproxy/pkg/tpu"
	"github.com/spf13/cobra"
)

type Syncer struct {
	Watcher     *k8s.Watcher
	SyncCommand string
	Kinds       []string
	Mux         sync.Mutex // protects the whole data structure
	Dirty       bool
	ModTime     time.Time
	SyncTime    time.Time
	SyncCount   int
	MinInterval time.Duration
	MaxInterval time.Duration
	WarmupDelay time.Duration
	router      *http.ServeMux
	port        string
	snapshotMux sync.Mutex // protects just the snapshot map
	snapshots   map[string]map[string][]byte
}

func (s *Syncer) maybeSync() {
	s.Mux.Lock()
	defer s.Mux.Unlock()

	if !s.Dirty {
		return
	}

	now := time.Now()
	if now.Sub(s.ModTime) > s.MinInterval || now.Sub(s.SyncTime) > s.MaxInterval {
		s.SyncTime = now
		s.Dirty = false
		s.sync()
	}
}

func (s *Syncer) sync() {
	s.SyncCount += 1
	snapshot_id := s.write()
	s.invoke(snapshot_id)
}

func (s *Syncer) write() string {
	s.snapshotMux.Lock()
	defer s.snapshotMux.Unlock()
	snapshot_id := fmt.Sprintf("%d", s.SyncCount)
	s.snapshots[snapshot_id] = make(map[string][]byte)
	for _, kind := range s.Kinds {
		resources := s.Watcher.List(kind)
		bytes, err := k8s.MarshalResources(resources)
		if err != nil {
			panic(err)
		}
		s.snapshots[snapshot_id][kind] = bytes
		for _, rsrc := range resources {
			qname := path.Join(kind, rsrc.Namespace(), rsrc.Name())
			bytes, err := k8s.MarshalResource(rsrc)
			if err != nil {
				panic(err)
			}
			s.snapshots[snapshot_id][qname] = bytes
		}
	}
	s.cleanup()
	return snapshot_id
}

func (s *Syncer) cleanup() {
	for k := range s.snapshots {
		keep := false
		for c := s.SyncCount; c > s.SyncCount-10; c-- {
			id := fmt.Sprintf("%d", c)
			if id == k {
				keep = true
			}
		}
		if !keep {
			delete(s.snapshots, k)
			log.Printf("deleting snapshot %s", k)
		}
	}
}

func (s *Syncer) invoke(snapshot_id string) {
	k := tpu.NewKeeper("SYNC", fmt.Sprintf("%s http://localhost:%s/api/snapshot/%s", s.SyncCommand, s.port, snapshot_id))
	k.Limit = 1
	k.Start()
	k.Wait()
}

func (s *Syncer) Run() {
	go func() {
		time.Sleep(s.WarmupDelay)
		for {
			s.maybeSync()
			time.Sleep(s.MinInterval)
		}
	}()

	for _, k := range s.Kinds {
		// this alias is important so the func picks up the
		// value from the current iteration instead of the
		// value from the last iteration
		kind := k
		s.Watcher.WatchNamespace(NAMESPACE, kind, func(_ *k8s.Watcher) {
			s.Mux.Lock()
			defer s.Mux.Unlock()
			s.Dirty = true
			s.ModTime = time.Now()
		})
	}
	s.Watcher.Start()
	s.serve()
}

func (s *Syncer) serve() {
	s.routes()

	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", PORT))
	if err != nil {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}

	_, port, err := net.SplitHostPort(ln.Addr().String())
	if err != nil {
		log.Fatalf("kubewatch: %v", err)
	}
	s.port = port

	server := http.Server{
		Handler: s.router,
	}

	if err := server.Serve(ln); err != http.ErrServerClosed {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}
}

func (s *Syncer) routes() {
	s.router.HandleFunc("/api/snapshot/", s.safe(s.handleSnapshot()))
}

func (s *Syncer) safe(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if r := recover(); r != nil {
				var msg string
				switch e := r.(type) {
				case error:
					msg = e.Error()
				default:
					msg = fmt.Sprintf("%v", r)
				}
				http.Error(w, fmt.Sprintf("Server Error: %s", msg), http.StatusInternalServerError)
			}
		}()
		h(w, r)
	}
}

func (s *Syncer) handleSnapshot() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.snapshotMux.Lock()
		defer s.snapshotMux.Unlock()
		parts := strings.Split(r.URL.Path, "/")
		parts = parts[3:]
		snapshot_id := parts[0]
		snapshot, ok := s.snapshots[snapshot_id]
		if !ok {
			http.NotFound(w, r)
			return
		}
		qname := strings.Join(parts[1:], "/")
		body, ok := snapshot[qname]
		if !ok {
			http.NotFound(w, r)
		}
		w.Write(body)
	}
}

var Version = "(unknown version)"

var KUBEWATCH = &cobra.Command{
	Use:  "kubewatch [options] <resources>",
	Args: cobra.MinimumNArgs(1),
	Run:  kubewatch,
}

func init() {
	KUBEWATCH.Version = Version
	KUBEWATCH.Flags().StringVarP(&PORT, "port", "p", "0", "port for kubewatch api")
	KUBEWATCH.Flags().StringVarP(&SYNC_COMMAND, "sync", "s", "curl", "sync command")
	KUBEWATCH.Flags().StringVarP(&NAMESPACE, "namespace", "n", "", "namespace to watch (defaults to all)")
	KUBEWATCH.Flags().DurationVarP(&MIN_INTERVAL, "min-interval", "m", 250*time.Millisecond, "min sync interval")
	KUBEWATCH.Flags().DurationVarP(&MAX_INTERVAL, "max-interval", "M", time.Second, "max sync interval")
	KUBEWATCH.Flags().DurationVarP(&WARMUP_DELAY, "warmup-delay", "w", 0, "warmup delay")
}

var (
	PORT         string
	SYNC_COMMAND string
	NAMESPACE    string
	MIN_INTERVAL time.Duration
	MAX_INTERVAL time.Duration
	WARMUP_DELAY time.Duration
)

func kubewatch(cmd *cobra.Command, args []string) {
	s := Syncer{
		Watcher:     k8s.NewClient(nil).Watcher(),
		SyncCommand: SYNC_COMMAND,
		Kinds:       args,
		MinInterval: MIN_INTERVAL,
		MaxInterval: MAX_INTERVAL,
		WarmupDelay: WARMUP_DELAY,
		router:      http.NewServeMux(),
		snapshots:   make(map[string]map[string][]byte),
	}

	s.Run()
}

func main() {
	KUBEWATCH.Execute()
}
<commit_msg>Add index of snapshot keys.<commit_after>package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/datawire/teleproxy/pkg/k8s"
	"github.com/datawire/teleproxy/pkg/tpu"
	"github.com/spf13/cobra"
)

type Syncer struct {
	Watcher     *k8s.Watcher
	SyncCommand string
	Kinds       []string
	Mux         sync.Mutex // protects the whole data structure
	Dirty       bool
	ModTime     time.Time
	SyncTime    time.Time
	SyncCount   int
	MinInterval time.Duration
	MaxInterval time.Duration
	WarmupDelay time.Duration
	router      *http.ServeMux
	port        string
	snapshotMux sync.Mutex // protects just the snapshot map
	snapshots   map[string]map[string][]byte
}

func (s *Syncer) maybeSync() {
	s.Mux.Lock()
	defer s.Mux.Unlock()

	if !s.Dirty {
		return
	}

	now := time.Now()
	if now.Sub(s.ModTime) > s.MinInterval || now.Sub(s.SyncTime) > s.MaxInterval {
		s.SyncTime = now
		s.Dirty = false
		s.sync()
	}
}

func (s *Syncer) sync() {
	s.SyncCount += 1
	snapshot_id := s.write()
	s.invoke(snapshot_id)
}

func (s *Syncer) write() string {
	s.snapshotMux.Lock()
	defer s.snapshotMux.Unlock()
	snapshot_id := fmt.Sprintf("%d", s.SyncCount)
	s.snapshots[snapshot_id] = make(map[string][]byte)
	for _, kind := range s.Kinds {
		resources := s.Watcher.List(kind)
		bytes, err := k8s.MarshalResources(resources)
		if err != nil {
			panic(err)
		}
		s.snapshots[snapshot_id][kind] = bytes
		for _, rsrc := range resources {
			qname := path.Join(kind, rsrc.Namespace(), rsrc.Name())
			bytes, err := k8s.MarshalResource(rsrc)
			if err != nil {
				panic(err)
			}
			s.snapshots[snapshot_id][qname] = bytes
		}
	}
	s.cleanup()
	return snapshot_id
}

func (s *Syncer) cleanup() {
	for k := range s.snapshots {
		keep := false
		for c := s.SyncCount; c > s.SyncCount-10; c-- {
			id := fmt.Sprintf("%d", c)
			if id == k {
				keep = true
			}
		}
		if !keep {
			delete(s.snapshots, k)
			log.Printf("deleting snapshot %s", k)
		}
	}
}

func (s *Syncer) invoke(snapshot_id string) {
	k := tpu.NewKeeper("SYNC", fmt.Sprintf("%s http://localhost:%s/api/snapshot/%s", s.SyncCommand, s.port, snapshot_id))
	k.Limit = 1
	k.Start()
	k.Wait()
}

func (s *Syncer) Run() {
	go func() {
		time.Sleep(s.WarmupDelay)
		for {
			s.maybeSync()
			time.Sleep(s.MinInterval)
		}
	}()

	for _, k := range s.Kinds {
		// this alias is important so the func picks up the
		// value from the current iteration instead of the
		// value from the last iteration
		kind := k
		s.Watcher.WatchNamespace(NAMESPACE, kind, func(_ *k8s.Watcher) {
			s.Mux.Lock()
			defer s.Mux.Unlock()
			s.Dirty = true
			s.ModTime = time.Now()
		})
	}
	s.Watcher.Start()
	s.serve()
}

func (s *Syncer) serve() {
	s.routes()

	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", PORT))
	if err != nil {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}

	_, port, err := net.SplitHostPort(ln.Addr().String())
	if err != nil {
		log.Fatalf("kubewatch: %v", err)
	}
	s.port = port

	server := http.Server{
		Handler: s.router,
	}

	if err := server.Serve(ln); err != http.ErrServerClosed {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}
}

func (s *Syncer) routes() {
	s.router.HandleFunc("/api/snapshot/", s.safe(s.handleSnapshot()))
}

func (s *Syncer) safe(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if r := recover(); r != nil {
				var msg string
				switch e := r.(type) {
				case error:
					msg = e.Error()
				default:
					msg = fmt.Sprintf("%v", r)
				}
				http.Error(w, fmt.Sprintf("Server Error: %s", msg), http.StatusInternalServerError)
			}
		}()
		h(w, r)
	}
}

func (s *Syncer) handleSnapshot() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.snapshotMux.Lock()
		defer s.snapshotMux.Unlock()
		parts := strings.Split(r.URL.Path, "/")
		parts = parts[3:]
		snapshot_id := parts[0]
		snapshot, ok := s.snapshots[snapshot_id]
		if !ok {
			http.NotFound(w, r)
			return
		}
		qname := strings.Join(parts[1:], "/")
		var body []byte
		if qname != "" {
			body, ok = snapshot[qname]
			if !ok {
				http.NotFound(w, r)
			}
		} else {
			var keys []string
			for k, _ := range snapshot {
				keys = append(keys, k)
			}
			body = []byte(fmt.Sprintf("Available snapshot keys:\n - %s\n", strings.Join(keys, "\n - ")))
		}
		w.Write(body)
	}
}

var Version = "(unknown version)"

var KUBEWATCH = &cobra.Command{
	Use:  "kubewatch [options] <resources>",
	Args: cobra.MinimumNArgs(1),
	Run:  kubewatch,
}

func init() {
	KUBEWATCH.Version = Version
	KUBEWATCH.Flags().StringVarP(&PORT, "port", "p", "0", "port for kubewatch api")
	KUBEWATCH.Flags().StringVarP(&SYNC_COMMAND, "sync", "s", "curl", "sync command")
	KUBEWATCH.Flags().StringVarP(&NAMESPACE, "namespace", "n", "", "namespace to watch (defaults to all)")
	KUBEWATCH.Flags().DurationVarP(&MIN_INTERVAL, "min-interval", "m", 250*time.Millisecond, "min sync interval")
	KUBEWATCH.Flags().DurationVarP(&MAX_INTERVAL, "max-interval", "M", time.Second, "max sync interval")
	KUBEWATCH.Flags().DurationVarP(&WARMUP_DELAY, "warmup-delay", "w", 0, "warmup delay")
}

var (
	PORT         string
	SYNC_COMMAND string
	NAMESPACE    string
	MIN_INTERVAL time.Duration
	MAX_INTERVAL time.Duration
	WARMUP_DELAY time.Duration
)

func kubewatch(cmd *cobra.Command, args []string) {
	s := Syncer{
		Watcher:     k8s.NewClient(nil).Watcher(),
		SyncCommand: SYNC_COMMAND,
		Kinds:       args,
		MinInterval: MIN_INTERVAL,
		MaxInterval: MAX_INTERVAL,
		WarmupDelay: WARMUP_DELAY,
		router:      http.NewServeMux(),
		snapshots:   make(map[string]map[string][]byte),
	}

	s.Run()
}

func main() {
	KUBEWATCH.Execute()
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"github.com/boltdb/bolt"
	"github.com/fsnotify/fsnotify"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"pget/premiumize"
	"pget/watcher"
	"strings"
	"time"
)

const premiumizeFinishedStatus = "finished"
const torrentListCheckInterval = 60 * time.Second
const boltDBFile = "pget.db"
const torrentsBucket = "torrents"

func (c *Cli) openBoltDB() error {
	c.boltMutex.Lock()
	defer c.boltMutex.Unlock()

	if c.bolt == nil {
		db, err := bolt.Open(boltDBFile, 0600, nil)
		if err != nil {
			return err
		}
		c.bolt = db
	}
	return nil
}

func (c *Cli) WatchAndUpload(directory string, strict bool, deleteAfterUpload bool) {
	stat, err := os.Stat(directory)
	if err != nil {
		fmt.Printf("Unable to retrieve directory stats: %s\n", err.Error())
		return
	}

	if !stat.IsDir() {
		fmt.Printf("%s is not a directory\n", directory)
		return
	}

	if err := c.openBoltDB(); err != nil {
		fmt.Printf("Unable to open database for upload/download tracking: %s\n", err.Error())
		return
	}
	c.bolt.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(torrentsBucket))
		return err
	})

	watcher := watcher.New(watcher.FileWatcherConfig{
		BaseDir:      directory,
		MatchPattern: ".*?\\.torrent",
		ScanInterval: 5 * time.Second,
	})

	pathCh := make(chan string)
	watcher.AddWatcher(pathCh)
	watcher.Run()

	var path string
	for {
		path = <-pathCh
		c.processTorrentFile(directory, path, strict, deleteAfterUpload)
	}
}

func (c *Cli) setupWatcher(watcher *fsnotify.Watcher, directory string) error {
	err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}

		err = watcher.Add(path)
		if err != nil {
			fmt.Printf("Failed to add directory %s to watcher: %s\n", path, err.Error())
		}
		fmt.Printf("Watching %s\n", path)
		return nil
	})

	return err
}

func (c *Cli) processTorrentFile(basePath string, filePath string, strict bool, deleteAfterUpload bool) {
	location := extractLocation(basePath, filePath)

	// TODO: Check if torrent is already in our db
	// TODO: Check torrent file integrity

	resp, err := c.upload(filePath)

	if err != nil {
		fmt.Printf("Failed to upload %s: %s\n", filePath, err.Error())
	} else {
		err := os.Remove(filePath)
		if err != nil {
			fmt.Printf("Could not delete torrent file after processing: %s\n", err.Error())
		}
		err = c.bolt.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(torrentsBucket))
			err := bucket.Put([]byte(resp.ID), []byte(location))
			return err
		})
		if err != nil {
			fmt.Printf("Failed to store torrent %s in database, this torrent will not be automatically downloaded: %s\n", filePath, err.Error())
		}
	}
}

func (c *Cli) upload(filePath string) (premiumize.UploadResponse, error) {
	if strings.HasSuffix(filePath, ".torrent") {
		return c.premiumize.UploadTorrentFile(filePath)
	} else {
		content, err := ioutil.ReadFile(filePath)
		if err != nil {
			return premiumize.UploadResponse{}, err
		}
		return c.premiumize.UploadMagnetLink(string(content))
	}
	return premiumize.UploadResponse{}, nil
}

func extractLocation(basePath string, filePath string) string {
	path := filePath[len(basePath)+1:]

	if strings.ContainsRune(path, os.PathSeparator) {
		file := filepath.Base(path)
		return filePath[:len(filePath)-len(file)-1]
	}
	return ""
}

func (c *Cli) WatchAndDownload(targetDirectory string, videoOnly bool, flatten bool, strict bool, deleteDownloaded bool, createSyncFile bool) {
	if strict {
		if err := c.openBoltDB(); err != nil {
			fmt.Printf("Unable to open database for upload/download tracking: %s\n", err.Error())
			return
		}
	}

	done := make(chan bool)
	go func() {
		for {
			if createSyncFile {
				c.createSyncFile(targetDirectory)
			}

			torrents, err := c.premiumize.ListTorrents()
			if err != nil {
				fmt.Printf("Could not retrieve list of torrents: %s\n", err.Error())
			} else {
				for _, transfer := range torrents.Transfers {
					isFinished := c.isTorrentFinished(transfer.Status)
					hasBeenUploaded := c.hasBeenUploadedWhenStrict(strict, transfer)

					if isFinished && hasBeenUploaded {
						id, err := c.DownloadTorrent(transfer.Name, targetDirectory, videoOnly, flatten, "")
						if deleteDownloaded && err == nil {
							c.premiumize.DeleteTorrent(id)
						}
					}
				}
			}
			if createSyncFile {
				c.deleteSyncFile(targetDirectory)
			}
			time.Sleep(torrentListCheckInterval)
		}
	}()

	<-done
}

func (c *Cli) mkdir(path string) {
	os.MkdirAll(path, 0770)
}

func (c *Cli) createSyncFile(directory string) {
	err := os.MkdirAll(directory, 0770)
	if err != nil {
		fmt.Printf("Unable to create directory where .sync file should be: %v", err)
	}
	file, err := os.OpenFile(path.Join(directory, ".sync"), os.O_RDONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("Could not create sync file: %v", err)
	}
	file.Close()
}

func (c *Cli) deleteSyncFile(directory string) {
	err := os.Remove(path.Join(directory, ".sync"))
	if err != nil {
		fmt.Printf("Could not delete sync file: %v", err)
	}
}

func (c *Cli) isTorrentFinished(status string) bool {
	return status == premiumizeFinishedStatus
}

func (c *Cli) hasBeenUploadedWhenStrict(strict bool, transfer premiumize.TorrentItem) bool {
	if !strict {
		return true
	}

	err := c.bolt.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(torrentsBucket))
		name := bucket.Get([]byte(transfer.ID))
		if name == nil {
			return fmt.Errorf("Not found")
		}
		return nil
	})

	return err == nil
}
<commit_msg>Increased watch upload/download cycle delay to 10 minutes.<commit_after>package cli

import (
	"fmt"
	"github.com/boltdb/bolt"
	"github.com/fsnotify/fsnotify"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"pget/premiumize"
	"pget/watcher"
	"strings"
	"time"
)

const premiumizeFinishedStatus = "finished"
const torrentListCheckInterval = 10 * time.Minute
const boltDBFile = "pget.db"
const torrentsBucket = "torrents"

func (c *Cli) openBoltDB() error {
	c.boltMutex.Lock()
	defer c.boltMutex.Unlock()

	if c.bolt == nil {
		db, err := bolt.Open(boltDBFile, 0600, nil)
		if err != nil {
			return err
		}
		c.bolt = db
	}
	return nil
}

func (c *Cli) WatchAndUpload(directory string, strict bool, deleteAfterUpload bool) {
	stat, err := os.Stat(directory)
	if err != nil {
		fmt.Printf("Unable to retrieve directory stats: %s\n", err.Error())
		return
	}

	if !stat.IsDir() {
		fmt.Printf("%s is not a directory\n", directory)
		return
	}

	if err := c.openBoltDB(); err != nil {
		fmt.Printf("Unable to open database for upload/download tracking: %s\n", err.Error())
		return
	}
	c.bolt.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(torrentsBucket))
		return err
	})

	watcher := watcher.New(watcher.FileWatcherConfig{
		BaseDir:      directory,
		MatchPattern: ".*?\\.torrent",
		ScanInterval: 5 * time.Second,
	})

	pathCh := make(chan string)
	watcher.AddWatcher(pathCh)
	watcher.Run()

	var path string
	for {
		path = <-pathCh
		c.processTorrentFile(directory, path, strict, deleteAfterUpload)
	}
}

func (c *Cli) setupWatcher(watcher *fsnotify.Watcher, directory string) error {
	err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}

		err = watcher.Add(path)
		if err != nil {
			fmt.Printf("Failed to add directory %s to watcher: %s\n", path, err.Error())
		}
		fmt.Printf("Watching %s\n", path)
		return nil
	})

	return err
}

func (c *Cli) processTorrentFile(basePath string, filePath string, strict bool, deleteAfterUpload bool) {
	location := extractLocation(basePath, filePath)

	// TODO: Check if torrent is already in our db
	// TODO: Check torrent file integrity

	resp, err := c.upload(filePath)

	if err != nil {
		fmt.Printf("Failed to upload %s: %s\n", filePath, err.Error())
	} else {
		err := os.Remove(filePath)
		if err != nil {
			fmt.Printf("Could not delete torrent file after processing: %s\n", err.Error())
		}
		err = c.bolt.Update(func(tx *bolt.Tx) error {
			bucket := tx.Bucket([]byte(torrentsBucket))
			err := bucket.Put([]byte(resp.ID), []byte(location))
			return err
		})
		if err != nil {
			fmt.Printf("Failed to store torrent %s in database, this torrent will not be automatically downloaded: %s\n", filePath, err.Error())
		}
	}
}

func (c *Cli) upload(filePath string) (premiumize.UploadResponse, error) {
	if strings.HasSuffix(filePath, ".torrent") {
		return c.premiumize.UploadTorrentFile(filePath)
	} else {
		content, err := ioutil.ReadFile(filePath)
		if err != nil {
			return premiumize.UploadResponse{}, err
		}
		return c.premiumize.UploadMagnetLink(string(content))
	}
	return premiumize.UploadResponse{}, nil
}

func extractLocation(basePath string, filePath string) string {
	path := filePath[len(basePath)+1:]

	if strings.ContainsRune(path, os.PathSeparator) {
		file := filepath.Base(path)
		return filePath[:len(filePath)-len(file)-1]
	}
	return ""
}

func (c *Cli) WatchAndDownload(targetDirectory string, videoOnly bool, flatten bool, strict bool, deleteDownloaded bool, createSyncFile bool) {
	if strict {
		if err := c.openBoltDB(); err != nil {
			fmt.Printf("Unable to open database for upload/download tracking: %s\n", err.Error())
			return
		}
	}

	done := make(chan bool)
	go func() {
		for {
			if createSyncFile {
				c.createSyncFile(targetDirectory)
			}

			torrents, err := c.premiumize.ListTorrents()
			if err != nil {
				fmt.Printf("Could not retrieve list of torrents: %s\n", err.Error())
			} else {
				for _, transfer := range torrents.Transfers {
					isFinished := c.isTorrentFinished(transfer.Status)
					hasBeenUploaded := c.hasBeenUploadedWhenStrict(strict, transfer)

					if isFinished && hasBeenUploaded {
						id, err := c.DownloadTorrent(transfer.Name, targetDirectory, videoOnly, flatten, "")
						if deleteDownloaded && err == nil {
							c.premiumize.DeleteTorrent(id)
						}
					}
				}
			}
			if createSyncFile {
				c.deleteSyncFile(targetDirectory)
			}
			time.Sleep(torrentListCheckInterval)
		}
	}()

	<-done
}

func (c *Cli) mkdir(path string) {
	os.MkdirAll(path, 0770)
}

func (c *Cli) createSyncFile(directory string) {
	err := os.MkdirAll(directory, 0770)
	if err != nil {
		fmt.Printf("Unable to create directory where .sync file should be: %v", err)
	}
	file, err := os.OpenFile(path.Join(directory, ".sync"), os.O_RDONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("Could not create sync file: %v", err)
	}
	file.Close()
}

func (c *Cli) deleteSyncFile(directory string) {
	err := os.Remove(path.Join(directory, ".sync"))
	if err != nil {
		fmt.Printf("Could not delete sync file: %v", err)
	}
}

func (c *Cli) isTorrentFinished(status string) bool {
	return status == premiumizeFinishedStatus
}

func (c *Cli) hasBeenUploadedWhenStrict(strict bool, transfer premiumize.TorrentItem) bool {
	if !strict {
		return true
	}

	err := c.bolt.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(torrentsBucket))
		name := bucket.Get([]byte(transfer.ID))
		if name == nil {
			return fmt.Errorf("Not found")
		}
		return nil
	})

	return err == nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/bradfitz/slice"
)

// Character is the type representing a role playing character
type Character struct {
	Name            string
	Backgrounds     map[string]Background
	Aptitudes       map[string]Aptitude
	Characteristics map[string]Characteristic
	Skills          map[string]Skill
	Talents         map[string]Talent
	Gauges          map[string]Gauge
	Rules           map[string]Rule
	Experience      int
	Spent           int
}

// NewCharacter creates a new character from the given sheet and universe.
func NewCharacter(universe Universe, sheet Sheet) (Character, error) {

	// Create a character
	c := Character{
		Name:            sheet.Header.Name,
		Backgrounds:     make(map[string]Background),
		Aptitudes:       make(map[string]Aptitude),
		Characteristics: make(map[string]Characteristic),
		Skills:          make(map[string]Skill),
		Talents:         make(map[string]Talent),
		Gauges:          make(map[string]Gauge),
		Rules:           make(map[string]Rule),
		Experience:      0,
		Spent:           0,
	}

	// The characteristics described in the header of the sheet are parsed as upgrades
	for _, upgrade := range sheet.Characteristics {

		// Get the characteristic from the universe
		characteristic, found := universe.FindCharacteristic(upgrade.Name)
		if !found {
			return c, NewError(UndefinedCharacteristic, upgrade.Line)
		}

		// Check it is not already applied
		_, found = c.Characteristics[characteristic.Name]
		if found {
			return c, NewError(DuplicateCharacteristic, upgrade.Line)
		}

		// Apply the upgrade
		err := c.ApplyCharacteristicUpgrade(characteristic, upgrade)
		if err != nil {
			return c, err
		}
	}

	// Next are the backgrounds
	for typ, metas := range sheet.Header.Metas {

		for _, meta := range metas {

			// Find the background corresponding to the meta
			background, err := universe.FindBackground(typ, meta.Label)
			if err != nil {
				return c, err
			}

			err = c.ApplyBackground(background, universe)
			if err != nil {
				return c, err
			}
		}
	}

	// Next are the sessions
	for _, session := range sheet.Sessions {

		// Apply the experience gain if needed
		if session.Reward != nil {
			c.Experience += *session.Reward
		}

		// Apply each upgrade in order
		for _, upgrade := range session.Upgrades {
			err := c.ApplyUpgrade(upgrade, universe)
			if err != nil {
				return c, err
			}
		}
	}

	return c, nil
}

// CountMatchingAptitudes return the number of aptitudes of the given slice
// that are in the character's aptitudes.
func (character Character) CountMatchingAptitudes(aptitudes []Aptitude) int {

	count := 0
	for _, aptitude := range aptitudes {
		if _, found := character.Aptitudes[string(aptitude)]; found {
			count++
		}
	}
	return count
}

// ApplyBackground changes the character's trait according to the history values
func (character *Character) ApplyBackground(background Background, universe Universe) error {

	cost := 0

	// For each upgrade associated to the history, apply each option.
	for _, upgrade := range background.Upgrades {
		err := character.ApplyUpgrade(Upgrade{
			Mark: MarkSpecial,
			Name: upgrade,
			Cost: &cost,
		}, universe)
		if err != nil {
			return err
		}
	}

	// Add the background to the character's backgrounds
	character.Backgrounds[background.Name] = background

	return nil
}

// ApplyUpgrade changes the character's attributes according to the given upgrade.
func (character *Character) ApplyUpgrade(upgrade Upgrade, universe Universe) error {

	var err error

	// Find the attribute corresponding to the upgrade., and initialize a new
	// rule if there isn't any.
	coster, found := universe.FindCoster(upgrade.Name)
	if !found {
		coster = Rule{
			Name: upgrade.Name,
		}
	}

	// If no cost is defined, compute it on the fly.
	if upgrade.Cost == nil {
		cost, err := coster.Cost(universe, *character)
		if err != nil {
			return err
		}

		upgrade.Cost = &cost
	}

	// Update the spent experience.
	character.Spent += *upgrade.Cost

	// Apply the upgrade depending on the target attribute.
	switch attribute := coster.(type) {
	case Characteristic:
		err = character.ApplyCharacteristicUpgrade(attribute, upgrade)
	case Skill:
		err = character.ApplySkillUpgrade(attribute, upgrade)
	case Talent:
		err = character.ApplyTalentUpgrade(attribute, upgrade)
	case Aptitude:
		err = character.ApplyAptitudeUpgrade(attribute, upgrade)
	case Gauge:
		err = character.ApplyGaugeUpgrade(attribute, upgrade)
	case Rule:
		err = character.ApplyRuleUpgrade(attribute, upgrade)
	}

	return err
}

func (character *Character) ApplyCharacteristicUpgrade(characteristic Characteristic, upgrade Upgrade) error {

	// Get the attribute from the character's characteristic map.
	c, found := character.Characteristics[characteristic.Name]
	if !found {
		c = characteristic
	}

	// Increment the tier if the mark is default.
	if upgrade.Mark == MarkDefault {
		c.Tier++
	}

	// Parse the characteristic's upgrade value.
	raw := strings.TrimSpace(strings.TrimLeft(upgrade.Name, characteristic.Name))
	value, err := strconv.Atoi(raw)
	if err != nil {
		return NewError(InvalidCharacteristicValue)
	}

	// Update the characteristic value.
	if strings.HasPrefix(raw, "+") || strings.HasPrefix(raw, "-") {
		c.Value += value
	} else {
		c.Value = value
	}

	character.Characteristics[c.Name] = c

	return nil
}

func (character *Character) ApplySkillUpgrade(skill Skill, upgrade Upgrade) error {

	// Get the skill from the character's skill map.
	s, found := character.Skills[skill.FullName()]
	if !found {
		s = skill
	}

	// Increment the tier if the mark is default.
	if upgrade.Mark == MarkDefault {
		s.Tier++
	}

	// Put the skill back on the map.
	character.Skills[skill.FullName()] = s

	return nil
}

func (character *Character) ApplyTalentUpgrade(talent Talent, upgrade Upgrade) error {

	// Get the talent from the character.
	t, found := character.Talents[talent.FullName()]
	if !found {
		t = talent
	}

	// Increment the value of the talent.
	t.Value++

	// Put it back on the map.
	character.Talents[talent.FullName()] = t

	return nil
}

func (character *Character) ApplyAptitudeUpgrade(aptitude Aptitude, upgrade Upgrade) error {

	// Add the aptitude to the character's aptitudes.
	character.Aptitudes[string(aptitude)] = aptitude

	return nil
}

func (character *Character) ApplyGaugeUpgrade(gauge Gauge, upgrade Upgrade) error {

	// Get the gauge from the character.
	g, found := character.Gauges[gauge.Name]
	if !found {
		g = gauge
	}

	// Parse the gauge's upgrade value.
	raw := strings.TrimSpace(strings.TrimLeft(upgrade.Name, g.Name))
	value, err := strconv.Atoi(raw)
	if err != nil {
		return NewError(InvalidGaugeValue)
	}

	// Update the gauge value.
	if strings.HasPrefix(raw, "+") || strings.HasPrefix(raw, "-") {
		g.Value += value
	} else {
		g.Value = value
	}

	// Set the gauge back on the map.
	character.Gauges[g.Name] = g

	return nil
}

func (character *Character) ApplyRuleUpgrade(rule Rule, upgrade Upgrade) error {

	// Add the rule to the character's rules.
	character.Rules[rule.Name] = rule

	return nil
}

// Print the character sheet on the screen
func (character Character) Print() {
	// Print the name
	fmt.Printf("%s\t%s\n", theme.Title("Name"), character.Name)

	// Print the backgrounds
	backgrounds := []Background{}

	for _, background := range character.Backgrounds {
		backgrounds = append(backgrounds, background)
	}

	slice.Sort(backgrounds, func(i, j int) bool {
		if backgrounds[i].Type != backgrounds[j].Type {
			return backgrounds[i].Type < backgrounds[j].Type
		}

		return backgrounds[i].Name < backgrounds[j].Name
	})

	for _, background := range backgrounds {
		fmt.Printf("%s\t%s\n", theme.Title(strings.Title(background.Type)), strings.Title(background.Name))
	}

	// Print the experience
	fmt.Printf("\n%s\t%d/%d\n", theme.Title("Experience"), character.Spent, character.Experience)

	// Print the characteristics
	fmt.Printf("\n%s\n", theme.Title("Characteristics"))

	characteristics := []Characteristic{}
	for _, characteristic := range character.Characteristics {
		characteristics = append(characteristics, characteristic)
	}

	slice.Sort(characteristics, func(i, j int) bool {
		return characteristics[i].Name < characteristics[j].Name
	})

	for _, characteristic := range characteristics {
		fmt.Printf("%s\t%s\n", characteristic.Name, theme.Value(characteristic.Value))
	}

	// Print the talents
	fmt.Printf("\n%s\n", theme.Title("Talents"))

	talents := []Talent{}
	for _, talent := range character.Talents {
		talents = append(talents, talent)
	}

	slice.Sort(talents, func(i, j int) bool {
		return talents[i].Name < talents[j].Name
	})

	for _, talent := range talents {
		if talent.Value != 1 {
			fmt.Printf("%s (%s)\n", strings.Title(talent.Name), theme.Value(talent.Value))
		} else {
			fmt.Printf("%s\n", strings.Title(talent.Name))
		}
	}

	// Print the skills using a tabwriter
	fmt.Printf("\n%s\n", theme.Title("Skills"))

	skills := []Skill{}
	for _, skill := range character.Skills {
		skills = append(skills, skill)
	}

	slice.Sort(skills, func(i, j int) bool {
		return skills[i].Name < skills[j].Name
	})

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 2, ' ', 0)
	for _, skill := range skills {
		fmt.Fprintf(w, "%s\t%s\n", strings.Title(skill.Name), theme.Value(skill.Tier))
	}
	w.Flush()

	// Print the special rules
	fmt.Printf("\n%s\n", theme.Title("Rules"))

	rules := []Rule{}

	for _, rule := range character.Rules {
		rules = append(rules, rule)
	}

	slice.Sort(rules, func(i, j int) bool {
		return rules[i].Name < rules[j].Name
	})

	for _, rule := range rules {
		fmt.Printf("%s\t%s\n", strings.Title(rule.Name), rule.Description)
	}
}
<commit_msg>Add display of the gauges<commit_after>package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/bradfitz/slice"
)

// Character is the type representing a role playing character
type Character struct {
	Name            string
	Backgrounds     map[string]Background
	Aptitudes       map[string]Aptitude
	Characteristics map[string]Characteristic
	Skills          map[string]Skill
	Talents         map[string]Talent
	Gauges          map[string]Gauge
	Rules           map[string]Rule
	Experience      int
	Spent           int
}

// NewCharacter creates a new character from the given sheet and universe.
func NewCharacter(universe Universe, sheet Sheet) (Character, error) {

	// Create a character
	c := Character{
		Name:            sheet.Header.Name,
		Backgrounds:     make(map[string]Background),
		Aptitudes:       make(map[string]Aptitude),
		Characteristics: make(map[string]Characteristic),
		Skills:          make(map[string]Skill),
		Talents:         make(map[string]Talent),
		Gauges:          make(map[string]Gauge),
		Rules:           make(map[string]Rule),
		Experience:      0,
		Spent:           0,
	}

	// The characteristics described in the header of the sheet are parsed as upgrades
	for _, upgrade := range sheet.Characteristics {

		// Get the characteristic from the universe
		characteristic, found := universe.FindCharacteristic(upgrade.Name)
		if !found {
			return c, NewError(UndefinedCharacteristic, upgrade.Line)
		}

		// Check it is not already applied
		_, found = c.Characteristics[characteristic.Name]
		if found {
			return c, NewError(DuplicateCharacteristic, upgrade.Line)
		}

		// Apply the upgrade
		err := c.ApplyCharacteristicUpgrade(characteristic, upgrade)
		if err != nil {
			return c, err
		}
	}

	// Next are the backgrounds
	for typ, metas := range sheet.Header.Metas {

		for _, meta := range metas {

			// Find the background corresponding to the meta
			background, err := universe.FindBackground(typ, meta.Label)
			if err != nil {
				return c, err
			}

			err = c.ApplyBackground(background, universe)
			if err != nil {
				return c, err
			}
		}
	}

	// Next are the sessions
	for _, session := range sheet.Sessions {

		// Apply the experience gain if needed
		if session.Reward != nil {
			c.Experience += *session.Reward
		}

		// Apply each upgrade in order
		for _, upgrade := range session.Upgrades {
			err := c.ApplyUpgrade(upgrade, universe)
			if err != nil {
				return c, err
			}
		}
	}

	return c, nil
}

// CountMatchingAptitudes return the number of aptitudes of the given slice
// that are in the character's aptitudes.
func (character Character) CountMatchingAptitudes(aptitudes []Aptitude) int {

	count := 0
	for _, aptitude := range aptitudes {
		if _, found := character.Aptitudes[string(aptitude)]; found {
			count++
		}
	}
	return count
}

// ApplyBackground changes the character's trait according to the history values
func (character *Character) ApplyBackground(background Background, universe Universe) error {

	cost := 0

	// For each upgrade associated to the history, apply each option.
	for _, upgrade := range background.Upgrades {
		err := character.ApplyUpgrade(Upgrade{
			Mark: MarkSpecial,
			Name: upgrade,
			Cost: &cost,
		}, universe)
		if err != nil {
			return err
		}
	}

	// Add the background to the character's backgrounds
	character.Backgrounds[background.Name] = background

	return nil
}

// ApplyUpgrade changes the character's attributes according to the given upgrade.
func (character *Character) ApplyUpgrade(upgrade Upgrade, universe Universe) error {

	var err error

	// Find the attribute corresponding to the upgrade., and initialize a new
	// rule if there isn't any.
	coster, found := universe.FindCoster(upgrade.Name)
	if !found {
		coster = Rule{
			Name: upgrade.Name,
		}
	}

	// If no cost is defined, compute it on the fly.
	if upgrade.Cost == nil {
		cost, err := coster.Cost(universe, *character)
		if err != nil {
			return err
		}

		upgrade.Cost = &cost
	}

	// Update the spent experience.
	character.Spent += *upgrade.Cost

	// Apply the upgrade depending on the target attribute.
	switch attribute := coster.(type) {
	case Characteristic:
		err = character.ApplyCharacteristicUpgrade(attribute, upgrade)
	case Skill:
		err = character.ApplySkillUpgrade(attribute, upgrade)
	case Talent:
		err = character.ApplyTalentUpgrade(attribute, upgrade)
	case Aptitude:
		err = character.ApplyAptitudeUpgrade(attribute, upgrade)
	case Gauge:
		err = character.ApplyGaugeUpgrade(attribute, upgrade)
	case Rule:
		err = character.ApplyRuleUpgrade(attribute, upgrade)
	}

	return err
}

func (character *Character) ApplyCharacteristicUpgrade(characteristic Characteristic, upgrade Upgrade) error {

	// Get the attribute from the character's characteristic map.
	c, found := character.Characteristics[characteristic.Name]
	if !found {
		c = characteristic
	}

	// Increment the tier if the mark is default.
	if upgrade.Mark == MarkDefault {
		c.Tier++
	}

	// Parse the characteristic's upgrade value.
	raw := strings.TrimSpace(strings.TrimLeft(upgrade.Name, characteristic.Name))
	value, err := strconv.Atoi(raw)
	if err != nil {
		return NewError(InvalidCharacteristicValue)
	}

	// Update the characteristic value.
	if strings.HasPrefix(raw, "+") || strings.HasPrefix(raw, "-") {
		c.Value += value
	} else {
		c.Value = value
	}

	character.Characteristics[c.Name] = c

	return nil
}

func (character *Character) ApplySkillUpgrade(skill Skill, upgrade Upgrade) error {

	// Get the skill from the character's skill map.
	s, found := character.Skills[skill.FullName()]
	if !found {
		s = skill
	}

	// Increment the tier if the mark is default.
	if upgrade.Mark == MarkDefault {
		s.Tier++
	}

	// Put the skill back on the map.
	character.Skills[skill.FullName()] = s

	return nil
}

func (character *Character) ApplyTalentUpgrade(talent Talent, upgrade Upgrade) error {

	// Get the talent from the character.
	t, found := character.Talents[talent.FullName()]
	if !found {
		t = talent
	}

	// Increment the value of the talent.
	t.Value++

	// Put it back on the map.
	character.Talents[talent.FullName()] = t

	return nil
}

func (character *Character) ApplyAptitudeUpgrade(aptitude Aptitude, upgrade Upgrade) error {

	// Add the aptitude to the character's aptitudes.
	character.Aptitudes[string(aptitude)] = aptitude

	return nil
}

func (character *Character) ApplyGaugeUpgrade(gauge Gauge, upgrade Upgrade) error {

	// Get the gauge from the character.
	g, found := character.Gauges[gauge.Name]
	if !found {
		g = gauge
	}

	// Parse the gauge's upgrade value.
	raw := strings.TrimSpace(strings.TrimLeft(upgrade.Name, g.Name))
	value, err := strconv.Atoi(raw)
	if err != nil {
		return NewError(InvalidGaugeValue)
	}

	// Update the gauge value.
	if strings.HasPrefix(raw, "+") || strings.HasPrefix(raw, "-") {
		g.Value += value
	} else {
		g.Value = value
	}

	// Set the gauge back on the map.
	character.Gauges[g.Name] = g

	return nil
}

func (character *Character) ApplyRuleUpgrade(rule Rule, upgrade Upgrade) error {

	// Add the rule to the character's rules.
	character.Rules[rule.Name] = rule

	return nil
}

// Print the character sheet on the screen
func (character Character) Print() {
	// Print the name
	fmt.Printf("%s\t%s\n", theme.Title("Name"), character.Name)

	// Print the backgrounds
	backgrounds := []Background{}

	for _, background := range character.Backgrounds {
		backgrounds = append(backgrounds, background)
	}

	slice.Sort(backgrounds, func(i, j int) bool {
		if backgrounds[i].Type != backgrounds[j].Type {
			return backgrounds[i].Type < backgrounds[j].Type
		}

		return backgrounds[i].Name < backgrounds[j].Name
	})

	for _, background := range backgrounds {
		fmt.Printf("%s\t%s\n", theme.Title(strings.Title(background.Type)), strings.Title(background.Name))
	}

	// Print the experience
	fmt.Printf("\n%s\t%d/%d\n", theme.Title("Experience"), character.Spent, character.Experience)

	// Print the characteristics
	fmt.Printf("\n%s\n", theme.Title("Characteristics"))

	characteristics := []Characteristic{}
	for _, characteristic := range character.Characteristics {
		characteristics = append(characteristics, characteristic)
	}

	slice.Sort(characteristics, func(i, j int) bool {
		return characteristics[i].Name < characteristics[j].Name
	})

	for _, characteristic := range characteristics {
		fmt.Printf("%s\t%s\n", characteristic.Name, theme.Value(characteristic.Value))
	}

	// Print the gauges
	fmt.Printf("\n%s\n", theme.Title("Gauges"))

	gauges := []Gauge{}
	for _, gauge := range character.Gauges {
		gauges = append(gauges, gauge)
	}

	slice.Sort(gauges, func(i, j int) bool {
		return gauges[i].Name < gauges[j].Name
	})

	for _, gauge := range gauges {
		fmt.Printf("%s\t%s\n", gauge.Name, theme.Value(gauge.Value))
	}

	// Print the talents
	fmt.Printf("\n%s\n", theme.Title("Talents"))

	talents := []Talent{}
	for _, talent := range character.Talents {
		talents = append(talents, talent)
	}

	slice.Sort(talents, func(i, j int) bool {
		return talents[i].Name < talents[j].Name
	})

	for _, talent := range talents {
		if talent.Value != 1 {
			fmt.Printf("%s (%s)\n", strings.Title(talent.Name), theme.Value(talent.Value))
		} else {
			fmt.Printf("%s\n", strings.Title(talent.Name))
		}
	}

	// Print the skills using a tabwriter
	fmt.Printf("\n%s\n", theme.Title("Skills"))

	skills := []Skill{}
	for _, skill := range character.Skills {
		skills = append(skills, skill)
	}

	slice.Sort(skills, func(i, j int) bool {
		return skills[i].Name < skills[j].Name
	})

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 2, ' ', 0)
	for _, skill := range skills {
		fmt.Fprintf(w, "%s\t%s\n", strings.Title(skill.Name), theme.Value(skill.Tier))
	}
	w.Flush()

	// Print the special rules
	fmt.Printf("\n%s\n", theme.Title("Rules"))

	rules := []Rule{}

	for _, rule := range character.Rules {
		rules = append(rules, rule)
	}

	slice.Sort(rules, func(i, j int) bool {
		return rules[i].Name < rules[j].Name
	})

	for _, rule := range rules {
		fmt.Printf("%s\t%s\n", strings.Title(rule.Name), rule.Description)
	}
}
<|endoftext|>


<commit_before>package qingcloud

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"
	qc "github.com/yunify/qingcloud-sdk-go/service"
)

func modifyInstanceAttributes(d *schema.ResourceData, meta interface{}, create bool) error {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.ModifyInstanceAttributesInput)
	input.Instance = qc.String(d.Id())
	if create {
		if description := d.Get("description").(string); description == "" {
			return nil
		}
		input.Description = qc.String(d.Get("description").(string))
	} else {
		if !d.HasChange("description") && !d.HasChange("name") {
			return nil
		}
		if d.HasChange("description") {
			input.Description = qc.String(d.Get("description").(string))
		}
		if d.HasChange("name") {
			input.InstanceName = qc.String(d.Get("name").(string))
		}
	}
	_, err := clt.ModifyInstanceAttributes(input)
	if err != nil {
		return fmt.Errorf("Error modify instance attributes: %s", err)
	}
	return nil
}

func instanceUpdateChangeVxNet(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("vxnet_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	vxnetClt := meta.(*QingCloudClient).vxnet
	oldV, newV := d.GetChange("vxnet_id")
	// leave old vxnet
	if oldV.(string) != "" {
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
		leaveVxnetInput := new(qc.LeaveVxNetInput)
		leaveVxnetInput.Instances = []*string{qc.String(d.Id())}
		leaveVxnetInput.VxNet = qc.String(oldV.(string))

		_, err := vxnetClt.LeaveVxNet(leaveVxnetInput)
		if err != nil {
			return fmt.Errorf("Error leave vxnet: %s", err)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	// join new vxnet
	if newV.(string) != "" {
		joinVxnetInput := new(qc.JoinVxNetInput)
		joinVxnetInput.Instances = []*string{qc.String(d.Id())}
		joinVxnetInput.VxNet = qc.String(newV.(string))
		_, err := vxnetClt.JoinVxNet(joinVxnetInput)
		if err != nil {
			return fmt.Errorf("Error leave vxnet: %s", err)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	return nil
}

func instanceUpdateChangeSecurityGroup(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("security_group_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	sgClt := meta.(*QingCloudClient).securitygroup
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	input := new(qc.ApplySecurityGroupInput)
	input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
	input.Instances = []*string{qc.String(d.Id())}
	_, err := sgClt.ApplySecurityGroup(input)
	if err != nil {
		return fmt.Errorf("Error apply security group: %s", err)
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func instanceUpdateChangeEip(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("eip_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	eipClt := meta.(*QingCloudClient).eip
	describeEIPInput := new(qc.DescribeEIPsInput)
	describeEIPInput.EIPs = []*string{qc.String(d.Get("eip_id").(string))}
	describeEIPOutput, err := eipClt.DescribeEIPs(describeEIPInput)
	if err != nil {
		return fmt.Errorf("Error describe eip: %s", err)
	}
	if qc.StringValue(describeEIPOutput.EIPSet[0].Status) != "available" {
		return fmt.Errorf("Error eip %s state is %s", d.Get("eip_id").(string), qc.StringValue(describeEIPOutput.EIPSet[0].Status))
	}
	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	oldV, newV := d.GetChange("eip_id")
	// dissociate old eip
	if oldV.(string) != "" {
		dissociateEIPInput := new(qc.DissociateEIPsInput)
		dissociateEIPInput.EIPs = []*string{qc.String(oldV.(string))}
		_, err := eipClt.DissociateEIPs(dissociateEIPInput)
		if err != nil {
			return fmt.Errorf("Error dissociate eip: %s", err)
		}
	}

	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// associate new eip
	if newV.(string) != "" {
		assoicateEIPInput := new(qc.AssociateEIPInput)
		assoicateEIPInput.EIP = qc.String(newV.(string))
		assoicateEIPInput.Instance = qc.String(d.Id())
		_, err := eipClt.AssociateEIP(assoicateEIPInput)
		if err != nil {
			return fmt.Errorf("Error assoicate eip: %s", err)
		}
	}
	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func instanceUpdateChangeKeyPairs(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("keypair_ids") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	kpClt := meta.(*QingCloudClient).keypair

	oldV, newV := d.GetChange("keypair_ids")
	var nkps []string
	var okps []string
	for _, v := range oldV.(*schema.Set).List() {
		okps = append(okps, v.(string))
	}
	for _, v := range newV.(*schema.Set).List() {
		nkps = append(nkps, v.(string))
	}
	additions, deletions := stringSliceDiff(nkps, okps)
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err == nil {
		return err
	}
	// attach new key_pair
	if len(additions) > 0 {
		attachInput := new(qc.AttachKeyPairsInput)
		attachInput.Instances = []*string{qc.String(d.Id())}
		attachInput.KeyPairs = qc.StringSlice(additions)
		_, err := kpClt.AttachKeyPairs(attachInput)
		if err != nil {
			return fmt.Errorf("Error attach keypairs: %s", err)
		}
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// dettach old key_pair
	if len(deletions) > 0 {
		detachInput := new(qc.DetachKeyPairsInput)
		detachInput.Instances = []*string{qc.String(d.Id())}
		detachInput.KeyPairs = qc.StringSlice(deletions)
		_, err := kpClt.DetachKeyPairs(detachInput)
		if err != nil {
			return fmt.Errorf("Errorr detach keypairs: %s", err)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	return nil
}

func instanceUpdateResize(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("cpu") && !d.HasChange("memory") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	// check instance state
	describeInstanceOutput, err := describeInstance(d, meta)
	if err != nil {
		return err
	}
	instance := describeInstanceOutput.InstanceSet[0]
	// stop instance
	if *instance.Status == "running" {
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			log.Printf("InstanceTransitionStateRefresh err: %s", err)
			return err
		}
		_, err := stopInstance(d, meta)
		if err != nil {
			return err
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	//  resize instance
	input := new(qc.ResizeInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	input.CPU = qc.Int(d.Get("cpu").(int))
	input.Memory = qc.Int(d.Get("memory").(int))
	_, err = clt.ResizeInstances(input)
	if err != nil {
		return fmt.Errorf("Error resize instance: %s", err)
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// start instance
	_, err = startInstance(d, meta)
	if err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func describeInstance(d *schema.ResourceData, meta interface{}) (*qc.DescribeInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.DescribeInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error describe instance: %s", err)
	}
	return output, nil
}

func stopInstance(d *schema.ResourceData, meta interface{}) (*qc.StopInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.StopInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	output, err := clt.StopInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error stop instance: %s", err)
	}
	return output, nil
}

func startInstance(d *schema.ResourceData, meta interface{}) (*qc.StartInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.StartInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	output, err := clt.StartInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error start instance: %s", err)
	}
	return output, nil
}

func deleteInstanceLeaveVxnet(d *schema.ResourceData, meta interface{}) (*qc.LeaveVxNetOutput, error) {
	vxnetID := d.Get("vxnet_id").(string)
	if vxnetID != "" {
		clt := meta.(*QingCloudClient).vxnet
		input := new(qc.LeaveVxNetInput)
		input.Instances = []*string{qc.String(d.Id())}
		input.VxNet = qc.String(vxnetID)
		_, err := clt.LeaveVxNet(input)
		if err != nil {
			return nil, fmt.Errorf("Error instance leave vxnet: %s", err)
		}
	}
	return nil, nil
}

func deleteInstanceDissociateEip(d *schema.ResourceData, meta interface{}) (*qc.LeaveVxNetOutput, error) {
	eipID := d.Get("eip_id").(string)
	if eipID != "" {
		clt := meta.(*QingCloudClient).eip
		if _, err := EIPTransitionStateRefresh(clt, eipID); err != nil {
			return nil, err
		}
		input := new(qc.DissociateEIPsInput)
		input.EIPs = []*string{qc.String(eipID)}
		_, err := clt.DissociateEIPs(input)
		if err != nil {
			return nil, fmt.Errorf("Error dissciate eip: %s", err)
		}
		if _, err := EIPTransitionStateRefresh(clt, eipID); err != nil {
			return nil, err
		}
	}
	return nil, nil
}

func updateInstanceVolume(d *schema.ResourceData, meta interface{}, create bool) error {
	// clt := meta.(*QingCloudClient).instance
	return nil
}
<commit_msg>fix instance keypairs error judge && add RetCode judge<commit_after>package qingcloud

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"
	qc "github.com/yunify/qingcloud-sdk-go/service"
)

func modifyInstanceAttributes(d *schema.ResourceData, meta interface{}, create bool) error {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.ModifyInstanceAttributesInput)
	input.Instance = qc.String(d.Id())
	if create {
		if description := d.Get("description").(string); description == "" {
			return nil
		}
		input.Description = qc.String(d.Get("description").(string))
	} else {
		if !d.HasChange("description") && !d.HasChange("name") {
			return nil
		}
		if d.HasChange("description") {
			input.Description = qc.String(d.Get("description").(string))
		}
		if d.HasChange("name") {
			input.InstanceName = qc.String(d.Get("name").(string))
		}
	}
	_, err := clt.ModifyInstanceAttributes(input)
	if err != nil {
		return fmt.Errorf("Error modify instance attributes: %s", err)
	}
	return nil
}

func instanceUpdateChangeVxNet(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("vxnet_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	vxnetClt := meta.(*QingCloudClient).vxnet
	oldV, newV := d.GetChange("vxnet_id")
	// leave old vxnet
	if oldV.(string) != "" {
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
		leaveVxnetInput := new(qc.LeaveVxNetInput)
		leaveVxnetInput.Instances = []*string{qc.String(d.Id())}
		leaveVxnetInput.VxNet = qc.String(oldV.(string))

		_, err := vxnetClt.LeaveVxNet(leaveVxnetInput)
		if err != nil {
			return fmt.Errorf("Error leave vxnet: %s", err)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	// join new vxnet
	if newV.(string) != "" {
		joinVxnetInput := new(qc.JoinVxNetInput)
		joinVxnetInput.Instances = []*string{qc.String(d.Id())}
		joinVxnetInput.VxNet = qc.String(newV.(string))
		_, err := vxnetClt.JoinVxNet(joinVxnetInput)
		if err != nil {
			return fmt.Errorf("Error leave vxnet: %s", err)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	return nil
}

func instanceUpdateChangeSecurityGroup(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("security_group_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	sgClt := meta.(*QingCloudClient).securitygroup
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	input := new(qc.ApplySecurityGroupInput)
	input.SecurityGroup = qc.String(d.Get("security_group_id").(string))
	input.Instances = []*string{qc.String(d.Id())}
	_, err := sgClt.ApplySecurityGroup(input)
	if err != nil {
		return fmt.Errorf("Error apply security group: %s", err)
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func instanceUpdateChangeEip(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("eip_id") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	eipClt := meta.(*QingCloudClient).eip
	describeEIPInput := new(qc.DescribeEIPsInput)
	describeEIPInput.EIPs = []*string{qc.String(d.Get("eip_id").(string))}
	describeEIPOutput, err := eipClt.DescribeEIPs(describeEIPInput)
	if err != nil {
		return fmt.Errorf("Error describe eip: %s", err)
	}
	if qc.StringValue(describeEIPOutput.EIPSet[0].Status) != "available" {
		return fmt.Errorf("Error eip %s state is %s", d.Get("eip_id").(string), qc.StringValue(describeEIPOutput.EIPSet[0].Status))
	}
	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	oldV, newV := d.GetChange("eip_id")
	// dissociate old eip
	if oldV.(string) != "" {
		dissociateEIPInput := new(qc.DissociateEIPsInput)
		dissociateEIPInput.EIPs = []*string{qc.String(oldV.(string))}
		_, err := eipClt.DissociateEIPs(dissociateEIPInput)
		if err != nil {
			return fmt.Errorf("Error dissociate eip: %s", err)
		}
	}

	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// associate new eip
	if newV.(string) != "" {
		assoicateEIPInput := new(qc.AssociateEIPInput)
		assoicateEIPInput.EIP = qc.String(newV.(string))
		assoicateEIPInput.Instance = qc.String(d.Id())
		_, err := eipClt.AssociateEIP(assoicateEIPInput)
		if err != nil {
			return fmt.Errorf("Error assoicate eip: %s", err)
		}
	}
	if _, err := EIPTransitionStateRefresh(eipClt, d.Get("eip_id").(string)); err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func instanceUpdateChangeKeyPairs(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("keypair_ids") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	kpClt := meta.(*QingCloudClient).keypair

	oldV, newV := d.GetChange("keypair_ids")
	var nkps []string
	var okps []string
	for _, v := range oldV.(*schema.Set).List() {
		okps = append(okps, v.(string))
	}
	for _, v := range newV.(*schema.Set).List() {
		nkps = append(nkps, v.(string))
	}
	additions, deletions := stringSliceDiff(nkps, okps)
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// attach new key_pair
	if len(additions) > 0 {
		attachInput := new(qc.AttachKeyPairsInput)
		attachInput.Instances = []*string{qc.String(d.Id())}
		attachInput.KeyPairs = qc.StringSlice(additions)
		output, err := kpClt.AttachKeyPairs(attachInput)
		if err != nil {
			return fmt.Errorf("Error attach keypairs: %s", err)
		} else if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
			return fmt.Errorf("Error attach keypair: %s", *output.Message)
		}
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// dettach old key_pair
	if len(deletions) > 0 {
		detachInput := new(qc.DetachKeyPairsInput)
		detachInput.Instances = []*string{qc.String(d.Id())}
		detachInput.KeyPairs = qc.StringSlice(deletions)
		output, err := kpClt.DetachKeyPairs(detachInput)
		if err != nil {
			return fmt.Errorf("Errorr detach keypairs: %s", err)
		} else if output.RetCode != nil && qc.IntValue(output.RetCode) != 0 {
			return fmt.Errorf("Error detach keypair: %s", *output.Message)
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	return nil
}

func instanceUpdateResize(d *schema.ResourceData, meta interface{}) error {
	if !d.HasChange("cpu") && !d.HasChange("memory") {
		return nil
	}
	clt := meta.(*QingCloudClient).instance
	// check instance state
	describeInstanceOutput, err := describeInstance(d, meta)
	if err != nil {
		return err
	}
	instance := describeInstanceOutput.InstanceSet[0]
	// stop instance
	if *instance.Status == "running" {
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			log.Printf("InstanceTransitionStateRefresh err: %s", err)
			return err
		}
		_, err := stopInstance(d, meta)
		if err != nil {
			return err
		}
		if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
			return err
		}
	}
	//  resize instance
	input := new(qc.ResizeInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	input.CPU = qc.Int(d.Get("cpu").(int))
	input.Memory = qc.Int(d.Get("memory").(int))
	_, err = clt.ResizeInstances(input)
	if err != nil {
		return fmt.Errorf("Error resize instance: %s", err)
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	// start instance
	_, err = startInstance(d, meta)
	if err != nil {
		return err
	}
	if _, err := InstanceTransitionStateRefresh(clt, d.Id()); err != nil {
		return err
	}
	return nil
}

func describeInstance(d *schema.ResourceData, meta interface{}) (*qc.DescribeInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.DescribeInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error describe instance: %s", err)
	}
	return output, nil
}

func stopInstance(d *schema.ResourceData, meta interface{}) (*qc.StopInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.StopInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	output, err := clt.StopInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error stop instance: %s", err)
	}
	return output, nil
}

func startInstance(d *schema.ResourceData, meta interface{}) (*qc.StartInstancesOutput, error) {
	clt := meta.(*QingCloudClient).instance
	input := new(qc.StartInstancesInput)
	input.Instances = []*string{qc.String(d.Id())}
	output, err := clt.StartInstances(input)
	if err != nil {
		return nil, fmt.Errorf("Error start instance: %s", err)
	}
	return output, nil
}

func deleteInstanceLeaveVxnet(d *schema.ResourceData, meta interface{}) (*qc.LeaveVxNetOutput, error) {
	vxnetID := d.Get("vxnet_id").(string)
	if vxnetID != "" {
		clt := meta.(*QingCloudClient).vxnet
		input := new(qc.LeaveVxNetInput)
		input.Instances = []*string{qc.String(d.Id())}
		input.VxNet = qc.String(vxnetID)
		_, err := clt.LeaveVxNet(input)
		if err != nil {
			return nil, fmt.Errorf("Error instance leave vxnet: %s", err)
		}
	}
	return nil, nil
}

func deleteInstanceDissociateEip(d *schema.ResourceData, meta interface{}) (*qc.LeaveVxNetOutput, error) {
	eipID := d.Get("eip_id").(string)
	if eipID != "" {
		clt := meta.(*QingCloudClient).eip
		if _, err := EIPTransitionStateRefresh(clt, eipID); err != nil {
			return nil, err
		}
		input := new(qc.DissociateEIPsInput)
		input.EIPs = []*string{qc.String(eipID)}
		_, err := clt.DissociateEIPs(input)
		if err != nil {
			return nil, fmt.Errorf("Error dissciate eip: %s", err)
		}
		if _, err := EIPTransitionStateRefresh(clt, eipID); err != nil {
			return nil, err
		}
	}
	return nil, nil
}

func updateInstanceVolume(d *schema.ResourceData, meta interface{}, create bool) error {
	// clt := meta.(*QingCloudClient).instance
	return nil
}
<|endoftext|>


<commit_before>package servers

import (
	"fmt"
	"net/http"
	"testing"

	"github.com/rackspace/gophercloud/pagination"
	th "github.com/rackspace/gophercloud/testhelper"
	"github.com/rackspace/gophercloud/testhelper/client"
)

func TestListServers(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()

	th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

		w.Header().Add("Content-Type", "application/json")
		fmt.Fprintf(w, ListOutput)
	})

	count := 0
	err := List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
		count++
		actual, err := ExtractServers(page)
		th.AssertNoErr(t, err)
		th.CheckDeepEquals(t, ExpectedServerSlice, actual)

		return true, nil
	})
	th.AssertNoErr(t, err)
	th.CheckEquals(t, 1, count)
}

func TestGetServer(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()

	th.Mux.HandleFunc("/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

		w.Header().Add("Content-Type", "application/json")
		fmt.Fprintf(w, GetOutput)
	})

	actual, err := Get(client.ServiceClient(), "8c65cb68-0681-4c30-bc88-6b83a8a26aee").Extract()
	th.AssertNoErr(t, err)
	th.CheckDeepEquals(t, &GophercloudServer, actual)
}
<commit_msg>Unit test for changing the admin password.<commit_after>package servers

import (
	"fmt"
	"net/http"
	"testing"

	"github.com/rackspace/gophercloud/pagination"
	th "github.com/rackspace/gophercloud/testhelper"
	"github.com/rackspace/gophercloud/testhelper/client"
)

func TestListServers(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()

	th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

		w.Header().Add("Content-Type", "application/json")
		fmt.Fprintf(w, ListOutput)
	})

	count := 0
	err := List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) {
		count++
		actual, err := ExtractServers(page)
		th.AssertNoErr(t, err)
		th.CheckDeepEquals(t, ExpectedServerSlice, actual)

		return true, nil
	})
	th.AssertNoErr(t, err)
	th.CheckEquals(t, 1, count)
}

func TestGetServer(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()

	th.Mux.HandleFunc("/servers/8c65cb68-0681-4c30-bc88-6b83a8a26aee", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "GET")
		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

		w.Header().Add("Content-Type", "application/json")
		fmt.Fprintf(w, GetOutput)
	})

	actual, err := Get(client.ServiceClient(), "8c65cb68-0681-4c30-bc88-6b83a8a26aee").Extract()
	th.AssertNoErr(t, err)
	th.CheckDeepEquals(t, &GophercloudServer, actual)
}

func TestChangeAdminPassword(t *testing.T) {
	th.SetupHTTP()
	defer th.TeardownHTTP()

	th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
		th.TestMethod(t, r, "POST")
		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
		th.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`)

		w.WriteHeader(http.StatusAccepted)
	})

	res := ChangeAdminPassword(client.ServiceClient(), "1234asdf", "new-password")
	th.AssertNoErr(t, res.Err)
}
<|endoftext|>