<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/flynn/flynn/controller/client"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/go-docopt"
)

func init() {
	register("release", runRelease, `
usage: flynn release [-q|--quiet]
       flynn release add [-t <type>] [-f <file>] <uri>
       flynn release update <file> [<id>] [--clean]
       flynn release show [--json] [<id>]
       flynn release delete [-y] <id>
       flynn release rollback [-y] [<id>]

Manage app releases.

Options:
	-q, --quiet        only print release IDs
	-t <type>          type of the release. Currently only 'docker' is supported. [default: docker]
	-f, --file=<file>  release configuration file
	--json             print release configuration in JSON format
	--clean            update from a clean slate (ignoring prior config)
	-y, --yes          skip the confirmation prompt when deleting a release

Commands:
	With no arguments, shows a list of releases associated with the app.

	add
		DEPRECATED: Only works on legacy clusters.

		Create a new release from a Docker image.

		The optional file argument takes a path to a file containing release
		configuration in a JSON format. It's primarily used for specifying the
		release environment and processes (similar to a Procfile). It can take any
		of the arguments the controller Release type can take.

	show
		Show information about a release.

		Omit the ID to show information about the current release.

	update
		Update an existing release.

		Takes a path to a file containing release configuration in a JSON format.
		It can take any of the arguments the controller Release type can take, and
		will override existing config with any values set thus. Omit the ID to
		update the current release.

		By default, the new release fields will be merged with the previous one,
		specify --clean to not copy any fields from the previous release.

	delete
		Delete a release.

		Any associated file artifacts (e.g. slugs) will also be deleted.

	rollback
		Rollback to a previous release. Deploys the previous release or specified release ID.

Examples:

	Release an echo server using the flynn/slugbuilder image as a base, running socat.

	$ cat config.json
	{
		"env": {"MY_VAR": "Hello World, this will be available in all process types."},
		"processes": {
			"echo": {
				"args": ["sh", "-c", "socat -v tcp-l:$PORT,fork exec:/bin/cat"],
				"env": {"ECHO": "This var is specific to the echo process type."},
				"ports": [{"proto": "tcp"}]
			}
		}
	}
	$ flynn release add -f config.json https://registry.hub.docker.com?name=flynn/slugbuilder&id=15d72b7f573b
	Created release 989ce4a8-0088-444c-8379-caddded4b957.

	$ flynn release
	ID                                Created
	989ce4a8-0088-444c-8379-caddded4b957  11 seconds ago

	$ flynn release show
	ID:             989ce4a8-0088-444c-8379-caddded4b957
	Artifact:       docker+https://registry.hub.docker.com?name=flynn/slugbuilder&id=15d72b7f573b
	Process Types:  echo
	Created At:     2015-05-06 21:58:12.751741 +0000 UTC
	ENV[MY_VAR]:    Hello World, this will be available in all process types.

	$ cat update.json
	{
		"processes": {
			"echo": {
				"omni": true
			}
		}
	}
	$ flynn release update update.json
	Created release 1a270395-8d31-4ec1-953a-0683b4f12635.

	$ flynn release delete --yes c6b7f512-ef49-46f7-bb57-dd39e97bfb09
	Deleted release c6b7f512-ef49-46f7-bb57-dd39e97bfb09 (deleted 1 files)
`)
}

func runRelease(args *docopt.Args, client controller.Client) error {
	if args.Bool["show"] {
		return runReleaseShow(args, client)
	}
	if args.Bool["add"] {
		if args.String["-t"] == "docker" {
			return runReleaseAddDocker(args, client)
		} else {
			return fmt.Errorf("Release type %s not supported.", args.String["-t"])
		}
	}
	if args.Bool["update"] {
		return runReleaseUpdate(args, client)
	}
	if args.Bool["delete"] {
		return runReleaseDelete(args, client)
	}
	if args.Bool["rollback"] {
		return runReleaseRollback(args, client)
	}
	return runReleaseList(args, client)
}

func runReleaseList(args *docopt.Args, client controller.Client) error {
	list, err := client.AppReleaseList(mustApp())
	if err != nil {
		return err
	}

	if args.Bool["--quiet"] {
		for _, r := range list {
			fmt.Println(r.ID)
		}
		return nil
	}

	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()
	listRec(w, "ID", "Created")
	for _, r := range list {
		listRec(w, r.ID, humanTime(r.CreatedAt))
	}
	return nil
}

func runReleaseShow(args *docopt.Args, client controller.Client) error {
	var release *ct.Release
	var err error
	if args.String["<id>"] != "" {
		release, err = client.GetRelease(args.String["<id>"])
	} else {
		release, err = client.GetAppRelease(mustApp())
	}
	if err != nil {
		return err
	}
	if args.Bool["--json"] {
		return json.NewEncoder(os.Stdout).Encode(release)
	}
	var artifacts []string
	for _, id := range release.ArtifactIDs {
		artifact, err := client.GetArtifact(id)
		if err != nil {
			return err
		}
		artifacts = append(artifacts, fmt.Sprintf("%s+%s", artifact.Type, artifact.URI))
	}
	types := make([]string, 0, len(release.Processes))
	for typ := range release.Processes {
		types = append(types, typ)
	}
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()
	listRec(w, "ID:", release.ID)
	for i, artifact := range artifacts {
		listRec(w, fmt.Sprintf("Artifact[%d]:", i), artifact)
	}
	listRec(w, "Process Types:", strings.Join(types, ", "))
	listRec(w, "Created At:", release.CreatedAt)
	for k, v := range release.Env {
		listRec(w, fmt.Sprintf("ENV[%s]", k), v)
	}
	return nil
}

func runReleaseAddDocker(args *docopt.Args, client controller.Client) error {
	fmt.Fprintln(os.Stderr, "WARN: The 'release add' command is deprecated and only works on legacy clusters, use 'docker push' to push Docker images")

	app, err := client.GetApp(mustApp())
	if err != nil {
		return err
	}
	release := &ct.Release{}
	if args.String["--file"] != "" {
		data, err := ioutil.ReadFile(args.String["--file"])
		if err != nil {
			return err
		}
		if err := json.Unmarshal(data, release); err != nil {
			return err
		}
	}

	artifact := &ct.Artifact{
		Type: ct.DeprecatedArtifactTypeDocker,
		URI:  args.String["<uri>"],
	}
	if err := client.CreateArtifact(artifact); err != nil {
		return err
	}

	release.ArtifactIDs = []string{artifact.ID}
	if err := client.CreateRelease(app.ID, release); err != nil {
		return err
	}

	if err := client.DeployAppRelease(app.ID, release.ID, nil); err != nil {
		return err
	}

	log.Printf("Created release %s.", release.ID)

	return nil
}

func runReleaseUpdate(args *docopt.Args, client controller.Client) error {
	app, err := client.GetApp(mustApp())
	if err != nil {
		return err
	}
	var release *ct.Release
	if args.String["<id>"] != "" {
		release, err = client.GetRelease(args.String["<id>"])
	} else {
		release, err = client.GetAppRelease(app.ID)
	}
	if err != nil {
		return err
	}

	updates := &ct.Release{}
	data, err := ioutil.ReadFile(args.String["<file>"])
	if err != nil {
		return err
	}
	if err := json.Unmarshal(data, updates); err != nil {
		return err
	}

	// Basically, there's no way to merge JSON that can reliably knock out set values.
	// Instead, throw the --clean flag to start from a largely empty Release.
	if args.Bool["--clean"] {
		updates.ArtifactIDs = release.ArtifactIDs
		release = updates
		release.ID = ""
	} else {
		release.ID = ""

		if len(updates.Env) > 0 && len(release.Env) == 0 {
			release.Env = make(map[string]string, len(updates.Env))
		}
		for key, value := range updates.Env {
			release.Env[key] = value
		}

		if len(updates.Meta) > 0 && len(release.Meta) == 0 {
			release.Meta = make(map[string]string, len(updates.Meta))
		}
		for key, value := range updates.Meta {
			release.Meta[key] = value
		}

		if len(updates.Processes) > 0 && len(release.Processes) == 0 {
			release.Processes = make(map[string]ct.ProcessType, len(updates.Processes))
		}
		for procKey, procUpdate := range updates.Processes {
			procRelease, ok := release.Processes[procKey]
			if !ok {
				release.Processes[procKey] = procUpdate
				continue
			}

			if len(procUpdate.Args) > 0 {
				procRelease.Args = procUpdate.Args
			}

			if len(procUpdate.Env) > 0 && len(procRelease.Env) == 0 {
				procRelease.Env = make(map[string]string, len(procUpdate.Env))
			}
			for key, value := range procUpdate.Env {
				procRelease.Env[key] = value
			}

			if len(procUpdate.Ports) > 0 {
				procRelease.Ports = procUpdate.Ports
			}
			if len(procUpdate.Volumes) > 0 {
				procRelease.Volumes = procUpdate.Volumes
			}
			if procUpdate.DeprecatedData {
				fmt.Fprintln(os.Stderr, `WARN: ProcessType.Data is deprecated and will be removed in future versions, populate ProcessType.Volumes instead e.g. "volumes": [{"path": "/data"}]`)
				procRelease.DeprecatedData = true
			}
			if procUpdate.Omni {
				procRelease.Omni = true
			}
			if procUpdate.HostNetwork {
				procRelease.HostNetwork = true
			}
			if procUpdate.HostPIDNamespace {
				procRelease.HostPIDNamespace = true
			}
			if len(procUpdate.Service) > 0 {
				procRelease.Service = procUpdate.Service
			}
			if procUpdate.Resurrect {
				procRelease.Resurrect = true
			}
			for resKey, resValue := range procUpdate.Resources {
				procRelease.Resources[resKey] = resValue
			}
			if len(procUpdate.LinuxCapabilities) > 0 {
				procRelease.LinuxCapabilities = procUpdate.LinuxCapabilities
			}
			if len(procUpdate.AllowedDevices) > 0 {
				procRelease.AllowedDevices = procUpdate.AllowedDevices
			}

			release.Processes[procKey] = procRelease
		}
	}

	if err := client.CreateRelease(app.ID, release); err != nil {
		return err
	}

	if err := client.DeployAppRelease(app.ID, release.ID, nil); err != nil {
		return err
	}

	log.Printf("Created release %s.", release.ID)

	return nil
}

func runReleaseDelete(args *docopt.Args, client controller.Client) error {
	releaseID := args.String["<id>"]
	if !args.Bool["--yes"] {
		if !promptYesNo(fmt.Sprintf("Are you sure you want to delete release %q?", releaseID)) {
			return nil
		}
	}
	res, err := client.DeleteRelease(mustApp(), releaseID)
	if err != nil {
		return err
	}
	if len(res.RemainingApps) > 0 {
		log.Printf("Release scaled down for app but not fully deleted (still associated with %d other apps)", len(res.RemainingApps))
	} else {
		log.Printf("Deleted release %s (deleted %d files)", releaseID, len(res.DeletedFiles))
	}
	return nil
}

func runReleaseRollback(args *docopt.Args, client controller.Client) error {
	currentRelease, err := client.GetAppRelease(mustApp())
	if err != nil {
		return err
	}
	releaseID := args.String["<id>"]
	if releaseID == "" {
		releases, err := client.AppReleaseList(mustApp())
		if err != nil {
			return err
		}
		if len(releases) < 2 {
			return fmt.Errorf("Not enough releases to perform a rollback.")
		}
		releaseID = releases[1].ID
	} else if releaseID == currentRelease.ID {
		return fmt.Errorf("Release id given is the current release.")
	}

	if !args.Bool["--yes"] {
		if !promptYesNo(fmt.Sprintf("Are you sure you want to rollback to release %q?", releaseID)) {
			return nil
		}
	}

	log.Printf("Rolling back to release %s from %s.\n", releaseID, currentRelease.ID)

	if err := client.DeployAppRelease(mustApp(), releaseID, nil); err != nil {
		return err
	}

	log.Printf("Successfully rolled back to release %s.\n", releaseID)

	return nil
}
<commit_msg>cli: Support updating process mounts<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/flynn/flynn/controller/client"
	ct "github.com/flynn/flynn/controller/types"
	"github.com/flynn/go-docopt"
)

