<commit_before>package edgectl

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/datawire/ambassador/pkg/metriton"
	"github.com/datawire/ambassador/pkg/supervisor"
)

func (d *Daemon) handleCommand(p *supervisor.Process, conn net.Conn, data *ClientMessage) error {
	out := NewEmitter(conn)
	rootCmd := d.GetRootCommand(p, out, data)
	rootCmd.SetOutput(conn) // FIXME replace with SetOut and SetErr
	rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
		if batch, _ := cmd.Flags().GetBool("batch"); batch {
			out.SetKV()
		}
	}
	rootCmd.SetArgs(data.Args[1:])
	err := rootCmd.Execute()
	if err != nil {
		out.SendExit(1)
	}
	return out.Err()
}

func (d *Daemon) GetRootCommand(p *supervisor.Process, out *Emitter, data *ClientMessage) *cobra.Command {
	reporter := &metriton.Reporter{
		Application:  "edgectl",
		Version:      Version,
		GetInstallID: func(_ *metriton.Reporter) (string, error) { return data.InstallID, nil },
		BaseMetadata: map[string]interface{}{"mode": "daemon"},
	}

	rootCmd := &cobra.Command{
		Use:          "edgectl",
		Short:        "Edge Control",
		SilenceUsage: true, // https://github.com/spf13/cobra/issues/340
	}
	_ = rootCmd.PersistentFlags().Bool("batch", false, "Emit machine-readable output")
	_ = rootCmd.PersistentFlags().MarkHidden("batch")

	rootCmd.AddCommand(&cobra.Command{
		Use:   "version",
		Short: "Show program's version number and exit",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			out.Println("Client", data.ClientVersion)
			out.Println("Daemon", DisplayVersion())
			out.Send("daemon.version", Version)
			out.Send("daemon.apiVersion", apiVersion)
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "status",
		Short: "Show connectivity status",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.Status(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "pause",
		Short: "Turn off network overrides (to use a VPN)",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if d.network == nil {
				out.Println("Network overrides are already paused")
				out.Send("paused", true)
				return out.Err()
			}
			if d.cluster != nil {
				out.Println("Edge Control is connected to a cluster.")
				out.Println("See \"edgectl status\" for details.")
				out.Println("Please disconnect before pausing.")
				out.Send("paused", false)
				out.SendExit(1)
				return out.Err()
			}

			if err := d.network.Close(); err != nil {
				p.Logf("pause: %v", err)
				out.Printf("Unexpected error while pausing: %v\n", err)
			}
			d.network = nil

			out.Println("Network overrides paused.")
			out.Println("Use \"edgectl resume\" to reestablish network overrides.")
			out.Send("paused", true)

			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:     "resume",
		Short:   "Turn network overrides on (after using edgectl pause)",
		Aliases: []string{"unpause"},
		Args:    cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if d.network != nil {
				if d.network.IsOkay() {
					out.Println("Network overrides are established (not paused)")
				} else {
					out.Println("Network overrides are being reestablished...")
				}
				out.Send("paused", false)
				return out.Err()
			}

			if err := d.MakeNetOverride(p); err != nil {
				p.Logf("resume: %v", err)
				out.Printf("Unexpected error establishing network overrides: %v", err)
			}
			out.Send("paused", d.network == nil)

			return out.Err()
		},
	})
	connectCmd := &cobra.Command{
		Use:   "connect [flags] [-- additional kubectl arguments...]",
		Short: "Connect to a cluster",
		RunE: func(cmd *cobra.Command, args []string) error {
			if _, err := reporter.Report(p.Context(), map[string]interface{}{"action": "connect"}); err != nil {
				p.Logf("report failed: %+v", err)
			}
			context, _ := cmd.Flags().GetString("context")
			namespace, _ := cmd.Flags().GetString("namespace")
			managerNs, _ := cmd.Flags().GetString("manager-namespace")
			isCI, _ := cmd.Flags().GetBool("ci")
			if err := d.Connect(
				p, out, data.RAI,
				context, namespace, managerNs, args,
				data.InstallID, isCI,
			); err != nil {
				return err
			}
			return out.Err()
		},
	}
	_ = connectCmd.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = connectCmd.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)
	_ = connectCmd.Flags().StringP(
		"manager-namespace", "m", "ambassador",
		"The Kubernetes namespace in which the Traffic Manager is running.",
	)
	_ = connectCmd.Flags().Bool("ci", false, "This session is a CI run.")
	rootCmd.AddCommand(connectCmd)
	rootCmd.AddCommand(&cobra.Command{
		Use:   "disconnect",
		Short: "Disconnect from the connected cluster",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.Disconnect(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "quit",
		Short: "Tell Edge Control Daemon to quit (for upgrades)",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			out.Println("Edge Control Daemon quitting...")
			out.Send("quit", true)
			p.Supervisor().Shutdown()
			return out.Err()
		},
	})

	interceptCmd := &cobra.Command{
		Use: "intercept",
		Long: "Manage deployment intercepts. An intercept arranges for a subset of requests to be " +
			"diverted to the local machine.",
		Short: "Manage deployment intercepts",
	}
	interceptCmd.AddCommand(&cobra.Command{
		Use:     "available",
		Aliases: []string{"avail"},
		Short:   "List deployments available for intercept",
		Args:    cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			msg := d.interceptMessage()
			if msg != "" {
				out.Println(msg)
				out.Send("intercept", msg)
				return out.Err()
			}
			out.Send("interceptable", len(d.trafficMgr.interceptables))
			switch {
			case len(d.trafficMgr.interceptables) == 0:
				out.Println("No interceptable deployments")
			default:
				out.Printf("Found %d interceptable deployment(s):\n", len(d.trafficMgr.interceptables))
				for idx, deployment := range d.trafficMgr.interceptables {
					fields := strings.SplitN(deployment, "/", 2)

					appName := fields[0]
					appNamespace := d.cluster.namespace

					if len(fields) > 1 {
						appNamespace = fields[0]
						appName = fields[1]
					}

					out.Printf("%4d. %s in namespace %s\n", idx+1, appName, appNamespace)
					out.Send(fmt.Sprintf("interceptable.deployment.%d", idx+1), deployment)
				}
			}
			return out.Err()
		},
	})

	interceptCmd.AddCommand(&cobra.Command{
		Use:   "list",
		Short: "List current intercepts",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.ListIntercepts(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	interceptCmd.AddCommand(&cobra.Command{
		Use:     "remove [flags] DEPLOYMENT",
		Aliases: []string{"delete"},
		Short:   "Deactivate and remove an existent intercept",
		Args:    cobra.MinimumNArgs(1),
		RunE: func(_ *cobra.Command, args []string) error {
			name := strings.TrimSpace(args[0])
			if err := d.RemoveIntercept(p, out, name); err != nil {
				return err
			}
			return out.Err()
		},
	})
	intercept := InterceptInfo{}
	interceptPreview := true
	var interceptAddCmdFlags *pflag.FlagSet
	interceptAddCmd := &cobra.Command{
		Use:   "add [flags] DEPLOYMENT -t [HOST:]PORT ([-p] | -m HEADER=REGEX ...)",
		Short: "Add a deployment intercept",
		Args:  cobra.ExactArgs(1),
		RunE: func(_ *cobra.Command, args []string) error {
			intercept.Deployment = args[0]
			if intercept.Name == "" {
				intercept.Name = fmt.Sprintf("cept-%d", time.Now().Unix())
			}

			// if intercept.Namespace == "" {
			// 	intercept.Namespace = "default"
			// }

			if intercept.Prefix == "" {
				intercept.Prefix = "/"
			}

			var host, portStr string
			hp := strings.SplitN(intercept.TargetHost, ":", 2)
			if len(hp) < 2 {
				portStr = hp[0]
			} else {
				host = strings.TrimSpace(hp[0])
				portStr = hp[1]
			}
			if len(host) == 0 {
				host = "127.0.0.1"
			}
			port, err := strconv.Atoi(portStr)
			if err != nil {
				out.Printf("Failed to parse %q as HOST:PORT: %v\n", intercept.TargetHost, err)
				out.Send("failed", "parse target")
				out.SendExit(1)
				return nil
			}
			intercept.TargetHost = host
			intercept.TargetPort = port

			// If the user specifies --preview on the command line, then use its
			// value (--preview is the same as --preview=true, or it could be
			// --preview=false). But if the user does not specify --preview on
			// the command line, compute its value from the presence or absence
			// of --match, since they are mutually exclusive.
			userSetPreviewFlag := interceptAddCmdFlags.Changed("preview")
			userSetMatchFlag := len(intercept.Patterns) > 0

			if userSetPreviewFlag && interceptPreview {
				// User specified --preview (or --preview=true) at the command line
				if userSetMatchFlag {
					out.Println("Error: Cannot use --match and --preview at the same time")
					out.Send("failed", "both match and preview")
					out.SendExit(1)
					return nil
				} else {
					// ok: --preview=true and no --match
				}
			} else if userSetPreviewFlag && !interceptPreview {
				// User specified --preview=false at the command line
				if userSetMatchFlag {
					// ok: --preview=false and at least one --match
				} else {
					out.Println("Error: Must specify --match when using --preview=false")
					out.Send("failed", "neither match nor preview")
					out.SendExit(1)
					return nil
				}
			} else {
				// User did not specify --preview at the command line
				if userSetMatchFlag {
					// ok: at least one --match
					interceptPreview = false
				} else {
					// ok: neither --match nor --preview, fall back to preview
					interceptPreview = true
				}
			}

			if interceptPreview {
				intercept.Patterns = make(map[string]string)
				intercept.Patterns["x-service-preview"] = data.InstallID
			}

			if err := d.AddIntercept(p, out, &intercept); err != nil {
				return err
			}

			if url := intercept.PreviewURL(d.trafficMgr.previewHost); url != "" {
				out.Println("Share a preview of your changes with anyone by visiting\n  ", url)
			}

			return out.Err()
		},
	}
	interceptAddCmd.Flags().StringVarP(&intercept.Name, "name", "n", "", "a name for this intercept")
	interceptAddCmd.Flags().StringVar(&intercept.Prefix, "prefix", "/", "prefix to intercept")
	interceptAddCmd.Flags().BoolVarP(&interceptPreview, "preview", "p", true, "use a preview URL") // this default is unused
	interceptAddCmd.Flags().StringVarP(&intercept.TargetHost, "target", "t", "", "the [HOST:]PORT to forward to")
	_ = interceptAddCmd.MarkFlagRequired("target")
	interceptAddCmd.Flags().StringToStringVarP(&intercept.Patterns, "match", "m", nil, "match expression (HEADER=REGEX)")
	interceptAddCmd.Flags().StringVarP(&intercept.Namespace, "namespace", "", "", "Kubernetes namespace in which to create mapping for intercept")
	interceptAddCmdFlags = interceptAddCmd.Flags()

	interceptCmd.AddCommand(interceptAddCmd)
	interceptCG := []CmdGroup{
		{
			GroupName: "Available Commands",
			CmdNames:  []string{"available", "list", "add", "remove"},
		},
	}
	interceptCmd.SetUsageFunc(NewCmdUsage(interceptCmd, interceptCG))
	rootCmd.AddCommand(interceptCmd)

	return rootCmd
}
<commit_msg>edgectl: Disallow --preview when no Host is configured for it<commit_after>package edgectl

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/datawire/ambassador/pkg/metriton"
	"github.com/datawire/ambassador/pkg/supervisor"
)

func (d *Daemon) handleCommand(p *supervisor.Process, conn net.Conn, data *ClientMessage) error {
	out := NewEmitter(conn)
	rootCmd := d.GetRootCommand(p, out, data)
	rootCmd.SetOutput(conn) // FIXME replace with SetOut and SetErr
	rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
		if batch, _ := cmd.Flags().GetBool("batch"); batch {
			out.SetKV()
		}
	}
	rootCmd.SetArgs(data.Args[1:])
	err := rootCmd.Execute()
	if err != nil {
		out.SendExit(1)
	}
	return out.Err()
}

