<commit_before>package werfexec

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

	"github.com/onsi/gomega/gexec"
)

type CommandOptions struct {
	OutputLineHandler func(string)
}

func ExecWerfCommand(dir, werfBinPath string, opts CommandOptions, arg ...string) error {
	cmd := exec.Command(werfBinPath, arg...)
	cmd.Dir = dir
	cmd.Env = os.Environ()

	absDir, _ := filepath.Abs(dir)
	fmt.Printf("[DEBUG] COMMAND in %s: %s %s\n", absDir, werfBinPath, strings.Join(arg, " "))

	stdoutReadPipe, stdoutWritePipe, err := os.Pipe()
	if err != nil {
		return fmt.Errorf("unable to create os pipe for stdout: %s", err)
	}

	stderrReadPipe, stderrWritePipe, err := os.Pipe()
	if err != nil {
		return fmt.Errorf("unable to create os pipe for stderr: %s", err)
	}

	outputReader := io.MultiReader(stdoutReadPipe, stderrReadPipe)

	session, err := gexec.Start(cmd, stdoutWritePipe, stderrWritePipe)
	if err != nil {
		return fmt.Errorf("error starting command: %s", err)
	}

	go func() {
		<-session.Exited

		// Initiate EOF for consumeOutputUntilEOF
		stdoutWritePipe.Close()
		stderrWritePipe.Close()
	}()

	lineBuf := make([]byte, 0, 4096)
	if err := consumeOutputUntilEOF(outputReader, func(data []byte) error {
		for _, b := range data {
			if b == '\n' {
				line := string(lineBuf)
				lineBuf = lineBuf[:0]

				fmt.Printf("[DEBUG] OUTPUT LINE: %s\n", line)

				if opts.OutputLineHandler != nil {
					func() {
						handlerDone := false
						defer func() {
							// Cleanup process in the case of gomega panic in OutputLineHandler.
							// Current werf process may held a lock and this will lead to a deadlock in the
							// case when another werf command has been ran from AfterEach when this panic was occurred.
							//
							// Panic in OutputLineHandler and current command kill allows to fail fast
							// and give user immediate feedback of failed assertion during command execution.
							if !handlerDone {
								session.Kill()
							}
						}()
						opts.OutputLineHandler(line)
						handlerDone = true
					}()
				}

				continue
			}

			lineBuf = append(lineBuf, b)
		}

		return nil
	}); err != nil {
		return fmt.Errorf("unable to consume command output: %s", err)
	}

	if exitCode := session.ExitCode(); exitCode != 0 {
		return fmt.Errorf("exit code %d", exitCode)
	}
	return nil
}

func consumeOutputUntilEOF(reader io.Reader, handleChunk func(data []byte) error) error {
	chunkBuf := make([]byte, 1024*64)

	for {
		n, err := reader.Read(chunkBuf)
		if n > 0 {
			if handleErr := handleChunk(chunkBuf[:n]); handleErr != nil {
				return handleErr
			}
		}

		if err == io.EOF {
			return nil
		}

		if err != nil {
			return fmt.Errorf("read error: %s", err)
		}
	}
}
<commit_msg>[tests] utils/werfexec: use GinkgoWriter<commit_after>package werfexec

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

	. "github.com/onsi/ginkgo"

	"github.com/onsi/gomega/gexec"
)

type CommandOptions struct {
	OutputLineHandler func(string)
}

func ExecWerfCommand(dir, werfBinPath string, opts CommandOptions, arg ...string) error {
	cmd := exec.Command(werfBinPath, arg...)
	cmd.Dir = dir
	cmd.Env = os.Environ()

	absDir, _ := filepath.Abs(dir)
	_, _ = fmt.Fprintf(GinkgoWriter, "\n[DEBUG] COMMAND in %s: %s %s\n\n", absDir, werfBinPath, strings.Join(arg, " "))

	stdoutReadPipe, stdoutWritePipe, err := os.Pipe()
	if err != nil {
		return fmt.Errorf("unable to create os pipe for stdout: %s", err)
	}

	stderrReadPipe, stderrWritePipe, err := os.Pipe()
	if err != nil {
		return fmt.Errorf("unable to create os pipe for stderr: %s", err)
	}

	outputReader := io.MultiReader(stdoutReadPipe, stderrReadPipe)

	session, err := gexec.Start(cmd, stdoutWritePipe, stderrWritePipe)
	if err != nil {
		return fmt.Errorf("error starting command: %s", err)
	}

	go func() {
		<-session.Exited

		// Initiate EOF for consumeOutputUntilEOF
		stdoutWritePipe.Close()
		stderrWritePipe.Close()
	}()

	lineBuf := make([]byte, 0, 4096)
	if err := consumeOutputUntilEOF(outputReader, func(data []byte) error {
		for _, b := range data {
			if b == '\n' {
				line := string(lineBuf)
				lineBuf = lineBuf[:0]

				_, _ = fmt.Fprintf(GinkgoWriter, "[DEBUG] OUTPUT LINE: %s\n", line)

				if opts.OutputLineHandler != nil {
					func() {
						handlerDone := false
						defer func() {
							// Cleanup process in the case of gomega panic in OutputLineHandler.
							// Current werf process may held a lock and this will lead to a deadlock in the
							// case when another werf command has been ran from AfterEach when this panic was occurred.
							//
							// Panic in OutputLineHandler and current command kill allows to fail fast
							// and give user immediate feedback of failed assertion during command execution.
							if !handlerDone {
								session.Kill()
							}
						}()
						opts.OutputLineHandler(line)
						handlerDone = true
					}()
				}

				continue
			}

			lineBuf = append(lineBuf, b)
		}

		return nil
	}); err != nil {
		return fmt.Errorf("unable to consume command output: %s", err)
	}

	if exitCode := session.ExitCode(); exitCode != 0 {
		return fmt.Errorf("exit code %d", exitCode)
	}
	return nil
}

func consumeOutputUntilEOF(reader io.Reader, handleChunk func(data []byte) error) error {
	chunkBuf := make([]byte, 1024*64)

	for {
		n, err := reader.Read(chunkBuf)
		if n > 0 {
			if handleErr := handleChunk(chunkBuf[:n]); handleErr != nil {
				return handleErr
			}
		}

		if err == io.EOF {
			return nil
		}

		if err != nil {
			return fmt.Errorf("read error: %s", err)
		}
	}
}
<|endoftext|>


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

package jtl

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"time"

	. "github.com/Comcast/eel/util"
)

// StatusHandler http handler for health and status checks. Writes JSON containing config.json, handler configs and basic stats to w.
func StatusHandler(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	ctx := Gctx.SubContext()
	w.Header().Set("Content-Type", "application/json")
	state := make(map[string]interface{}, 0)
	state["Version"] = GetConfig(ctx).Version
	state["Config"] = GetConfig(ctx)
	callstats := make(map[string]interface{}, 0)
	tenantId := ""
	if ctx.Value(EelTenantId) != nil {
		tenantId = Gctx.Value(EelTenantId).(string)
	}
	if ctx.Value(EelDispatcher+"_"+tenantId) != nil {
		callstats["WorkQueueFillLevel"] = len(GetWorkDispatcher(ctx, tenantId).WorkQueue)
		callstats["WorkersIdle"] = len(GetWorkDispatcher(ctx, tenantId).WorkerQueue)
	}
	if ctx.Value(EelTotalStats) != nil {
		callstats["TotalStats"] = ctx.Value(EelTotalStats)
	}
	if ctx.Value(Eel1MinStats) != nil {
		callstats[Eel1MinStats] = ctx.Value(Eel1MinStats)
	}
	if ctx.Value(Eel5MinStats) != nil {
		callstats[Eel5MinStats] = ctx.Value(Eel5MinStats)
	}
	if ctx.Value(Eel1hrStats) != nil {
		callstats[Eel1hrStats] = ctx.Value(Eel1hrStats)
	}
	if ctx.Value(Eel24hrStats) != nil {
		callstats[Eel24hrStats] = ctx.Value(Eel24hrStats)
	}
	callstats["StartTime"] = ctx.Value(EelStartTime)
	host, _ := os.Hostname()
	if host != "" {
		callstats["Hostname"] = host
	}
	elapsed1 := time.Since(start)
	addrs, err := net.InterfaceAddrs()
	if err == nil {
		for _, addr := range addrs {
			if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
				if ipnet.IP.To4() != nil {
					callstats["IpAddress"] = ipnet.IP.String()
				}
			}
		}
	}
	elapsed2 := time.Since(start)
	state["Stats"] = callstats
	state["CustomHandlers"] = GetHandlerFactory(ctx).CustomHandlerMap
	state["TopicHandlers"] = GetHandlerFactory(ctx).TopicHandlerMap
	buf, err := json.MarshalIndent(state, "", "\t")
	elapsed3 := time.Since(start)
	if err != nil {
		fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
	} else {
		fmt.Fprintf(w, string(buf))
	}
	elapsed4 := time.Since(start)
	ctx.Log().Info("action", "health", "d1", int64(elapsed1/1e6), "d2", int64(elapsed2/1e6), "d3", int64(elapsed3/1e6), "d4", int64(elapsed4/1e6))
}

// VetHandler http handler for vetting all handler configurations. Writes JSON with list of warnings (if any) to w.
func VetHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	_, warnings := NewHandlerFactory(Gctx, HandlerPaths)
	if len(warnings) == 0 {
		fmt.Fprintf(w, `{"status":"ok"}`)
	} else {
		buf, err := json.MarshalIndent(warnings, "", "\t")
		if err != nil {
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			fmt.Fprintf(w, string(buf))
		}
	}
}

// NilHandler http handler to to do almost nothing.
func NilHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, `{"status":"ok"}`)
}

// ReloadConfigHandler http handler to relaod all configs from disk. Response is similar to StatusHandler.
func ReloadConfigHandler(w http.ResponseWriter, r *http.Request) {
	tenantId := ""
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}
	tenantId = "xh"
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}
	tenantId = "sport"
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}

	ReloadConfig()
	InitHttpTransport(Gctx)

	tenantId = ""
	dp := NewWorkDispatcher(GetConfig(Gctx).WorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dp.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dp)
	tenantId = "xh"
	dpXH := NewWorkDispatcher(GetConfig(Gctx).WorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dpXH.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dpXH)
	tenantId = "sport"
	dpSport := NewWorkDispatcher(GetConfig(Gctx).WorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dpSport.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dpSport)

	dc := NewLocalInMemoryDupChecker(GetConfig(Gctx).DuplicateTimeout, 10000)
	Gctx.AddValue(EelDuplicateChecker, dc)
	StatusHandler(w, r)
}
<commit_msg>use own pool size<commit_after>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jtl

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"time"

	. "github.com/Comcast/eel/util"
)