func init() {
	register("release", runRelease, `
usage: flynn release [-q|--quiet]
       flynn release add [-t <type>] [-f <file>] <uri>
       flynn release update <file> [<id>] [--clean]
       flynn release show [--json] [<id>]
       flynn release delete [-y] <id>
       flynn release rollback [-y] [<id>]

Manage app releases.

Options:
	-q, --quiet        only print release IDs
	-t <type>          type of the release. Currently only 'docker' is supported. [default: docker]
	-f, --file=<file>  release configuration file
	--json             print release configuration in JSON format
	--clean            update from a clean slate (ignoring prior config)
	-y, --yes          skip the confirmation prompt when deleting a release

Commands:
	With no arguments, shows a list of releases associated with the app.

	add
		DEPRECATED: Only works on legacy clusters.

		Create a new release from a Docker image.

		The optional file argument takes a path to a file containing release
		configuration in a JSON format. It's primarily used for specifying the
		release environment and processes (similar to a Procfile). It can take any
		of the arguments the controller Release type can take.

	show
		Show information about a release.

		Omit the ID to show information about the current release.

	update
		Update an existing release.

		Takes a path to a file containing release configuration in a JSON format.
		It can take any of the arguments the controller Release type can take, and
		will override existing config with any values set thus. Omit the ID to
		update the current release.

		By default, the new release fields will be merged with the previous one,
		specify --clean to not copy any fields from the previous release.

	delete
		Delete a release.

		Any associated file artifacts (e.g. slugs) will also be deleted.

	rollback
		Rollback to a previous release. Deploys the previous release or specified release ID.

Examples:

	Release an echo server using the flynn/slugbuilder image as a base, running socat.

	$ cat config.json
	{
		"env": {"MY_VAR": "Hello World, this will be available in all process types."},
		"processes": {
			"echo": {
				"args": ["sh", "-c", "socat -v tcp-l:$PORT,fork exec:/bin/cat"],
				"env": {"ECHO": "This var is specific to the echo process type."},
				"ports": [{"proto": "tcp"}]
			}
		}
	}
	$ flynn release add -f config.json https://registry.hub.docker.com?name=flynn/slugbuilder&id=15d72b7f573b
	Created release 989ce4a8-0088-444c-8379-caddded4b957.

	$ flynn release
	ID                                Created
	989ce4a8-0088-444c-8379-caddded4b957  11 seconds ago

	$ flynn release show
	ID:             989ce4a8-0088-444c-8379-caddded4b957
	Artifact:       docker+https://registry.hub.docker.com?name=flynn/slugbuilder&id=15d72b7f573b
	Process Types:  echo
	Created At:     2015-05-06 21:58:12.751741 +0000 UTC
	ENV[MY_VAR]:    Hello World, this will be available in all process types.

	$ cat update.json
	{
		"processes": {
			"echo": {
				"omni": true
			}
		}
	}
	$ flynn release update update.json
	Created release 1a270395-8d31-4ec1-953a-0683b4f12635.

	$ flynn release delete --yes c6b7f512-ef49-46f7-bb57-dd39e97bfb09
	Deleted release c6b7f512-ef49-46f7-bb57-dd39e97bfb09 (deleted 1 files)
`)
}

func runRelease(args *docopt.Args, client controller.Client) error {
	if args.Bool["show"] {
		return runReleaseShow(args, client)
	}
	if args.Bool["add"] {
		if args.String["-t"] == "docker" {
			return runReleaseAddDocker(args, client)
		} else {
			return fmt.Errorf("Release type %s not supported.", args.String["-t"])
		}
	}
	if args.Bool["update"] {
		return runReleaseUpdate(args, client)
	}
	if args.Bool["delete"] {
		return runReleaseDelete(args, client)
	}
	if args.Bool["rollback"] {
		return runReleaseRollback(args, client)
	}
	return runReleaseList(args, client)
}

func runReleaseList(args *docopt.Args, client controller.Client) error {
	list, err := client.AppReleaseList(mustApp())
	if err != nil {
		return err
	}

	if args.Bool["--quiet"] {
		for _, r := range list {
			fmt.Println(r.ID)
		}
		return nil
	}

	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()
	listRec(w, "ID", "Created")
	for _, r := range list {
		listRec(w, r.ID, humanTime(r.CreatedAt))
	}
	return nil
}

func runReleaseShow(args *docopt.Args, client controller.Client) error {
	var release *ct.Release
	var err error
	if args.String["<id>"] != "" {
		release, err = client.GetRelease(args.String["<id>"])
	} else {
		release, err = client.GetAppRelease(mustApp())
	}
	if err != nil {
		return err
	}
	if args.Bool["--json"] {
		return json.NewEncoder(os.Stdout).Encode(release)
	}
	var artifacts []string
	for _, id := range release.ArtifactIDs {
		artifact, err := client.GetArtifact(id)
		if err != nil {
			return err
		}
		artifacts = append(artifacts, fmt.Sprintf("%s+%s", artifact.Type, artifact.URI))
	}
	types := make([]string, 0, len(release.Processes))
	for typ := range release.Processes {
		types = append(types, typ)
	}
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()
	listRec(w, "ID:", release.ID)
	for i, artifact := range artifacts {
		listRec(w, fmt.Sprintf("Artifact[%d]:", i), artifact)
	}
	listRec(w, "Process Types:", strings.Join(types, ", "))
	listRec(w, "Created At:", release.CreatedAt)
	for k, v := range release.Env {
		listRec(w, fmt.Sprintf("ENV[%s]", k), v)
	}
	return nil
}

func runReleaseAddDocker(args *docopt.Args, client controller.Client) error {
	fmt.Fprintln(os.Stderr, "WARN: The 'release add' command is deprecated and only works on legacy clusters, use 'docker push' to push Docker images")

	app, err := client.GetApp(mustApp())
	if err != nil {
		return err
	}
	release := &ct.Release{}
	if args.String["--file"] != "" {
		data, err := ioutil.ReadFile(args.String["--file"])
		if err != nil {
			return err
		}
		if err := json.Unmarshal(data, release); err != nil {
			return err
		}
	}

	artifact := &ct.Artifact{
		Type: ct.DeprecatedArtifactTypeDocker,
		URI:  args.String["<uri>"],
	}
	if err := client.CreateArtifact(artifact); err != nil {
		return err
	}

	release.ArtifactIDs = []string{artifact.ID}
	if err := client.CreateRelease(app.ID, release); err != nil {
		return err
	}

	if err := client.DeployAppRelease(app.ID, release.ID, nil); err != nil {
		return err
	}

	log.Printf("Created release %s.", release.ID)

	return nil
}

func runReleaseUpdate(args *docopt.Args, client controller.Client) error {
	app, err := client.GetApp(mustApp())
	if err != nil {
		return err
	}
	var release *ct.Release
	if args.String["<id>"] != "" {
		release, err = client.GetRelease(args.String["<id>"])
	} else {
		release, err = client.GetAppRelease(app.ID)
	}
	if err != nil {
		return err
	}

	updates := &ct.Release{}
	data, err := ioutil.ReadFile(args.String["<file>"])
	if err != nil {
		return err
	}
	if err := json.Unmarshal(data, updates); err != nil {
		return err
	}

	// Basically, there's no way to merge JSON that can reliably knock out set values.
	// Instead, throw the --clean flag to start from a largely empty Release.
	if args.Bool["--clean"] {
		updates.ArtifactIDs = release.ArtifactIDs
		release = updates
		release.ID = ""
	} else {
		release.ID = ""

		if len(updates.Env) > 0 && len(release.Env) == 0 {
			release.Env = make(map[string]string, len(updates.Env))
		}
		for key, value := range updates.Env {
			release.Env[key] = value
		}

		if len(updates.Meta) > 0 && len(release.Meta) == 0 {
			release.Meta = make(map[string]string, len(updates.Meta))
		}
		for key, value := range updates.Meta {
			release.Meta[key] = value
		}

		if len(updates.Processes) > 0 && len(release.Processes) == 0 {
			release.Processes = make(map[string]ct.ProcessType, len(updates.Processes))
		}
		for procKey, procUpdate := range updates.Processes {
			procRelease, ok := release.Processes[procKey]
			if !ok {
				release.Processes[procKey] = procUpdate
				continue
			}

			if len(procUpdate.Args) > 0 {
				procRelease.Args = procUpdate.Args
			}

			if len(procUpdate.Env) > 0 && len(procRelease.Env) == 0 {
				procRelease.Env = make(map[string]string, len(procUpdate.Env))
			}
			for key, value := range procUpdate.Env {
				procRelease.Env[key] = value
			}

			if len(procUpdate.Ports) > 0 {
				procRelease.Ports = procUpdate.Ports
			}
			if len(procUpdate.Volumes) > 0 {
				procRelease.Volumes = procUpdate.Volumes
			}
			if len(procUpdate.Mounts) > 0 {
				procRelease.Mounts = procUpdate.Mounts
			}
			if procUpdate.DeprecatedData {
				fmt.Fprintln(os.Stderr, `WARN: ProcessType.Data is deprecated and will be removed in future versions, populate ProcessType.Volumes instead e.g. "volumes": [{"path": "/data"}]`)
				procRelease.DeprecatedData = true
			}
			if procUpdate.Omni {
				procRelease.Omni = true
			}
			if procUpdate.HostNetwork {
				procRelease.HostNetwork = true
			}
			if procUpdate.HostPIDNamespace {
				procRelease.HostPIDNamespace = true
			}
			if len(procUpdate.Service) > 0 {
				procRelease.Service = procUpdate.Service
			}
			if procUpdate.Resurrect {
				procRelease.Resurrect = true
			}
			for resKey, resValue := range procUpdate.Resources {
				procRelease.Resources[resKey] = resValue
			}
			if len(procUpdate.LinuxCapabilities) > 0 {
				procRelease.LinuxCapabilities = procUpdate.LinuxCapabilities
			}
			if len(procUpdate.AllowedDevices) > 0 {
				procRelease.AllowedDevices = procUpdate.AllowedDevices
			}

			release.Processes[procKey] = procRelease
		}
	}

	if err := client.CreateRelease(app.ID, release); err != nil {
		return err
	}

	if err := client.DeployAppRelease(app.ID, release.ID, nil); err != nil {
		return err
	}

	log.Printf("Created release %s.", release.ID)

	return nil
}

func runReleaseDelete(args *docopt.Args, client controller.Client) error {
	releaseID := args.String["<id>"]
	if !args.Bool["--yes"] {
		if !promptYesNo(fmt.Sprintf("Are you sure you want to delete release %q?", releaseID)) {
			return nil
		}
	}
	res, err := client.DeleteRelease(mustApp(), releaseID)
	if err != nil {
		return err
	}
	if len(res.RemainingApps) > 0 {
		log.Printf("Release scaled down for app but not fully deleted (still associated with %d other apps)", len(res.RemainingApps))
	} else {
		log.Printf("Deleted release %s (deleted %d files)", releaseID, len(res.DeletedFiles))
	}
	return nil
}