func (d *Daemon) GetRootCommand(p *supervisor.Process, out *Emitter, data *ClientMessage) *cobra.Command {
	reporter := &metriton.Reporter{
		Application:  "edgectl",
		Version:      Version,
		GetInstallID: func(_ *metriton.Reporter) (string, error) { return data.InstallID, nil },
		BaseMetadata: map[string]interface{}{"mode": "daemon"},
	}

	rootCmd := &cobra.Command{
		Use:          "edgectl",
		Short:        "Edge Control",
		SilenceUsage: true, // https://github.com/spf13/cobra/issues/340
	}
	_ = rootCmd.PersistentFlags().Bool("batch", false, "Emit machine-readable output")
	_ = rootCmd.PersistentFlags().MarkHidden("batch")

	rootCmd.AddCommand(&cobra.Command{
		Use:   "version",
		Short: "Show program's version number and exit",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			out.Println("Client", data.ClientVersion)
			out.Println("Daemon", DisplayVersion())
			out.Send("daemon.version", Version)
			out.Send("daemon.apiVersion", apiVersion)
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "status",
		Short: "Show connectivity status",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.Status(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "pause",
		Short: "Turn off network overrides (to use a VPN)",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if d.network == nil {
				out.Println("Network overrides are already paused")
				out.Send("paused", true)
				return out.Err()
			}
			if d.cluster != nil {
				out.Println("Edge Control is connected to a cluster.")
				out.Println("See \"edgectl status\" for details.")
				out.Println("Please disconnect before pausing.")
				out.Send("paused", false)
				out.SendExit(1)
				return out.Err()
			}

			if err := d.network.Close(); err != nil {
				p.Logf("pause: %v", err)
				out.Printf("Unexpected error while pausing: %v\n", err)
			}
			d.network = nil

			out.Println("Network overrides paused.")
			out.Println("Use \"edgectl resume\" to reestablish network overrides.")
			out.Send("paused", true)

			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:     "resume",
		Short:   "Turn network overrides on (after using edgectl pause)",
		Aliases: []string{"unpause"},
		Args:    cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if d.network != nil {
				if d.network.IsOkay() {
					out.Println("Network overrides are established (not paused)")
				} else {
					out.Println("Network overrides are being reestablished...")
				}
				out.Send("paused", false)
				return out.Err()
			}

			if err := d.MakeNetOverride(p); err != nil {
				p.Logf("resume: %v", err)
				out.Printf("Unexpected error establishing network overrides: %v", err)
			}
			out.Send("paused", d.network == nil)

			return out.Err()
		},
	})
	connectCmd := &cobra.Command{
		Use:   "connect [flags] [-- additional kubectl arguments...]",
		Short: "Connect to a cluster",
		RunE: func(cmd *cobra.Command, args []string) error {
			if _, err := reporter.Report(p.Context(), map[string]interface{}{"action": "connect"}); err != nil {
				p.Logf("report failed: %+v", err)
			}
			context, _ := cmd.Flags().GetString("context")
			namespace, _ := cmd.Flags().GetString("namespace")
			managerNs, _ := cmd.Flags().GetString("manager-namespace")
			isCI, _ := cmd.Flags().GetBool("ci")
			if err := d.Connect(
				p, out, data.RAI,
				context, namespace, managerNs, args,
				data.InstallID, isCI,
			); err != nil {
				return err
			}
			return out.Err()
		},
	}
	_ = connectCmd.Flags().StringP(
		"context", "c", "",
		"The Kubernetes context to use. Defaults to the current kubectl context.",
	)
	_ = connectCmd.Flags().StringP(
		"namespace", "n", "",
		"The Kubernetes namespace to use. Defaults to kubectl's default for the context.",
	)
	_ = connectCmd.Flags().StringP(
		"manager-namespace", "m", "ambassador",
		"The Kubernetes namespace in which the Traffic Manager is running.",
	)
	_ = connectCmd.Flags().Bool("ci", false, "This session is a CI run.")
	rootCmd.AddCommand(connectCmd)
	rootCmd.AddCommand(&cobra.Command{
		Use:   "disconnect",
		Short: "Disconnect from the connected cluster",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.Disconnect(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	rootCmd.AddCommand(&cobra.Command{
		Use:   "quit",
		Short: "Tell Edge Control Daemon to quit (for upgrades)",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			out.Println("Edge Control Daemon quitting...")
			out.Send("quit", true)
			p.Supervisor().Shutdown()
			return out.Err()
		},
	})

	interceptCmd := &cobra.Command{
		Use: "intercept",
		Long: "Manage deployment intercepts. An intercept arranges for a subset of requests to be " +
			"diverted to the local machine.",
		Short: "Manage deployment intercepts",
	}
	interceptCmd.AddCommand(&cobra.Command{
		Use:     "available",
		Aliases: []string{"avail"},
		Short:   "List deployments available for intercept",
		Args:    cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			msg := d.interceptMessage()
			if msg != "" {
				out.Println(msg)
				out.Send("intercept", msg)
				return out.Err()
			}
			out.Send("interceptable", len(d.trafficMgr.interceptables))
			switch {
			case len(d.trafficMgr.interceptables) == 0:
				out.Println("No interceptable deployments")
			default:
				out.Printf("Found %d interceptable deployment(s):\n", len(d.trafficMgr.interceptables))
				for idx, deployment := range d.trafficMgr.interceptables {
					fields := strings.SplitN(deployment, "/", 2)

					appName := fields[0]
					appNamespace := d.cluster.namespace

					if len(fields) > 1 {
						appNamespace = fields[0]
						appName = fields[1]
					}

					out.Printf("%4d. %s in namespace %s\n", idx+1, appName, appNamespace)
					out.Send(fmt.Sprintf("interceptable.deployment.%d", idx+1), deployment)
				}
			}
			return out.Err()
		},
	})

	interceptCmd.AddCommand(&cobra.Command{
		Use:   "list",
		Short: "List current intercepts",
		Args:  cobra.ExactArgs(0),
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := d.ListIntercepts(p, out); err != nil {
				return err
			}
			return out.Err()
		},
	})
	interceptCmd.AddCommand(&cobra.Command{
		Use:     "remove [flags] DEPLOYMENT",
		Aliases: []string{"delete"},
		Short:   "Deactivate and remove an existent intercept",
		Args:    cobra.MinimumNArgs(1),
		RunE: func(_ *cobra.Command, args []string) error {
			name := strings.TrimSpace(args[0])
			if err := d.RemoveIntercept(p, out, name); err != nil {
				return err
			}
			return out.Err()
		},
	})
	intercept := InterceptInfo{}
	interceptPreview := true
	var interceptAddCmdFlags *pflag.FlagSet
	interceptAddCmd := &cobra.Command{
		Use:   "add [flags] DEPLOYMENT -t [HOST:]PORT ([-p] | -m HEADER=REGEX ...)",
		Short: "Add a deployment intercept",
		Args:  cobra.ExactArgs(1),
		RunE: func(_ *cobra.Command, args []string) error {
			intercept.Deployment = args[0]
			if intercept.Name == "" {
				intercept.Name = fmt.Sprintf("cept-%d", time.Now().Unix())
			}

			// if intercept.Namespace == "" {
			// 	intercept.Namespace = "default"
			// }

			if intercept.Prefix == "" {
				intercept.Prefix = "/"
			}

			var host, portStr string
			hp := strings.SplitN(intercept.TargetHost, ":", 2)
			if len(hp) < 2 {
				portStr = hp[0]
			} else {
				host = strings.TrimSpace(hp[0])
				portStr = hp[1]
			}
			if len(host) == 0 {
				host = "127.0.0.1"
			}
			port, err := strconv.Atoi(portStr)
			if err != nil {
				out.Printf("Failed to parse %q as HOST:PORT: %v\n", intercept.TargetHost, err)
				out.Send("failed", "parse target")
				out.SendExit(1)
				return nil
			}
			intercept.TargetHost = host
			intercept.TargetPort = port

			// If the user specifies --preview on the command line, then use its
			// value (--preview is the same as --preview=true, or it could be
			// --preview=false). But if the user does not specify --preview on
			// the command line, compute its value from the presence or absence
			// of --match, since they are mutually exclusive.
			userSetPreviewFlag := interceptAddCmdFlags.Changed("preview")
			userSetMatchFlag := len(intercept.Patterns) > 0

			if userSetPreviewFlag && interceptPreview {
				// User specified --preview (or --preview=true) at the command line
				if userSetMatchFlag {
					out.Println("Error: Cannot use --match and --preview at the same time")
					out.Send("failed", "both match and preview")
					out.SendExit(1)
					return nil
				} else {
					// ok: --preview=true and no --match
				}
			} else if userSetPreviewFlag && !interceptPreview {
				// User specified --preview=false at the command line
				if userSetMatchFlag {
					// ok: --preview=false and at least one --match
				} else {
					out.Println("Error: Must specify --match when using --preview=false")
					out.Send("failed", "neither match nor preview")
					out.SendExit(1)
					return nil
				}
			} else {
				// User did not specify --preview at the command line
				if userSetMatchFlag {
					// ok: at least one --match
					interceptPreview = false
				} else {
					// ok: neither --match nor --preview, fall back to preview
					interceptPreview = true
				}
			}

			if interceptPreview {
				if d.trafficMgr.previewHost == "" {
					out.Println("Your cluster is not configured for Preview URLs.")
					out.Println("(Could not find a Host resource that enables Path-type Preview URLs.)")
					out.Println("Please specify one or more header matches using --match.")
					out.Send("failed", "preview requested but unavailable")
					out.SendExit(1)
					return nil
				}

				intercept.Patterns = make(map[string]string)
				intercept.Patterns["x-service-preview"] = data.InstallID
			}

			if err := d.AddIntercept(p, out, &intercept); err != nil {
				return err
			}

			if url := intercept.PreviewURL(d.trafficMgr.previewHost); url != "" {
				out.Println("Share a preview of your changes with anyone by visiting\n  ", url)
			}

			return out.Err()
		},
	}
	interceptAddCmd.Flags().StringVarP(&intercept.Name, "name", "n", "", "a name for this intercept")
	interceptAddCmd.Flags().StringVar(&intercept.Prefix, "prefix", "/", "prefix to intercept")
	interceptAddCmd.Flags().BoolVarP(&interceptPreview, "preview", "p", true, "use a preview URL") // this default is unused
	interceptAddCmd.Flags().StringVarP(&intercept.TargetHost, "target", "t", "", "the [HOST:]PORT to forward to")
	_ = interceptAddCmd.MarkFlagRequired("target")
	interceptAddCmd.Flags().StringToStringVarP(&intercept.Patterns, "match", "m", nil, "match expression (HEADER=REGEX)")
	interceptAddCmd.Flags().StringVarP(&intercept.Namespace, "namespace", "", "", "Kubernetes namespace in which to create mapping for intercept")
	interceptAddCmdFlags = interceptAddCmd.Flags()

	interceptCmd.AddCommand(interceptAddCmd)
	interceptCG := []CmdGroup{
		{
			GroupName: "Available Commands",
			CmdNames:  []string{"available", "list", "add", "remove"},
		},
	}
	interceptCmd.SetUsageFunc(NewCmdUsage(interceptCmd, interceptCG))
	rootCmd.AddCommand(interceptCmd)

	return rootCmd
}
<|endoftext|>


<commit_before>package mount

import (
	"sort"
	"strconv"
	"strings"

	"github.com/containers/storage/pkg/fileutils"
)

// mountError holds an error from a mount or unmount operation
type mountError struct {
	op             string
	source, target string
	flags          uintptr
	data           string
	err            error
}