// StatusHandler http handler for health and status checks. Writes JSON containing config.json, handler configs and basic stats to w.
func StatusHandler(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	ctx := Gctx.SubContext()
	w.Header().Set("Content-Type", "application/json")
	state := make(map[string]interface{}, 0)
	state["Version"] = GetConfig(ctx).Version
	state["Config"] = GetConfig(ctx)
	callstats := make(map[string]interface{}, 0)
	tenantId := ""
	if ctx.Value(EelTenantId) != nil {
		tenantId = Gctx.Value(EelTenantId).(string)
	}
	if ctx.Value(EelDispatcher+"_"+tenantId) != nil {
		callstats["WorkQueueFillLevel"] = len(GetWorkDispatcher(ctx, tenantId).WorkQueue)
		callstats["WorkersIdle"] = len(GetWorkDispatcher(ctx, tenantId).WorkerQueue)
	}
	if ctx.Value(EelTotalStats) != nil {
		callstats["TotalStats"] = ctx.Value(EelTotalStats)
	}
	if ctx.Value(Eel1MinStats) != nil {
		callstats[Eel1MinStats] = ctx.Value(Eel1MinStats)
	}
	if ctx.Value(Eel5MinStats) != nil {
		callstats[Eel5MinStats] = ctx.Value(Eel5MinStats)
	}
	if ctx.Value(Eel1hrStats) != nil {
		callstats[Eel1hrStats] = ctx.Value(Eel1hrStats)
	}
	if ctx.Value(Eel24hrStats) != nil {
		callstats[Eel24hrStats] = ctx.Value(Eel24hrStats)
	}
	callstats["StartTime"] = ctx.Value(EelStartTime)
	host, _ := os.Hostname()
	if host != "" {
		callstats["Hostname"] = host
	}
	elapsed1 := time.Since(start)
	addrs, err := net.InterfaceAddrs()
	if err == nil {
		for _, addr := range addrs {
			if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
				if ipnet.IP.To4() != nil {
					callstats["IpAddress"] = ipnet.IP.String()
				}
			}
		}
	}
	elapsed2 := time.Since(start)
	state["Stats"] = callstats
	state["CustomHandlers"] = GetHandlerFactory(ctx).CustomHandlerMap
	state["TopicHandlers"] = GetHandlerFactory(ctx).TopicHandlerMap
	buf, err := json.MarshalIndent(state, "", "\t")
	elapsed3 := time.Since(start)
	if err != nil {
		fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
	} else {
		fmt.Fprintf(w, string(buf))
	}
	elapsed4 := time.Since(start)
	ctx.Log().Info("action", "health", "d1", int64(elapsed1/1e6), "d2", int64(elapsed2/1e6), "d3", int64(elapsed3/1e6), "d4", int64(elapsed4/1e6))
}

// VetHandler http handler for vetting all handler configurations. Writes JSON with list of warnings (if any) to w.
func VetHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	_, warnings := NewHandlerFactory(Gctx, HandlerPaths)
	if len(warnings) == 0 {
		fmt.Fprintf(w, `{"status":"ok"}`)
	} else {
		buf, err := json.MarshalIndent(warnings, "", "\t")
		if err != nil {
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			fmt.Fprintf(w, string(buf))
		}
	}
}

// NilHandler http handler to to do almost nothing.
func NilHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, `{"status":"ok"}`)
}

// ReloadConfigHandler http handler to relaod all configs from disk. Response is similar to StatusHandler.
func ReloadConfigHandler(w http.ResponseWriter, r *http.Request) {
	tenantId := ""
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}
	tenantId = "xh"
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}
	tenantId = "sport"
	if Gctx.Value(EelDispatcher+"_"+tenantId) != nil {
		dp := Gctx.Value(EelDispatcher + "_" + tenantId).(*WorkDispatcher)
		dp.Stop(Gctx)
	}

	ReloadConfig()
	InitHttpTransport(Gctx)

	tenantId = ""
	dp := NewWorkDispatcher(GetConfig(Gctx).WorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dp.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dp)
	tenantId = "xh"
	dpXH := NewWorkDispatcher(GetConfig(Gctx).XHWorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dpXH.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dpXH)
	tenantId = "sport"
	dpSport := NewWorkDispatcher(GetConfig(Gctx).SportWorkerPoolSize, GetConfig(Gctx).MessageQueueDepth, tenantId)
	dpSport.Start(Gctx)
	Gctx.AddValue(EelDispatcher+"_"+tenantId, dpSport)

	dc := NewLocalInMemoryDupChecker(GetConfig(Gctx).DuplicateTimeout, 10000)
	Gctx.AddValue(EelDuplicateChecker, dc)
	StatusHandler(w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/andrew-d/go-termutil"
	"github.com/cloudfoundry/cli/plugin"
)

//AppStatus represents the sattus of a app in CF
type AppStatus struct {
	name           string
	countRunning   int
	countRequested int
	state          string
	routes         []string
}

//ScaleoverCmd is this plugin
type ScaleoverCmd struct {
	app1     AppStatus
	app2     AppStatus
	maxcount int
}

//GetMetadata returns metatada
func (cmd *ScaleoverCmd) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "scaleover",
		Version: plugin.VersionType{
			Major: 0,
			Minor: 1,
			Build: 0,
		},
		Commands: []plugin.Command{
			{
				Name:     "scaleover",
				HelpText: "Roll http traffic from one application to another",
				UsageDetails: plugin.Usage{
					Usage: "cf scaleover APP1 APP2 ROLLOVER_DURATION [--no-route-check]",
				},
			},
		},
	}
}

func main() {
	plugin.Start(new(ScaleoverCmd))
}

func (cmd *ScaleoverCmd) usage(args []string) error {
	badArgs := 4 != len(args)

	if 5 == len(args) {
		if "--no-route-check" == args[4] {
			badArgs = false
		}
	}

	if badArgs {
		return errors.New("Usage: cf scaleover\n\tcf scaleover APP1 APP2 ROLLOVER_DURATION [--no-route-check]")
	}
	return nil
}

func (cmd *ScaleoverCmd) shouldEnforceRoutes(args []string) bool {
	return !("--no-route-check" == args[len(args)-1])
}

func (cmd *ScaleoverCmd) parseTime(duration string) (time.Duration, error) {
	rolloverTime := time.Duration(0)
	var err error
	rolloverTime, err = time.ParseDuration(duration)

	if err != nil {
		return rolloverTime, err
	}
	if 0 > rolloverTime {
		return rolloverTime, errors.New("Duration must be a positive number in the format of 1m")
	}

	return rolloverTime, nil
}

//Run runs the plugin
func (cmd *ScaleoverCmd) Run(cliConnection plugin.CliConnection, args []string) {
	if args[0] == "scaleover" {
		cmd.ScaleoverCommand(cliConnection, args)
	}
}

//ScaleoverCommand creates a new instance of this plugin
func (cmd *ScaleoverCmd) ScaleoverCommand(cliConnection plugin.CliConnection, args []string) {
	enforceRoutes := cmd.shouldEnforceRoutes(args)

	if err := cmd.usage(args); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	rolloverTime, err := cmd.parseTime(args[3])
	if nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	// The getAppStatus calls will exit with an error if the named apps don't exist
	if cmd.app1, err = cmd.getAppStatus(cliConnection, args[1]); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	if cmd.app2, err = cmd.getAppStatus(cliConnection, args[2]); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	if enforceRoutes {
		if err = cmd.errorIfNoSharedRoute(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	cmd.showStatus()

	count := cmd.app1.countRequested
	if count == 0 {
		fmt.Println("There are no instances of the source app to scale over")
		os.Exit(0)
	}
	sleepInterval := time.Duration(rolloverTime.Nanoseconds() / int64(count))

	for count > 0 {
		count--
		cmd.app2.scaleUp(cliConnection)
		cmd.app1.scaleDown(cliConnection)
		cmd.showStatus()
		if count > 0 {
			time.Sleep(sleepInterval)
		}
	}
	fmt.Println()
}

func (cmd *ScaleoverCmd) getAppStatus(cliConnection plugin.CliConnection, name string) (AppStatus, error) {
	status := AppStatus{
		name:           name,
		countRunning:   0,
		countRequested: 0,
		state:          "unknown",
		routes:         []string{},
	}

	output, _ := cliConnection.CliCommandWithoutTerminalOutput("app", name)

	for idx, v := range output {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "FAILED") {
			e := output[idx+1]
			return status, errors.New(e)
		}
		if strings.HasPrefix(v, "requested state: ") {
			status.state = strings.TrimPrefix(v, "requested state: ")
		}
		if strings.HasPrefix(v, "instances: ") {
			instances := strings.TrimPrefix(v, "instances: ")
			split := strings.Split(instances, "/")
			status.countRunning, _ = strconv.Atoi(split[0])
			status.countRequested, _ = strconv.Atoi(split[1])
		}
		if strings.HasPrefix(v, "urls: ") {
			urls := strings.TrimPrefix(v, "urls: ")
			status.routes = strings.Split(urls, ", ")
		}
	}
	// Compensate for some CF weirdness that leaves the requested instances non-zero
	// even though the app is stopped
	if "stopped" == status.state {
		status.countRequested = 0
	}
	return status, nil
}

func (app *AppStatus) scaleUp(cliConnection plugin.CliConnection) {
	// If not already started, start it
	if app.state != "started" {
		cliConnection.CliCommandWithoutTerminalOutput("start", app.name)
		app.state = "started"
	}
	app.countRequested++
	cliConnection.CliCommandWithoutTerminalOutput("scale", "-i", strconv.Itoa(app.countRequested), app.name)
}

func (app *AppStatus) scaleDown(cliConnection plugin.CliConnection) {
	app.countRequested--
	// If going to zero, stop the app
	if app.countRequested == 0 {
		cliConnection.CliCommandWithoutTerminalOutput("stop", app.name)
		app.state = "stopped"
	} else {
		cliConnection.CliCommandWithoutTerminalOutput("scale", "-i", strconv.Itoa(app.countRequested), app.name)
	}
}

func (cmd *ScaleoverCmd) showStatus() {
	if termutil.Isatty(os.Stdout.Fd()) {
		fmt.Printf("%s (%s) %s %s %s (%s) \r",
			cmd.app1.name,
			cmd.app1.state,
			strings.Repeat("<", cmd.app1.countRequested),
			strings.Repeat(">", cmd.app2.countRequested),
			cmd.app2.name,
			cmd.app2.state,
		)
	} else {
		fmt.Printf("%s (%s) %d instances, %s (%s) %d instances\n",
			cmd.app1.name,
			cmd.app1.state,
			cmd.app1.countRequested,
			cmd.app2.name,
			cmd.app2.state,
			cmd.app2.countRequested,
		)
	}
}

func (cmd *ScaleoverCmd) appsShareARoute() bool {
	for _, r1 := range cmd.app1.routes {
		for _, r2 := range cmd.app2.routes {
			if r1 == r2 {
				return true
			}
		}
	}
	return false
}

func (cmd *ScaleoverCmd) errorIfNoSharedRoute() error {
	if cmd.appsShareARoute() {
		return nil
	}
	return errors.New("Apps do not share a route!")
}
<commit_msg>Simplify parsing of --no-route-check because it can only occur at the end<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/andrew-d/go-termutil"
	"github.com/cloudfoundry/cli/plugin"
)