func runReleaseRollback(args *docopt.Args, client controller.Client) error {
	currentRelease, err := client.GetAppRelease(mustApp())
	if err != nil {
		return err
	}
	releaseID := args.String["<id>"]
	if releaseID == "" {
		releases, err := client.AppReleaseList(mustApp())
		if err != nil {
			return err
		}
		if len(releases) < 2 {
			return fmt.Errorf("Not enough releases to perform a rollback.")
		}
		releaseID = releases[1].ID
	} else if releaseID == currentRelease.ID {
		return fmt.Errorf("Release id given is the current release.")
	}

	if !args.Bool["--yes"] {
		if !promptYesNo(fmt.Sprintf("Are you sure you want to rollback to release %q?", releaseID)) {
			return nil
		}
	}

	log.Printf("Rolling back to release %s from %s.\n", releaseID, currentRelease.ID)

	if err := client.DeployAppRelease(mustApp(), releaseID, nil); err != nil {
		return err
	}

	log.Printf("Successfully rolled back to release %s.\n", releaseID)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"./ssh_ca"
	"crypto/rand"
	"encoding/base32"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"log"
	"net"
	"net/http"
	"os"
	"time"
)

const MAX_SIGNERS_ALLOWED = 5

type CertRequest struct {
	request     *ssh.Certificate
	submit_time time.Time
	environment string
	signatures  map[string]bool
	cert_signed bool
}

func new_cert_request() CertRequest {
	var cr CertRequest
	cr.submit_time = time.Now()
	cr.cert_signed = false
	cr.signatures = make(map[string]bool)
	return cr
}

type CertRequestHandler struct {
	Config    map[string]ssh_ca.SignerdConfig
	state     map[string]CertRequest
	ssh_agent agent.Agent
}

type CertRequestForm struct {
	cert string
}

func (h *CertRequestHandler) form_boilerplate(req *http.Request) (*ssh_ca.SignerdConfig, string, error) {
	err := req.ParseForm()
	if err != nil {
		err := errors.New(fmt.Sprintf("%v", err))
		return nil, "", err
	}
	if req.Form["environment"] == nil {
		err := errors.New("Must specify environment")
		return nil, "", err
	}
	environment := req.Form["environment"][0]
	config, ok := h.Config[environment]
	if !ok {
		err := errors.New("Environment is not configured (is it valid?)")
		return nil, "", err
	}
	return &config, environment, nil
}

func (h *CertRequestHandler) create_signing_request(rw http.ResponseWriter, req *http.Request) {
	log.Println("create_signing_request")
	var request_data SigningRequest
	config, environment, err := h.form_boilerplate(req)
	request_data.config = config
	request_data.environment = environment
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}
	err = h.extract_cert_from_request(req, &request_data, config.AuthorizedUsers)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	request_id := make([]byte, 15)
	rand.Reader.Read(request_id)
	request_id_str := base32.StdEncoding.EncodeToString(request_id)

	cert_request := new_cert_request()
	cert_request.request = request_data.cert
	cert_request.environment = request_data.environment
	h.state[request_id_str] = cert_request

	rw.WriteHeader(http.StatusCreated)
	rw.Write([]byte(request_id_str))
	return
}

type SigningRequest struct {
	config      *ssh_ca.SignerdConfig
	environment string
	cert        *ssh.Certificate
}

func (h *CertRequestHandler) extract_cert_from_request(req *http.Request, request_data *SigningRequest, authorized_signers []string) error {

	if req.PostForm["cert"] == nil || len(req.PostForm["cert"]) == 0 {
		err := errors.New("Please specify exactly one cert request")
		return err
	}

	raw_cert_request, err := base64.StdEncoding.DecodeString(req.PostForm["cert"][0])
	if err != nil {
		err := errors.New("Unable to base64 decode cert request")
		return err
	}
	pub_key, err := ssh.ParsePublicKey(raw_cert_request)
	if err != nil {
		err := errors.New("Unable to parse cert request")
		return err
	}

	cert := pub_key.(*ssh.Certificate)
	request_data.cert = cert
	log.Println("Cert serial", cert.Serial)
	log.Println("Cert principals", cert.ValidPrincipals)
	log.Println("Signed by key", ssh_ca.MakeFingerprint(cert.SignatureKey.Marshal()))
	log.Println("Valid between", cert.ValidAfter, cert.ValidBefore)

	var cert_checker ssh.CertChecker
	cert_checker.IsAuthority = func(auth ssh.PublicKey) bool {
		for _, v := range authorized_signers {
			if v == ssh_ca.MakeFingerprint(auth.Marshal()) {
				return true
			}
		}
		return false
	}
	err = cert_checker.CheckCert(cert.ValidPrincipals[0], cert)
	if err != nil {
		err := errors.New(fmt.Sprintf("Cert not valid: %v", err))
		return err
	}
	return nil
}

func (h *CertRequestHandler) list_pending_requests(rw http.ResponseWriter, req *http.Request) {
	log.Println("list_pending_requests")
	_, environment, err := h.form_boilerplate(req)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	var cert_request_id string
	cert_request_ids, ok := req.Form["cert_request_id"]
	log.Println("request form", req.Form)
	if ok {
		cert_request_id = cert_request_ids[0]
	}

	found_something := false
	results := make(map[string]string)
	for k, v := range h.state {
		if v.environment == environment {
			encoded_cert := base64.StdEncoding.EncodeToString(v.request.Marshal())
			// Two ways to use this URL. If caller specified a cert_request_id
			// then we return only that one. Otherwise everything.
			if cert_request_id == "" {
				log.Println("cert request id was empty, returning all", k)
				results[k] = encoded_cert
				found_something = true
			} else {
				if cert_request_id == k {
					log.Println("cert request id not empty", cert_request_id, k)
					results[k] = encoded_cert
					found_something = true
					break
				}
			}
		}
	}
	if found_something {
		output, err := json.Marshal(results)
		if err != nil {
			http.Error(rw, fmt.Sprintf("Trouble marshaling json response %v", err), http.StatusInternalServerError)
			return
		}
		rw.Write(output)
	} else {
		http.Error(rw, fmt.Sprintf("No certs found."), http.StatusNotFound)
		return
	}
}

func (h *CertRequestHandler) get_request_status(rw http.ResponseWriter, req *http.Request) {
	log.Println("get_request_status", req)

	uri_vars := mux.Vars(req)
	request_id := uri_vars["request_id"]
	log.Printf("%#v", h.state[request_id])

	type Response struct {
		cert_signed bool
		cert        string
	}
	if h.state[request_id].cert_signed == true {
		rw.Write([]byte(h.state[request_id].request.Type()))
		rw.Write([]byte(" "))
		rw.Write([]byte(base64.StdEncoding.EncodeToString(h.state[request_id].request.Marshal())))
	} else {
		http.Error(rw, "Cert not signed yet.", http.StatusPreconditionFailed)
	}
}

func (h *CertRequestHandler) sign_request(rw http.ResponseWriter, req *http.Request) {
	log.Println("sign_request")

	uri_vars := mux.Vars(req)
	request_id := uri_vars["request_id"]

	var request_data SigningRequest
	config, environment, err := h.form_boilerplate(req)
	request_data.config = config
	request_data.environment = environment
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}
	err = h.extract_cert_from_request(req, &request_data, config.AuthorizedSigners)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	h.state[request_id].signatures[ssh_ca.MakeFingerprint(request_data.cert.SignatureKey.Marshal())] = true

	if len(h.state[request_id].signatures) >= config.NumberSignersRequired {

		signers, err := h.ssh_agent.Signers()
		var signer *ssh.Signer
		if err != nil {
			log.Println("No keys found.")
		} else {
			for i := range signers {
				fp := ssh_ca.MakeFingerprint(signers[i].PublicKey().Marshal())
				if fp == config.SigningKeyFingerprint {
					log.Println("-- Cert will be signed by ", fp)
					signer = &signers[i]
					break
				}
			}
		}
		if signer == nil {
			log.Println("Couldn't find signing key, unable to sign request")
			http.Error(rw, "Couldn't find signing key, unable to sign. Sorry.", http.StatusNotFound)
			return
		}
		state_info := h.state[request_id]
		state_info.request.SignCert(rand.Reader, *signer)
		state_info.cert_signed = true
		// this is weird. see: https://code.google.com/p/go/issues/detail?id=3117
		h.state[request_id] = state_info
	}

}

func main() {
	home := os.Getenv("HOME")
	if home == "" {
		home = "/"
	}
	config_path := home + "/.ssh_ca/sign_certd_config.json"
	flag.StringVar(&config_path, "config_path", config_path, "Path to config json.")
	flag.Parse()

	config, err := ssh_ca.LoadSignerdConfig(config_path)
	if err != nil {
		log.Println("Load Config failed:", err)
		os.Exit(1)
	}
	log.Println("Server started with config", config)
	log.Println("Using SSH agent at", os.Getenv("SSH_AUTH_SOCK"))

	conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err != nil {
		log.Println("Dial failed:", err)
		os.Exit(1)
	}
	ssh_agent := agent.NewClient(conn)

	var request_handler CertRequestHandler
	request_handler.Config = config
	request_handler.state = make(map[string]CertRequest)
	request_handler.ssh_agent = ssh_agent

	r := mux.NewRouter()
	requests := r.Path("/cert/requests").Subrouter()
	requests.Methods("POST").HandlerFunc(request_handler.create_signing_request)
	requests.Methods("GET").HandlerFunc(request_handler.list_pending_requests)
	request := r.Path("/cert/requests/{request_id}").Subrouter()
	request.Methods("GET").HandlerFunc(request_handler.get_request_status)
	request.Methods("POST").HandlerFunc(request_handler.sign_request)
	http.ListenAndServe(":8080", r)
}
<commit_msg>Put a newline at the end of our exported cert<commit_after>package main

import (
	"./ssh_ca"
	"crypto/rand"
	"encoding/base32"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"log"
	"net"
	"net/http"
	"os"
	"time"
)

const MAX_SIGNERS_ALLOWED = 5

type CertRequest struct {
	request     *ssh.Certificate
	submit_time time.Time
	environment string
	signatures  map[string]bool
	cert_signed bool
}

func new_cert_request() CertRequest {
	var cr CertRequest
	cr.submit_time = time.Now()
	cr.cert_signed = false
	cr.signatures = make(map[string]bool)
	return cr
}

type CertRequestHandler struct {
	Config    map[string]ssh_ca.SignerdConfig
	state     map[string]CertRequest
	ssh_agent agent.Agent
}

type CertRequestForm struct {
	cert string
}

func (h *CertRequestHandler) form_boilerplate(req *http.Request) (*ssh_ca.SignerdConfig, string, error) {
	err := req.ParseForm()
	if err != nil {
		err := errors.New(fmt.Sprintf("%v", err))
		return nil, "", err
	}
	if req.Form["environment"] == nil {
		err := errors.New("Must specify environment")
		return nil, "", err
	}
	environment := req.Form["environment"][0]
	config, ok := h.Config[environment]
	if !ok {
		err := errors.New("Environment is not configured (is it valid?)")
		return nil, "", err
	}
	return &config, environment, nil
}

func (h *CertRequestHandler) create_signing_request(rw http.ResponseWriter, req *http.Request) {
	log.Println("create_signing_request")
	var request_data SigningRequest
	config, environment, err := h.form_boilerplate(req)
	request_data.config = config
	request_data.environment = environment
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}
	err = h.extract_cert_from_request(req, &request_data, config.AuthorizedUsers)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	request_id := make([]byte, 15)
	rand.Reader.Read(request_id)
	request_id_str := base32.StdEncoding.EncodeToString(request_id)

	cert_request := new_cert_request()
	cert_request.request = request_data.cert
	cert_request.environment = request_data.environment
	h.state[request_id_str] = cert_request

	rw.WriteHeader(http.StatusCreated)
	rw.Write([]byte(request_id_str))
	return
}