// Error returns a string representation of mountError
func (e *mountError) Error() string {
	out := e.op + " "

	if e.source != "" {
		out += e.source + ":" + e.target
	} else {
		out += e.target
	}

	if e.flags != uintptr(0) {
		out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
	}
	if e.data != "" {
		out += ", data: " + e.data
	}

	out += ": " + e.err.Error()
	return out
}

// Cause returns the underlying cause of the error
func (e *mountError) Cause() error {
	return e.err
}

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	mountpoint, err = fileutils.ReadSymlinkedDirectory(mountpoint)
	if err != nil {
		return false, err
	}
	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return mount(device, target, mType, uintptr(flag), data)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Slice(mounts, func(i, j int) bool {
		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
	})

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
			return err
			// Ignore errors for submounts and continue trying to unmount others
			// The final unmount should fail if there ane any submounts remaining
		}
	}
	return nil
}

// ForceUnmount lazily unmounts a filesystem on supported platforms,
// otherwise does a normal unmount.
//
// Deprecated: please use Unmount instead, it is identical.
func ForceUnmount(target string) error {
	return unmount(target, mntDetach)
}
<commit_msg>Make mount.Mounted() capable of handling files<commit_after>package mount

import (
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/containers/storage/pkg/fileutils"
)

// mountError holds an error from a mount or unmount operation
type mountError struct {
	op             string
	source, target string
	flags          uintptr
	data           string
	err            error
}

// Error returns a string representation of mountError
func (e *mountError) Error() string {
	out := e.op + " "

	if e.source != "" {
		out += e.source + ":" + e.target
	} else {
		out += e.target
	}

	if e.flags != uintptr(0) {
		out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
	}
	if e.data != "" {
		out += ", data: " + e.data
	}

	out += ": " + e.err.Error()
	return out
}

// Cause returns the underlying cause of the error
func (e *mountError) Cause() error {
	return e.err
}

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	info, err := os.Stat(mountpoint)
	if err != nil {
		return false, err
	}
	if info.IsDir() {
		mountpoint, err = fileutils.ReadSymlinkedDirectory(mountpoint)
		if err != nil {
			return false, err
		}
	}

	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return mount(device, target, mType, uintptr(flag), data)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Slice(mounts, func(i, j int) bool {
		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
	})

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
			return err
			// Ignore errors for submounts and continue trying to unmount others
			// The final unmount should fail if there ane any submounts remaining
		}
	}
	return nil
}

// ForceUnmount lazily unmounts a filesystem on supported platforms,
// otherwise does a normal unmount.
//
// Deprecated: please use Unmount instead, it is identical.
func ForceUnmount(target string) error {
	return unmount(target, mntDetach)
}
<|endoftext|>


<commit_before>package proc

// Target represents the process being debugged.
type Target struct {
	Process

	// fncallForG stores a mapping of current active function calls.
	fncallForG map[int]*callInjection

	asyncPreemptChanged bool  // runtime/debug.asyncpreemptoff was changed
	asyncPreemptOff     int64 // cached value of runtime/debug.asyncpreemptoff

	// gcache is a cache for Goroutines that we
	// have read and parsed from the targets memory.
	// This must be cleared whenever the target is resumed.
	gcache goroutineCache
}

// NewTarget returns an initialized Target object.
func NewTarget(p Process, disableAsyncPreempt bool) *Target {
	t := &Target{
		Process:    p,
		fncallForG: make(map[int]*callInjection),
	}
	t.gcache.init(p.BinInfo())

	if disableAsyncPreempt {
		setAsyncPreemptOff(t, 1)
	}

	return t
}

// SupportsFunctionCalls returns whether or not the backend supports
// calling functions during a debug session.
// Currently only non-recorded processes running on AMD64 support
// function calls.
func (t *Target) SupportsFunctionCalls() bool {
	if ok, _ := t.Process.Recorded(); ok {
		return false
	}
	_, ok := t.Process.BinInfo().Arch.(*AMD64)
	return ok
}

// ClearAllGCache clears the internal Goroutine cache.
// This should be called anytime the target process executes instructions.
func (t *Target) ClearAllGCache() {
	t.gcache.Clear()
}

func (t *Target) Restart(from string) error {
	t.ClearAllGCache()
	return t.Process.Restart(from)
}

func (t *Target) Detach(kill bool) error {
	if !kill && t.asyncPreemptChanged {
		setAsyncPreemptOff(t, t.asyncPreemptOff)
	}
	return t.Process.Detach(kill)
}
<commit_msg>pkg/proc: Add doc comments to Target Restart and Detach<commit_after>package proc

// Target represents the process being debugged.
type Target struct {
	Process

	// fncallForG stores a mapping of current active function calls.
	fncallForG map[int]*callInjection

	asyncPreemptChanged bool  // runtime/debug.asyncpreemptoff was changed
	asyncPreemptOff     int64 // cached value of runtime/debug.asyncpreemptoff

	// gcache is a cache for Goroutines that we
	// have read and parsed from the targets memory.
	// This must be cleared whenever the target is resumed.
	gcache goroutineCache
}

// NewTarget returns an initialized Target object.
func NewTarget(p Process, disableAsyncPreempt bool) *Target {
	t := &Target{
		Process:    p,
		fncallForG: make(map[int]*callInjection),
	}
	t.gcache.init(p.BinInfo())

	if disableAsyncPreempt {
		setAsyncPreemptOff(t, 1)
	}

	return t
}

// SupportsFunctionCalls returns whether or not the backend supports
// calling functions during a debug session.
// Currently only non-recorded processes running on AMD64 support
// function calls.
func (t *Target) SupportsFunctionCalls() bool {
	if ok, _ := t.Process.Recorded(); ok {
		return false
	}
	_, ok := t.Process.BinInfo().Arch.(*AMD64)
	return ok
}

// ClearAllGCache clears the internal Goroutine cache.
// This should be called anytime the target process executes instructions.
func (t *Target) ClearAllGCache() {
	t.gcache.Clear()
}

// Restart will start the process over from the location specified by the "from" locspec.
// This is only useful for recorded targets.
// Restarting of a normal process happens at a higher level (debugger.Restart).
func (t *Target) Restart(from string) error {
	t.ClearAllGCache()
	return t.Process.Restart(from)
}

// Detach will detach the target from the underylying process.
// This means the debugger will no longer receive events from the process
// we were previously debugging.
// If kill is true then the process will be killed when we detach.
func (t *Target) Detach(kill bool) error {
	if !kill && t.asyncPreemptChanged {
		setAsyncPreemptOff(t, t.asyncPreemptOff)
	}
	return t.Process.Detach(kill)
}
<|endoftext|>


<commit_before>package stack

import (
	"fmt"
	"io"
	"reflect"

	"github.com/mobingilabs/mobingi-sdk-go/pkg/pretty"
)

// Changes:
//
// 2017-07-18:
//   - Max, MaxOrigin, Min, MinOrigin - changed to int (we still need to support old string)
type Configuration struct {
	AWS                 string      `json:"AWS,omitempty"`
	AWSAccountName      string      `json:"AWS_ACCOUNT_NAME,omitempty"`
	AssociatePublicIp   string      `json:"AssociatePublicIP,omitempty"`
	ELBOpen443Port      string      `json:"ELBOpen443Port,omitempty"`
	ELBOpen80Port       string      `json:"ELBOpen80Port,omitempty"`
	SpotInstanceMaxSize int         `json:"SpotInstanceMaxSize,omitempty"`
	SpotInstanceMinSize int         `json:"SpotInstanceMinSize,omitempty"`
	SpotPrice           string      `json:"SpotPrice,omitempty"`
	Architecture        string      `json:"architecture,omitempty"`
	Code                string      `json:"code,omitempty"`
	Image               string      `json:"image,omitempty"`
	Max                 interface{} `json:"max,omitempty"`
	MaxOrigin           interface{} `json:"maxOrigin,omitempty"`
	Min                 interface{} `json:"min,omitempty"`
	MinOrigin           interface{} `json:"minOrigin,omitempty"`
	Nickname            string      `json:"nickname,omitempty"`
	Region              string      `json:"region,omitempty"`
	Type                string      `json:"type,omitempty"`
}

type StackOutput struct {
	// list
	Description string `json:"Description,omitempty"`
	OutputKey   string `json:"OutputKey,omitempty"`
	OutputValue string `json:"OutputValue,omitempty"`
	// describe
	Address                     string `json:"Address,omitempty"`
	DBAddress                   string `json:"DBAddress,omitempty"`
	DBPort                      string `json:"DBPort,omitempty"`
	DBSlave1Address             string `json:"DBSlave1Address,omitempty"`
	DBSlave2Address             string `json:"DBSlave2Address,omitempty"`
	DBSlave3Address             string `json:"DBSlave3Address,omitempty"`
	DBSlave4Address             string `json:"DBSlave4Address,omitempty"`
	DBSlave5Address             string `json:"DBSlave5Address,omitempty"`
	MemcachedEndPointAddress    string `json:"MemcachedEndPointAddress,omitempty"`
	MemcachedEndPointPort       string `json:"MemcachedEndPointPort,omitempty"`
	NATInstance                 string `json:"NATInstance,omitempty"`
	RedisPrimaryEndPointAddress string `json:"RedisPrimaryEndPointAddress,omitempty"`
	RedisPrimaryEndPointPort    string `json:"RedisPrimaryEndPointPort,omitempty"`
	RedisReadEndPointAddresses  string `json:"RedisReadEndPointAddresses,omitempty"`
	RedisReadEndPointPorts      string `json:"RedisReadEndPointPorts,omitempty"`
}

type Ebs struct {
	AttachTime          string `json:"AttachTime,omitempty"`
	DeleteOnTermination bool   `json:"DeleteOnTermination,omitempty"`
	Status              string `json:"Status,omitempty"`
	VolumeId            string `json:"VolumeId,omitempty"`
}

type BlockDeviceMappings struct {
	DeviceName string `json:"DeviceName,omitempty"`
	Ebs        Ebs    `json:"Ebs,omitempty"`
}

type Monitoring struct {
	State string `json:"State,omitempty"`
}

type Association struct {
	IpOwnerId     string `json:"IpOwnerId,omitempty"`
	PublicDnsName string `json:"PublicDnsName,omitempty"`
	PublicIp      string `json:"PublicIp,omitempty"`
}

type Attachment struct {
	AttachTime          string `json:"AttachTime,omitempty"`
	AttachmentId        string `json:"AttachmentId,omitempty"`
	DeleteOnTermination bool   `json:"DeleteOnTermination,omitempty"`
	DeviceIndex         string `json:"DeviceIndex,omitempty"`
	Status              string `json:"Status,omitempty"`
}

type Group struct {
	GroupId   string `json:"GroupId,omitempty"`
	GroupName string `json:"GroupName,omitempty"`
}

type PrivateIpAddress struct {
	Association      Association `json:"Association,omitempty"`
	Primary          bool        `json:"Primary,omitempty"`
	PrivateDnsName   string      `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress string      `json:"PrivateIpAddress,omitempty"`
}

type NetworkInterface struct {
	Association        Association        `json:"Association,omitempty"`
	Attachment         Attachment         `json:"Attachment,omitempty"`
	Description        string             `json:"Description,omitempty"`
	Groups             []Group            `json:"Groups,omitempty"`
	MacAddress         string             `json:"MacAddress,omitempty"`
	NetworkInterfaceId string             `json:"NetworkInterfaceId,omitempty"`
	OwnerId            string             `json:"OwnerId,omitempty"`
	PrivateDnsName     string             `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress   string             `json:"PrivateIpAddress,omitempty"`
	PrivateIpAddresses []PrivateIpAddress `json:"PrivateIpAddresses,omitempty"`
	SourceDestCheck    bool               `json:"SourceDestCheck,omitempty"`
	Status             string             `json:"Status,omitempty"`
	SubnetId           string             `json:"SubnetId,omitempty"`
	VpcId              string             `json:"VpcId,omitempty"`
}