//AppStatus represents the sattus of a app in CF
type AppStatus struct {
	name           string
	countRunning   int
	countRequested int
	state          string
	routes         []string
}

//ScaleoverCmd is this plugin
type ScaleoverCmd struct {
	app1     AppStatus
	app2     AppStatus
	maxcount int
}

//GetMetadata returns metatada
func (cmd *ScaleoverCmd) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "scaleover",
		Version: plugin.VersionType{
			Major: 0,
			Minor: 1,
			Build: 0,
		},
		Commands: []plugin.Command{
			{
				Name:     "scaleover",
				HelpText: "Roll http traffic from one application to another",
				UsageDetails: plugin.Usage{
					Usage: "cf scaleover APP1 APP2 ROLLOVER_DURATION [--no-route-check]",
				},
			},
		},
	}
}

func main() {
	plugin.Start(new(ScaleoverCmd))
}

func (cmd *ScaleoverCmd) usage(args []string) error {
	badArgs := 4 != len(args)

	if 5 == len(args) {
		if "--no-route-check" == args[4] {
			badArgs = false
		}
	}

	if badArgs {
		return errors.New("Usage: cf scaleover\n\tcf scaleover APP1 APP2 ROLLOVER_DURATION [--no-route-check]")
	}
	return nil
}

func (cmd *ScaleoverCmd) shouldEnforceRoutes(args []string) bool {
	return "--no-route-check" != args[len(args)-1]
}

func (cmd *ScaleoverCmd) parseTime(duration string) (time.Duration, error) {
	rolloverTime := time.Duration(0)
	var err error
	rolloverTime, err = time.ParseDuration(duration)

	if err != nil {
		return rolloverTime, err
	}
	if 0 > rolloverTime {
		return rolloverTime, errors.New("Duration must be a positive number in the format of 1m")
	}

	return rolloverTime, nil
}

//Run runs the plugin
func (cmd *ScaleoverCmd) Run(cliConnection plugin.CliConnection, args []string) {
	if args[0] == "scaleover" {
		cmd.ScaleoverCommand(cliConnection, args)
	}
}

//ScaleoverCommand creates a new instance of this plugin
func (cmd *ScaleoverCmd) ScaleoverCommand(cliConnection plugin.CliConnection, args []string) {
	enforceRoutes := cmd.shouldEnforceRoutes(args)

	if err := cmd.usage(args); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	rolloverTime, err := cmd.parseTime(args[3])
	if nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	// The getAppStatus calls will exit with an error if the named apps don't exist
	if cmd.app1, err = cmd.getAppStatus(cliConnection, args[1]); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	if cmd.app2, err = cmd.getAppStatus(cliConnection, args[2]); nil != err {
		fmt.Println(err)
		os.Exit(1)
	}

	if enforceRoutes {
		if err = cmd.errorIfNoSharedRoute(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	cmd.showStatus()

	count := cmd.app1.countRequested
	if count == 0 {
		fmt.Println("There are no instances of the source app to scale over")
		os.Exit(0)
	}
	sleepInterval := time.Duration(rolloverTime.Nanoseconds() / int64(count))

	for count > 0 {
		count--
		cmd.app2.scaleUp(cliConnection)
		cmd.app1.scaleDown(cliConnection)
		cmd.showStatus()
		if count > 0 {
			time.Sleep(sleepInterval)
		}
	}
	fmt.Println()
}

func (cmd *ScaleoverCmd) getAppStatus(cliConnection plugin.CliConnection, name string) (AppStatus, error) {
	status := AppStatus{
		name:           name,
		countRunning:   0,
		countRequested: 0,
		state:          "unknown",
		routes:         []string{},
	}

	output, _ := cliConnection.CliCommandWithoutTerminalOutput("app", name)

	for idx, v := range output {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "FAILED") {
			e := output[idx+1]
			return status, errors.New(e)
		}
		if strings.HasPrefix(v, "requested state: ") {
			status.state = strings.TrimPrefix(v, "requested state: ")
		}
		if strings.HasPrefix(v, "instances: ") {
			instances := strings.TrimPrefix(v, "instances: ")
			split := strings.Split(instances, "/")
			status.countRunning, _ = strconv.Atoi(split[0])
			status.countRequested, _ = strconv.Atoi(split[1])
		}
		if strings.HasPrefix(v, "urls: ") {
			urls := strings.TrimPrefix(v, "urls: ")
			status.routes = strings.Split(urls, ", ")
		}
	}
	// Compensate for some CF weirdness that leaves the requested instances non-zero
	// even though the app is stopped
	if "stopped" == status.state {
		status.countRequested = 0
	}
	return status, nil
}

func (app *AppStatus) scaleUp(cliConnection plugin.CliConnection) {
	// If not already started, start it
	if app.state != "started" {
		cliConnection.CliCommandWithoutTerminalOutput("start", app.name)
		app.state = "started"
	}
	app.countRequested++
	cliConnection.CliCommandWithoutTerminalOutput("scale", "-i", strconv.Itoa(app.countRequested), app.name)
}

func (app *AppStatus) scaleDown(cliConnection plugin.CliConnection) {
	app.countRequested--
	// If going to zero, stop the app
	if app.countRequested == 0 {
		cliConnection.CliCommandWithoutTerminalOutput("stop", app.name)
		app.state = "stopped"
	} else {
		cliConnection.CliCommandWithoutTerminalOutput("scale", "-i", strconv.Itoa(app.countRequested), app.name)
	}
}

func (cmd *ScaleoverCmd) showStatus() {
	if termutil.Isatty(os.Stdout.Fd()) {
		fmt.Printf("%s (%s) %s %s %s (%s) \r",
			cmd.app1.name,
			cmd.app1.state,
			strings.Repeat("<", cmd.app1.countRequested),
			strings.Repeat(">", cmd.app2.countRequested),
			cmd.app2.name,
			cmd.app2.state,
		)
	} else {
		fmt.Printf("%s (%s) %d instances, %s (%s) %d instances\n",
			cmd.app1.name,
			cmd.app1.state,
			cmd.app1.countRequested,
			cmd.app2.name,
			cmd.app2.state,
			cmd.app2.countRequested,
		)
	}
}

func (cmd *ScaleoverCmd) appsShareARoute() bool {
	for _, r1 := range cmd.app1.routes {
		for _, r2 := range cmd.app2.routes {
			if r1 == r2 {
				return true
			}
		}
	}
	return false
}

func (cmd *ScaleoverCmd) errorIfNoSharedRoute() error {
	if cmd.appsShareARoute() {
		return nil
	}
	return errors.New("Apps do not share a route!")
}
<|endoftext|>


<commit_before>package scan

import "github.com/cloudflare/cf-tls/tls"

// TLSSession contains tests of host TLS Session Resumption via
// Session Tickets and Session IDs
var TLSSession = &Family{
	Description: "Scans host's implementation of TLS session resumption using session tickets/session IDs",
	Scanners: map[string]*Scanner{
		"SessionResume": {
			"Host is able to resume sessions across all addresses",
			sessionResumeScan,
		},
	},
}

// SessionResumeScan tests that host is able to resume sessions across all addresses.
func sessionResumeScan(host string) (grade Grade, output Output, err error) {
	config := defaultTLSConfig(host)
	config.ClientSessionCache = tls.NewLRUClientSessionCache(1)

	conn, err := tls.DialWithDialer(Dialer, Network, host, config)
	if err != nil {
		return
	}
	if err = conn.Close(); err != nil {
		return
	}

	return multiscan(host, func(addrport string) (g Grade, o Output, e error) {
		g = Good
		conn, e1 := tls.DialWithDialer(Dialer, Network, addrport, config)
		if e1 != nil {
			return
		}
		conn.Close()

		o = conn.ConnectionState().DidResume
		if !conn.ConnectionState().DidResume {
			grade = Bad
		}
		return
	})
}
<commit_msg>Fix multiscan output for SessionResumeScan<commit_after>package scan

import "github.com/cloudflare/cf-tls/tls"

// TLSSession contains tests of host TLS Session Resumption via
// Session Tickets and Session IDs
var TLSSession = &Family{
	Description: "Scans host's implementation of TLS session resumption using session tickets/session IDs",
	Scanners: map[string]*Scanner{
		"SessionResume": {
			"Host is able to resume sessions across all addresses",
			sessionResumeScan,
		},
	},
}

// SessionResumeScan tests that host is able to resume sessions across all addresses.
func sessionResumeScan(host string) (grade Grade, output Output, err error) {
	config := defaultTLSConfig(host)
	config.ClientSessionCache = tls.NewLRUClientSessionCache(1)

	conn, err := tls.DialWithDialer(Dialer, Network, host, config)
	if err != nil {
		return
	}
	if err = conn.Close(); err != nil {
		return
	}

	return multiscan(host, func(addrport string) (g Grade, o Output, e error) {
		var conn *tls.Conn
		if conn, e = tls.DialWithDialer(Dialer, Network, addrport, config); e != nil {
			return
		}
		conn.Close()

		if o = conn.ConnectionState().DidResume; o.(bool) {
			g = Good
		}
		return
	})
}
<|endoftext|>


<commit_before>// Package designate implements a DNS provider for solving the DNS-01 challenge using the Designate DNSaaS for Openstack.
package designate

import (
	"errors"
	"fmt"
	"log"
	"os"
	"sync"
	"time"

	"github.com/go-acme/lego/v3/challenge/dns01"
	"github.com/go-acme/lego/v3/platform/config/env"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
	"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
)

// Config is used to configure the creation of the DNSProvider
type Config struct {
	PropagationTimeout time.Duration
	PollingInterval    time.Duration
	TTL                int
	opts               gophercloud.AuthOptions
}

// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
	return &Config{
		TTL:                env.GetOrDefaultInt("DESIGNATE_TTL", 10),
		PropagationTimeout: env.GetOrDefaultSecond("DESIGNATE_PROPAGATION_TIMEOUT", 10*time.Minute),
		PollingInterval:    env.GetOrDefaultSecond("DESIGNATE_POLLING_INTERVAL", 10*time.Second),
	}
}

// DNSProvider describes a provider for Designate
type DNSProvider struct {
	config       *Config
	client       *gophercloud.ServiceClient
	dnsEntriesMu sync.Mutex
}