type SigningRequest struct {
	config      *ssh_ca.SignerdConfig
	environment string
	cert        *ssh.Certificate
}

func (h *CertRequestHandler) extract_cert_from_request(req *http.Request, request_data *SigningRequest, authorized_signers []string) error {

	if req.PostForm["cert"] == nil || len(req.PostForm["cert"]) == 0 {
		err := errors.New("Please specify exactly one cert request")
		return err
	}

	raw_cert_request, err := base64.StdEncoding.DecodeString(req.PostForm["cert"][0])
	if err != nil {
		err := errors.New("Unable to base64 decode cert request")
		return err
	}
	pub_key, err := ssh.ParsePublicKey(raw_cert_request)
	if err != nil {
		err := errors.New("Unable to parse cert request")
		return err
	}

	cert := pub_key.(*ssh.Certificate)
	request_data.cert = cert
	log.Println("Cert serial", cert.Serial)
	log.Println("Cert principals", cert.ValidPrincipals)
	log.Println("Signed by key", ssh_ca.MakeFingerprint(cert.SignatureKey.Marshal()))
	log.Println("Valid between", cert.ValidAfter, cert.ValidBefore)

	var cert_checker ssh.CertChecker
	cert_checker.IsAuthority = func(auth ssh.PublicKey) bool {
		for _, v := range authorized_signers {
			if v == ssh_ca.MakeFingerprint(auth.Marshal()) {
				return true
			}
		}
		return false
	}
	err = cert_checker.CheckCert(cert.ValidPrincipals[0], cert)
	if err != nil {
		err := errors.New(fmt.Sprintf("Cert not valid: %v", err))
		return err
	}
	return nil
}

func (h *CertRequestHandler) list_pending_requests(rw http.ResponseWriter, req *http.Request) {
	log.Println("list_pending_requests")
	_, environment, err := h.form_boilerplate(req)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	var cert_request_id string
	cert_request_ids, ok := req.Form["cert_request_id"]
	log.Println("request form", req.Form)
	if ok {
		cert_request_id = cert_request_ids[0]
	}

	found_something := false
	results := make(map[string]string)
	for k, v := range h.state {
		if v.environment == environment {
			encoded_cert := base64.StdEncoding.EncodeToString(v.request.Marshal())
			// Two ways to use this URL. If caller specified a cert_request_id
			// then we return only that one. Otherwise everything.
			if cert_request_id == "" {
				log.Println("cert request id was empty, returning all", k)
				results[k] = encoded_cert
				found_something = true
			} else {
				if cert_request_id == k {
					log.Println("cert request id not empty", cert_request_id, k)
					results[k] = encoded_cert
					found_something = true
					break
				}
			}
		}
	}
	if found_something {
		output, err := json.Marshal(results)
		if err != nil {
			http.Error(rw, fmt.Sprintf("Trouble marshaling json response %v", err), http.StatusInternalServerError)
			return
		}
		rw.Write(output)
	} else {
		http.Error(rw, fmt.Sprintf("No certs found."), http.StatusNotFound)
		return
	}
}

func (h *CertRequestHandler) get_request_status(rw http.ResponseWriter, req *http.Request) {
	log.Println("get_request_status", req)

	uri_vars := mux.Vars(req)
	request_id := uri_vars["request_id"]
	log.Printf("%#v", h.state[request_id])

	type Response struct {
		cert_signed bool
		cert        string
	}
	if h.state[request_id].cert_signed == true {
		rw.Write([]byte(h.state[request_id].request.Type()))
		rw.Write([]byte(" "))
		rw.Write([]byte(base64.StdEncoding.EncodeToString(h.state[request_id].request.Marshal())))
		rw.Write([]byte("\n"))
	} else {
		http.Error(rw, "Cert not signed yet.", http.StatusPreconditionFailed)
	}
}

func (h *CertRequestHandler) sign_request(rw http.ResponseWriter, req *http.Request) {
	log.Println("sign_request")

	uri_vars := mux.Vars(req)
	request_id := uri_vars["request_id"]

	var request_data SigningRequest
	config, environment, err := h.form_boilerplate(req)
	request_data.config = config
	request_data.environment = environment
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}
	err = h.extract_cert_from_request(req, &request_data, config.AuthorizedSigners)
	if err != nil {
		http.Error(rw, fmt.Sprintf("%v", err), http.StatusBadRequest)
		return
	}

	h.state[request_id].signatures[ssh_ca.MakeFingerprint(request_data.cert.SignatureKey.Marshal())] = true

	if len(h.state[request_id].signatures) >= config.NumberSignersRequired {

		signers, err := h.ssh_agent.Signers()
		var signer *ssh.Signer
		if err != nil {
			log.Println("No keys found.")
		} else {
			for i := range signers {
				fp := ssh_ca.MakeFingerprint(signers[i].PublicKey().Marshal())
				if fp == config.SigningKeyFingerprint {
					log.Println("-- Cert will be signed by ", fp)
					signer = &signers[i]
					break
				}
			}
		}
		if signer == nil {
			log.Println("Couldn't find signing key, unable to sign request")
			http.Error(rw, "Couldn't find signing key, unable to sign. Sorry.", http.StatusNotFound)
			return
		}
		state_info := h.state[request_id]
		state_info.request.SignCert(rand.Reader, *signer)
		state_info.cert_signed = true
		// this is weird. see: https://code.google.com/p/go/issues/detail?id=3117
		h.state[request_id] = state_info
	}

}

func main() {
	home := os.Getenv("HOME")
	if home == "" {
		home = "/"
	}
	config_path := home + "/.ssh_ca/sign_certd_config.json"
	flag.StringVar(&config_path, "config_path", config_path, "Path to config json.")
	flag.Parse()

	config, err := ssh_ca.LoadSignerdConfig(config_path)
	if err != nil {
		log.Println("Load Config failed:", err)
		os.Exit(1)
	}
	log.Println("Server started with config", config)
	log.Println("Using SSH agent at", os.Getenv("SSH_AUTH_SOCK"))

	conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err != nil {
		log.Println("Dial failed:", err)
		os.Exit(1)
	}
	ssh_agent := agent.NewClient(conn)

	var request_handler CertRequestHandler
	request_handler.Config = config
	request_handler.state = make(map[string]CertRequest)
	request_handler.ssh_agent = ssh_agent

	r := mux.NewRouter()
	requests := r.Path("/cert/requests").Subrouter()
	requests.Methods("POST").HandlerFunc(request_handler.create_signing_request)
	requests.Methods("GET").HandlerFunc(request_handler.list_pending_requests)
	request := r.Path("/cert/requests/{request_id}").Subrouter()
	request.Methods("GET").HandlerFunc(request_handler.get_request_status)
	request.Methods("POST").HandlerFunc(request_handler.sign_request)
	http.ListenAndServe(":8080", r)
}
<|endoftext|>


<commit_before>package tlj_test

import (
	. "github.com/hkparker/TLJ"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"reflect"
	"net"

)

var _ = Describe("Client", func() {

	var (
		populated_type_store    TypeStore
		thingy                  Thingy
	)

	BeforeEach(func() {
		populated_type_store = NewTypeStore()
		inst_type := reflect.TypeOf(Thingy{})
		ptr_type := reflect.TypeOf(&Thingy{})
		populated_type_store.AddType(inst_type, ptr_type, BuildThingy)
		thingy = Thingy {
			Name:   "test",
			ID:     1,
		}
	})

	It("can use message to write a struct to a socket", func() {
		listener, err := net.Listen("tcp", "localhost:5008")
		Expect(err).To(BeNil())
		defer listener.Close()
		sockets := make(chan net.Conn, 1)
		go func() {
			conn, _ := listener.Accept()
			sockets <- conn
		}()
		client_side, err := net.Dial("tcp", "localhost:5008")
		Expect(err).To(BeNil())
		defer client_side.Close()
		server_side := <- sockets
		client := NewClient(client_side, &populated_type_store)
		err = client.Message(thingy)
		Expect(err).To(BeNil())
		iface, err := NextStruct(server_side, &populated_type_store)
		if received_thingy, correct_type := iface.(*Thingy); correct_type {
			Expect(received_thingy.ID).To(Equal(thingy.ID))
			Expect(received_thingy.Name).To(Equal(thingy.Name))
		} else {
			Expect(correct_type).To(Equal(true))
		}
	})

	It("can use request to write a capsule to a socket", func() {

	})

	It("can run on response callbacks", func() {

	})
})
<commit_msg>client request test<commit_after>package tlj_test

import (
	. "github.com/hkparker/TLJ"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"reflect"
	"net"
	"encoding/json"
)

var _ = Describe("Client", func() {

	var (
		populated_type_store    TypeStore
		thingy                  Thingy
	)

	BeforeEach(func() {
		populated_type_store = NewTypeStore()
		inst_type := reflect.TypeOf(Thingy{})
		ptr_type := reflect.TypeOf(&Thingy{})
		populated_type_store.AddType(inst_type, ptr_type, BuildThingy)
		thingy = Thingy {
			Name:   "test",
			ID:     1,
		}
	})

	It("can use message to write a struct to a socket", func() {
		listener, err := net.Listen("tcp", "localhost:5008")
		Expect(err).To(BeNil())
		defer listener.Close()
		sockets := make(chan net.Conn, 1)
		go func() {
			conn, _ := listener.Accept()
			sockets <- conn
		}()
		client_side, err := net.Dial("tcp", "localhost:5008")
		Expect(err).To(BeNil())
		defer client_side.Close()
		server_side := <- sockets
		client := NewClient(client_side, &populated_type_store)
		err = client.Message(thingy)
		Expect(err).To(BeNil())
		iface, err := NextStruct(server_side, &populated_type_store)
		if received_thingy, correct_type := iface.(*Thingy); correct_type {
			Expect(received_thingy.ID).To(Equal(thingy.ID))
			Expect(received_thingy.Name).To(Equal(thingy.Name))
		} else {
			Expect(correct_type).To(Equal(true))
		}
	})

	It("can use request to write a capsule to a socket", func() {
		listener, err := net.Listen("tcp", "localhost:5009")
		Expect(err).To(BeNil())
		defer listener.Close()
		sockets := make(chan net.Conn, 1)
		go func() {
			conn, _ := listener.Accept()
			sockets <- conn
		}()
		client_side, err := net.Dial("tcp", "localhost:5009")
		Expect(err).To(BeNil())
		defer client_side.Close()
		server_side := <- sockets
		client := NewClient(client_side, &populated_type_store)
		_, err = client.Request(thingy)
		Expect(err).To(BeNil())
		iface, err := NextStruct(server_side, &populated_type_store)
		if capsule, correct_type := iface.(*Capsule); correct_type {
			Expect(capsule.Type).To(Equal(uint16(1)))
			restored_thing := &Thingy{}
			err = json.Unmarshal([]byte(capsule.Data), &restored_thing)
			Expect(err).To(BeNil())
			Expect(restored_thing.ID).To(Equal(thingy.ID))
			Expect(restored_thing.Name).To(Equal(thingy.Name))
		} else {
			Expect(correct_type).To(Equal(true))
		}
	})

	It("can run on response callbacks", func() {

	})
})
<|endoftext|>


<commit_before>// Copyright 2011-2012 Michael Shields
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clocky

import (
	"io"
	"text/template" // TODO: Switch to Go 1's html/template.
	"time"

	"appengine"

	"solar"
)

const TimeFormat = "3:04 pm"

const Zone = "America/Los_Angeles"