type Placement struct {
	AvailabilityZone string `json:"AvailabilityZone,omitempty"`
	GroupName        string `json:"GroupName,omitempty"`
	Tenancy          string `json:"Tenancy,omitempty"`
}

type Reservation struct {
	Groups        []Group `json:"Groups,omitempty"`
	OwnerId       string  `json:"OwnerId,omitempty"`
	RequesterId   string  `json:"RequesterId,omitempty"`
	ReservationId string  `json:"ReservationId,omitempty"`
}

type State struct {
	Code string `json:"Code,omitempty"`
	Name string `json:"Name,omitempty"`
}

type Tag struct {
	Key   string `json:"Key,omitempty"`
	Value string `json:"Value,omitempty"`
}

type Instance struct {
	AmiLaunchIndex        string                `json:"AmiLaunchIndex,omitempty"`
	Architecture          string                `json:"Architecture,omitempty"`
	BlockDeviceMappings   []BlockDeviceMappings `json:"BlockDeviceMappings,omitempty"`
	ClientToken           string                `json:"ClientToken,omitempty"`
	EbsOptimized          bool                  `json:"EbsOptimized,omitempty"`
	Hypervisor            string                `json:"Hypervisor,omitempty"`
	ImageId               string                `json:"ImageId,omitempty"`
	InstanceId            string                `json:"InstanceId,omitempty"`
	InstanceType          string                `json:"InstanceType,omitempty"`
	InstanceLifecycle     string                `json:"InstanceLifecycle,omitempty"`
	SpotInstanceRequestId string                `json:"SpotInstanceRequestId,omitempty"`
	KeyName               string                `json:"KeyName,omitempty"`
	LaunchTime            string                `json:"LaunchTime,omitempty"`
	Monitoring            Monitoring            `json:"Monitoring,omitempty"`
	NetworkInterfaces     []NetworkInterface    `json:"NetworkInterfaces,omitempty"`
	Placement             Placement             `json:"Placement,omitempty"`
	PrivateDnsName        string                `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress      string                `json:"PrivateIpAddress,omitempty"`
	ProductCodes          []string              `json:"ProductCodes,omitempty"`
	PublicDnsName         string                `json:"PublicDnsName,omitempty"`
	PublicIpAddress       string                `json:"PublicIpAddress,omitempty"`
	Reservation           Reservation           `json:"Reservation,omitempty"`
	RootDeviceName        string                `json:"RootDeviceName,omitempty"`
	RootDeviceType        string                `json:"RootDeviceType,omitempty"`
	SecurityGroups        []Group               `json:"SecurityGroups,omitempty"`
	SourceDestCheck       bool                  `json:"SourceDestCheck,omitempty"`
	State                 State                 `json:"State,omitempty"`
	StateTransitionReason string                `json:"StateTransitionReason,omitempty"`
	SubnetId              string                `json:"SubnetId,omitempty"`
	Tags                  []Tag                 `json:"Tags,omitempty"`
	VirtualizationType    string                `json:"VirtualizationType,omitempty"`
	VpcId                 string                `json:"VpcId,omitempty"`
	EnaSupport            string                `json:"enaSupport,omitempty"`
}

type ListStack struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  []StackOutput `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

// Workaround for inconsistencies in API output:
// When stack creation is still in progress, StackOutputs is a slice. Upon completion,
// it will be a struct. It will cause errors in Unmarshal.
type DescribeStack1 struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Instances     []Instance    `json:"Instances,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  StackOutput   `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

type DescribeStack2 struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Instances     []Instance    `json:"Instances,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  []StackOutput `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

type CreateStackDb struct {
	Engine       string `json:"Engine,omitempty"`
	Type         string `json:"DBType,omitempty"`
	Storage      string `json:"DBStorage,omitempty"`
	ReadReplica1 bool   `json:"ReadReplica1,omitempty"`
	ReadReplica2 bool   `json:"ReadReplica2,omitempty"`
	ReadReplica3 bool   `json:"ReadReplica3,omitempty"`
	ReadReplica4 bool   `json:"ReadReplica4,omitempty"`
	ReadReplica5 bool   `json:"ReadReplica5,omitempty"`
}

type CreateStackElasticache struct {
	Engine    string `json:"ElastiCacheEngine,omitempty"`
	NodeType  string `json:"ElastiCacheNodeType,omitempty"`
	NodeCount string `json:"ElastiCacheNodes,omitempty"`
}

/*
type CreateStackConfig struct {
	Region            string      `json:"region,omitempty"`
	Architecture      string      `json:"architecture,omitempty"`
	Type              string      `json:"type,omitempty"`
	Image             string      `json:"image,omitempty"`
	DockerHubUsername string      `json:"dockerHubUsername,omitempty"`
	DockerHubPassword string      `json:"dockerHubPassword,omitempty"`
	Min               int         `json:"min,omitempty"`
	Max               int         `json:"max,omitempty"`
	SpotRange         int         `json:"spotRange,omitempty"`
	Nickname          string      `json:"nickname,omitempty"`
	Code              string      `json:"code,omitempty"`
	GitReference      string      `json:"gitReference,omitempty"`
	GitPrivateKey     string      `json:"gitPrivateKey,omitempty"`
	Database          interface{} `json:"database,omitempty"`
	ElastiCache       interface{} `json:"elasticache,omitempty"`
}
*/

type CreateStackConfig struct {
	Region            interface{} `json:"region,omitempty"`
	Architecture      interface{} `json:"architecture,omitempty"`
	Type              interface{} `json:"type,omitempty"`
	Image             interface{} `json:"image,omitempty"`
	DockerHubUsername interface{} `json:"dockerHubUsername,omitempty"`
	DockerHubPassword interface{} `json:"dockerHubPassword,omitempty"`
	Min               interface{} `json:"min,omitempty"`
	Max               interface{} `json:"max,omitempty"`
	SpotRange         interface{} `json:"spotRange,omitempty"`
	Nickname          interface{} `json:"nickname,omitempty"`
	Code              interface{} `json:"code,omitempty"`
	GitReference      interface{} `json:"gitReference,omitempty"`
	GitPrivateKey     interface{} `json:"gitPrivateKey,omitempty"`
	Database          interface{} `json:"database,omitempty"`
	ElastiCache       interface{} `json:"elasticache,omitempty"`
}

// PrintR prints the `field: value` of the input struct recursively. Recursion level `lvl` and `indent`
// are provided for indention in printing. For slices, we have to do an explicit type assertion
// to get the underlying slice from reflect.
func PrintR(w io.Writer, s interface{}, lvl, indent int) {
	pad := pretty.Indent(lvl * indent)
	rt := reflect.TypeOf(s).Elem()
	rv := reflect.ValueOf(s).Elem()

	for i := 0; i < rt.NumField(); i++ {
		field := rt.Field(i).Name
		value := rv.Field(i).Interface()

		switch rv.Field(i).Kind() {
		case reflect.Interface:
			fmt.Fprintf(w, "%s%s: %v\n", pad, field, value)
		case reflect.String:
			fmt.Fprintf(w, "%s%s: %s\n", pad, field, value)
		case reflect.Int32:
			fmt.Fprintf(w, "%s%s: %i\n", pad, field, value)
		case reflect.Struct:
			fmt.Fprintf(w, "%s[%s]\n", pad, field)
			v := rv.Field(i).Addr()
			PrintR(w, v.Interface(), lvl+1, indent)
		case reflect.Slice:
			fmt.Fprintf(w, "%s[%s]\n", pad, field)
			instances, ok := value.([]Instance)
			if ok && len(instances) > 0 {
				for _, slice := range instances {
					PrintR(w, &slice, lvl+1, indent)
					if len(instances) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			mappings, ok := value.([]BlockDeviceMappings)
			if ok && len(mappings) > 0 {
				for _, slice := range mappings {
					PrintR(w, &slice, lvl+1, indent)
					if len(mappings) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			networks, ok := value.([]NetworkInterface)
			if ok && len(networks) > 0 {
				for _, slice := range networks {
					PrintR(w, &slice, lvl+1, indent)
					if len(networks) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			groups, ok := value.([]Group)
			if ok && len(groups) > 0 {
				for _, slice := range groups {
					PrintR(w, &slice, lvl+1, indent)
					if len(groups) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			ipaddrs, ok := value.([]PrivateIpAddress)
			if ok && len(ipaddrs) > 0 {
				for _, slice := range ipaddrs {
					PrintR(w, &slice, lvl+1, indent)
					if len(ipaddrs) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			tags, ok := value.([]Tag)
			if ok && len(tags) > 0 {
				for _, slice := range tags {
					PrintR(w, &slice, lvl+1, indent)
					if len(tags) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			stackouts, ok := value.([]StackOutput)
			if ok && len(stackouts) > 0 {
				for _, slice := range stackouts {
					PrintR(w, &slice, lvl+1, indent)
					if len(stackouts) > 1 {
						fmt.Fprintf(w, "\n")
					}
				}

				break
			}

			// when slice type is not explicitly specified in our conversion
			fmt.Fprintf(w, "%s*** Not available ***\n", pad)
		}
	}
}
<commit_msg>Remove unused function.<commit_after>package stack

// Changes:
//
// 2017-07-18:
//   - Max, MaxOrigin, Min, MinOrigin - changed to int (we still need to support old string)
type Configuration struct {
	AWS                 string      `json:"AWS,omitempty"`
	AWSAccountName      string      `json:"AWS_ACCOUNT_NAME,omitempty"`
	AssociatePublicIp   string      `json:"AssociatePublicIP,omitempty"`
	ELBOpen443Port      string      `json:"ELBOpen443Port,omitempty"`
	ELBOpen80Port       string      `json:"ELBOpen80Port,omitempty"`
	SpotInstanceMaxSize int         `json:"SpotInstanceMaxSize,omitempty"`
	SpotInstanceMinSize int         `json:"SpotInstanceMinSize,omitempty"`
	SpotPrice           string      `json:"SpotPrice,omitempty"`
	Architecture        string      `json:"architecture,omitempty"`
	Code                string      `json:"code,omitempty"`
	Image               string      `json:"image,omitempty"`
	Max                 interface{} `json:"max,omitempty"`
	MaxOrigin           interface{} `json:"maxOrigin,omitempty"`
	Min                 interface{} `json:"min,omitempty"`
	MinOrigin           interface{} `json:"minOrigin,omitempty"`
	Nickname            string      `json:"nickname,omitempty"`
	Region              string      `json:"region,omitempty"`
	Type                string      `json:"type,omitempty"`
}

type StackOutput struct {
	// list
	Description string `json:"Description,omitempty"`
	OutputKey   string `json:"OutputKey,omitempty"`
	OutputValue string `json:"OutputValue,omitempty"`
	// describe
	Address                     string `json:"Address,omitempty"`
	DBAddress                   string `json:"DBAddress,omitempty"`
	DBPort                      string `json:"DBPort,omitempty"`
	DBSlave1Address             string `json:"DBSlave1Address,omitempty"`
	DBSlave2Address             string `json:"DBSlave2Address,omitempty"`
	DBSlave3Address             string `json:"DBSlave3Address,omitempty"`
	DBSlave4Address             string `json:"DBSlave4Address,omitempty"`
	DBSlave5Address             string `json:"DBSlave5Address,omitempty"`
	MemcachedEndPointAddress    string `json:"MemcachedEndPointAddress,omitempty"`
	MemcachedEndPointPort       string `json:"MemcachedEndPointPort,omitempty"`
	NATInstance                 string `json:"NATInstance,omitempty"`
	RedisPrimaryEndPointAddress string `json:"RedisPrimaryEndPointAddress,omitempty"`
	RedisPrimaryEndPointPort    string `json:"RedisPrimaryEndPointPort,omitempty"`
	RedisReadEndPointAddresses  string `json:"RedisReadEndPointAddresses,omitempty"`
	RedisReadEndPointPorts      string `json:"RedisReadEndPointPorts,omitempty"`
}

type Ebs struct {
	AttachTime          string `json:"AttachTime,omitempty"`
	DeleteOnTermination bool   `json:"DeleteOnTermination,omitempty"`
	Status              string `json:"Status,omitempty"`
	VolumeId            string `json:"VolumeId,omitempty"`
}

type BlockDeviceMappings struct {
	DeviceName string `json:"DeviceName,omitempty"`
	Ebs        Ebs    `json:"Ebs,omitempty"`
}

type Monitoring struct {
	State string `json:"State,omitempty"`
}

type Association struct {
	IpOwnerId     string `json:"IpOwnerId,omitempty"`
	PublicDnsName string `json:"PublicDnsName,omitempty"`
	PublicIp      string `json:"PublicIp,omitempty"`
}

type Attachment struct {
	AttachTime          string `json:"AttachTime,omitempty"`
	AttachmentId        string `json:"AttachmentId,omitempty"`
	DeleteOnTermination bool   `json:"DeleteOnTermination,omitempty"`
	DeviceIndex         string `json:"DeviceIndex,omitempty"`
	Status              string `json:"Status,omitempty"`
}

type Group struct {
	GroupId   string `json:"GroupId,omitempty"`
	GroupName string `json:"GroupName,omitempty"`
}

type PrivateIpAddress struct {
	Association      Association `json:"Association,omitempty"`
	Primary          bool        `json:"Primary,omitempty"`
	PrivateDnsName   string      `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress string      `json:"PrivateIpAddress,omitempty"`
}

type NetworkInterface struct {
	Association        Association        `json:"Association,omitempty"`
	Attachment         Attachment         `json:"Attachment,omitempty"`
	Description        string             `json:"Description,omitempty"`
	Groups             []Group            `json:"Groups,omitempty"`
	MacAddress         string             `json:"MacAddress,omitempty"`
	NetworkInterfaceId string             `json:"NetworkInterfaceId,omitempty"`
	OwnerId            string             `json:"OwnerId,omitempty"`
	PrivateDnsName     string             `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress   string             `json:"PrivateIpAddress,omitempty"`
	PrivateIpAddresses []PrivateIpAddress `json:"PrivateIpAddresses,omitempty"`
	SourceDestCheck    bool               `json:"SourceDestCheck,omitempty"`
	Status             string             `json:"Status,omitempty"`
	SubnetId           string             `json:"SubnetId,omitempty"`
	VpcId              string             `json:"VpcId,omitempty"`
}

type Placement struct {
	AvailabilityZone string `json:"AvailabilityZone,omitempty"`
	GroupName        string `json:"GroupName,omitempty"`
	Tenancy          string `json:"Tenancy,omitempty"`
}

type Reservation struct {
	Groups        []Group `json:"Groups,omitempty"`
	OwnerId       string  `json:"OwnerId,omitempty"`
	RequesterId   string  `json:"RequesterId,omitempty"`
	ReservationId string  `json:"ReservationId,omitempty"`
}

type State struct {
	Code string `json:"Code,omitempty"`
	Name string `json:"Name,omitempty"`
}

type Tag struct {
	Key   string `json:"Key,omitempty"`
	Value string `json:"Value,omitempty"`
}

type Instance struct {
	AmiLaunchIndex        string                `json:"AmiLaunchIndex,omitempty"`
	Architecture          string                `json:"Architecture,omitempty"`
	BlockDeviceMappings   []BlockDeviceMappings `json:"BlockDeviceMappings,omitempty"`
	ClientToken           string                `json:"ClientToken,omitempty"`
	EbsOptimized          bool                  `json:"EbsOptimized,omitempty"`
	Hypervisor            string                `json:"Hypervisor,omitempty"`
	ImageId               string                `json:"ImageId,omitempty"`
	InstanceId            string                `json:"InstanceId,omitempty"`
	InstanceType          string                `json:"InstanceType,omitempty"`
	InstanceLifecycle     string                `json:"InstanceLifecycle,omitempty"`
	SpotInstanceRequestId string                `json:"SpotInstanceRequestId,omitempty"`
	KeyName               string                `json:"KeyName,omitempty"`
	LaunchTime            string                `json:"LaunchTime,omitempty"`
	Monitoring            Monitoring            `json:"Monitoring,omitempty"`
	NetworkInterfaces     []NetworkInterface    `json:"NetworkInterfaces,omitempty"`
	Placement             Placement             `json:"Placement,omitempty"`
	PrivateDnsName        string                `json:"PrivateDnsName,omitempty"`
	PrivateIpAddress      string                `json:"PrivateIpAddress,omitempty"`
	ProductCodes          []string              `json:"ProductCodes,omitempty"`
	PublicDnsName         string                `json:"PublicDnsName,omitempty"`
	PublicIpAddress       string                `json:"PublicIpAddress,omitempty"`
	Reservation           Reservation           `json:"Reservation,omitempty"`
	RootDeviceName        string                `json:"RootDeviceName,omitempty"`
	RootDeviceType        string                `json:"RootDeviceType,omitempty"`
	SecurityGroups        []Group               `json:"SecurityGroups,omitempty"`
	SourceDestCheck       bool                  `json:"SourceDestCheck,omitempty"`
	State                 State                 `json:"State,omitempty"`
	StateTransitionReason string                `json:"StateTransitionReason,omitempty"`
	SubnetId              string                `json:"SubnetId,omitempty"`
	Tags                  []Tag                 `json:"Tags,omitempty"`
	VirtualizationType    string                `json:"VirtualizationType,omitempty"`
	VpcId                 string                `json:"VpcId,omitempty"`
	EnaSupport            string                `json:"enaSupport,omitempty"`
}

type ListStack struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  []StackOutput `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

// Workaround for inconsistencies in API output:
// When stack creation is still in progress, StackOutputs is a slice. Upon completion,
// it will be a struct. It will cause errors in Unmarshal.
type DescribeStack1 struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Instances     []Instance    `json:"Instances,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  StackOutput   `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

type DescribeStack2 struct {
	AuthToken     string        `json:"auth_token,omitempty"`
	Configuration Configuration `json:"configuration,omitempty"`
	CreateTime    string        `json:"create_time,omitempty"`
	Instances     []Instance    `json:"Instances,omitempty"`
	Nickname      string        `json:"nickname,omitempty"`
	StackId       string        `json:"stack_id,omitempty"`
	StackOutputs  []StackOutput `json:"stack_outputs,omitempty"`
	StackStatus   string        `json:"stack_status,omitempty"`
	UserId        string        `json:"user_id,omitempty"`
}

type CreateStackDb struct {
	Engine       string `json:"Engine,omitempty"`
	Type         string `json:"DBType,omitempty"`
	Storage      string `json:"DBStorage,omitempty"`
	ReadReplica1 bool   `json:"ReadReplica1,omitempty"`
	ReadReplica2 bool   `json:"ReadReplica2,omitempty"`
	ReadReplica3 bool   `json:"ReadReplica3,omitempty"`
	ReadReplica4 bool   `json:"ReadReplica4,omitempty"`
	ReadReplica5 bool   `json:"ReadReplica5,omitempty"`
}

type CreateStackElasticache struct {
	Engine    string `json:"ElastiCacheEngine,omitempty"`
	NodeType  string `json:"ElastiCacheNodeType,omitempty"`
	NodeCount string `json:"ElastiCacheNodes,omitempty"`
}

/*
type CreateStackConfig struct {
	Region            string      `json:"region,omitempty"`
	Architecture      string      `json:"architecture,omitempty"`
	Type              string      `json:"type,omitempty"`
	Image             string      `json:"image,omitempty"`
	DockerHubUsername string      `json:"dockerHubUsername,omitempty"`
	DockerHubPassword string      `json:"dockerHubPassword,omitempty"`
	Min               int         `json:"min,omitempty"`
	Max               int         `json:"max,omitempty"`
	SpotRange         int         `json:"spotRange,omitempty"`
	Nickname          string      `json:"nickname,omitempty"`
	Code              string      `json:"code,omitempty"`
	GitReference      string      `json:"gitReference,omitempty"`
	GitPrivateKey     string      `json:"gitPrivateKey,omitempty"`
	Database          interface{} `json:"database,omitempty"`
	ElastiCache       interface{} `json:"elasticache,omitempty"`
}
*/

type CreateStackConfig struct {
	Region            interface{} `json:"region,omitempty"`
	Architecture      interface{} `json:"architecture,omitempty"`
	Type              interface{} `json:"type,omitempty"`
	Image             interface{} `json:"image,omitempty"`
	DockerHubUsername interface{} `json:"dockerHubUsername,omitempty"`
	DockerHubPassword interface{} `json:"dockerHubPassword,omitempty"`
	Min               interface{} `json:"min,omitempty"`
	Max               interface{} `json:"max,omitempty"`
	SpotRange         interface{} `json:"spotRange,omitempty"`
	Nickname          interface{} `json:"nickname,omitempty"`
	Code              interface{} `json:"code,omitempty"`
	GitReference      interface{} `json:"gitReference,omitempty"`
	GitPrivateKey     interface{} `json:"gitPrivateKey,omitempty"`
	Database          interface{} `json:"database,omitempty"`
	ElastiCache       interface{} `json:"elasticache,omitempty"`
}
<|endoftext|>


<commit_before>package yaml

import (
	"errors"
	"fmt"
	"github.com/qorio/maestro/pkg/circleci"
	"github.com/qorio/maestro/pkg/docker"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

const CIRCLECI_API_TOKEN = "CIRCLECI_API_TOKEN"
const DOCKER_EMAIL = "DOCKER_EMAIL"
const DOCKER_AUTH = "DOCKER_AUTH"
const DOCKER_ACCOUNT = "DOCKER_ACCOUNT"
const TEST_MODE = "TEST_MODE"

func (this *Image) Validate(c Context) error {
	// Check required vars
	if _, has := c[DOCKER_EMAIL]; !has {
		return errors.New("Missing DOCKER_EMAIL var")
	}
	if _, has := c[DOCKER_AUTH]; !has {
		return errors.New("Missing DOCKER_AUTH var")
	}

	c.eval(&this.Dockerfile)
	c.eval(&this.RepoId)

	if len(this.artifacts) == 0 && this.RepoId == "" {
		return errors.New("No artifacts reference to build this image or no Docker hub repo id specified.")
	}

	for _, artifact := range this.artifacts {
		log.Println("Validating asset", artifact.Name)
		if err := artifact.Validate(c); err != nil {
			return err
		}
	}

	// check to see if docker file exists.
	fi, err := os.Stat(this.Dockerfile)
	if err != nil {
		return errors.New(fmt.Sprint("Missing dockerfile at", this.Dockerfile, ":", err))
	}
	if fi.IsDir() {
		return errors.New(fmt.Sprint("Dockerfile", this.Dockerfile, "is a directory."))
	}

	return nil
}

func (this *Artifact) circleci(c Context) (*circleci.Config, int64, error) {
	parts := strings.Split(this.Project, "/")
	if len(parts) != 2 {
		return nil, 0, errors.New("Project not in format of <user>/<proj>: " + this.Project)
	}

	token, ok := c[CIRCLECI_API_TOKEN].(string)
	if !ok {
		return nil, 0, errors.New("CIRCLECI_API_TOKEN not a string.")
	}

	api := circleci.Config{
		User:     parts[0],
		Project:  parts[1],
		ApiToken: token,
	}
	build, err := strconv.ParseInt(this.BuildNumber, 10, 64)
	if err != nil {
		return nil, 0, errors.New("Must be a numeric build number")
	}
	return &api, build, nil
}

func (this *Artifact) Validate(c Context) error {
	// Apply the variables to all the string fields since they can reference variables
	c.eval(&this.Project)
	c.eval(&this.Source)
	c.eval(&this.BuildNumber)
	c.eval(&this.Artifact)
	c.eval(&this.Platform)

	filter, err := circleci.MatchPathAndBinary(this.Platform, string(this.Name))
	if err != nil {
		return err
	}
	// Currently only support circleci
	switch this.Source {
	case "circleci":
		if _, has := c["CIRCLECI_API_TOKEN"]; !has {
			return errors.New("CIRCLECI_API_TOKEN var is missing")
		} else {
			api, build, err := this.circleci(c)
			if err != nil {
				return err
			}
			log.Println("Checking availability of", this.Name, ", build", build)
			binaries, err := api.FetchBuildArtifacts(build, filter)
			if err != nil {
				return err
			}
			if len(binaries) == 0 {
				return errors.New("Binary for " + string(this.Name) + " not found on " + this.Source)
			} else {
				log.Println("Found binary for", this.Name, "from", this.Source, "path=", binaries[0].Path)
			}
		}
	default:
		return errors.New("Source " + this.Source + " not supported.")
	}
	return nil
}

func (this *Image) InDesiredState(c Context) (bool, error) {
	return true, nil
}

func (this *Artifact) Prepare(c Context) error {
	dir := c["binary_dir"]
	api, build, err := this.circleci(c)
	if err != nil {
		return err
	}
	filter, err := circleci.MatchPathAndBinary(this.Platform, string(this.Name))
	if err != nil {
		return err
	}
	binaries, err := api.FetchBuildArtifacts(build, filter)
	if err != nil {
		return err
	}
	if len(binaries) == 0 {
		return errors.New("Binary for " + string(this.Name) + " not found on " + this.Source)
	}
	log.Println("Downloading binary", this.Name, "build", build, "to", dir)
	bytes, err := binaries[0].Download(dir.(string))
	if err != nil {
		log.Println("error", err)
		return err
	}
	log.Println(bytes, "bytes")
	return nil
}

func docker_config(c Context) (*docker.Config, error) {
	email, ok := c[DOCKER_EMAIL].(string)
	if !ok {
		return nil, errors.New("DOCKER_EMAIL not a string")
	}
	auth, ok := c[DOCKER_AUTH].(string)
	if !ok {
		return nil, errors.New("DOCKER_AUTH not a string")
	}

	account, ok := c[DOCKER_ACCOUNT].(string)
	if !ok {
		return nil, errors.New("DOCKER_ACCOUNT not a string")
	}

	return &docker.Config{
		Email:   email,
		Auth:    auth,
		Account: account,
	}, nil
}

func (this *Image) Prepare(c Context) error {
	// for each artifact, pull the binary and place in the dockerfile's directory
	dir := filepath.Dir(this.Dockerfile)
	c["binary_dir"] = dir
	defer delete(c, "binary_dir")

	for _, artifact := range this.artifacts {
		err := artifact.Prepare(c)
		if err != nil {
			return err
		}
	}

	// set up dockercfg file
	f := filepath.Join(os.Getenv("HOME"), ".dockercfg")
	fi, err := os.Stat(f)
	switch {
	case err == nil && fi.IsDir():
		return errors.New("~/.dockercfg is a directory.")
	case err == nil: // overwrite
	case os.IsNotExist(err): // no file
		docker_config, err := docker_config(c)
		if err != nil {
			return nil
		}
		err = docker_config.GenerateDockerCfg(f)
		if err != nil {
			return err
		}
	default:
		return err
	}

	return nil
}

func (this *Image) Execute(c Context) error {

	docker_config, err := docker_config(c)
	if err != nil {
		return nil
	}
	_, docker_config.TestMode = c[TEST_MODE]

	image, err := docker_config.NewTaggedImage(this.RepoId, this.Dockerfile)
	if err != nil {
		return err
	}

	err = image.Build()
	if err != nil {
		return err
	}

	log.Println("Finished building", this.Name, "Now pushing.")

	err = image.Push()
	if err != nil {
		return err
	}

	return nil
}

func (this *Image) Finish(c Context) error {
	return nil
}
<commit_msg>Add logging to dockercfg generation.<commit_after>package yaml

import (
	"errors"
	"fmt"
	"github.com/qorio/maestro/pkg/circleci"
	"github.com/qorio/maestro/pkg/docker"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

const CIRCLECI_API_TOKEN = "CIRCLECI_API_TOKEN"
const DOCKER_EMAIL = "DOCKER_EMAIL"
const DOCKER_AUTH = "DOCKER_AUTH"
const DOCKER_ACCOUNT = "DOCKER_ACCOUNT"
const TEST_MODE = "TEST_MODE"

func (this *Image) Validate(c Context) error {
	// Check required vars
	if _, has := c[DOCKER_EMAIL]; !has {
		return errors.New("Missing DOCKER_EMAIL var")
	}
	if _, has := c[DOCKER_AUTH]; !has {
		return errors.New("Missing DOCKER_AUTH var")
	}

	c.eval(&this.Dockerfile)
	c.eval(&this.RepoId)

	if len(this.artifacts) == 0 && this.RepoId == "" {
		return errors.New("No artifacts reference to build this image or no Docker hub repo id specified.")
	}

	for _, artifact := range this.artifacts {
		log.Println("Validating asset", artifact.Name)
		if err := artifact.Validate(c); err != nil {
			return err
		}
	}

	// check to see if docker file exists.
	fi, err := os.Stat(this.Dockerfile)
	if err != nil {
		return errors.New(fmt.Sprint("Missing dockerfile at", this.Dockerfile, ":", err))
	}
	if fi.IsDir() {
		return errors.New(fmt.Sprint("Dockerfile", this.Dockerfile, "is a directory."))
	}

	return nil
}

func (this *Artifact) circleci(c Context) (*circleci.Config, int64, error) {
	parts := strings.Split(this.Project, "/")
	if len(parts) != 2 {
		return nil, 0, errors.New("Project not in format of <user>/<proj>: " + this.Project)
	}

	token, ok := c[CIRCLECI_API_TOKEN].(string)
	if !ok {
		return nil, 0, errors.New("CIRCLECI_API_TOKEN not a string.")
	}

	api := circleci.Config{
		User:     parts[0],
		Project:  parts[1],
		ApiToken: token,
	}
	build, err := strconv.ParseInt(this.BuildNumber, 10, 64)
	if err != nil {
		return nil, 0, errors.New("Must be a numeric build number")
	}
	return &api, build, nil
}

func (this *Artifact) Validate(c Context) error {
	// Apply the variables to all the string fields since they can reference variables
	c.eval(&this.Project)
	c.eval(&this.Source)
	c.eval(&this.BuildNumber)
	c.eval(&this.Artifact)
	c.eval(&this.Platform)

	filter, err := circleci.MatchPathAndBinary(this.Platform, string(this.Name))
	if err != nil {
		return err
	}
	// Currently only support circleci
	switch this.Source {
	case "circleci":
		if _, has := c["CIRCLECI_API_TOKEN"]; !has {
			return errors.New("CIRCLECI_API_TOKEN var is missing")
		} else {
			api, build, err := this.circleci(c)
			if err != nil {
				return err
			}
			log.Println("Checking availability of", this.Name, ", build", build)
			binaries, err := api.FetchBuildArtifacts(build, filter)
			if err != nil {
				return err
			}
			if len(binaries) == 0 {
				return errors.New("Binary for " + string(this.Name) + " not found on " + this.Source)
			} else {
				log.Println("Found binary for", this.Name, "from", this.Source, "path=", binaries[0].Path)
			}
		}
	default:
		return errors.New("Source " + this.Source + " not supported.")
	}
	return nil
}

func (this *Image) InDesiredState(c Context) (bool, error) {
	return true, nil
}

func (this *Artifact) Prepare(c Context) error {
	dir := c["binary_dir"]
	api, build, err := this.circleci(c)
	if err != nil {
		return err
	}
	filter, err := circleci.MatchPathAndBinary(this.Platform, string(this.Name))
	if err != nil {
		return err
	}
	binaries, err := api.FetchBuildArtifacts(build, filter)
	if err != nil {
		return err
	}
	if len(binaries) == 0 {
		return errors.New("Binary for " + string(this.Name) + " not found on " + this.Source)
	}
	log.Println("Downloading binary", this.Name, "build", build, "to", dir)
	bytes, err := binaries[0].Download(dir.(string))
	if err != nil {
		log.Println("error", err)
		return err
	}
	log.Println(bytes, "bytes")
	return nil
}

func docker_config(c Context) (*docker.Config, error) {
	email, ok := c[DOCKER_EMAIL].(string)
	if !ok {
		return nil, errors.New("DOCKER_EMAIL not a string")
	}
	auth, ok := c[DOCKER_AUTH].(string)
	if !ok {
		return nil, errors.New("DOCKER_AUTH not a string")
	}

	account, ok := c[DOCKER_ACCOUNT].(string)
	if !ok {
		return nil, errors.New("DOCKER_ACCOUNT not a string")
	}

	return &docker.Config{
		Email:   email,
		Auth:    auth,
		Account: account,
	}, nil
}

func (this *Image) Prepare(c Context) error {
	// for each artifact, pull the binary and place in the dockerfile's directory
	dir := filepath.Dir(this.Dockerfile)
	c["binary_dir"] = dir
	defer delete(c, "binary_dir")

	for _, artifact := range this.artifacts {
		err := artifact.Prepare(c)
		if err != nil {
			return err
		}
	}

	// set up dockercfg file
	log.Println("Setting up .dockercfg")
	f := filepath.Join(os.Getenv("HOME"), ".dockercfg")
	fi, err := os.Stat(f)
	switch {
	case err == nil && fi.IsDir():
		return errors.New("~/.dockercfg is a directory.")
	case err == nil: // overwrite
	case os.IsNotExist(err): // no file
		docker_config, err := docker_config(c)
		if err != nil {
			return nil
		}
		err = docker_config.GenerateDockerCfg(f)
		if err != nil {
			return err
		}
		log.Println("Created dockercfg.")
	default:
		return err
	}

	return nil
}

func (this *Image) Execute(c Context) error {

	docker_config, err := docker_config(c)
	if err != nil {
		return nil
	}
	_, docker_config.TestMode = c[TEST_MODE]

	image, err := docker_config.NewTaggedImage(this.RepoId, this.Dockerfile)
	if err != nil {
		return err
	}

	err = image.Build()
	if err != nil {
		return err
	}

	log.Println("Finished building", this.Name, "Now pushing.")

	err = image.Push()
	if err != nil {
		return err
	}

	return nil
}

func (this *Image) Finish(c Context) error {
	return nil
}
<|endoftext|>


<commit_before>package assert

import (
	"fmt"
	"reflect"
	"runtime"
	"testing"
)

type Caller struct {
	Func     *runtime.Func
	Name     string
	Filename string
	Line     int
}

func getCallerName(skip int) *Caller {
	// Increase skip since they surely don't want *this* function
	pc, file, line, _ := runtime.Caller(skip + 1)
	fn := runtime.FuncForPC(pc)
	return &Caller{
		Func:     fn,
		Name:     fn.Name(),
		Filename: file,
		Line:     line,
	}
}

func success(caller *Caller, message string, t *testing.T) {
	fmt.Printf("\033[32mok\033[0m        %s(): %s\n", caller.Name, message)
}

func failure(caller *Caller, message string, t *testing.T) {
	t.Errorf("\033[31;1mnot ok\033[0m    %s(): %s\n", caller.Name, message)
	t.Errorf("          - %s:%d\n", caller.Filename, caller.Line)
	t.FailNow()
}

func True(expression bool, message string, t *testing.T) {
	caller := getCallerName(1)
	if !expression {
		failure(caller, message, t)
		return
	}
	success(caller, message, t)
}

func False(exp bool, m string, t *testing.T) {
	True(!exp, m, t)
}

func Equal(expected, actual interface{}, message string, t *testing.T) {
	caller := getCallerName(1)
	if expected != actual {
		failure(caller, fmt.Sprintf("Expected %#v, but got %#v - %s", expected, actual, message), t)
		return
	}
	success(caller, message, t)
}

func Nil(value interface{}, message string, t *testing.T) {
	caller := getCallerName(1)
	if reflect.ValueOf(value).Elem().IsValid() {
		failure(caller, fmt.Sprintf("Expected %#v to be nil - %s", value, message), t)
		return
	}
	success(caller, message, t)
}
<commit_msg>Kill assert.Nil<commit_after>package assert

import (
	"fmt"
	"runtime"
	"testing"
)

type Caller struct {
	Func     *runtime.Func
	Name     string
	Filename string
	Line     int
}

func getCallerName(skip int) *Caller {
	// Increase skip since they surely don't want *this* function
	pc, file, line, _ := runtime.Caller(skip + 1)
	fn := runtime.FuncForPC(pc)
	return &Caller{
		Func:     fn,
		Name:     fn.Name(),
		Filename: file,
		Line:     line,
	}
}

func success(caller *Caller, message string, t *testing.T) {
	fmt.Printf("\033[32mok\033[0m        %s(): %s\n", caller.Name, message)
}

func failure(caller *Caller, message string, t *testing.T) {
	t.Errorf("\033[31;1mnot ok\033[0m    %s(): %s\n", caller.Name, message)
	t.Errorf("          - %s:%d\n", caller.Filename, caller.Line)
	t.FailNow()
}

func True(expression bool, message string, t *testing.T) {
	caller := getCallerName(1)
	if !expression {
		failure(caller, message, t)
		return
	}
	success(caller, message, t)
}

func False(exp bool, m string, t *testing.T) {
	True(!exp, m, t)
}

func Equal(expected, actual interface{}, message string, t *testing.T) {
	caller := getCallerName(1)
	if expected != actual {
		failure(caller, fmt.Sprintf("Expected %#v, but got %#v - %s", expected, actual, message), t)
		return
	}
	success(caller, message, t)
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Matthew Dawson <matthew@mjdsystems.ca>
 *
 * 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 main

import (
	"errors"

	"net/url"

	"sort"

	riak "github.com/tpjg/goriakpbc"
)

var FeedNotFound = errors.New("Failed to find feed in riak!")

const (
	MaximumFeedItems = 10000
)

func drainErrorChannelIntoSlice(errCh <-chan error, errorSlice *[]error, responses int) {
	for i := 0; i < responses; i++ {
		err := <-errCh
		if err != nil {
			*errorSlice = append(*errorSlice, err)
		}
	}
}

func InsertItem(con *riak.Client, itemKey ItemKey, item ParsedFeedItem) error {
	itemModel := FeedItem{
		Title:   item.Title,
		Author:  item.Author,
		Content: item.Content,
		Url:     item.Url,
		PubDate: item.PubDate,
	}
	if err := con.NewModel(string(itemKey), &itemModel); err != nil {
		return err
	} else if err = itemModel.Save(); err != nil {
		return err
	}
	return nil
}

func UpdateItem(con *riak.Client, itemKey ItemKey, item ParsedFeedItem, itemModel *FeedItem) error {
	itemModel.Title = item.Title
	itemModel.Author = item.Author
	itemModel.Content = item.Content
	itemModel.Url = item.Url
	itemModel.PubDate = item.PubDate

	if err := itemModel.Save(); err != nil {
		return err
	}
	return nil
}

func itemDiffersFromModel(feedItem ParsedFeedItem, itemModel *FeedItem) bool {
	return itemModel.Title != feedItem.Title ||
	itemModel.Author != feedItem.Author ||
	itemModel.Content != feedItem.Content ||
	itemModel.Url != feedItem.Url ||
	itemModel.PubDate != feedItem.PubDate
}

func updateFeed(con *riak.Client, feedUrl url.URL, feedData ParsedFeedData, ids <-chan uint64) error {
	feed := &Feed{Url: feedUrl}
	if err := con.LoadModel(feed.UrlKey(), feed); err == riak.NotFound {
		return FeedNotFound
	} else if err != nil {
		return err
	}
	// First finish clean off all deleted items, and clean out inserted item keys.  Since either existing
	// means a previous operation has been left in a bad state.  TBI

	// Next update the basic attributes (title basically)
	feed.Title = feedData.Title

	/* Next find all the feed items to insert/update.  If the item doesn't exist, create it's id and
	 * mark for insert.  Otherwise mark it for an read/update/store pass.  Make sure to mark for
	 * deletion items as necessary.
	 */
	// This struct holds an ItemKey and a ParsedFeedItem for later parsing.
	type ToProcess struct {
		ItemKey ItemKey
		Data    ParsedFeedItem
		Model   *FeedItem
	}
	NewItems := make([]ToProcess, 0)
	UpdatedItems := make([]ToProcess, 0)

	for _, rawItem := range feedData.Items {
		// Try to find the raw Item in the Item Keys list.
		index := feed.ItemKeys.FindRawItemId(rawItem.GenericKey)
		if index != -1 {
			// Found it!  Load the details.  Also load the model, which will be re-used later.
			p := ToProcess{
				ItemKey: feed.ItemKeys[index],
				Data:    rawItem,
				Model:   &FeedItem{},
			}

			if err := con.LoadModel(string(p.ItemKey), p.Model); err != nil {
				return err
			}

			// Ok, now is this have a new pub date?  If so, pull it out of its current position, and
			// move it up the chain.  Otherwise, just update the content.  If an item has no pub date,
			// assume that it has changed if the any part of the item changed.
			if p.Model.PubDate.Equal(p.Data.PubDate) && !(p.Data.PubDate.IsZero() && itemDiffersFromModel(p.Data, p.Model)) {
				// Pub dates are the same.  Just modify the item to match what is in the feed.
				UpdatedItems = append(UpdatedItems, p)
			} else {
				// Pub dates differ.  Delete the item, and re-insert it.
				feed.DeletedItemKeys = append(feed.DeletedItemKeys, p.ItemKey)
				feed.ItemKeys.RemoveAt(index)

				// Delete the model from the to process struct.
				p.Model = &FeedItem{}

				NewItems = append(NewItems, p) // This gives us the new id.
			}
		} else {
			// Nope, lets insert it!
			NewItems = append(NewItems, ToProcess{
				Data: rawItem,
			})
		}
	}

	/* Alright, any new items are mentioned in the Feed before being inserted.  In case something
	 * happens, I'd prefer not to lose an item.  Note the order is reversed so that the oldest story
	 * will get the smallest id, preserving sort order.  Inserted Item Keys needs to be sorted (well,
	 * reversed) after this so it is in correct order as well.  This loop violates ItemKeys sort
	 * order, so the sort is necessary for now. */
	for i := len(NewItems) - 1; i >= 0; i-- {
		newItem := &NewItems[i]
		newItem.ItemKey = NewItemKey(<-ids, newItem.Data.GenericKey)
		feed.InsertedItemKeys = append(feed.InsertedItemKeys, newItem.ItemKey)
	}
	sort.Sort(feed.InsertedItemKeys)

	// Ok, we must save here.  Otherwise planned changes may occur that will not be cleaned up!
	if err := feed.Save(); err != nil {
		return err
	}

	errCh := make(chan error) // All of the errors go into here, to be pulled out.

	// Good, now implement the change and update the Feed.

	// First add new items
	for _, newItem := range NewItems {
		feed.ItemKeys = append(feed.ItemKeys, newItem.ItemKey)
		go func(newItem ToProcess) {
			errCh <- InsertItem(con, newItem.ItemKey, newItem.Data)
		}(newItem)
	}
	feed.InsertedItemKeys = nil

	// Now update them.
	for _, newItem := range UpdatedItems {
		go func(newItem ToProcess) {
			errCh <- UpdateItem(con, newItem.ItemKey, newItem.Data, newItem.Model)
		}(newItem)
	}

	// Finally delete items.
	for _, deleteItemKey := range feed.DeletedItemKeys {
		go func(toDelete ItemKey) {
			errCh <- con.DeleteFrom("items", string(toDelete))
		}(deleteItemKey)
	}
	deletedItemCount := len(feed.DeletedItemKeys) // Need this to drain the error channel later.
	// Ok, deleted.  So clear the list
	feed.DeletedItemKeys = nil

	sort.Sort(sort.Reverse(feed.ItemKeys)) // Just sort this.  TBD: Actually maintain this sort order to avoid this!

	//Now, collect the errors
	var errs []error
	drainErrorChannelIntoSlice(errCh, &errs, len(NewItems))
	drainErrorChannelIntoSlice(errCh, &errs, len(UpdatedItems))
	drainErrorChannelIntoSlice(errCh, &errs, deletedItemCount)
	if len(errs) != 0 {
		return MultiError(errs)
	}

	if err := feed.Save(); err != nil {
		return err
	}

	_, _ = NewItems, UpdatedItems
	return nil
}
<commit_msg>Whitespace.<commit_after>/*
 * Copyright (C) 2013 Matthew Dawson <matthew@mjdsystems.ca>
 *
 * 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 main

import (
	"errors"

	"net/url"

	"sort"

	riak "github.com/tpjg/goriakpbc"
)

var FeedNotFound = errors.New("Failed to find feed in riak!")

const (
	MaximumFeedItems = 10000
)

func drainErrorChannelIntoSlice(errCh <-chan error, errorSlice *[]error, responses int) {
	for i := 0; i < responses; i++ {
		err := <-errCh
		if err != nil {
			*errorSlice = append(*errorSlice, err)
		}
	}
}

func InsertItem(con *riak.Client, itemKey ItemKey, item ParsedFeedItem) error {
	itemModel := FeedItem{
		Title:   item.Title,
		Author:  item.Author,
		Content: item.Content,
		Url:     item.Url,
		PubDate: item.PubDate,
	}
	if err := con.NewModel(string(itemKey), &itemModel); err != nil {
		return err
	} else if err = itemModel.Save(); err != nil {
		return err
	}
	return nil
}

func UpdateItem(con *riak.Client, itemKey ItemKey, item ParsedFeedItem, itemModel *FeedItem) error {
	itemModel.Title = item.Title
	itemModel.Author = item.Author
	itemModel.Content = item.Content
	itemModel.Url = item.Url
	itemModel.PubDate = item.PubDate

	if err := itemModel.Save(); err != nil {
		return err
	}
	return nil
}

func itemDiffersFromModel(feedItem ParsedFeedItem, itemModel *FeedItem) bool {
	return itemModel.Title != feedItem.Title ||
		itemModel.Author != feedItem.Author ||
		itemModel.Content != feedItem.Content ||
		itemModel.Url != feedItem.Url ||
		itemModel.PubDate != feedItem.PubDate
}

func updateFeed(con *riak.Client, feedUrl url.URL, feedData ParsedFeedData, ids <-chan uint64) error {
	feed := &Feed{Url: feedUrl}
	if err := con.LoadModel(feed.UrlKey(), feed); err == riak.NotFound {
		return FeedNotFound
	} else if err != nil {
		return err
	}
	// First finish clean off all deleted items, and clean out inserted item keys.  Since either existing
	// means a previous operation has been left in a bad state.  TBI

	// Next update the basic attributes (title basically)
	feed.Title = feedData.Title

	/* Next find all the feed items to insert/update.  If the item doesn't exist, create it's id and
	 * mark for insert.  Otherwise mark it for an read/update/store pass.  Make sure to mark for
	 * deletion items as necessary.
	 */
	// This struct holds an ItemKey and a ParsedFeedItem for later parsing.
	type ToProcess struct {
		ItemKey ItemKey
		Data    ParsedFeedItem
		Model   *FeedItem
	}
	NewItems := make([]ToProcess, 0)
	UpdatedItems := make([]ToProcess, 0)

	for _, rawItem := range feedData.Items {
		// Try to find the raw Item in the Item Keys list.
		index := feed.ItemKeys.FindRawItemId(rawItem.GenericKey)
		if index != -1 {
			// Found it!  Load the details.  Also load the model, which will be re-used later.
			p := ToProcess{
				ItemKey: feed.ItemKeys[index],
				Data:    rawItem,
				Model:   &FeedItem{},
			}

			if err := con.LoadModel(string(p.ItemKey), p.Model); err != nil {
				return err
			}

			// Ok, now is this have a new pub date?  If so, pull it out of its current position, and
			// move it up the chain.  Otherwise, just update the content.  If an item has no pub date,
			// assume that it has changed if the any part of the item changed.
			if p.Model.PubDate.Equal(p.Data.PubDate) && !(p.Data.PubDate.IsZero() && itemDiffersFromModel(p.Data, p.Model)) {
				// Pub dates are the same.  Just modify the item to match what is in the feed.
				UpdatedItems = append(UpdatedItems, p)
			} else {
				// Pub dates differ.  Delete the item, and re-insert it.
				feed.DeletedItemKeys = append(feed.DeletedItemKeys, p.ItemKey)
				feed.ItemKeys.RemoveAt(index)

				// Delete the model from the to process struct.
				p.Model = &FeedItem{}

				NewItems = append(NewItems, p) // This gives us the new id.
			}
		} else {
			// Nope, lets insert it!
			NewItems = append(NewItems, ToProcess{
				Data: rawItem,
			})
		}
	}

	/* Alright, any new items are mentioned in the Feed before being inserted.  In case something
	 * happens, I'd prefer not to lose an item.  Note the order is reversed so that the oldest story
	 * will get the smallest id, preserving sort order.  Inserted Item Keys needs to be sorted (well,
	 * reversed) after this so it is in correct order as well.  This loop violates ItemKeys sort
	 * order, so the sort is necessary for now. */
	for i := len(NewItems) - 1; i >= 0; i-- {
		newItem := &NewItems[i]
		newItem.ItemKey = NewItemKey(<-ids, newItem.Data.GenericKey)
		feed.InsertedItemKeys = append(feed.InsertedItemKeys, newItem.ItemKey)
	}
	sort.Sort(feed.InsertedItemKeys)

	// Ok, we must save here.  Otherwise planned changes may occur that will not be cleaned up!
	if err := feed.Save(); err != nil {
		return err
	}

	errCh := make(chan error) // All of the errors go into here, to be pulled out.

	// Good, now implement the change and update the Feed.

	// First add new items
	for _, newItem := range NewItems {
		feed.ItemKeys = append(feed.ItemKeys, newItem.ItemKey)
		go func(newItem ToProcess) {
			errCh <- InsertItem(con, newItem.ItemKey, newItem.Data)
		}(newItem)
	}
	feed.InsertedItemKeys = nil

	// Now update them.
	for _, newItem := range UpdatedItems {
		go func(newItem ToProcess) {
			errCh <- UpdateItem(con, newItem.ItemKey, newItem.Data, newItem.Model)
		}(newItem)
	}

	// Finally delete items.
	for _, deleteItemKey := range feed.DeletedItemKeys {
		go func(toDelete ItemKey) {
			errCh <- con.DeleteFrom("items", string(toDelete))
		}(deleteItemKey)
	}
	deletedItemCount := len(feed.DeletedItemKeys) // Need this to drain the error channel later.
	// Ok, deleted.  So clear the list
	feed.DeletedItemKeys = nil

	sort.Sort(sort.Reverse(feed.ItemKeys)) // Just sort this.  TBD: Actually maintain this sort order to avoid this!

	//Now, collect the errors
	var errs []error
	drainErrorChannelIntoSlice(errCh, &errs, len(NewItems))
	drainErrorChannelIntoSlice(errCh, &errs, len(UpdatedItems))
	drainErrorChannelIntoSlice(errCh, &errs, deletedItemCount)
	if len(errs) != 0 {
		return MultiError(errs)
	}

	if err := feed.Save(); err != nil {
		return err
	}

	_, _ = NewItems, UpdatedItems
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 AFP Authors
// This source code is released under the terms of the
// MIT license. Please see the file LICENSE for license details.

package delay

import (
	"afp"
	"afp/flags"
	"os"
)

type DelayFilter struct {
	context               *afp.Context
	header                afp.StreamHeader
	samplesPerMillisecond int
	delayTimeInMs         int
	delayAttenuation      float32
	extraSamples          int
	bufferSize            int32
	mixBuffer             [][]float32
}

func NewDelayFilter() afp.Filter {
	return &DelayFilter{}
}

func (self *DelayFilter) GetType() int {
	return afp.PIPE_LINK
}

func (self *DelayFilter) Init(ctx *afp.Context, args []string) os.Error {
	self.context = ctx

	parser := flags.FlagParser(args)
	var t *int = parser.Int("t", 125, "The delay time in milliseconds")
	var a *float = parser.Float("a", .5, "The wet (delayed) signal amplitude (0 - 1.0)")
	parser.Parse()

	self.delayTimeInMs = *t
	self.delayAttenuation = float32(*a)

	if self.delayTimeInMs <= 0 {
		return os.NewError("Delay time must be greater than zero")
	}

	if self.delayAttenuation < 0 || self.delayAttenuation > 1.0 {
		return os.NewError("Delay signal attenuation must between 0.0 and 1.0")
	}

	return nil
}

func (self *DelayFilter) Start() {
	self.header = <-self.context.HeaderSource
	self.context.HeaderSink <- self.header

	self.samplesPerMillisecond = int(self.header.SampleRate / 1000)
	self.extraSamples = self.delayTimeInMs * self.samplesPerMillisecond

	println("Extra Samples: ", self.extraSamples)

	self.initBuffers()
	self.process()
}

func (self *DelayFilter) process() {
	var (
		t0       int64 = 0
		mbOffset int   = 0
	)

	// loop over all input data
	for audio := range self.context.Source {
		// create a destination buffer
		destBuffer := makeBuffer(self.header.FrameSize, self.header.Channels)

		for t, sample := range audio {
			// mix delayed signal with raw signal
			for c, amplitude := range sample {
				destBuffer[t][c] = amplitude + self.mixBuffer[mbOffset][c]*self.delayAttenuation
			}

			// copy the raw signal into the delay line
			for c, amplitude := range sample {
				self.mixBuffer[mbOffset][c] = amplitude
			}

			// increment the offset into the delay
			mbOffset++
			if mbOffset == self.extraSamples {
				mbOffset = 0
			}
		}

		// send the mixed audio down the pipe
		self.context.Sink <- destBuffer
	}

	// create a destination buffer
	destBuffer := makeBuffer(self.header.FrameSize, self.header.Channels)

	// fill out the rest of the data
	for i := 0; i < self.extraSamples; i++ {
		for c, amplitude := range self.mixBuffer[mbOffset] {
			destBuffer[t0][c] = amplitude * self.delayAttenuation
		}
		t0++
		mbOffset++

		// increment the offset into the delay
		if mbOffset == self.extraSamples {
			mbOffset = 0
		}

		// check to see if we've filled a frame
		if t0 == int64(self.header.FrameSize) {
			// send the mixed audio down the pipe
			self.context.Sink <- destBuffer

			// create a destination buffer
			destBuffer = makeBuffer(self.header.FrameSize, self.header.Channels)
			t0 = 0
		}
	}

	if t0 < int64(self.header.FrameSize) {
		// send the mixed audio down the pipe
		self.context.Sink <- destBuffer
	}
}

// allocate a buffer for samples
func makeBuffer(size int32, channels int8) [][]float32 {
	b := make([][]float32, size)
	for i, _ := range b {
		b[i] = make([]float32, channels)
	}

	return b
}

func (self *DelayFilter) initBuffers() {
	self.mixBuffer = make([][]float32, self.extraSamples)
	for i, _ := range self.mixBuffer {
		self.mixBuffer[i] = make([]float32, self.header.Channels)
	}
}

func (self *DelayFilter) Stop() os.Error {
	return nil
}
<commit_msg>remove debugging output<commit_after>// Copyright (c) 2010 AFP Authors
// This source code is released under the terms of the
// MIT license. Please see the file LICENSE for license details.

package delay

import (
	"afp"
	"afp/flags"
	"os"
)

type DelayFilter struct {
	context               *afp.Context
	header                afp.StreamHeader
	samplesPerMillisecond int
	delayTimeInMs         int
	delayAttenuation      float32
	extraSamples          int
	bufferSize            int32
	mixBuffer             [][]float32
}

func NewDelayFilter() afp.Filter {
	return &DelayFilter{}
}

func (self *DelayFilter) GetType() int {
	return afp.PIPE_LINK
}

func (self *DelayFilter) Init(ctx *afp.Context, args []string) os.Error {
	self.context = ctx

	parser := flags.FlagParser(args)
	var t *int = parser.Int("t", 125, "The delay time in milliseconds")
	var a *float = parser.Float("a", .5, "The wet (delayed) signal amplitude (0 - 1.0)")
	parser.Parse()

	self.delayTimeInMs = *t
	self.delayAttenuation = float32(*a)

	if self.delayTimeInMs <= 0 {
		return os.NewError("Delay time must be greater than zero")
	}

	if self.delayAttenuation < 0 || self.delayAttenuation > 1.0 {
		return os.NewError("Delay signal attenuation must between 0.0 and 1.0")
	}

	return nil
}

func (self *DelayFilter) Start() {
	self.header = <-self.context.HeaderSource
	self.context.HeaderSink <- self.header

	self.samplesPerMillisecond = int(self.header.SampleRate / 1000)
	self.extraSamples = self.delayTimeInMs * self.samplesPerMillisecond

	self.initBuffers()
	self.process()
}

func (self *DelayFilter) process() {
	var (
		t0       int64 = 0
		mbOffset int   = 0
	)

	// loop over all input data
	for audio := range self.context.Source {
		// create a destination buffer
		destBuffer := makeBuffer(self.header.FrameSize, self.header.Channels)

		for t, sample := range audio {
			// mix delayed signal with raw signal
			for c, amplitude := range sample {
				destBuffer[t][c] = amplitude + self.mixBuffer[mbOffset][c]*self.delayAttenuation
			}

			// copy the raw signal into the delay line
			for c, amplitude := range sample {
				self.mixBuffer[mbOffset][c] = amplitude
			}

			// increment the offset into the delay
			mbOffset++
			if mbOffset == self.extraSamples {
				mbOffset = 0
			}
		}

		// send the mixed audio down the pipe
		self.context.Sink <- destBuffer
	}

	// create a destination buffer
	destBuffer := makeBuffer(self.header.FrameSize, self.header.Channels)

	// fill out the rest of the data
	for i := 0; i < self.extraSamples; i++ {
		for c, amplitude := range self.mixBuffer[mbOffset] {
			destBuffer[t0][c] = amplitude * self.delayAttenuation
		}
		t0++
		mbOffset++

		// increment the offset into the delay
		if mbOffset == self.extraSamples {
			mbOffset = 0
		}

		// check to see if we've filled a frame
		if t0 == int64(self.header.FrameSize) {
			// send the mixed audio down the pipe
			self.context.Sink <- destBuffer

			// create a destination buffer
			destBuffer = makeBuffer(self.header.FrameSize, self.header.Channels)
			t0 = 0
		}
	}

	if t0 < int64(self.header.FrameSize) {
		// send the mixed audio down the pipe
		self.context.Sink <- destBuffer
	}
}

// allocate a buffer for samples
func makeBuffer(size int32, channels int8) [][]float32 {
	b := make([][]float32, size)
	for i, _ := range b {
		b[i] = make([]float32, channels)
	}

	return b
}

func (self *DelayFilter) initBuffers() {
	self.mixBuffer = make([][]float32, self.extraSamples)
	for i, _ := range self.mixBuffer {
		self.mixBuffer[i] = make([]float32, self.header.Channels)
	}
}

func (self *DelayFilter) Stop() os.Error {
	return nil
}
<|endoftext|>