// NewDNSProvider returns a DNSProvider instance configured for Designate.
// Credentials must be passed in the environment variables:
// OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_REGION_NAME.
func NewDNSProvider() (*DNSProvider, error) {
	_, err := env.Get("OS_AUTH_URL", "OS_USERNAME", "OS_PASSWORD", "OS_TENANT_NAME", "OS_REGION_NAME")
	if err != nil {
		return nil, fmt.Errorf("designate: %v", err)
	}

	opts, err := openstack.AuthOptionsFromEnv()
	if err != nil {
		return nil, fmt.Errorf("designate: %v", err)
	}

	config := NewDefaultConfig()
	config.opts = opts

	return NewDNSProviderConfig(config)
}

// NewDNSProviderConfig return a DNSProvider instance configured for Designate.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
	if config == nil {
		return nil, errors.New("designate: the configuration of the DNS provider is nil")
	}

	provider, err := openstack.AuthenticatedClient(config.opts)
	if err != nil {
		return nil, fmt.Errorf("designate: failed to authenticate: %v", err)
	}

	dnsClient, err := openstack.NewDNSV2(provider, gophercloud.EndpointOpts{
		Region: os.Getenv("OS_REGION_NAME"),
	})
	if err != nil {
		return nil, fmt.Errorf("designate: failed to get DNS provider: %v", err)
	}

	return &DNSProvider{client: dnsClient, config: config}, nil
}

// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
	return d.config.PropagationTimeout, d.config.PollingInterval
}

// Present creates a TXT record to fulfill the dns-01 challenge
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
	fqdn, value := dns01.GetRecord(domain, keyAuth)

	authZone, err := dns01.FindZoneByFqdn(fqdn)
	if err != nil {
		return fmt.Errorf("designate: couldn't get zone ID in Present: %v", err)
	}

	zoneID, err := d.getZoneID(authZone)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	// use mutex to prevent race condition between creating the record and verifying it
	d.dnsEntriesMu.Lock()
	defer d.dnsEntriesMu.Unlock()

	existingRecord, err := d.getRecord(zoneID, fqdn)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	if existingRecord != nil {
		if contains(existingRecord.Records, value) {
			log.Printf("designate: the record already exists: %s", value)
			return nil
		}

		return d.updateRecord(existingRecord, value)
	}

	err = d.createRecord(zoneID, fqdn, value)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	return nil
}

// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
	fqdn, _ := dns01.GetRecord(domain, keyAuth)

	authZone, err := dns01.FindZoneByFqdn(fqdn)
	if err != nil {
		return err
	}

	zoneID, err := d.getZoneID(authZone)
	if err != nil {
		return fmt.Errorf("designate: couldn't get zone ID in CleanUp: %v", err)
	}

	// use mutex to prevent race condition between getting the record and deleting it
	d.dnsEntriesMu.Lock()
	defer d.dnsEntriesMu.Unlock()

	record, err := d.getRecord(zoneID, fqdn)
	if err != nil {
		return fmt.Errorf("designate: couldn't get Record ID in CleanUp: %v", err)
	}

	if record == nil {
		// Record is already deleted
		return nil
	}

	err = recordsets.Delete(d.client, zoneID, record.ID).ExtractErr()
	if err != nil {
		return fmt.Errorf("designate: error for %s in CleanUp: %v", fqdn, err)
	}
	return nil
}

func contains(values []string, value string) bool {
	for _, v := range values {
		if v == value {
			return true
		}
	}
	return false
}

func (d *DNSProvider) createRecord(zoneID, fqdn, value string) error {
	createOpts := recordsets.CreateOpts{
		Name:        fqdn,
		Type:        "TXT",
		TTL:         d.config.TTL,
		Description: "ACME verification record",
		Records:     []string{value},
	}

	actual, err := recordsets.Create(d.client, zoneID, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("error for %s in Present while creating record: %v", fqdn, err)
	}

	if actual.Name != fqdn || actual.TTL != d.config.TTL {
		return fmt.Errorf("the created record doesn't match what we wanted to create")
	}

	return nil
}

func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) error {
	if contains(record.Records, value) {
		log.Printf("skip: the record already exists: %s", value)
		return nil
	}

	values := append([]string{value}, record.Records...)

	updateOpts := recordsets.UpdateOpts{
		Description: &record.Description,
		TTL:         &record.TTL,
		Records:     values,
	}

	result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts)
	return result.Err
}

func (d *DNSProvider) getZoneID(wanted string) (string, error) {
	allPages, err := zones.List(d.client, nil).AllPages()
	if err != nil {
		return "", err
	}
	allZones, err := zones.ExtractZones(allPages)
	if err != nil {
		return "", err
	}

	for _, zone := range allZones {
		if zone.Name == wanted {
			return zone.ID, nil
		}
	}
	return "", fmt.Errorf("zone id not found for %s", wanted)
}

func (d *DNSProvider) getRecord(zoneID string, wanted string) (*recordsets.RecordSet, error) {
	allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages()
	if err != nil {
		return nil, err
	}
	allRecords, err := recordsets.ExtractRecordSets(allPages)
	if err != nil {
		return nil, err
	}

	for _, record := range allRecords {
		if record.Name == wanted {
			return &record, nil
		}
	}

	return nil, nil
}
<commit_msg>designate: Don't clean up managed records like SOA and NS (#1044)<commit_after>// Package designate implements a DNS provider for solving the DNS-01 challenge using the Designate DNSaaS for Openstack.
package designate

import (
	"errors"
	"fmt"
	"log"
	"os"
	"sync"
	"time"

	"github.com/go-acme/lego/v3/challenge/dns01"
	"github.com/go-acme/lego/v3/platform/config/env"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
	"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
)

// Config is used to configure the creation of the DNSProvider
type Config struct {
	PropagationTimeout time.Duration
	PollingInterval    time.Duration
	TTL                int
	opts               gophercloud.AuthOptions
}

// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
	return &Config{
		TTL:                env.GetOrDefaultInt("DESIGNATE_TTL", 10),
		PropagationTimeout: env.GetOrDefaultSecond("DESIGNATE_PROPAGATION_TIMEOUT", 10*time.Minute),
		PollingInterval:    env.GetOrDefaultSecond("DESIGNATE_POLLING_INTERVAL", 10*time.Second),
	}
}

// DNSProvider describes a provider for Designate
type DNSProvider struct {
	config       *Config
	client       *gophercloud.ServiceClient
	dnsEntriesMu sync.Mutex
}

// NewDNSProvider returns a DNSProvider instance configured for Designate.
// Credentials must be passed in the environment variables:
// OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_REGION_NAME.
func NewDNSProvider() (*DNSProvider, error) {
	_, err := env.Get("OS_AUTH_URL", "OS_USERNAME", "OS_PASSWORD", "OS_TENANT_NAME", "OS_REGION_NAME")
	if err != nil {
		return nil, fmt.Errorf("designate: %v", err)
	}

	opts, err := openstack.AuthOptionsFromEnv()
	if err != nil {
		return nil, fmt.Errorf("designate: %v", err)
	}

	config := NewDefaultConfig()
	config.opts = opts

	return NewDNSProviderConfig(config)
}

// NewDNSProviderConfig return a DNSProvider instance configured for Designate.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
	if config == nil {
		return nil, errors.New("designate: the configuration of the DNS provider is nil")
	}

	provider, err := openstack.AuthenticatedClient(config.opts)
	if err != nil {
		return nil, fmt.Errorf("designate: failed to authenticate: %v", err)
	}

	dnsClient, err := openstack.NewDNSV2(provider, gophercloud.EndpointOpts{
		Region: os.Getenv("OS_REGION_NAME"),
	})
	if err != nil {
		return nil, fmt.Errorf("designate: failed to get DNS provider: %v", err)
	}

	return &DNSProvider{client: dnsClient, config: config}, nil
}

// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
	return d.config.PropagationTimeout, d.config.PollingInterval
}

// Present creates a TXT record to fulfill the dns-01 challenge
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
	fqdn, value := dns01.GetRecord(domain, keyAuth)

	authZone, err := dns01.FindZoneByFqdn(fqdn)
	if err != nil {
		return fmt.Errorf("designate: couldn't get zone ID in Present: %v", err)
	}

	zoneID, err := d.getZoneID(authZone)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	// use mutex to prevent race condition between creating the record and verifying it
	d.dnsEntriesMu.Lock()
	defer d.dnsEntriesMu.Unlock()

	existingRecord, err := d.getRecord(zoneID, fqdn)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	if existingRecord != nil {
		if contains(existingRecord.Records, value) {
			log.Printf("designate: the record already exists: %s", value)
			return nil
		}

		return d.updateRecord(existingRecord, value)
	}

	err = d.createRecord(zoneID, fqdn, value)
	if err != nil {
		return fmt.Errorf("designate: %v", err)
	}

	return nil
}

// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
	fqdn, _ := dns01.GetRecord(domain, keyAuth)

	authZone, err := dns01.FindZoneByFqdn(fqdn)
	if err != nil {
		return err
	}

	zoneID, err := d.getZoneID(authZone)
	if err != nil {
		return fmt.Errorf("designate: couldn't get zone ID in CleanUp: %v", err)
	}

	// use mutex to prevent race condition between getting the record and deleting it
	d.dnsEntriesMu.Lock()
	defer d.dnsEntriesMu.Unlock()

	record, err := d.getRecord(zoneID, fqdn)
	if err != nil {
		return fmt.Errorf("designate: couldn't get Record ID in CleanUp: %v", err)
	}

	if record == nil {
		// Record is already deleted
		return nil
	}

	err = recordsets.Delete(d.client, zoneID, record.ID).ExtractErr()
	if err != nil {
		return fmt.Errorf("designate: error for %s in CleanUp: %v", fqdn, err)
	}
	return nil
}

func contains(values []string, value string) bool {
	for _, v := range values {
		if v == value {
			return true
		}
	}
	return false
}

func (d *DNSProvider) createRecord(zoneID, fqdn, value string) error {
	createOpts := recordsets.CreateOpts{
		Name:        fqdn,
		Type:        "TXT",
		TTL:         d.config.TTL,
		Description: "ACME verification record",
		Records:     []string{value},
	}

	actual, err := recordsets.Create(d.client, zoneID, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("error for %s in Present while creating record: %v", fqdn, err)
	}

	if actual.Name != fqdn || actual.TTL != d.config.TTL {
		return fmt.Errorf("the created record doesn't match what we wanted to create")
	}

	return nil
}

func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) error {
	if contains(record.Records, value) {
		log.Printf("skip: the record already exists: %s", value)
		return nil
	}

	values := append([]string{value}, record.Records...)

	updateOpts := recordsets.UpdateOpts{
		Description: &record.Description,
		TTL:         &record.TTL,
		Records:     values,
	}

	result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts)
	return result.Err
}