func Time(w io.Writer, c appengine.Context) {
	location, _ := time.LoadLocation(Zone)
	now := time.Now().In(location)
	sunrise := solar.Rise(now, Lat, Lng).In(location)
	sunset := solar.Set(now, Lat, Lng).In(location)
	sun1 := "sunrise " + sunrise.Format(TimeFormat)
	sun2 := "sunset " + sunset.Format(TimeFormat)
	if sunrise.Sub(sunset) > 0 {
		sun1, sun2 = sun2, sun1
	}
	timeTmpl.Execute(w, map[string]string{
		"Big":   now.Format("3:04"),
		"Small": now.Format(":05 pm"),
		"Date":  now.Format("Monday, January 2"),
		"Sun1":  sun1,
		"Sun2":  sun2,
	})
}

var timeTmpl = template.Must(template.New("time").Parse(`

{{.Big}}{{.Small}}

{{.Date}}

{{.Sun1}}, {{.Sun2}}

`))
<commit_msg>Switch time.go to html/template.<commit_after>// Copyright 2011-2012 Michael Shields
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clocky

import (
	"html/template"
	"io"
	"time"

	"appengine"

	"solar"
)

const TimeFormat = "3:04\u2009pm"

const Zone = "America/Los_Angeles"

func Time(w io.Writer, c appengine.Context) {
	location, _ := time.LoadLocation(Zone)
	now := time.Now().In(location)
	sunrise := solar.Rise(now, Lat, Lng).In(location)
	sunset := solar.Set(now, Lat, Lng).In(location)
	sun1 := "sunrise " + sunrise.Format(TimeFormat)
	sun2 := "sunset " + sunset.Format(TimeFormat)
	if sunrise.Sub(sunset) > 0 {
		sun1, sun2 = sun2, sun1
	}
	timeTmpl.Execute(w, map[string]string{
		"Big":   now.Format("3:04"),
		"Small": now.Format(":05\u2009pm"),
		"Date":  now.Format("Monday, January 2"),
		"Sun1":  sun1,
		"Sun2":  sun2,
	})
}

var timeTmpl = template.Must(template.New("time").Parse(`

{{.Big}}{{.Small}}

{{.Date}}

{{.Sun1}}, {{.Sun2}}

`))
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"log"

	"github.com/anacrolix/dht/v2"
	"github.com/anacrolix/dht/v2/exts/getput"
	"github.com/anacrolix/dht/v2/traversal"
	"github.com/anacrolix/torrent/bencode"
)

type PutCmd struct {
	Strings bool
	Data    []string `arg:"positional"`
}

func put(cmd *PutCmd) (err error) {
	s, err := dht.NewServer(nil)
	if err != nil {
		return
	}
	defer s.Close()
	if len(cmd.Data) == 0 {
		return errors.New("no payloads given")
	}
	for _, data := range cmd.Data {
		putBytes := []byte(data)
		var put *interface{}
		if cmd.Strings {
			var s interface{} = string(putBytes)
			put = &s
			putBytes, err = bencode.Marshal(*put)
			if err != nil {
				return fmt.Errorf("marshalling string arg to bytes: %w", err)
			}
		} else {
			err = bencode.Unmarshal(putBytes, &put)
			if err != nil {
				return
			}
		}
		target := sha1.Sum(putBytes)
		var stats *traversal.Stats
		stats, err = getput.Put(context.Background(), target, s, put)
		if err != nil {
			err = fmt.Errorf("in traversal: %w", err)
			return
		}
		log.Print(stats)
		fmt.Printf("%x\n", target)
	}
	return nil
}
<commit_msg>Include field names in put stats log<commit_after>package main

import (
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"log"

	"github.com/anacrolix/dht/v2"
	"github.com/anacrolix/dht/v2/exts/getput"
	"github.com/anacrolix/dht/v2/traversal"
	"github.com/anacrolix/torrent/bencode"
)

type PutCmd struct {
	Strings bool
	Data    []string `arg:"positional"`
}