func (d *DNSProvider) getZoneID(wanted string) (string, error) {
	allPages, err := zones.List(d.client, nil).AllPages()
	if err != nil {
		return "", err
	}
	allZones, err := zones.ExtractZones(allPages)
	if err != nil {
		return "", err
	}

	for _, zone := range allZones {
		if zone.Name == wanted {
			return zone.ID, nil
		}
	}
	return "", fmt.Errorf("zone id not found for %s", wanted)
}

func (d *DNSProvider) getRecord(zoneID string, wanted string) (*recordsets.RecordSet, error) {
	allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages()
	if err != nil {
		return nil, err
	}
	allRecords, err := recordsets.ExtractRecordSets(allPages)
	if err != nil {
		return nil, err
	}

	for _, record := range allRecords {
		if record.Name == wanted && record.Type == "TXT" {
			return &record, nil
		}
	}

	return nil, nil
}
<|endoftext|>


<commit_before>package sqlite_backend

import (
	"sync"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"

	"gitlab.com/shuhao/towncrier/backend"
	"gitlab.com/shuhao/towncrier/testhelpers"

	. "gopkg.in/check.v1"
)

var logrusTestHook = testhelpers.NewLogrusTestHook()

func Test(t *testing.T) {
	realLogger.Hooks.Add(logrusTestHook)

	TestingT(t)
}

type SQLiteNotificationBackendSuite struct {
	backend  *SQLiteNotificationBackend
	notifier *TestNotifier

	jimmy        backend.Subscriber
	timmy        backend.Subscriber
	bob          backend.Subscriber
	notification backend.Notification
}

var _ = Suite(&SQLiteNotificationBackendSuite{})

func (s *SQLiteNotificationBackendSuite) SetUpSuite(c *C) {
	s.jimmy = backend.Subscriber{
		UniqueName:  "jimmy",
		Name:        "Jimmy the Cat",
		Email:       "jimmy@the.cat",
		PhoneNumber: "123-456-7890",
	}

	s.timmy = backend.Subscriber{
		UniqueName:  "timmy",
		Name:        "Timmy the Cat",
		Email:       "timmy@the.cat",
		PhoneNumber: "123-456-7890",
	}

	s.bob = backend.Subscriber{
		UniqueName:  "bob",
		Name:        "Bob the Cat",
		Email:       "bob@the.cat",
		PhoneNumber: "098-765-4321",
	}

	s.notification = backend.Notification{
		Subject:  "subject",
		Content:  "content",
		Origin:   "origin",
		Tags:     []string{"tag1", "tag2"},
		Priority: backend.NormalPriority,
	}
}

func (s *SQLiteNotificationBackendSuite) SetUpTest(c *C) {
	logrusTestHook.ClearLogs()

	s.backend = backend.GetBackend(BackendName).(*SQLiteNotificationBackend)
	c.Assert(s.backend.Name(), Equals, BackendName)

	err := s.backend.Initialize(":memory:,test_config/standard.conf.json")
	c.Assert(err, IsNil)

	testhelpers.ResetTestDatabase(s.backend.DbMap)

	s.notifier = newTestNotifier()
	backend.ClearAllNotifiers()
	backend.RegisterNotifier(s.notifier)
}

func (s *SQLiteNotificationBackendSuite) TestBackendInitialize(c *C) {
	s.backend = &SQLiteNotificationBackend{
		NeverSendNotifications: false,
		quitChannel:            make(chan struct{}),
	}

	err := s.backend.Initialize(":memory:,test_config/standard.conf.json")
	c.Assert(err, IsNil)

	c.Assert(s.backend.config, NotNil)
	c.Assert(s.backend.config.Subscribers, HasLen, 2)
	c.Assert(s.backend.config.Subscribers["jimmy"], DeepEquals, s.jimmy)
	c.Assert(s.backend.config.Subscribers["bob"], DeepEquals, s.bob)

	c.Assert(s.backend.config.Channels, HasLen, 2)

	channel1 := &Channel{
		Name:         "Channel1",
		Subscribers:  []string{"jimmy"},
		Notifiers:    []string{"testnotify"},
		TimeToNotify: "@immediately",
	}

	channel2 := &Channel{
		Name:         "Channel2",
		Subscribers:  []string{"jimmy", "bob"},
		Notifiers:    []string{"testnotify"},
		TimeToNotify: "@daily",
	}
	c.Assert(s.backend.config.Channels["Channel1"], DeepEquals, channel1)
	c.Assert(s.backend.config.Channels["Channel2"], DeepEquals, channel2)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationSendImmediately(c *C) {
	notification := s.notification
	notification.Channel = "Channel1"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 1)
	c.Assert(s.notifier.log[0].notification, DeepEquals, notification)
	c.Assert(s.notifier.log[0].subscriber, DeepEquals, s.jimmy)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationDoNotSendImmediately(c *C) {
	notification := s.notification
	notification.Channel = "Channel2"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 0)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationNeverSendNotification(c *C) {
	s.backend.NeverSendNotifications = true
	defer func() { s.backend.NeverSendNotifications = false }()

	notification := s.notification
	notification.Channel = "Channel1"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 0)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestName(c *C) {
	c.Assert(s.backend.Name(), Equals, BackendName)
}

func (s *SQLiteNotificationBackendSuite) TestStartsConfigReloaderAndNotificationDelivery(c *C) {
	wg := &sync.WaitGroup{}
	s.backend.Start(wg)
	s.backend.BlockUntilReady()
	defer s.backend.Shutdown()

	c.Assert(logrusTestHook.Logs[logrus.InfoLevel], HasLen, 2)
	entries := make(map[string]bool)

	for _, entry := range logrusTestHook.Logs[logrus.InfoLevel] {
		entries[entry.Message] = true
	}

	c.Assert(entries["started config reloader"], Equals, true)
	c.Assert(entries["started notification delivery"], Equals, true)

	err := changeTestConfig()
	c.Assert(err, IsNil)
	defer restoreTestConfig()

	s.backend.ForceConfigReload()

	// I'M SORRY
	time.Sleep(500 * time.Millisecond)

	channels := s.backend.GetChannels()
	subscribers := s.backend.GetSubscribers()

	c.Assert(channels, HasLen, 2)
	c.Assert(channels[0].Name, Equals, "Channel1")
	c.Assert(channels[0].Subscribers, DeepEquals, []string{"timmy"})

	c.Assert(subscribers, HasLen, 2)
	c.Assert(subscribers[0], DeepEquals, s.timmy)
}

func (s *SQLiteNotificationBackendSuite) TestGetChannelsGetSubscribers(c *C) {
}
<commit_msg>Updated test more<commit_after>package sqlite_backend

import (
	"sync"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"

	"gitlab.com/shuhao/towncrier/backend"
	"gitlab.com/shuhao/towncrier/testhelpers"

	. "gopkg.in/check.v1"
)

var logrusTestHook = testhelpers.NewLogrusTestHook()

func Test(t *testing.T) {
	realLogger.Hooks.Add(logrusTestHook)

	TestingT(t)
}

type SQLiteNotificationBackendSuite struct {
	backend  *SQLiteNotificationBackend
	notifier *TestNotifier

	jimmy        backend.Subscriber
	timmy        backend.Subscriber
	bob          backend.Subscriber
	notification backend.Notification
	channel1     *Channel
	channel2     *Channel
}

var _ = Suite(&SQLiteNotificationBackendSuite{})

func (s *SQLiteNotificationBackendSuite) SetUpSuite(c *C) {
	s.jimmy = backend.Subscriber{
		UniqueName:  "jimmy",
		Name:        "Jimmy the Cat",
		Email:       "jimmy@the.cat",
		PhoneNumber: "123-456-7890",
	}

	s.timmy = backend.Subscriber{
		UniqueName:  "timmy",
		Name:        "Timmy the Cat",
		Email:       "timmy@the.cat",
		PhoneNumber: "123-456-7890",
	}

	s.bob = backend.Subscriber{
		UniqueName:  "bob",
		Name:        "Bob the Cat",
		Email:       "bob@the.cat",
		PhoneNumber: "098-765-4321",
	}

	s.notification = backend.Notification{
		Subject:  "subject",
		Content:  "content",
		Origin:   "origin",
		Tags:     []string{"tag1", "tag2"},
		Priority: backend.NormalPriority,
	}

	s.channel1 = &Channel{
		Name:         "Channel1",
		Subscribers:  []string{"jimmy"},
		Notifiers:    []string{"testnotify"},
		TimeToNotify: "@immediately",
	}

	s.channel2 = &Channel{
		Name:         "Channel2",
		Subscribers:  []string{"jimmy", "bob"},
		Notifiers:    []string{"testnotify"},
		TimeToNotify: "@daily",
	}
}

func (s *SQLiteNotificationBackendSuite) SetUpTest(c *C) {
	logrusTestHook.ClearLogs()

	s.backend = backend.GetBackend(BackendName).(*SQLiteNotificationBackend)
	c.Assert(s.backend.Name(), Equals, BackendName)

	err := s.backend.Initialize(":memory:,test_config/standard.conf.json")
	c.Assert(err, IsNil)

	testhelpers.ResetTestDatabase(s.backend.DbMap)

	s.notifier = newTestNotifier()
	backend.ClearAllNotifiers()
	backend.RegisterNotifier(s.notifier)
}

func (s *SQLiteNotificationBackendSuite) TestBackendInitialize(c *C) {
	s.backend = &SQLiteNotificationBackend{
		NeverSendNotifications: false,
		quitChannel:            make(chan struct{}),
	}

	err := s.backend.Initialize(":memory:,test_config/standard.conf.json")
	c.Assert(err, IsNil)

	c.Assert(s.backend.config, NotNil)
	c.Assert(s.backend.config.Subscribers, HasLen, 2)
	c.Assert(s.backend.config.Subscribers["jimmy"], DeepEquals, s.jimmy)
	c.Assert(s.backend.config.Subscribers["bob"], DeepEquals, s.bob)

	c.Assert(s.backend.config.Channels, HasLen, 2)
	c.Assert(s.backend.config.Channels["Channel1"], DeepEquals, s.channel1)
	c.Assert(s.backend.config.Channels["Channel2"], DeepEquals, s.channel2)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationSendImmediately(c *C) {
	notification := s.notification
	notification.Channel = "Channel1"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 1)
	c.Assert(s.notifier.log[0].notification, DeepEquals, notification)
	c.Assert(s.notifier.log[0].subscriber, DeepEquals, s.jimmy)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationDoNotSendImmediately(c *C) {
	notification := s.notification
	notification.Channel = "Channel2"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 0)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestQueueNotificationNeverSendNotification(c *C) {
	s.backend.NeverSendNotifications = true
	defer func() { s.backend.NeverSendNotifications = false }()

	notification := s.notification
	notification.Channel = "Channel1"

	err := s.backend.QueueNotification(notification)
	c.Assert(err, IsNil)

	c.Assert(s.notifier.log, HasLen, 0)

	notifications := []*Notification{}
	_, err = s.backend.Select(&notifications, "SELECT * FROM notifications WHERE Channel = ?", notification.Channel)
	c.Assert(err, IsNil)
	c.Assert(notifications, HasLen, 1)

	c.Assert(notifications[0].Notification, DeepEquals, notification)
}

func (s *SQLiteNotificationBackendSuite) TestName(c *C) {
	c.Assert(s.backend.Name(), Equals, BackendName)
}

func (s *SQLiteNotificationBackendSuite) TestStartsConfigReloaderAndNotificationDelivery(c *C) {
	wg := &sync.WaitGroup{}
	s.backend.Start(wg)
	s.backend.BlockUntilReady()
	defer s.backend.Shutdown()

	c.Assert(logrusTestHook.Logs[logrus.InfoLevel], HasLen, 2)
	entries := make(map[string]bool)

	for _, entry := range logrusTestHook.Logs[logrus.InfoLevel] {
		entries[entry.Message] = true
	}

	c.Assert(entries["started config reloader"], Equals, true)
	c.Assert(entries["started notification delivery"], Equals, true)

	err := changeTestConfig()
	c.Assert(err, IsNil)
	defer restoreTestConfig()

	s.backend.ForceConfigReload()

	// I'M SORRY
	time.Sleep(500 * time.Millisecond)

	channels := s.backend.GetChannels()
	subscribers := s.backend.GetSubscribers()

	c.Assert(channels, HasLen, 2)
	c.Assert(channels[0].Name, Equals, "Channel1")
	c.Assert(channels[0].Subscribers, DeepEquals, []string{"timmy"})

	c.Assert(subscribers, HasLen, 2)
	c.Assert(subscribers[0], DeepEquals, s.timmy)
}

func (s *SQLiteNotificationBackendSuite) TestGetChannels(c *C) {
	channels := s.backend.GetChannels()
	c.Assert(channels, HasLen, 2)

	c.Assert(channels[0], DeepEquals, s.channel1)
	c.Assert(channels[1], DeepEquals, s.channel2)
}

func (s *SQLiteNotificationBackendSuite) TestGetSubscribers(c *C) {
	subscribers := s.backend.GetSubscribers()
	c.Assert(subscribers, HasLen, 2)

	c.Assert(subscribers[0], DeepEquals, s.jimmy)
	c.Assert(subscribers[1], DeepEquals, s.bob)
}
<|endoftext|>


<commit_before>// Copyright 2019 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package kubernetes

import (
	"context"
	"time"

	"github.com/tsuru/tsuru/app"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/router/rebuild"
	check "gopkg.in/check.v1"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/watch"
	ktesting "k8s.io/client-go/testing"
)

func (s *S) TestNewClusterController(c *check.C) {
	s.clusterClient.CustomData = map[string]string{
		routerAddressLocalKey: "true",
	}
	watchFake := watch.NewFake()
	s.client.Fake.PrependWatchReactor("pods", ktesting.DefaultWatchReactor(watchFake, nil))
	a := &app.App{Name: "myapp", TeamOwner: s.team.Name}
	err := app.CreateApp(context.TODO(), a, s.user)
	c.Assert(err, check.IsNil)
	labels, err := provision.ServiceLabels(provision.ServiceLabelsOpts{
		App:     a,
		Process: "p1",
		ServiceLabelExtendedOpts: provision.ServiceLabelExtendedOpts{
			Prefix:      tsuruLabelPrefix,
			Provisioner: provisionerName,
		},
	})
	c.Assert(err, check.IsNil)
	rebuildCalled := make(chan struct{})
	oldRebuildFunc := runRoutesRebuild
	defer func() {
		runRoutesRebuild = oldRebuildFunc
	}()
	runRoutesRebuild = func(appName string) {
		defer func() { rebuildCalled <- struct{}{} }()
		c.Assert(appName, check.Equals, "myapp")
	}
	c.Assert(err, check.IsNil)
	defer rebuild.Shutdown(context.Background())
	_, err = getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)

	basePod := &apiv1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:            "pod1",
			Labels:          labels.ToLabels(),
			ResourceVersion: "0",
		},
	}
	watchFake.Add(basePod.DeepCopy())
	basePod.ResourceVersion = "1"
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
	case <-time.After(5 * time.Second):
		c.Fatal("timeout waiting for first rebuild call")
	}

	basePod.ResourceVersion = "2"
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
		c.Fatal("rebuild called when no call was expected")
	case <-time.After(5 * time.Second):
	}

	basePod.ResourceVersion = "3"
	basePod.Status.Conditions = []apiv1.PodCondition{
		{Type: apiv1.PodReady, Status: apiv1.ConditionFalse},
	}
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
	case <-time.After(5 * time.Second):
		c.Fatal("timeout waiting for second rebuild call")
	}
}

func (s *S) TestNewRouterControllerSameInstance(c *check.C) {
	c1, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	c2, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	c.Assert(c1, check.Equals, c2)
}

type podListenerImpl struct {
}

func (*podListenerImpl) OnPodEvent(pod *apiv1.Pod) {
}

func (s *S) TestPodListeners(c *check.C) {

	podListener1 := &podListenerImpl{}
	podListener2 := &podListenerImpl{}

	clusterController, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	clusterController.addPodListener("my-app", "listerner1", podListener1)
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 1)
	clusterController.addPodListener("my-app", "listerner2", podListener2)
	clusterController.removePodListener("my-app", "listerner1")
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 1)

	_, contains := clusterController.podListeners["my-app"]["listerner2"]
	c.Assert(contains, check.Equals, true)
	clusterController.removePodListener("my-app", "listerner2")
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 0)
}
<commit_msg>provision/kubernetes: fix flaky controller test<commit_after>// Copyright 2019 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package kubernetes

import (
	"context"
	"time"

	"github.com/tsuru/tsuru/app"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/router/rebuild"
	check "gopkg.in/check.v1"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/watch"
	ktesting "k8s.io/client-go/testing"
)

func (s *S) TestNewClusterController(c *check.C) {
	s.clusterClient.CustomData = map[string]string{
		routerAddressLocalKey: "true",
	}
	watchFake := watch.NewFake()
	s.client.Fake.PrependWatchReactor("pods", ktesting.DefaultWatchReactor(watchFake, nil))
	a := &app.App{Name: "myapp", TeamOwner: s.team.Name}
	err := app.CreateApp(context.TODO(), a, s.user)
	c.Assert(err, check.IsNil)
	labels, err := provision.ServiceLabels(provision.ServiceLabelsOpts{
		App:     a,
		Process: "p1",
		ServiceLabelExtendedOpts: provision.ServiceLabelExtendedOpts{
			Prefix:      tsuruLabelPrefix,
			Provisioner: provisionerName,
		},
	})
	c.Assert(err, check.IsNil)
	rebuildCalled := make(chan struct{})
	oldRebuildFunc := runRoutesRebuild
	defer func() {
		runRoutesRebuild = oldRebuildFunc
	}()
	runRoutesRebuild = func(appName string) {
		defer func() { rebuildCalled <- struct{}{} }()
		c.Assert(appName, check.Equals, "myapp")
	}
	c.Assert(err, check.IsNil)
	defer rebuild.Shutdown(context.Background())
	ctr, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	_, err = ctr.getPodInformerWait(true)
	c.Assert(err, check.IsNil)

	basePod := &apiv1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:            "pod1",
			Labels:          labels.ToLabels(),
			ResourceVersion: "0",
		},
	}
	watchFake.Add(basePod.DeepCopy())
	basePod.ResourceVersion = "1"
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
	case <-time.After(5 * time.Second):
		c.Fatal("timeout waiting for first rebuild call")
	}

	basePod.ResourceVersion = "2"
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
		c.Fatal("rebuild called when no call was expected")
	case <-time.After(5 * time.Second):
	}

	basePod.ResourceVersion = "3"
	basePod.Status.Conditions = []apiv1.PodCondition{
		{Type: apiv1.PodReady, Status: apiv1.ConditionFalse},
	}
	watchFake.Modify(basePod.DeepCopy())
	select {
	case <-rebuildCalled:
	case <-time.After(5 * time.Second):
		c.Fatal("timeout waiting for second rebuild call")
	}
}

func (s *S) TestNewRouterControllerSameInstance(c *check.C) {
	c1, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	c2, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	c.Assert(c1, check.Equals, c2)
}

type podListenerImpl struct {
}

func (*podListenerImpl) OnPodEvent(pod *apiv1.Pod) {
}

func (s *S) TestPodListeners(c *check.C) {

	podListener1 := &podListenerImpl{}
	podListener2 := &podListenerImpl{}

	clusterController, err := getClusterController(s.p, s.clusterClient)
	c.Assert(err, check.IsNil)
	clusterController.addPodListener("my-app", "listerner1", podListener1)
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 1)
	clusterController.addPodListener("my-app", "listerner2", podListener2)
	clusterController.removePodListener("my-app", "listerner1")
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 1)

	_, contains := clusterController.podListeners["my-app"]["listerner2"]
	c.Assert(contains, check.Equals, true)
	clusterController.removePodListener("my-app", "listerner2")
	c.Assert(clusterController.podListeners["my-app"], check.HasLen, 0)
}
<|endoftext|>


<commit_before>package update_test

import (
	"context"
	"testing"
	"time"

	"github.com/itchio/butler/database"
	"github.com/itchio/butler/database/models"
	"github.com/jinzhu/gorm"

	"github.com/mitchellh/mapstructure"

	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/butlerd/mockharness"
	"github.com/itchio/butler/cmd/operate/loopbackconn"
	"github.com/itchio/butler/endpoints/update"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/itchio/wharf/wtest"
	"github.com/stretchr/testify/assert"
	httpmock "gopkg.in/jarcoal/httpmock.v1"
)