func put(cmd *PutCmd) (err error) {
	s, err := dht.NewServer(nil)
	if err != nil {
		return
	}
	defer s.Close()
	if len(cmd.Data) == 0 {
		return errors.New("no payloads given")
	}
	for _, data := range cmd.Data {
		putBytes := []byte(data)
		var put *interface{}
		if cmd.Strings {
			var s interface{} = string(putBytes)
			put = &s
			putBytes, err = bencode.Marshal(*put)
			if err != nil {
				return fmt.Errorf("marshalling string arg to bytes: %w", err)
			}
		} else {
			err = bencode.Unmarshal(putBytes, &put)
			if err != nil {
				return
			}
		}
		target := sha1.Sum(putBytes)
		var stats *traversal.Stats
		stats, err = getput.Put(context.Background(), target, s, put)
		if err != nil {
			err = fmt.Errorf("in traversal: %w", err)
			return
		}
		log.Printf("%+v", stats)
		fmt.Printf("%x\n", target)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"github.com/stts-se/rbg2p"
)

var l = log.New(os.Stderr, "", 0)

func print(orth string, transes []string) {

}

type transResult struct {
	orth    string
	transes []string
	result  bool
}

func transcribe(ruleSet rbg2p.RuleSet, orth string) transResult {
	transes, err := ruleSet.Apply(orth)
	if err != nil {
		l.Printf("Couldn't transcribe '%s' : %s", orth, err)
		return transResult{orth: orth, transes: transes, result: false}
	}
	return transResult{orth: orth, transes: transes, result: true}
}

var removeStressAndBoundaries = regexp.MustCompile("[.\"%!~] *")

func cleanTransForDiff(t string) string {
	var res = t
	res = removeStressAndBoundaries.ReplaceAllString(res, "")
	res = strings.Replace(res, "'", "", -1)
	return res
}

func cleanTransForIJDiff(t string) string {
	var res = t
	res = strings.Replace(res, " i ", " j ", -1)
	return res
}

func compareForDiff(old []string, new []string) (string, bool) {
	for i, s := range old {
		old[i] = cleanTransForDiff(s)
	}
	for i, s := range new {
		new[i] = cleanTransForDiff(s)
	}
	var oldIJ = []string{}
	var newIJ = []string{}
	for _, s := range old {
		oldIJ = append(oldIJ, cleanTransForIJDiff(s))
	}
	for _, s := range new {
		newIJ = append(newIJ, cleanTransForIJDiff(s))
	}
	if reflect.DeepEqual(old, new) {
		return "ALL EQ", true
	} else if old[0] == new[0] {
		return "#1 EQ", false
		// } else if reflect.DeepEqual(oldIJ, newIJ) {
		// 	return "ALL EQ IJ", false
		// } else if oldIJ[0] == newIJ[0] {
		// 	return "#1 EQ IJ", false
	} else {
		return "DIFF", false
	}
}

func main() {

	var f = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	var force = f.Bool("force", false, "print transcriptions even if errors are found (default: false)")
	var test = f.Bool("test", false, "test g2p against input file; orth <tab> trans (default: false)")
	var ssFile = f.String("symbolset", "", "use specified symbol set file for validating the symbols in the g2p rule set (default: none; overrides the g2p rule file's symbolset, if any)")
	var help = f.Bool("help", false, "print help message")

	var usage = `go run g2p.go <FLAGS> <G2P RULE FILE> <WORDS (FILES OR LIST OF WORDS)> (optional)

FLAGS:
   -force      bool    print transcriptions even if errors are found (default: false)
   -test       bool    test g2p against input file; orth <tab> trans (default: false)
   -symbolset  string  use specified symbol set file for validating the symbols in the g2p rule set (default: none)
   -help       bool    print help message`

	f.Usage = func() {
		l.Printf(usage)
	}

	var args = os.Args
	if strings.HasSuffix(args[0], "g2p") {
		args = args[1:] // remove first argument if it's the program name
	}
	err := f.Parse(args)
	if err != nil {
		os.Exit(1)
	}

	args = f.Args()

	if *help {
		l.Println(usage)
		os.Exit(1)
	}

	if len(args) < 1 {
		l.Println(usage)
		os.Exit(1)
	}

	g2pFile := args[0]
	ruleSet, err := rbg2p.LoadFile(g2pFile)
	if err != nil {
		l.Printf("couldn't load rule file %s : %s", g2pFile, err)
		os.Exit(1)
	}

	if *ssFile != "" {
		phonemeSet, err := rbg2p.LoadPhonemeSetFile(*ssFile, ruleSet.PhonemeDelimiter)
		if err != nil {
			l.Printf("couldn't load symbol set : %s", err)
			os.Exit(1)
		}
		ruleSet.PhonemeSet = phonemeSet
	}

	haltingError := false
	result := ruleSet.Test()
	for _, e := range result.Errors {
		l.Printf("ERROR: %v\n", e)
	}
	l.Printf("%d ERROR(S) FOR %s\n", len(result.Errors), g2pFile)
	for _, e := range result.Warnings {
		l.Printf("WARNING: %v\n", e)
	}
	l.Printf("%d WARNING(S) FOR %s\n", len(result.Warnings), g2pFile)
	if len(result.Errors) > 0 {
		haltingError = true
	}
	if len(result.FailedTests) > 0 {
		for _, e := range result.FailedTests {
			l.Printf("FAILED TEST: %v\n", e)
		}
		l.Printf("%d OF %d TESTS FAILED FOR %s\n", len(result.FailedTests), len(ruleSet.Tests), g2pFile)
		haltingError = true
	} else {
		l.Printf("ALL %d TESTS PASSED FOR %s\n", len(ruleSet.Tests), g2pFile)
	}

	if haltingError && !*force {
		os.Exit(1)
	}

	nTotal := 0
	nErrs := 0
	nTrans := 0
	nTests := 0
	testRes := make(map[string]int)
	if *test {
		fmt.Println("ORTH\tDIFFTAG\tNEW TRANSES\tOLD TRANSES")
	}
	for i := 1; i < len(args); i++ {
		s := args[i]
		if _, err := os.Stat(s); os.IsNotExist(err) {
			nTotal = nTotal + 1
			res := transcribe(ruleSet, strings.ToLower(s))
			if res.result || *force {
				nTrans = nTrans + 1
				fmt.Printf("%s\t%s\n", s, strings.Join(res.transes, "\t"))
			}
			if !res.result {
				nErrs = nErrs + 1
			}
		} else {
			fh, err := os.Open(s)
			defer fh.Close()
			if err != nil {
				l.Println(err)
				os.Exit(1)
			}
			sc := bufio.NewScanner(fh)
			for sc.Scan() {
				if err := sc.Err(); err != nil {
					l.Println(err)
					os.Exit(1)
				}
				nTotal = nTotal + 1
				line := sc.Text()
				if strings.HasPrefix(strings.TrimSpace(line), "#") {
					l.Println("Skipping line " + line)
					continue
				}
				fs := strings.Split(line, "\t")
				o, refTranses := fs[0], fs[1:]
				res := transcribe(ruleSet, strings.ToLower(o))
				if res.result || *force {
					nTrans = nTrans + 1
					if *test {
						nTests++
						info, _ := compareForDiff(res.transes, refTranses)
						testRes[info]++
						outFs := []string{res.orth, info, strings.Join(res.transes, " # "), strings.Join(refTranses, "#")}
						fmt.Println(strings.Join(outFs, "\t"))
					} else {
						print(res.orth, res.transes)
					}
				}
				if !res.result {
					nErrs = nErrs + 1
				}
			}
		}
	}
	l.Printf("%-18s: % 7d", "TOTAL WORDS", nTotal)
	l.Printf("%-18s: % 7d", "ERRORS", nErrs)
	l.Printf("%-18s: % 7d", "TRANSCRIBED", nTrans)
	if *test {
		l.Printf("%-18s: % 7d", "TESTED", nTests)
		var keys []string
		for k := range testRes {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, tag := range keys {
			freq := testRes[tag]
			s := " > TEST " + tag
			l.Printf("%-18s: % 7d", s, freq)
		}
	}
}
<commit_msg>better output<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"github.com/stts-se/rbg2p"
)

var l = log.New(os.Stderr, "", 0)

func print(orth string, transes []string) {

}

type transResult struct {
	orth    string
	transes []string
	result  bool
}

func transcribe(ruleSet rbg2p.RuleSet, orth string) transResult {
	transes, err := ruleSet.Apply(orth)
	if err != nil {
		l.Printf("Couldn't transcribe '%s' : %s", orth, err)
		return transResult{orth: orth, transes: transes, result: false}
	}
	return transResult{orth: orth, transes: transes, result: true}
}

var removeStressAndBoundaries = regexp.MustCompile("[.\"%!~] *")

func cleanTransForDiff(t string) string {
	var res = t
	res = removeStressAndBoundaries.ReplaceAllString(res, "")
	res = strings.Replace(res, "'", "", -1)
	return res
}

func cleanTransForIJDiff(t string) string {
	var res = t
	res = strings.Replace(res, " i ", " j ", -1)
	return res
}

func compareForDiff(old []string, new []string) (string, bool) {
	for i, s := range old {
		old[i] = cleanTransForDiff(s)
	}
	for i, s := range new {
		new[i] = cleanTransForDiff(s)
	}
	var oldIJ = []string{}
	var newIJ = []string{}
	for _, s := range old {
		oldIJ = append(oldIJ, cleanTransForIJDiff(s))
	}
	for _, s := range new {
		newIJ = append(newIJ, cleanTransForIJDiff(s))
	}
	if reflect.DeepEqual(old, new) {
		return "ALL EQ", true
	} else if old[0] == new[0] {
		return "#1 EQ", false
		// } else if reflect.DeepEqual(oldIJ, newIJ) {
		// 	return "ALL EQ IJ", false
		// } else if oldIJ[0] == newIJ[0] {
		// 	return "#1 EQ IJ", false
	} else {
		return "DIFF", false
	}
}

func main() {

	var f = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	var force = f.Bool("force", false, "print transcriptions even if errors are found (default: false)")
	var test = f.Bool("test", false, "test g2p against input file; orth <tab> trans (default: false)")
	var ssFile = f.String("symbolset", "", "use specified symbol set file for validating the symbols in the g2p rule set (default: none; overrides the g2p rule file's symbolset, if any)")
	var help = f.Bool("help", false, "print help message")

	var usage = `go run g2p.go <FLAGS> <G2P RULE FILE> <WORDS (FILES OR LIST OF WORDS)> (optional)

FLAGS:
   -force      bool    print transcriptions even if errors are found (default: false)
   -test       bool    test g2p against input file; orth <tab> trans (default: false)
   -symbolset  string  use specified symbol set file for validating the symbols in the g2p rule set (default: none)
   -help       bool    print help message`

	f.Usage = func() {
		l.Printf(usage)
	}

	var args = os.Args
	if strings.HasSuffix(args[0], "g2p") {
		args = args[1:] // remove first argument if it's the program name
	}
	err := f.Parse(args)
	if err != nil {
		os.Exit(1)
	}

	args = f.Args()

	if *help {
		l.Println(usage)
		os.Exit(1)
	}

	if len(args) < 1 {
		l.Println(usage)
		os.Exit(1)
	}

	g2pFile := args[0]
	ruleSet, err := rbg2p.LoadFile(g2pFile)
	if err != nil {
		l.Printf("couldn't load rule file %s : %s", g2pFile, err)
		os.Exit(1)
	}

	if *ssFile != "" {
		phonemeSet, err := rbg2p.LoadPhonemeSetFile(*ssFile, ruleSet.PhonemeDelimiter)
		if err != nil {
			l.Printf("couldn't load symbol set : %s", err)
			os.Exit(1)
		}
		ruleSet.PhonemeSet = phonemeSet
	}

	haltingError := false
	result := ruleSet.Test()
	for _, e := range result.Errors {
		l.Printf("ERROR: %v\n", e)
	}
	l.Printf("%d ERROR(S) FOR %s\n", len(result.Errors), g2pFile)
	for _, e := range result.Warnings {
		l.Printf("WARNING: %v\n", e)
	}
	l.Printf("%d WARNING(S) FOR %s\n", len(result.Warnings), g2pFile)
	if len(result.Errors) > 0 {
		haltingError = true
	}
	if len(result.FailedTests) > 0 {
		for _, e := range result.FailedTests {
			l.Printf("FAILED TEST: %v\n", e)
		}
		l.Printf("%d OF %d TESTS FAILED FOR %s\n", len(result.FailedTests), len(ruleSet.Tests), g2pFile)
		haltingError = true
	} else {
		l.Printf("ALL %d TESTS PASSED FOR %s\n", len(ruleSet.Tests), g2pFile)
	}

	if haltingError && !*force {
		os.Exit(1)
	}

	nTotal := 0
	nErrs := 0
	nTrans := 0
	nTests := 0
	testRes := make(map[string]int)
	if *test {
		fmt.Println("ORTH\tNEW TRANSES\tOLD TRANSES\tDIFFTAG")
	}
	for i := 1; i < len(args); i++ {
		s := args[i]
		if _, err := os.Stat(s); os.IsNotExist(err) {
			nTotal = nTotal + 1
			res := transcribe(ruleSet, strings.ToLower(s))
			if res.result || *force {
				nTrans = nTrans + 1
				fmt.Printf("%s\t%s\n", s, strings.Join(res.transes, "\t"))
			}
			if !res.result {
				nErrs = nErrs + 1
			}
		} else {
			fh, err := os.Open(s)
			defer fh.Close()
			if err != nil {
				l.Println(err)
				os.Exit(1)
			}
			sc := bufio.NewScanner(fh)
			for sc.Scan() {
				if err := sc.Err(); err != nil {
					l.Println(err)
					os.Exit(1)
				}
				nTotal = nTotal + 1
				line := sc.Text()
				if strings.HasPrefix(strings.TrimSpace(line), "#") {
					l.Println("Skipping line " + line)
					continue
				}
				fs := strings.Split(line, "\t")
				o, refTranses := fs[0], fs[1:]
				res := transcribe(ruleSet, strings.ToLower(o))
				if res.result || *force {
					nTrans = nTrans + 1
					if *test {
						nTests++
						info, _ := compareForDiff(res.transes, refTranses)
						testRes[info]++
						outFs := []string{res.orth, strings.Join(res.transes, " # "), strings.Join(refTranses, "#"), info}
						fmt.Println(strings.Join(outFs, "\t"))
					} else {
						print(res.orth, res.transes)
					}
				}
				if !res.result {
					nErrs = nErrs + 1
				}
			}
		}
	}
	l.Printf("%-18s: % 7d", "TOTAL WORDS", nTotal)
	l.Printf("%-18s: % 7d", "ERRORS", nErrs)
	l.Printf("%-18s: % 7d", "TRANSCRIBED", nTrans)
	if *test {
		l.Printf("%-18s: % 7d", "TESTED", nTests)
		var keys []string
		for k := range testRes {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, tag := range keys {
			freq := testRes[tag]
			s := " > TEST " + tag
			l.Printf("%-18s: % 7d", s, freq)
		}
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/acarl005/stripansi"
	"github.com/stretchr/testify/require"
)

func Test_mrCmd(t *testing.T) {
	repo := copyTestRepo(t)
	var mrID string
	t.Run("prepare", func(t *testing.T) {
		cmd := exec.Command("sh", "-c", labBinaryPath+` mr list lab-testing | grep -m1 'mr title' | cut -c2- | awk '{print $1}' | xargs `+labBinaryPath+` mr lab-testing -d`)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			//t.Fatal(err)
		}
	})
	t.Run("create", func(t *testing.T) {
		git := exec.Command("git", "checkout", "mrtest")
		git.Dir = repo
		b, err := git.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		cmd := exec.Command(labBinaryPath, "mr", "create", "lab-testing", "master",
			"-m", "mr title",
			"-m", "mr description",
			"-a", "lab-testing",
		)
		cmd.Dir = repo

		b, _ = cmd.CombinedOutput()
		out := string(b)
		t.Log(out)
		require.Contains(t, out, "https://gitlab.com/lab-testing/test/-/merge_requests")

		i := strings.Index(out, "/diffs\n")
		mrID = strings.TrimPrefix(out[:i], "https://gitlab.com/lab-testing/test/-/merge_requests/")
		t.Log(mrID)
	})
	t.Run("show", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "show", "lab-testing", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		out := string(b)
		outStripped := stripansi.Strip(out) // This is required because glamour adds a lot of ansi chars
		require.Contains(t, out, "Project: lab-testing/test\n")
		require.Contains(t, out, "Branches: mrtest->master\n")
		require.Contains(t, out, "Status: Open\n")
		require.Contains(t, out, "Assignee: lab-testing\n")
		require.Contains(t, out, fmt.Sprintf("#%s mr title", mrID))
		require.Contains(t, out, "===================================")
		require.Contains(t, outStripped, "mr description")
		require.Contains(t, out, fmt.Sprintf("WebURL: https://gitlab.com/lab-testing/test/-/merge_requests/%s", mrID))
	})
	t.Run("delete", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "lab-testing", "-d", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		require.Contains(t, string(b), fmt.Sprintf("Merge Request #%s closed", mrID))
	})
}

func Test_mrCmd_file(t *testing.T) {
	repo := copyTestRepo(t)
	var mrID string
	t.Run("prepare", func(t *testing.T) {
		cmd := exec.Command("sh", "-c", labBinaryPath+` mr list lab-testing | grep -m1 'Fancy Description' | cut -c2- | awk '{print $1}' | xargs `+labBinaryPath+` mr lab-testing -d`)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			//t.Fatal(err)
		}
	})
	t.Run("create", func(t *testing.T) {
		git := exec.Command("git", "checkout", "mrtest")
		git.Dir = repo
		b, err := git.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		err = ioutil.WriteFile(filepath.Join(repo, "hellolab.txt"), []byte("Fancy Description\n\nFancy body of text describing this merge request.\n"), 0644)
		if err != nil {
			t.Fatal(err)
		}

		cmd := exec.Command(labBinaryPath, "mr", "create", "lab-testing", "master",
			"-F", "hellolab.txt",
			"-a", "lab-testing",
		)
		cmd.Dir = repo

		b, _ = cmd.CombinedOutput()
		out := string(b)
		t.Log(out)
		require.Contains(t, out, "https://gitlab.com/lab-testing/test/-/merge_requests")

		i := strings.Index(out, "/diffs\n")
		mrID = strings.TrimPrefix(out[:i], "https://gitlab.com/lab-testing/test/-/merge_requests/")
		t.Log(mrID)

	})
	t.Run("delete", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create -F likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "lab-testing", "-d", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		require.Contains(t, string(b), fmt.Sprintf("Merge Request #%s closed", mrID))
	})
}

func Test_mrCmd_noArgs(t *testing.T) {
	repo := copyTestRepo(t)
	cmd := exec.Command(labBinaryPath, "mr")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Fatal(err)
	}
	require.Contains(t, string(b), `Usage:
  lab mr [flags]
  lab mr [command]`)
}
<commit_msg>lab: MR edit functionality testing<commit_after>package cmd

import (
	"fmt"
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/acarl005/stripansi"
	"github.com/stretchr/testify/require"
)

func Test_mrCmd(t *testing.T) {
	repo := copyTestRepo(t)
	var mrID string
	t.Run("prepare", func(t *testing.T) {
		cmd := exec.Command("sh", "-c", labBinaryPath+` mr list lab-testing | grep -m1 'mr title' | cut -c2- | awk '{print $1}' | xargs `+labBinaryPath+` mr lab-testing -d`)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			//t.Fatal(err)
		}
	})
	t.Run("create", func(t *testing.T) {
		git := exec.Command("git", "checkout", "mrtest")
		git.Dir = repo
		b, err := git.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		cmd := exec.Command(labBinaryPath, "mr", "create", "lab-testing", "master",
			"-m", "mr title",
			"-m", "mr description",
			"-a", "lab-testing",
		)
		cmd.Dir = repo

		b, _ = cmd.CombinedOutput()
		out := string(b)
		t.Log(out)
		require.Contains(t, out, "https://gitlab.com/lab-testing/test/-/merge_requests")

		i := strings.Index(out, "/diffs\n")
		mrID = strings.TrimPrefix(out[:i], "https://gitlab.com/lab-testing/test/-/merge_requests/")
		t.Log(mrID)
	})
	t.Run("show", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "show", "lab-testing", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		out := string(b)
		outStripped := stripansi.Strip(out) // This is required because glamour adds a lot of ansi chars
		require.Contains(t, out, "Project: lab-testing/test\n")
		require.Contains(t, out, "Branches: mrtest->master\n")
		require.Contains(t, out, "Status: Open\n")
		require.Contains(t, out, "Assignee: lab-testing\n")
		require.Contains(t, out, fmt.Sprintf("#%s mr title", mrID))
		require.Contains(t, out, "===================================")
		require.Contains(t, outStripped, "mr description")
		require.Contains(t, out, fmt.Sprintf("WebURL: https://gitlab.com/lab-testing/test/-/merge_requests/%s", mrID))
	})
	t.Run("delete", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "lab-testing", "-d", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		require.Contains(t, string(b), fmt.Sprintf("Merge Request #%s closed", mrID))
	})
}

func Test_mrCmd_MR_description_and_options(t *testing.T) {
	repo := copyTestRepo(t)
	var (
		mrID      string
		commentID string
	)
	t.Run("prepare", func(t *testing.T) {
		cmd := exec.Command("sh", "-c", labBinaryPath+` mr list lab-testing | grep -m1 'Fancy Description' | cut -c2- | awk '{print $1}' | xargs `+labBinaryPath+` mr lab-testing -d`)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			//t.Fatal(err)
		}
	})
	t.Run("create MR from file", func(t *testing.T) {
		git := exec.Command("git", "checkout", "mrtest")
		git.Dir = repo
		b, err := git.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}

		err = ioutil.WriteFile(filepath.Join(repo, "hellolab.txt"), []byte("Fancy Description\n\nFancy body of text describing this merge request.\n"), 0644)
		if err != nil {
			t.Fatal(err)
		}

		cmd := exec.Command(labBinaryPath, "mr", "create", "lab-testing", "master",
			"-F", "hellolab.txt",
			"-a", "lab-testing",
		)
		cmd.Dir = repo

		b, _ = cmd.CombinedOutput()
		out := string(b)
		t.Log(out)
		require.Contains(t, out, "https://gitlab.com/lab-testing/test/-/merge_requests")

		i := strings.Index(out, "/diffs\n")
		mrID = strings.TrimPrefix(out[:i], "https://gitlab.com/lab-testing/test/-/merge_requests/")
		t.Log(mrID)

	})
	t.Run("update MR description", func(t *testing.T) {
		update := exec.Command(labBinaryPath, "mr", "edit", "lab-testing", mrID, "-m", "Updated Description", "-m", "Updated body of text describing this merge request.")
		update.Dir = repo
		b, err := update.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		cmd := exec.Command(labBinaryPath, "mr", "show", "lab-testing", mrID)
		cmd.Dir = repo
		b, err = cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		out := string(b)

		require.Contains(t, out, "Updated Description")
		require.Contains(t, out, "Updated body of text describing this merge request.")
		require.NotContains(t, out, "Fancy")
	})
	t.Run("add MR comment", func(t *testing.T) {
		addComment := exec.Command(labBinaryPath, "mr", "note", "lab-testing", mrID, "-m", "Fancy comment on this merge request.")
		addComment.Dir = repo
		b, err := addComment.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		out := string(b)
		s := strings.Split(out, "_")
		commentID = s[2]
		s = strings.Split(commentID, "\n")
		commentID = s[0]

		t.Log("commentID =", commentID)

		url := "https://gitlab.com/lab-testing/test/merge_requests/" + mrID + "#note_" + commentID
		require.Contains(t, out, url)
	})
	t.Run("show MR with comment", func(t *testing.T) {
		showComment := exec.Command(labBinaryPath, "mr", "show", "lab-testing", mrID, "--comments")
		showComment.Dir = repo
		b, err := showComment.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		out := string(b)
		t.Log("commentID =", commentID)
		_commentID := "#" + commentID + ": lab-testing"
		require.Contains(t, out, _commentID)
	})
	t.Run("delete", func(t *testing.T) {
		if mrID == "" {
			t.Skip("mrID is empty, create -F likely failed")
		}
		cmd := exec.Command(labBinaryPath, "mr", "lab-testing", "-d", mrID)
		cmd.Dir = repo

		b, err := cmd.CombinedOutput()
		if err != nil {
			t.Log(string(b))
			t.Fatal(err)
		}
		require.Contains(t, string(b), fmt.Sprintf("Merge Request #%s closed", mrID))
	})
}

func Test_mrCmd_noArgs(t *testing.T) {
	repo := copyTestRepo(t)
	cmd := exec.Command(labBinaryPath, "mr")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Fatal(err)
	}
	require.Contains(t, string(b), `Usage:
  lab mr [flags]
  lab mr [command]`)
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/spf13/cobra"
)

type oneshotCmd struct {
	cluster     string
	taskDefName string
	command     []string
	revision    int
}

func NewOneshotCommand(out, errOut io.Writer) *cobra.Command {
	f := &oneshotCmd{}
	cmd := &cobra.Command{
		Use:   "oneshot [options] COMMAND",
		Short: "",
		Run: func(cmd *cobra.Command, args []string) {
			f.command = args

			l := NewLogger(f.cluster, f.taskDefName, "", out)
			err := f.execute(cmd, args, l)
			if err != nil {
				l.log(fmt.Sprintf("error: %s\n", err.Error()))
			}
		},
	}
	cmd.Flags().StringVar(&f.cluster, "cluster", "", "ECS cluster name")
	cmd.Flags().StringVar(&f.taskDefName, "taskdef-name", "", "ECS task definition name")
	cmd.Flags().IntVar(&f.revision, "revision", 0, "revision of ECS task definition")

	return cmd
}

func (f *oneshotCmd) execute(_ *cobra.Command, args []string, l *logger) error {
	if f.cluster == "" {
		return errors.New("--cluster is required")
	}

	if f.taskDefName == "" {
		return errors.New("--taskdef-name is required")
	}

	if len(f.command) == 0 {
		return errors.New("COMMAND is required")
	}

	region := getAWSRegion()
	if region == "" {
		return errors.New("AWS region is not found. please set a AWS_DEFAULT_REGION or AWS_REGION")
	}

	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	client := ecs.New(sess, &aws.Config{
		Region: aws.String(region),
	})

	taskDef, err := f.describeTaskDefinition(client, f.taskDefName)
	if err != nil {
		return err
	}

	arn := *taskDef.TaskDefinitionArn
	arn, err = specifyRevision(f.revision, arn)
	if err != nil {
		return err
	}

	taskDef, err = f.describeTaskDefinition(client, arn)
	if err != nil {
		return err
	}

	task, err := f.runTask(client, taskDef, f.command)
	if err != nil {
		return err
	}

	l.log("task started\n")

	status, err := f.waitTask(client, task, l)
	if err != nil {
		return err
	}

	os.Exit(status.ExitCode)

	return nil
}

type taskStatus struct {
	ExitCode      int
	StoppedReason string
}

func (f *oneshotCmd) runTask(client *ecs.ECS, taskDef *ecs.TaskDefinition, command []string) (*ecs.Task, error) {
	var commands []*string
	for _, v := range command {
		commands = append(commands, aws.String(v))
	}

	params := &ecs.RunTaskInput{
		Cluster:        aws.String(f.cluster),
		TaskDefinition: taskDef.TaskDefinitionArn,
		Overrides: &ecs.TaskOverride{
			ContainerOverrides: []*ecs.ContainerOverride{
				{
					Name:    aws.String(f.taskDefName),
					Command: commands,
				},
			},
		},
		Count:     aws.Int64(1),
		StartedBy: aws.String("shipctl oneshot"),
	}
	res, err := client.RunTask(params)
	if err != nil {
		return nil, err
	}

	if len(res.Failures) > 0 {
		msg := ""
		for _, v := range res.Failures {
			msg += fmt.Sprintf("    %s\n", *v.Reason)
		}
		return nil, errors.New("failed to runTask\n" + msg)
	}

	return res.Tasks[0], nil
}

func (f *oneshotCmd) waitTask(client *ecs.ECS, task *ecs.Task, l *logger) (*taskStatus, error) {
	start := time.Now()
	t := time.NewTicker(10 * time.Second)
	for {
		select {
		case <-t.C:
			re, err := f.describeTask(client, task)
			if err != nil {
				return nil, err
			}

			elapsed := time.Now().Sub(start)
			l.log(fmt.Sprintf("still running... [%s]\n", (elapsed/time.Second)*time.Second))

			if *re.LastStatus == "STOPPED" {
				status := &taskStatus{
					StoppedReason: *re.StoppedReason,
				}
				if re.Containers[0].ExitCode != nil {
					status.ExitCode = int(*re.Containers[0].ExitCode)
				}
				return status, nil
			}
		}
	}
}

func (f *oneshotCmd) describeTask(client *ecs.ECS, task *ecs.Task) (*ecs.Task, error) {
	params := &ecs.DescribeTasksInput{
		Tasks: []*string{
			task.TaskArn,
		},
		Cluster: task.ClusterArn,
	}
	res, err := client.DescribeTasks(params)
	if err != nil {
		return nil, err
	}

	if len(res.Failures) > 0 {
		msg := ""
		for _, v := range res.Failures {
			msg += fmt.Sprintf("    %s\n", *v.Reason)
		}
		return nil, errors.New("failed to runTask\n" + msg)
	}

	return res.Tasks[0], nil
}

func (f *oneshotCmd) describeTaskDefinition(client *ecs.ECS, name string) (*ecs.TaskDefinition, error) {
	params := &ecs.DescribeTaskDefinitionInput{
		TaskDefinition: aws.String(name),
	}

	res, err := client.DescribeTaskDefinition(params)
	if err != nil {
		return nil, err
	}

	return res.TaskDefinition, nil
}
<commit_msg>support SIGINT<commit_after>package cmd

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/spf13/cobra"
)

type oneshotCmd struct {
	cluster     string
	taskDefName string
	command     []string
	revision    int
}

func NewOneshotCommand(out, errOut io.Writer) *cobra.Command {
	f := &oneshotCmd{}
	cmd := &cobra.Command{
		Use:   "oneshot [options] COMMAND",
		Short: "",
		Run: func(cmd *cobra.Command, args []string) {
			f.command = args

			l := NewLogger(f.cluster, f.taskDefName, "", out)
			err := f.execute(cmd, args, l)
			if err != nil {
				l.log(fmt.Sprintf("error: %s\n", err.Error()))
			}
		},
	}
	cmd.Flags().StringVar(&f.cluster, "cluster", "", "ECS cluster name")
	cmd.Flags().StringVar(&f.taskDefName, "taskdef-name", "", "ECS task definition name")
	cmd.Flags().IntVar(&f.revision, "revision", 0, "revision of ECS task definition")

	return cmd
}