func TestCheckUpdateMissingFields(t *testing.T) {
	wtest.Must(t, mockharness.With(func(harness butlerd.Harness) error {
		router := butlerd.NewRouter(nil, nil)
		update.Register(router)

		item := &butlerd.CheckUpdateItem{
			InstalledAt: time.Date(2017, 04, 04, 9, 32, 00, 0, time.UTC),
		}
		consumer := &state.Consumer{
			OnMessage: func(level string, message string) {
				t.Logf("[%s] [%s]", level, message)
			},
		}
		ctx := context.Background()
		conn := loopbackconn.New(consumer)

		db, err := database.Open(":memory:")
		wtest.Must(t, err)

		err = database.Prepare(db)
		wtest.Must(t, err)

		var testCredentials = &models.Profile{
			ID:     1,
			APIKey: "KEY",
		}
		wtest.Must(t, db.Save(testCredentials).Error)

		checkUpdate := func(params *butlerd.CheckUpdateParams) (*butlerd.CheckUpdateResult, error) {
			rc := &butlerd.RequestContext{
				Ctx:            ctx,
				Conn:           conn,
				Consumer:       consumer,
				MansionContext: router.MansionContext,
				Harness:        harness,
				DB: func() *gorm.DB {
					return db
				},
			}
			return update.CheckUpdate(rc, params)
		}

		params := &butlerd.CheckUpdateParams{
			Items: []*butlerd.CheckUpdateItem{
				item,
			},
		}

		{
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing itemId")
		}

		{
			item.ItemID = "foo-bar"
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing game")
		}

		{
			item.Game = testGame
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing upload")
		}

		{
			dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
				TagName:          "json",
				WeaklyTypedInput: true,
				Result:           &item.Upload,
				DecodeHook:       mapstructure.StringToTimeHookFunc(itchio.APIDateFormat),
			})
			wtest.Must(t, err)
			wtest.Must(t, dec.Decode(testUpload()))

			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", httpmock.NewBytesResponder(404, nil))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "Server returned 404")
		}

		{
			t.Logf("All uploads gone")
			httpmock.Reset()
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": nil,
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			t.Logf("Same upload exactly")
			httpmock.Reset()
			freshUpload := testUpload()
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			t.Logf("Upload updated recently")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 1, len(res.Updates))
			assert.EqualValues(t, freshUpload["id"], res.Updates[0].Upload.ID)
		}

		{
			t.Logf("Upload went wharf")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": 1230,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "have no build installed but fresh upload has one")
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			item.Build = &itchio.Build{
				ID: 12345,
			}

			t.Logf("Same build (wharf)")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": item.Build.ID,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			item.Build = &itchio.Build{
				ID: 12345,
			}

			t.Logf("Greater build ID (wharf)")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": 12346,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 1, len(res.Updates))
			assert.EqualValues(t, item.ItemID, res.Updates[0].ItemID)
			assert.EqualValues(t, item.Game, res.Updates[0].Game)
			assert.EqualValues(t, freshUpload["id"], res.Updates[0].Upload.ID)
			assert.EqualValues(t, freshUpload["build"].(map[string]interface{})["id"], res.Updates[0].Build.ID)
		}

		{
			t.Logf("Upload went wharf-less")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "have a build installed but fresh upload has none")
			assert.Equal(t, 0, len(res.Updates))
		}

		return nil
	}))
}

func mustJsonResponder(t *testing.T, status int, body interface{}) httpmock.Responder {
	r, err := httpmock.NewJsonResponder(status, body)
	wtest.Must(t, err)
	return r
}

var testGame = &itchio.Game{
	ID:    123,
	Title: "Not plausible",
	URL:   "https://insanity.itch.io/not-plausible",
}

func testUpload() map[string]interface{} {
	return map[string]interface{}{
		"id":         768,
		"filename":   "foobar.zip",
		"updated_at": "2017-02-03 12:13:00",
		"size":       6273984,
		"windows":    true,
		"type":       "default",
	}
}
<commit_msg>use protocol sqlite connection string in updater test<commit_after>package update_test

import (
	"context"
	"testing"
	"time"

	"github.com/itchio/butler/database"
	"github.com/itchio/butler/database/models"
	"github.com/jinzhu/gorm"

	"github.com/mitchellh/mapstructure"

	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/butlerd/mockharness"
	"github.com/itchio/butler/cmd/operate/loopbackconn"
	"github.com/itchio/butler/endpoints/update"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/itchio/wharf/wtest"
	"github.com/stretchr/testify/assert"
	httpmock "gopkg.in/jarcoal/httpmock.v1"
)

func TestCheckUpdateMissingFields(t *testing.T) {
	wtest.Must(t, mockharness.With(func(harness butlerd.Harness) error {
		router := butlerd.NewRouter(nil, nil)
		update.Register(router)

		item := &butlerd.CheckUpdateItem{
			InstalledAt: time.Date(2017, 04, 04, 9, 32, 00, 0, time.UTC),
		}
		consumer := &state.Consumer{
			OnMessage: func(level string, message string) {
				t.Logf("[%s] [%s]", level, message)
			},
		}
		ctx := context.Background()
		conn := loopbackconn.New(consumer)

		db, err := database.Open("file::memory:?cache=shared")
		wtest.Must(t, err)

		err = database.Prepare(db)
		wtest.Must(t, err)

		var testCredentials = &models.Profile{
			ID:     1,
			APIKey: "KEY",
		}
		wtest.Must(t, db.Save(testCredentials).Error)

		checkUpdate := func(params *butlerd.CheckUpdateParams) (*butlerd.CheckUpdateResult, error) {
			rc := &butlerd.RequestContext{
				Ctx:            ctx,
				Conn:           conn,
				Consumer:       consumer,
				MansionContext: router.MansionContext,
				Harness:        harness,
				DB: func() *gorm.DB {
					return db
				},
			}
			return update.CheckUpdate(rc, params)
		}

		params := &butlerd.CheckUpdateParams{
			Items: []*butlerd.CheckUpdateItem{
				item,
			},
		}

		{
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing itemId")
		}

		{
			item.ItemID = "foo-bar"
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing game")
		}

		{
			item.Game = testGame
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "missing upload")
		}

		{
			dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
				TagName:          "json",
				WeaklyTypedInput: true,
				Result:           &item.Upload,
				DecodeHook:       mapstructure.StringToTimeHookFunc(itchio.APIDateFormat),
			})
			wtest.Must(t, err)
			wtest.Must(t, dec.Decode(testUpload()))

			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", httpmock.NewBytesResponder(404, nil))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "Server returned 404")
		}

		{
			t.Logf("All uploads gone")
			httpmock.Reset()
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": nil,
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			t.Logf("Same upload exactly")
			httpmock.Reset()
			freshUpload := testUpload()
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			t.Logf("Upload updated recently")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 1, len(res.Updates))
			assert.EqualValues(t, freshUpload["id"], res.Updates[0].Upload.ID)
		}

		{
			t.Logf("Upload went wharf")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": 1230,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "have no build installed but fresh upload has one")
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			item.Build = &itchio.Build{
				ID: 12345,
			}

			t.Logf("Same build (wharf)")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": item.Build.ID,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 0, len(res.Updates))
		}

		{
			item.Build = &itchio.Build{
				ID: 12345,
			}

			t.Logf("Greater build ID (wharf)")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["build"] = map[string]interface{}{
				"id": 12346,
			}
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 0, len(res.Warnings))
			assert.Equal(t, 1, len(res.Updates))
			assert.EqualValues(t, item.ItemID, res.Updates[0].ItemID)
			assert.EqualValues(t, item.Game, res.Updates[0].Game)
			assert.EqualValues(t, freshUpload["id"], res.Updates[0].Upload.ID)
			assert.EqualValues(t, freshUpload["build"].(map[string]interface{})["id"], res.Updates[0].Build.ID)
		}

		{
			t.Logf("Upload went wharf-less")
			httpmock.Reset()
			freshUpload := testUpload()
			freshUpload["updated_at"] = "2018-01-01 04:12:00"
			otherUpload := testUpload()
			otherUpload["id"] = 235987
			otherUpload["build"] = map[string]interface{}{
				"id": 65432,
			}
			httpmock.RegisterResponder("GET", "https://itch.io/api/1/KEY/game/123/uploads", mustJsonResponder(t, 200, map[string]interface{}{
				"uploads": []interface{}{
					freshUpload,
					otherUpload,
				},
			}))
			res, err := checkUpdate(params)
			assert.NoError(t, err)
			assert.Equal(t, 1, len(res.Warnings))
			assert.Contains(t, res.Warnings[0], "have a build installed but fresh upload has none")
			assert.Equal(t, 0, len(res.Updates))
		}

		return nil
	}))
}

func mustJsonResponder(t *testing.T, status int, body interface{}) httpmock.Responder {
	r, err := httpmock.NewJsonResponder(status, body)
	wtest.Must(t, err)
	return r
}

var testGame = &itchio.Game{
	ID:    123,
	Title: "Not plausible",
	URL:   "https://insanity.itch.io/not-plausible",
}

func testUpload() map[string]interface{} {
	return map[string]interface{}{
		"id":         768,
		"filename":   "foobar.zip",
		"updated_at": "2017-02-03 12:13:00",
		"size":       6273984,
		"windows":    true,
		"type":       "default",
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package staticpod

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/url"
	"os"
	"strings"

	"k8s.io/api/core/v1"

	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	"k8s.io/kubernetes/cmd/kubeadm/app/util"
	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)

const (
	// kubeControllerManagerAddressArg represents the address argument of the kube-controller-manager configuration.
	kubeControllerManagerAddressArg = "address"

	// kubeSchedulerAddressArg represents the address argument of the kube-scheduler configuration.
	kubeSchedulerAddressArg = "address"

	// etcdListenClientURLsArg represents the listen-client-urls argument of the etcd configuration.
	etcdListenClientURLsArg = "listen-client-urls"
)

// ComponentPod returns a Pod object from the container and volume specifications
func ComponentPod(container v1.Container, volumes map[string]v1.Volume) v1.Pod {
	return v1.Pod{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:        container.Name,
			Namespace:   metav1.NamespaceSystem,
			Annotations: map[string]string{kubetypes.CriticalPodAnnotationKey: ""},
			// The component and tier labels are useful for quickly identifying the control plane Pods when doing a .List()
			// against Pods in the kube-system namespace. Can for example be used together with the WaitForPodsWithLabel function
			Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
		},
		Spec: v1.PodSpec{
			Containers:  []v1.Container{container},
			HostNetwork: true,
			Volumes:     VolumeMapToSlice(volumes),
		},
	}
}

// ComponentResources returns the v1.ResourceRequirements object needed for allocating a specified amount of the CPU
func ComponentResources(cpu string) v1.ResourceRequirements {
	return v1.ResourceRequirements{
		Requests: v1.ResourceList{
			v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu),
		},
	}
}

// ComponentProbe is a helper function building a ready v1.Probe object from some simple parameters
func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, path string, scheme v1.URIScheme) *v1.Probe {
	return &v1.Probe{
		Handler: v1.Handler{
			HTTPGet: &v1.HTTPGetAction{
				Host:   GetProbeAddress(cfg, componentName),
				Path:   path,
				Port:   intstr.FromInt(port),
				Scheme: scheme,
			},
		},
		InitialDelaySeconds: 15,
		TimeoutSeconds:      15,
		FailureThreshold:    8,
	}
}