func (f *oneshotCmd) execute(_ *cobra.Command, args []string, l *logger) error {
	if f.cluster == "" {
		return errors.New("--cluster is required")
	}

	if f.taskDefName == "" {
		return errors.New("--taskdef-name is required")
	}

	if len(f.command) == 0 {
		return errors.New("COMMAND is required")
	}

	region := getAWSRegion()
	if region == "" {
		return errors.New("AWS region is not found. please set a AWS_DEFAULT_REGION or AWS_REGION")
	}

	sess, err := session.NewSession()
	if err != nil {
		return err
	}

	client := ecs.New(sess, &aws.Config{
		Region: aws.String(region),
	})

	taskDef, err := f.describeTaskDefinition(client, f.taskDefName)
	if err != nil {
		return err
	}

	arn := *taskDef.TaskDefinitionArn
	arn, err = specifyRevision(f.revision, arn)
	if err != nil {
		return err
	}

	taskDef, err = f.describeTaskDefinition(client, arn)
	if err != nil {
		return err
	}

	task, err := f.runTask(client, taskDef, f.command)
	if err != nil {
		return err
	}

	l.log("task started\n")

	status, err := f.waitTask(client, task, l)
	if err != nil {
		return err
	}

	os.Exit(status.ExitCode)

	return nil
}

type taskStatus struct {
	ExitCode      int
	StoppedReason string
}

func (f *oneshotCmd) runTask(client *ecs.ECS, taskDef *ecs.TaskDefinition, command []string) (*ecs.Task, error) {
	var commands []*string
	for _, v := range command {
		commands = append(commands, aws.String(v))
	}

	params := &ecs.RunTaskInput{
		Cluster:        aws.String(f.cluster),
		TaskDefinition: taskDef.TaskDefinitionArn,
		Overrides: &ecs.TaskOverride{
			ContainerOverrides: []*ecs.ContainerOverride{
				{
					Name:    aws.String(f.taskDefName),
					Command: commands,
				},
			},
		},
		Count:     aws.Int64(1),
		StartedBy: aws.String("shipctl oneshot"),
	}
	res, err := client.RunTask(params)
	if err != nil {
		return nil, err
	}

	if len(res.Failures) > 0 {
		msg := ""
		for _, v := range res.Failures {
			msg += fmt.Sprintf("    %s\n", *v.Reason)
		}
		return nil, errors.New("failed to runTask\n" + msg)
	}

	return res.Tasks[0], nil
}

func (f *oneshotCmd) waitTask(client *ecs.ECS, task *ecs.Task, l *logger) (*taskStatus, error) {
	start := time.Now()
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT)
	t := time.NewTicker(10 * time.Second)
	s := "running"
	for {
		select {
		case <-t.C:
			re, err := f.describeTask(client, task)
			if err != nil {
				return nil, err
			}

			elapsed := time.Now().Sub(start)
			l.log(fmt.Sprintf("still %s... [%s]\n", s, (elapsed/time.Second)*time.Second))

			if *re.LastStatus == "STOPPED" {
				status := &taskStatus{
					StoppedReason: *re.StoppedReason,
				}
				if re.Containers[0].ExitCode != nil {
					status.ExitCode = int(*re.Containers[0].ExitCode)
				}
				return status, nil
			}
		case <-sig:
			_ = f.stopTask(client, task)
			l.log(fmt.Sprintf("send stop signal\n"))
			s = "stopping"
		}
	}
}

func (f *oneshotCmd) describeTask(client *ecs.ECS, task *ecs.Task) (*ecs.Task, error) {
	params := &ecs.DescribeTasksInput{
		Tasks: []*string{
			task.TaskArn,
		},
		Cluster: task.ClusterArn,
	}
	res, err := client.DescribeTasks(params)
	if err != nil {
		return nil, err
	}

	if len(res.Failures) > 0 {
		msg := ""
		for _, v := range res.Failures {
			msg += fmt.Sprintf("    %s\n", *v.Reason)
		}
		return nil, errors.New("failed to runTask\n" + msg)
	}

	return res.Tasks[0], nil
}

func (f *oneshotCmd) describeTaskDefinition(client *ecs.ECS, name string) (*ecs.TaskDefinition, error) {
	params := &ecs.DescribeTaskDefinitionInput{
		TaskDefinition: aws.String(name),
	}

	res, err := client.DescribeTaskDefinition(params)
	if err != nil {
		return nil, err
	}

	return res.TaskDefinition, nil
}

func (f *oneshotCmd) stopTask(client *ecs.ECS, task *ecs.Task) error {
	params := &ecs.StopTaskInput{
		Cluster: task.ClusterArn,
		Reason:  aws.String("SIGINT"),
		Task:    task.TaskArn,
	}

	_, err := client.StopTask(params)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"net"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/ui"
	"github.com/pkg/errors"
	"github.com/spf13/pflag"
)

func optionFlagSet() *pflag.FlagSet {
	flags := pflag.NewFlagSet("", 0)
	flags.SortFlags = false
	flags.Int64P("vus", "u", 1, "number of virtual users")
	flags.Int64P("max", "m", 0, "max available virtual users")
	flags.DurationP("duration", "d", 0, "test duration limit")
	flags.Int64P("iterations", "i", 0, "script iteration limit")
	flags.StringSliceP("stage", "s", nil, "add a `stage`, as `[duration]:[target]`")
	flags.BoolP("paused", "p", false, "start the test in a paused state")
	flags.Int64("max-redirects", 10, "follow at most n redirects")
	flags.Int64("batch", 10, "max parallel batch reqs")
	flags.Int64("batch-per-host", 0, "max parallel batch reqs per host")
	flags.Int64("rps", 0, "limit requests per second")
	flags.String("user-agent", "", "user agent for http requests")
	flags.Bool("insecure-skip-tls-verify", false, "skip verification of TLS certificates")
	flags.Bool("no-connection-reuse", false, "don't reuse connections between iterations")
	flags.BoolP("throw", "w", false, "throw warnings (like failed http requests) as errors")
	flags.StringSlice("blacklist-ip", nil, "blacklist an `ip range` from being called")
	flags.StringSlice("summary-trend-stats", nil, "define `stats` for trend metrics (response times)")
	return flags
}

func getOptions(flags *pflag.FlagSet) (lib.Options, error) {
	opts := lib.Options{
		VUs:                   getNullInt64(flags, "vus"),
		VUsMax:                getNullInt64(flags, "max"),
		Duration:              getNullDuration(flags, "duration"),
		Iterations:            getNullInt64(flags, "iterations"),
		Paused:                getNullBool(flags, "paused"),
		MaxRedirects:          getNullInt64(flags, "max-redirects"),
		Batch:                 getNullInt64(flags, "batch"),
		RPS:                   getNullInt64(flags, "rps"),
		UserAgent:             getNullString(flags, "user-agent"),
		InsecureSkipTLSVerify: getNullBool(flags, "insecure-skip-tls-verify"),
		NoConnectionReuse:     getNullBool(flags, "no-connection-reuse"),
		Throw:                 getNullBool(flags, "throw"),
	}

	stageStrings, err := flags.GetStringSlice("stage")
	if err != nil {
		return opts, err
	}
	if len(stageStrings) > 0 {
		opts.Stages = make([]lib.Stage, len(stageStrings))
		for i, s := range stageStrings {
			var stage lib.Stage
			if err := stage.UnmarshalText([]byte(s)); err != nil {
				return opts, errors.Wrapf(err, "stage %d", i)
			}
			opts.Stages[i] = stage
		}
	}

	blacklistIPStrings, err := flags.GetStringSlice("blacklist-ip")
	if err != nil {
		return opts, err
	}
	for _, s := range blacklistIPStrings {
		_, net, err := net.ParseCIDR(s)
		if err != nil {
			return opts, errors.Wrap(err, "blacklist-ip")
		}
		opts.BlacklistIPs = append(opts.BlacklistIPs, net)
	}

	trendStatStrings, err := flags.GetStringSlice("summary-trend-stats")
	if err != nil {
		return opts, err
	}
	for _, s := range trendStatStrings {
		if ui.VerifyTrendColumn(s) {
			opts.SummaryTrendStats = append(opts.SummaryTrendStats, s)
		}
	}

	return opts, nil
}
<commit_msg>Fix renaming typo<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"net"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/ui"
	"github.com/pkg/errors"
	"github.com/spf13/pflag"
)

func optionFlagSet() *pflag.FlagSet {
	flags := pflag.NewFlagSet("", 0)
	flags.SortFlags = false
	flags.Int64P("vus", "u", 1, "number of virtual users")
	flags.Int64P("max", "m", 0, "max available virtual users")
	flags.DurationP("duration", "d", 0, "test duration limit")
	flags.Int64P("iterations", "i", 0, "script iteration limit")
	flags.StringSliceP("stage", "s", nil, "add a `stage`, as `[duration]:[target]`")
	flags.BoolP("paused", "p", false, "start the test in a paused state")
	flags.Int64("max-redirects", 10, "follow at most n redirects")
	flags.Int64("batch", 10, "max parallel batch reqs")
	flags.Int64("batch-per-host", 0, "max parallel batch reqs per host")
	flags.Int64("rps", 0, "limit requests per second")
	flags.String("user-agent", "", "user agent for http requests")
	flags.Bool("insecure-skip-tls-verify", false, "skip verification of TLS certificates")
	flags.Bool("no-connection-reuse", false, "don't reuse connections between iterations")
	flags.BoolP("throw", "w", false, "throw warnings (like failed http requests) as errors")
	flags.StringSlice("blacklist-ip", nil, "blacklist an `ip range` from being called")
	flags.StringSlice("summary-trend-stats", nil, "define `stats` for trend metrics (response times)")
	return flags
}

func getOptions(flags *pflag.FlagSet) (lib.Options, error) {
	opts := lib.Options{
		VUs:                   getNullInt64(flags, "vus"),
		VUsMax:                getNullInt64(flags, "max"),
		Duration:              getNullDuration(flags, "duration"),
		Iterations:            getNullInt64(flags, "iterations"),
		Paused:                getNullBool(flags, "paused"),
		MaxRedirects:          getNullInt64(flags, "max-redirects"),
		Batch:                 getNullInt64(flags, "batch"),
		RPS:                   getNullInt64(flags, "rps"),
		UserAgent:             getNullString(flags, "user-agent"),
		InsecureSkipTLSVerify: getNullBool(flags, "insecure-skip-tls-verify"),
		NoConnectionReuse:     getNullBool(flags, "no-connection-reuse"),
		Throw:                 getNullBool(flags, "throw"),
	}

	stageStrings, err := flags.GetStringSlice("stage")
	if err != nil {
		return opts, err
	}
	if len(stageStrings) > 0 {
		opts.Stages = make([]lib.Stage, len(stageStrings))
		for i, s := range stageStrings {
			var stage lib.Stage
			if err := stage.UnmarshalText([]byte(s)); err != nil {
				return opts, errors.Wrapf(err, "stage %d", i)
			}
			opts.Stages[i] = stage
		}
	}

	blacklistIPStrings, err := flags.GetStringSlice("blacklist-ip")
	if err != nil {
		return opts, err
	}
	for _, s := range blacklistIPStrings {
		_, net, err := net.ParseCIDR(s)
		if err != nil {
			return opts, errors.Wrap(err, "blacklist-ip")
		}
		opts.BlacklistIPs = append(opts.BlacklistIPs, net)
	}

	trendStatStrings, err := flags.GetStringSlice("summary-trend-stats")
	if err != nil {
		return opts, err
	}
	for _, s := range trendStatStrings {
		if ui.VerifyTrendColumnStat(s) {
			opts.SummaryTrendStats = append(opts.SummaryTrendStats, s)
		}
	}

	return opts, nil
}
<|endoftext|>