// NewVolume creates a v1.Volume with a hostPath mount to the specified location
func NewVolume(name, path string, pathType *v1.HostPathType) v1.Volume {
	return v1.Volume{
		Name: name,
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: path,
				Type: pathType,
			},
		},
	}
}

// NewVolumeMount creates a v1.VolumeMount to the specified location
func NewVolumeMount(name, path string, readOnly bool) v1.VolumeMount {
	return v1.VolumeMount{
		Name:      name,
		MountPath: path,
		ReadOnly:  readOnly,
	}
}

// VolumeMapToSlice returns a slice of volumes from a map's values
func VolumeMapToSlice(volumes map[string]v1.Volume) []v1.Volume {
	v := make([]v1.Volume, 0, len(volumes))

	for _, vol := range volumes {
		v = append(v, vol)
	}

	return v
}

// VolumeMountMapToSlice returns a slice of volumes from a map's values
func VolumeMountMapToSlice(volumeMounts map[string]v1.VolumeMount) []v1.VolumeMount {
	v := make([]v1.VolumeMount, 0, len(volumeMounts))

	for _, volMount := range volumeMounts {
		v = append(v, volMount)
	}

	return v
}

// GetExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides
func GetExtraParameters(overrides map[string]string, defaults map[string]string) []string {
	var command []string
	for k, v := range overrides {
		if len(v) > 0 {
			command = append(command, fmt.Sprintf("--%s=%s", k, v))
		}
	}
	for k, v := range defaults {
		if _, overrideExists := overrides[k]; !overrideExists {
			command = append(command, fmt.Sprintf("--%s=%s", k, v))
		}
	}
	return command
}

// WriteStaticPodToDisk writes a static pod file to disk
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {

	// creates target folder if not already exists
	if err := os.MkdirAll(manifestDir, 0700); err != nil {
		return fmt.Errorf("failed to create directory %q: %v", manifestDir, err)
	}

	// writes the pod to disk
	serialized, err := util.MarshalToYaml(&pod, v1.SchemeGroupVersion)
	if err != nil {
		return fmt.Errorf("failed to marshal manifest for %q to YAML: %v", componentName, err)
	}

	filename := kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir)

	if err := ioutil.WriteFile(filename, serialized, 0700); err != nil {
		return fmt.Errorf("failed to write static pod manifest file for %q (%q): %v", componentName, filename, err)
	}

	return nil
}

// GetProbeAddress returns an IP address or 127.0.0.1 to use for liveness probes
// in static pod manifests.
func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string) string {
	switch {
	case componentName == kubeadmconstants.KubeAPIServer:
		if cfg.API.AdvertiseAddress != "" {
			return cfg.API.AdvertiseAddress
		}
	case componentName == kubeadmconstants.KubeControllerManager:
		if addr, exists := cfg.ControllerManagerExtraArgs[kubeControllerManagerAddressArg]; exists {
			return addr
		}
	case componentName == kubeadmconstants.KubeScheduler:
		if addr, exists := cfg.SchedulerExtraArgs[kubeSchedulerAddressArg]; exists {
			return addr
		}
	case componentName == kubeadmconstants.Etcd:
		if cfg.Etcd.ExtraArgs != nil {
			if arg, exists := cfg.Etcd.ExtraArgs[etcdListenClientURLsArg]; exists {
				// Use the first url in the listen-client-urls if multiple url's are specified.
				if strings.ContainsAny(arg, ",") {
					arg = strings.Split(arg, ",")[0]
				}
				parsedURL, err := url.Parse(arg)
				if err != nil || parsedURL.Hostname() == "" {
					break
				}
				// Return the IP if the URL contains an address instead of a name.
				if ip := net.ParseIP(parsedURL.Hostname()); ip != nil {
					return ip.String()
				}
				// Use the local resolver to try resolving the name within the URL.
				// If the name can not be resolved, return an IPv4 loopback address.
				// Otherwise, select the first valid IPv4 address.
				// If the name does not resolve to an IPv4 address, select the first valid IPv6 address.
				addrs, err := net.LookupIP(parsedURL.Hostname())
				if err != nil {
					break
				}
				var ip net.IP
				for _, addr := range addrs {
					if addr.To4() != nil {
						ip = addr
						break
					}
					if addr.To16() != nil && ip == nil {
						ip = addr
					}
				}
				return ip.String()
			}
		}
	}
	return "127.0.0.1"
}
<commit_msg>Change manifest file perms to remove execute<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package staticpod

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/url"
	"os"
	"strings"

	"k8s.io/api/core/v1"

	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	"k8s.io/kubernetes/cmd/kubeadm/app/util"
	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)

const (
	// kubeControllerManagerAddressArg represents the address argument of the kube-controller-manager configuration.
	kubeControllerManagerAddressArg = "address"

	// kubeSchedulerAddressArg represents the address argument of the kube-scheduler configuration.
	kubeSchedulerAddressArg = "address"

	// etcdListenClientURLsArg represents the listen-client-urls argument of the etcd configuration.
	etcdListenClientURLsArg = "listen-client-urls"
)

// ComponentPod returns a Pod object from the container and volume specifications
func ComponentPod(container v1.Container, volumes map[string]v1.Volume) v1.Pod {
	return v1.Pod{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:        container.Name,
			Namespace:   metav1.NamespaceSystem,
			Annotations: map[string]string{kubetypes.CriticalPodAnnotationKey: ""},
			// The component and tier labels are useful for quickly identifying the control plane Pods when doing a .List()
			// against Pods in the kube-system namespace. Can for example be used together with the WaitForPodsWithLabel function
			Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
		},
		Spec: v1.PodSpec{
			Containers:  []v1.Container{container},
			HostNetwork: true,
			Volumes:     VolumeMapToSlice(volumes),
		},
	}
}

// ComponentResources returns the v1.ResourceRequirements object needed for allocating a specified amount of the CPU
func ComponentResources(cpu string) v1.ResourceRequirements {
	return v1.ResourceRequirements{
		Requests: v1.ResourceList{
			v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu),
		},
	}
}

// ComponentProbe is a helper function building a ready v1.Probe object from some simple parameters
func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, path string, scheme v1.URIScheme) *v1.Probe {
	return &v1.Probe{
		Handler: v1.Handler{
			HTTPGet: &v1.HTTPGetAction{
				Host:   GetProbeAddress(cfg, componentName),
				Path:   path,
				Port:   intstr.FromInt(port),
				Scheme: scheme,
			},
		},
		InitialDelaySeconds: 15,
		TimeoutSeconds:      15,
		FailureThreshold:    8,
	}
}

// NewVolume creates a v1.Volume with a hostPath mount to the specified location
func NewVolume(name, path string, pathType *v1.HostPathType) v1.Volume {
	return v1.Volume{
		Name: name,
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: path,
				Type: pathType,
			},
		},
	}
}

// NewVolumeMount creates a v1.VolumeMount to the specified location
func NewVolumeMount(name, path string, readOnly bool) v1.VolumeMount {
	return v1.VolumeMount{
		Name:      name,
		MountPath: path,
		ReadOnly:  readOnly,
	}
}

// VolumeMapToSlice returns a slice of volumes from a map's values
func VolumeMapToSlice(volumes map[string]v1.Volume) []v1.Volume {
	v := make([]v1.Volume, 0, len(volumes))

	for _, vol := range volumes {
		v = append(v, vol)
	}

	return v
}

// VolumeMountMapToSlice returns a slice of volumes from a map's values
func VolumeMountMapToSlice(volumeMounts map[string]v1.VolumeMount) []v1.VolumeMount {
	v := make([]v1.VolumeMount, 0, len(volumeMounts))

	for _, volMount := range volumeMounts {
		v = append(v, volMount)
	}

	return v
}

// GetExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides
func GetExtraParameters(overrides map[string]string, defaults map[string]string) []string {
	var command []string
	for k, v := range overrides {
		if len(v) > 0 {
			command = append(command, fmt.Sprintf("--%s=%s", k, v))
		}
	}
	for k, v := range defaults {
		if _, overrideExists := overrides[k]; !overrideExists {
			command = append(command, fmt.Sprintf("--%s=%s", k, v))
		}
	}
	return command
}

// WriteStaticPodToDisk writes a static pod file to disk
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {

	// creates target folder if not already exists
	if err := os.MkdirAll(manifestDir, 0700); err != nil {
		return fmt.Errorf("failed to create directory %q: %v", manifestDir, err)
	}

	// writes the pod to disk
	serialized, err := util.MarshalToYaml(&pod, v1.SchemeGroupVersion)
	if err != nil {
		return fmt.Errorf("failed to marshal manifest for %q to YAML: %v", componentName, err)
	}

	filename := kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir)

	if err := ioutil.WriteFile(filename, serialized, 0600); err != nil {
		return fmt.Errorf("failed to write static pod manifest file for %q (%q): %v", componentName, filename, err)
	}

	return nil
}

// GetProbeAddress returns an IP address or 127.0.0.1 to use for liveness probes
// in static pod manifests.
func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string) string {
	switch {
	case componentName == kubeadmconstants.KubeAPIServer:
		if cfg.API.AdvertiseAddress != "" {
			return cfg.API.AdvertiseAddress
		}
	case componentName == kubeadmconstants.KubeControllerManager:
		if addr, exists := cfg.ControllerManagerExtraArgs[kubeControllerManagerAddressArg]; exists {
			return addr
		}
	case componentName == kubeadmconstants.KubeScheduler:
		if addr, exists := cfg.SchedulerExtraArgs[kubeSchedulerAddressArg]; exists {
			return addr
		}
	case componentName == kubeadmconstants.Etcd:
		if cfg.Etcd.ExtraArgs != nil {
			if arg, exists := cfg.Etcd.ExtraArgs[etcdListenClientURLsArg]; exists {
				// Use the first url in the listen-client-urls if multiple url's are specified.
				if strings.ContainsAny(arg, ",") {
					arg = strings.Split(arg, ",")[0]
				}
				parsedURL, err := url.Parse(arg)
				if err != nil || parsedURL.Hostname() == "" {
					break
				}
				// Return the IP if the URL contains an address instead of a name.
				if ip := net.ParseIP(parsedURL.Hostname()); ip != nil {
					return ip.String()
				}
				// Use the local resolver to try resolving the name within the URL.
				// If the name can not be resolved, return an IPv4 loopback address.
				// Otherwise, select the first valid IPv4 address.
				// If the name does not resolve to an IPv4 address, select the first valid IPv6 address.
				addrs, err := net.LookupIP(parsedURL.Hostname())
				if err != nil {
					break
				}
				var ip net.IP
				for _, addr := range addrs {
					if addr.To4() != nil {
						ip = addr
						break
					}
					if addr.To16() != nil && ip == nil {
						ip = addr
					}
				}
				return ip.String()
			}
		}
	}
	return "127.0.0.1"
}
<|endoftext|>