<commit_before>package container

import (
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/docker/docker/builder/dockerfile"
	"github.com/docker/docker/pkg/ioutils"
	"github.com/docker/docker/runconfig"
	"github.com/docker/docker/utils"
	"github.com/docker/engine-api/types"
	"github.com/docker/engine-api/types/container"
	timetypes "github.com/docker/engine-api/types/time"
	"github.com/golang/glog"
	"github.com/hyperhq/hyper/daemon"
	"github.com/hyperhq/hyper/server/httputils"
	"golang.org/x/net/context"
)

func (c *containerRouter) getContainerInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	data, err := c.backend.CmdGetContainerInfo(r.Form.Get("container"))
	if err != nil {
		return err
	}

	return httputils.WriteJSON(w, http.StatusOK, data)
}

func (c *containerRouter) getContainerLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	// Args are validated before the stream starts because when it starts we're
	// sending HTTP 200 by writing an empty chunk of data to tell the client that
	// daemon is going to stream. By sending this initial HTTP 200 we can't report
	// any error after the stream starts (i.e. container not found, wrong parameters)
	// with the appropriate status code.
	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
	if !(stdout || stderr) {
		return fmt.Errorf("Bad parameters: you must choose at least one stream")
	}

	var since time.Time
	if r.Form.Get("since") != "" {
		s, n, err := timetypes.ParseTimestamps(r.Form.Get("since"), 0)
		if err != nil {
			return err
		}
		since = time.Unix(s, n)
	}

	var closeNotifier <-chan bool
	if notifier, ok := w.(http.CloseNotifier); ok {
		closeNotifier = notifier.CloseNotify()
	}

	containerName := r.Form.Get("container")
	/*
		if !s.backend.Exists(containerName) {
			return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
		}
	*/
	// write an empty chunk of data (this is to ensure that the
	// HTTP Response is sent immediately, even if the container has
	// not yet produced any data)
	w.WriteHeader(http.StatusOK)
	if flusher, ok := w.(http.Flusher); ok {
		flusher.Flush()
	}

	output := ioutils.NewWriteFlusher(w)
	defer output.Close()

	logsConfig := &daemon.ContainerLogsConfig{
		Follow:     httputils.BoolValue(r, "follow"),
		Timestamps: httputils.BoolValue(r, "timestamps"),
		Since:      since,
		Tail:       r.Form.Get("tail"),
		UseStdout:  stdout,
		UseStderr:  stderr,
		OutStream:  output,
		Stop:       closeNotifier,
	}

	if err := c.backend.CmdGetContainerLogs(containerName, logsConfig); err != nil {
		// The client may be expecting all of the data we're sending to
		// be multiplexed, so send it through OutStream, which will
		// have been set up to handle that if needed.
		fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
	}

	return nil
}

func (c *containerRouter) postContainerCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}

	imageName := r.Form.Get("imageName")
	glog.V(1).Infof("Image name is %s", imageName)

	config, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
	if err != nil {
		return err
	}

	env, err := c.backend.CmdCreateContainer(types.ContainerCreateConfig{
		Name:       imageName,
		Config:     config,
		HostConfig: hostConfig,
	})

	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusCreated)
}

func (c *containerRouter) postContainerCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}

	cname := r.Form.Get("container")
	pause := httputils.BoolValue(r, "pause")

	config, _, _, err := runconfig.DecodeContainerConfig(r.Body)
	if err != nil && err != io.EOF { //Do not fail if body is empty.
		return err
	}
	if config == nil {
		config = &container.Config{}
	}

	newConfig, err := dockerfile.BuildFromConfig(config, r.Form["changes"])
	if err != nil {
		return err
	}

	commitCfg := &types.ContainerCommitConfig{
		Pause:        pause,
		Repo:         r.Form.Get("repo"),
		Tag:          r.Form.Get("tag"),
		Author:       r.Form.Get("author"),
		Comment:      r.Form.Get("comment"),
		Config:       newConfig,
		MergeConfigs: true,
	}

	env, err := c.backend.CmdCommitImage(cname, commitCfg)
	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusOK)
}

func (c *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	newName := r.Form.Get("newName")
	oldName := r.Form.Get("oldName")
	env, err := c.backend.CmdContainerRename(oldName, newName)
	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusOK)
}
<commit_msg>commit: do not request http content type is json<commit_after>package container

import (
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/docker/docker/builder/dockerfile"
	"github.com/docker/docker/pkg/ioutils"
	"github.com/docker/docker/runconfig"
	"github.com/docker/docker/utils"
	"github.com/docker/engine-api/types"
	"github.com/docker/engine-api/types/container"
	timetypes "github.com/docker/engine-api/types/time"
	"github.com/golang/glog"
	"github.com/hyperhq/hyper/daemon"
	"github.com/hyperhq/hyper/server/httputils"
	"golang.org/x/net/context"
)

func (c *containerRouter) getContainerInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	data, err := c.backend.CmdGetContainerInfo(r.Form.Get("container"))
	if err != nil {
		return err
	}

	return httputils.WriteJSON(w, http.StatusOK, data)
}

func (c *containerRouter) getContainerLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	// Args are validated before the stream starts because when it starts we're
	// sending HTTP 200 by writing an empty chunk of data to tell the client that
	// daemon is going to stream. By sending this initial HTTP 200 we can't report
	// any error after the stream starts (i.e. container not found, wrong parameters)
	// with the appropriate status code.
	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
	if !(stdout || stderr) {
		return fmt.Errorf("Bad parameters: you must choose at least one stream")
	}

	var since time.Time
	if r.Form.Get("since") != "" {
		s, n, err := timetypes.ParseTimestamps(r.Form.Get("since"), 0)
		if err != nil {
			return err
		}
		since = time.Unix(s, n)
	}

	var closeNotifier <-chan bool
	if notifier, ok := w.(http.CloseNotifier); ok {
		closeNotifier = notifier.CloseNotify()
	}

	containerName := r.Form.Get("container")
	/*
		if !s.backend.Exists(containerName) {
			return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
		}
	*/
	// write an empty chunk of data (this is to ensure that the
	// HTTP Response is sent immediately, even if the container has
	// not yet produced any data)
	w.WriteHeader(http.StatusOK)
	if flusher, ok := w.(http.Flusher); ok {
		flusher.Flush()
	}

	output := ioutils.NewWriteFlusher(w)
	defer output.Close()

	logsConfig := &daemon.ContainerLogsConfig{
		Follow:     httputils.BoolValue(r, "follow"),
		Timestamps: httputils.BoolValue(r, "timestamps"),
		Since:      since,
		Tail:       r.Form.Get("tail"),
		UseStdout:  stdout,
		UseStderr:  stderr,
		OutStream:  output,
		Stop:       closeNotifier,
	}

	if err := c.backend.CmdGetContainerLogs(containerName, logsConfig); err != nil {
		// The client may be expecting all of the data we're sending to
		// be multiplexed, so send it through OutStream, which will
		// have been set up to handle that if needed.
		fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
	}

	return nil
}

func (c *containerRouter) postContainerCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}

	imageName := r.Form.Get("imageName")
	glog.V(1).Infof("Image name is %s", imageName)

	config, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
	if err != nil {
		return err
	}

	env, err := c.backend.CmdCreateContainer(types.ContainerCreateConfig{
		Name:       imageName,
		Config:     config,
		HostConfig: hostConfig,
	})

	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusCreated)
}

func (c *containerRouter) postContainerCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	cname := r.Form.Get("container")
	pause := httputils.BoolValue(r, "pause")

	config, _, _, err := runconfig.DecodeContainerConfig(r.Body)
	if err != nil && err != io.EOF { //Do not fail if body is empty.
		return err
	}
	if config == nil {
		config = &container.Config{}
	}

	newConfig, err := dockerfile.BuildFromConfig(config, r.Form["changes"])
	if err != nil {
		return err
	}

	commitCfg := &types.ContainerCommitConfig{
		Pause:        pause,
		Repo:         r.Form.Get("repo"),
		Tag:          r.Form.Get("tag"),
		Author:       r.Form.Get("author"),
		Comment:      r.Form.Get("comment"),
		Config:       newConfig,
		MergeConfigs: true,
	}

	env, err := c.backend.CmdCommitImage(cname, commitCfg)
	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusOK)
}

func (c *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}

	newName := r.Form.Get("newName")
	oldName := r.Form.Get("oldName")
	env, err := c.backend.CmdContainerRename(oldName, newName)
	if err != nil {
		return err
	}

	return env.WriteJSON(w, http.StatusOK)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 deployer

import (
	"fmt"
	"os"
	"path"
	"strings"

	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/resources"
	"sigs.k8s.io/kubetest2/pkg/exec"
	"sigs.k8s.io/yaml"
)

func (d *deployer) DumpClusterLogs() error {
	yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "toolbox-dump.yaml"))
	if err != nil {
		panic(err)
	}
	defer yamlFile.Close()

	args := []string{
		d.KopsBinaryPath, "toolbox", "dump",
		"--name", d.ClusterName,
		"--dir", d.ArtifactsDir,
		"--private-key", d.SSHPrivateKeyPath,
		"--ssh-user", d.SSHUser,
	}
	klog.Info(strings.Join(args, " "))
	cmd := exec.Command(args[0], args[1:]...)
	cmd.SetEnv(d.env()...)
	cmd.SetStdout(yamlFile)
	if err := cmd.Run(); err != nil {
		return err
	}

	if err := d.dumpClusterManifest(); err != nil {
		return err
	}

	if err := d.dumpClusterInfo(); err != nil {
		return err
	}

	return nil
}

func (d *deployer) dumpClusterManifest() error {
	resourceTypes := []string{"cluster", "instancegroups"}
	for _, rt := range resourceTypes {
		yamlFile, err := os.Create(path.Join(d.ArtifactsDir, fmt.Sprintf("%v.yaml", rt)))
		if err != nil {
			panic(err)
		}
		defer yamlFile.Close()

		args := []string{
			d.KopsBinaryPath, "get", rt,
			"--name", d.ClusterName,
			"-o", "yaml",
		}
		klog.Info(strings.Join(args, " "))

		cmd := exec.Command(args[0], args[1:]...)
		cmd.SetStdout(yamlFile)
		cmd.SetEnv(d.env()...)
		if err := cmd.Run(); err != nil {
			return err
		}
	}
	return nil
}

func (d *deployer) dumpClusterInfo() error {
	args := []string{
		"kubectl", "cluster-info", "dump",
		"--all-namespaces",
		"-o", "yaml",
		"--output-directory", path.Join(d.ArtifactsDir, "cluster-info"),
	}
	klog.Info(strings.Join(args, " "))

	cmd := exec.Command(args[0], args[1:]...)
	cmd.SetEnv(d.env()...)
	if err := cmd.Run(); err != nil {
		if err = d.dumpClusterInfoSSH(); err != nil {
			return err
		}
	}

	resourceTypes := []string{"csinodes", "csidrivers", "storageclasses", "persistentvolumes",
		"mutatingwebhookconfigurations", "validatingwebhookconfigurations"}
	if err := os.MkdirAll(path.Join(d.ArtifactsDir, "cluster-info"), 0755); err != nil {
		return err
	}
	for _, resType := range resourceTypes {
		yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "cluster-info", fmt.Sprintf("%v.yaml", resType)))
		if err != nil {
			return err
		}
		defer yamlFile.Close()

		args = []string{
			"kubectl", "get", resType,
			"--all-namespaces",
			"-o", "yaml",
		}
		klog.Info(strings.Join(args, " "))

		cmd := exec.Command(args[0], args[1:]...)
		cmd.SetEnv(d.env()...)
		cmd.SetStdout(yamlFile)
		if err := cmd.Run(); err != nil {
			klog.Warningf("Failed to get %v: %v", resType, err)
		}
	}

	nsCmd := exec.Command(
		"kubectl", "get", "namespaces", "--no-headers", "-o", "custom-columns=name:.metadata.name",
	)
	namespaces, err := exec.OutputLines(nsCmd)
	if err != nil {
		return fmt.Errorf("failed to get namespaces: %s", err)
	}

	namespacedResourceTypes := []string{"configmaps", "endpoints", "endpointslices", "leases", "persistentvolumeclaims"}
	for _, namespace := range namespaces {
		namespace = strings.TrimSpace(namespace)
		if err := os.MkdirAll(path.Join(d.ArtifactsDir, "cluster-info", namespace), 0755); err != nil {
			return err
		}
		for _, resType := range namespacedResourceTypes {
			yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "cluster-info", namespace, fmt.Sprintf("%v.yaml", resType)))
			if err != nil {
				return err
			}
			defer yamlFile.Close()

			args = []string{
				"kubectl", "get", resType,
				"-n", namespace,
				"-o", "yaml",
			}
			klog.Info(strings.Join(args, " "))

			cmd := exec.Command(args[0], args[1:]...)
			cmd.SetEnv(d.env()...)
			cmd.SetStdout(yamlFile)
			if err := cmd.Run(); err != nil {
				if err = d.dumpClusterInfoSSH(); err != nil {
					klog.Warningf("Failed to get %v: %v", resType, err)
				}
			}
		}
	}
	return nil
}

// dumpClusterInfoSSH runs `kubectl cluster-info dump` on a control plane host via SSH
// and copies the output to the local artifacts directory.
// This can be useful when the k8s API is inaccessible from kubetest2-kops directly
func (d *deployer) dumpClusterInfoSSH() error {
	toolboxDumpArgs := []string{
		d.KopsBinaryPath, "toolbox", "dump",
		"--name", d.ClusterName,
		"--private-key", d.SSHPrivateKeyPath,
		"--ssh-user", d.SSHUser,
		"-o", "yaml",
	}
	klog.Info(strings.Join(toolboxDumpArgs, " "))

	cmd := exec.Command(toolboxDumpArgs[0], toolboxDumpArgs[1:]...)
	dumpOutput, err := exec.Output(cmd)
	if err != nil {
		return err
	}

	var dump resources.Dump
	err = yaml.Unmarshal(dumpOutput, &dump)
	if err != nil {
		return err
	}
	controlPlaneIP, controlPlaneUser, found := findControlPlaneIPUser(dump)
	if !found {
		return nil
	}

	sshURL := fmt.Sprintf("%v@%v", controlPlaneUser, controlPlaneIP)
	sshArgs := []string{
		"ssh", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null",
		sshURL, "--",
		"kubectl", "cluster-info", "dump",
		"--all-namespaces",
		"-o", "yaml",
		"--output-directory", "/tmp/cluster-info",
	}
	klog.Info(strings.Join(sshArgs, " "))

	cmd = exec.Command(sshArgs[0], sshArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}
	scpArgs := []string{
		"scp", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null", "-r",
		fmt.Sprintf("%v:/tmp/cluster-info", sshURL),
		path.Join(d.ArtifactsDir, "cluster-info"),
	}
	klog.Info(strings.Join(scpArgs, " "))

	cmd = exec.Command(scpArgs[0], scpArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}

	rmArgs := []string{
		"ssh", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null",
		sshURL, "--",
		"rm", "-rf", "/tmp/cluster-info",
	}
	klog.Info(strings.Join(rmArgs, " "))

	cmd = exec.Command(rmArgs[0], rmArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func findControlPlaneIPUser(dump resources.Dump) (string, string, bool) {
	for _, instance := range dump.Instances {
		if len(instance.PublicAddresses) == 0 {
			continue
		}
		for _, role := range instance.Roles {
			if role == "master" {
				return instance.PublicAddresses[0], instance.SSHUser, true
			}
		}
	}
	klog.Warning("ControlPlane instance not found from kops toolbox dump")
	return "", "", false
}

func runWithOutput(cmd exec.Cmd) error {
	exec.InheritOutput(cmd)
	return cmd.Run()
}
<commit_msg>Add --request-timeout to some kubectl commands to shorten wait times when the cluster isnt up<commit_after>/*
Copyright 2020 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 deployer

import (
	"fmt"
	"os"
	"path"
	"strings"

	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/resources"
	"sigs.k8s.io/kubetest2/pkg/exec"
	"sigs.k8s.io/yaml"
)

func (d *deployer) DumpClusterLogs() error {
	yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "toolbox-dump.yaml"))
	if err != nil {
		panic(err)
	}
	defer yamlFile.Close()

	args := []string{
		d.KopsBinaryPath, "toolbox", "dump",
		"--name", d.ClusterName,
		"--dir", d.ArtifactsDir,
		"--private-key", d.SSHPrivateKeyPath,
		"--ssh-user", d.SSHUser,
	}
	klog.Info(strings.Join(args, " "))
	cmd := exec.Command(args[0], args[1:]...)
	cmd.SetEnv(d.env()...)
	cmd.SetStdout(yamlFile)
	if err := cmd.Run(); err != nil {
		return err
	}

	if err := d.dumpClusterManifest(); err != nil {
		return err
	}

	if err := d.dumpClusterInfo(); err != nil {
		return err
	}

	return nil
}

func (d *deployer) dumpClusterManifest() error {
	resourceTypes := []string{"cluster", "instancegroups"}
	for _, rt := range resourceTypes {
		yamlFile, err := os.Create(path.Join(d.ArtifactsDir, fmt.Sprintf("%v.yaml", rt)))
		if err != nil {
			panic(err)
		}
		defer yamlFile.Close()

		args := []string{
			d.KopsBinaryPath, "get", rt,
			"--name", d.ClusterName,
			"-o", "yaml",
		}
		klog.Info(strings.Join(args, " "))

		cmd := exec.Command(args[0], args[1:]...)
		cmd.SetStdout(yamlFile)
		cmd.SetEnv(d.env()...)
		if err := cmd.Run(); err != nil {
			return err
		}
	}
	return nil
}

func (d *deployer) dumpClusterInfo() error {
	args := []string{
		"kubectl", "cluster-info", "dump",
		"--all-namespaces",
		"-o", "yaml",
		"--output-directory", path.Join(d.ArtifactsDir, "cluster-info"),
	}
	klog.Info(strings.Join(args, " "))

	cmd := exec.Command(args[0], args[1:]...)
	cmd.SetEnv(d.env()...)
	if err := cmd.Run(); err != nil {
		if err = d.dumpClusterInfoSSH(); err != nil {
			return err
		}
	}

	resourceTypes := []string{"csinodes", "csidrivers", "storageclasses", "persistentvolumes",
		"mutatingwebhookconfigurations", "validatingwebhookconfigurations"}
	if err := os.MkdirAll(path.Join(d.ArtifactsDir, "cluster-info"), 0755); err != nil {
		return err
	}
	for _, resType := range resourceTypes {
		yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "cluster-info", fmt.Sprintf("%v.yaml", resType)))
		if err != nil {
			return err
		}
		defer yamlFile.Close()

		args = []string{
			"kubectl", "--request-timeout", "5s", "get", resType,
			"--all-namespaces",
			"-o", "yaml",
		}
		klog.Info(strings.Join(args, " "))

		cmd := exec.Command(args[0], args[1:]...)
		cmd.SetEnv(d.env()...)
		cmd.SetStdout(yamlFile)
		if err := cmd.Run(); err != nil {
			klog.Warningf("Failed to get %v: %v", resType, err)
		}
	}

	nsCmd := exec.Command(
		"kubectl", "--request-timeout", "5s", "get", "namespaces", "--no-headers", "-o", "custom-columns=name:.metadata.name",
	)
	namespaces, err := exec.OutputLines(nsCmd)
	if err != nil {
		return fmt.Errorf("failed to get namespaces: %s", err)
	}

	namespacedResourceTypes := []string{"configmaps", "endpoints", "endpointslices", "leases", "persistentvolumeclaims"}
	for _, namespace := range namespaces {
		namespace = strings.TrimSpace(namespace)
		if err := os.MkdirAll(path.Join(d.ArtifactsDir, "cluster-info", namespace), 0755); err != nil {
			return err
		}
		for _, resType := range namespacedResourceTypes {
			yamlFile, err := os.Create(path.Join(d.ArtifactsDir, "cluster-info", namespace, fmt.Sprintf("%v.yaml", resType)))
			if err != nil {
				return err
			}
			defer yamlFile.Close()

			args = []string{
				"kubectl", "get", resType,
				"-n", namespace,
				"-o", "yaml",
			}
			klog.Info(strings.Join(args, " "))

			cmd := exec.Command(args[0], args[1:]...)
			cmd.SetEnv(d.env()...)
			cmd.SetStdout(yamlFile)
			if err := cmd.Run(); err != nil {
				if err = d.dumpClusterInfoSSH(); err != nil {
					klog.Warningf("Failed to get %v: %v", resType, err)
				}
			}
		}
	}
	return nil
}

// dumpClusterInfoSSH runs `kubectl cluster-info dump` on a control plane host via SSH
// and copies the output to the local artifacts directory.
// This can be useful when the k8s API is inaccessible from kubetest2-kops directly
func (d *deployer) dumpClusterInfoSSH() error {
	toolboxDumpArgs := []string{
		d.KopsBinaryPath, "toolbox", "dump",
		"--name", d.ClusterName,
		"--private-key", d.SSHPrivateKeyPath,
		"--ssh-user", d.SSHUser,
		"-o", "yaml",
	}
	klog.Info(strings.Join(toolboxDumpArgs, " "))

	cmd := exec.Command(toolboxDumpArgs[0], toolboxDumpArgs[1:]...)
	dumpOutput, err := exec.Output(cmd)
	if err != nil {
		return err
	}

	var dump resources.Dump
	err = yaml.Unmarshal(dumpOutput, &dump)
	if err != nil {
		return err
	}
	controlPlaneIP, controlPlaneUser, found := findControlPlaneIPUser(dump)
	if !found {
		return nil
	}

	sshURL := fmt.Sprintf("%v@%v", controlPlaneUser, controlPlaneIP)
	sshArgs := []string{
		"ssh", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null",
		sshURL, "--",
		"kubectl", "cluster-info", "dump",
		"--all-namespaces",
		"-o", "yaml",
		"--output-directory", "/tmp/cluster-info",
	}
	klog.Info(strings.Join(sshArgs, " "))

	cmd = exec.Command(sshArgs[0], sshArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}
	scpArgs := []string{
		"scp", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null", "-r",
		fmt.Sprintf("%v:/tmp/cluster-info", sshURL),
		path.Join(d.ArtifactsDir, "cluster-info"),
	}
	klog.Info(strings.Join(scpArgs, " "))

	cmd = exec.Command(scpArgs[0], scpArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}

	rmArgs := []string{
		"ssh", "-i", d.SSHPrivateKeyPath,
		"-o", "StrictHostKeyChecking=no",
		"-o", "UserKnownHostsFile=/dev/null",
		sshURL, "--",
		"rm", "-rf", "/tmp/cluster-info",
	}
	klog.Info(strings.Join(rmArgs, " "))

	cmd = exec.Command(rmArgs[0], rmArgs[1:]...)
	exec.InheritOutput(cmd)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func findControlPlaneIPUser(dump resources.Dump) (string, string, bool) {
	for _, instance := range dump.Instances {
		if len(instance.PublicAddresses) == 0 {
			continue
		}
		for _, role := range instance.Roles {
			if role == "master" {
				return instance.PublicAddresses[0], instance.SSHUser, true
			}
		}
	}
	klog.Warning("ControlPlane instance not found from kops toolbox dump")
	return "", "", false
}

func runWithOutput(cmd exec.Cmd) error {
	exec.InheritOutput(cmd)
	return cmd.Run()
}
<|endoftext|>


<commit_before>// Package ratelimiter provides a mechanism for limiting the rate at which
// clients can make requests to specific domains.
package ratelimiter

import (
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/getlantern/golog"
	"github.com/getlantern/http-proxy/filters"
	"github.com/hashicorp/golang-lru"
)

var log = golog.LoggerFor("ratelimiter")

type ratelimiter struct {
	hostPeriods          map[string]time.Duration
	hostAccessesByClient *lru.Cache
	mx                   sync.Mutex
}

// New creates a new rate limiting filter that only allows access to the hosts
// listed in the given hostPeriods, and limits the periodicity of requests to
// each host to the given duration. It limits the number of clients tracked to
// the the numClients with the most recent activity.
func New(numClients int, hostPeriods map[string]time.Duration) filters.Filter {
	if numClients <= 0 {
		numClients = 5000
	}
	// We can safely ignore the error, since the only thing that would cause an
	// error is numClients <= 0
	cache, _ := lru.New(numClients)
	return &ratelimiter{
		hostPeriods:          hostPeriods,
		hostAccessesByClient: cache,
	}
}

func (f *ratelimiter) Apply(resp http.ResponseWriter, req *http.Request, next filters.Next) error {
	host, _, err := net.SplitHostPort(req.Host)
	if err != nil {
		host = req.Host
	}
	f.mx.Lock()
	period := f.hostPeriods[host]
	if period == 0 {
		f.mx.Unlock()
		return fail(resp, "Access to %v not allowed", host)
	}
	client, _, _ := net.SplitHostPort(req.RemoteAddr)
	var hostAccesses map[string]time.Time
	_hostAccesses, found := f.hostAccessesByClient.Get(client)
	if found {
		hostAccesses = _hostAccesses.(map[string]time.Time)
	} else {
		hostAccesses = make(map[string]time.Time)
	}
	now := time.Now()
	allowed := now.Sub(hostAccesses[host]) > period
	if allowed {
		hostAccesses[host] = now
		f.hostAccessesByClient.Add(client, hostAccesses)
	}
	f.mx.Unlock()
	if !allowed {
		return fail(resp, "Rate limit for %v exceeded", host)
	}
	return next()
}

func fail(resp http.ResponseWriter, msg string, args ...interface{}) error {
	resp.WriteHeader(http.StatusForbidden)
	fmt.Fprintf(resp, msg, args...)
	return filters.Stop()
}
<commit_msg>Unlocking ratelimiter mutex with defer<commit_after>// Package ratelimiter provides a mechanism for limiting the rate at which
// clients can make requests to specific domains.
package ratelimiter

import (
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/getlantern/golog"
	"github.com/getlantern/http-proxy/filters"
	"github.com/hashicorp/golang-lru"
)

var log = golog.LoggerFor("ratelimiter")

type ratelimiter struct {
	hostPeriods          map[string]time.Duration
	hostAccessesByClient *lru.Cache
	mx                   sync.Mutex
}

// New creates a new rate limiting filter that only allows access to the hosts
// listed in the given hostPeriods, and limits the periodicity of requests to
// each host to the given duration. It limits the number of clients tracked to
// the the numClients with the most recent activity.
func New(numClients int, hostPeriods map[string]time.Duration) filters.Filter {
	if numClients <= 0 {
		numClients = 5000
	}
	// We can safely ignore the error, since the only thing that would cause an
	// error is numClients <= 0
	cache, _ := lru.New(numClients)
	return &ratelimiter{
		hostPeriods:          hostPeriods,
		hostAccessesByClient: cache,
	}
}

func (f *ratelimiter) Apply(resp http.ResponseWriter, req *http.Request, next filters.Next) error {
	err := f.verifyAllowed(resp, req, next)
	if err != nil {
		return fail(resp, err)
	}
	return next()
}

func (f *ratelimiter) verifyAllowed(resp http.ResponseWriter, req *http.Request, next filters.Next) error {
	host, _, err := net.SplitHostPort(req.Host)
	if err != nil {
		host = req.Host
	}
	client, _, _ := net.SplitHostPort(req.RemoteAddr)
	now := time.Now()

	f.mx.Lock()
	defer f.mx.Unlock()
	period := f.hostPeriods[host]
	if period == 0 {
		return fmt.Errorf("Access to %v not allowed", host)
	}
	var hostAccesses map[string]time.Time
	_hostAccesses, found := f.hostAccessesByClient.Get(client)
	if found {
		hostAccesses = _hostAccesses.(map[string]time.Time)
	} else {
		hostAccesses = make(map[string]time.Time)
	}
	allowed := now.Sub(hostAccesses[host]) > period
	if allowed {
		hostAccesses[host] = now
		f.hostAccessesByClient.Add(client, hostAccesses)
	}
	if !allowed {
		return fmt.Errorf("Rate limit for %v exceeded", host)
	}

	return nil
}

func fail(resp http.ResponseWriter, err error) error {
	resp.WriteHeader(http.StatusForbidden)
	fmt.Fprint(resp, err)
	return filters.Stop()
}
<|endoftext|>


<commit_before>package confirm

import (
	"fmt"

	"github.com/wanliu/flow/builtin/config"
	"github.com/wanliu/flow/builtin/resolves"
	"github.com/wanliu/flow/context"
)

type AddressConfirm struct {
	Values []string
	// order  *resolves.OrderResolve
}

func (ac *AddressConfirm) SetUp(ctx context.Context) {
	ctx.SetValue(config.CtxKeyConfirm, *ac)
}

func (ac *AddressConfirm) ClearUp(ctx context.Context) {
	ctx.SetValue(config.CtxKeyConfirm, nil)
}

func (ac *AddressConfirm) Notice(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		if len(ac.Values) > 0 {
			return fmt.Sprintf("是否将 “%v” 做为收货地址?", ac.Values[0])
		} else {
			// ctx.SetValue(config.CtxKeyConfirm, nil)
			ac.ClearUp(ctx)

			if order.Address == "" {
				return "取消操作完成，当前订单收货地址尚未确认，请输入收货地址"
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return ""
}

func (ac *AddressConfirm) Cancel(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		if len(ac.Values) > 1 {
			newValues := ac.Values[1:]
			ac.Values = newValues
			// ctx.SetValue(config.CtxKeyConfirm, ac)
			ac.SetUp(ctx)

			return fmt.Sprintf("是否将 “%v” 做为收货地址?", newValues[0])
		} else {
			// ctx.SetValue(config.CtxKeyConfirm, nil)
			ac.ClearUp(ctx)

			if order.Address == "" {
				return "取消操作完成，当前订单收货地址尚未确认，请输入收货地址"
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return ""
}

func (ac *AddressConfirm) Confirm(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		// cConfirm := confirm.(AddressConfirm)

		if order.Address == "" {
			if len(ac.Values) > 0 {
				order.Address = ac.Values[0]

				if order.Fulfiled() {
					ctx.SetValue(config.CtxKeyOrder, nil)
					ctx.SetValue(config.CtxKeyLastOrder, order)
				} else {
					ctx.SetValue(config.CtxKeyOrder, order)
				}

				return fmt.Sprintf("已经确认\"%v\"为收货地址\n%v", ac.Values[0], order.Answer())

			} else {
				// ctx.SetValue(config.CtxKeyConfirm, nil)
				ac.ClearUp(ctx)
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return "确认操作已经过期"
}
<commit_msg>fix confirm<commit_after>package confirm

import (
	"fmt"

	"github.com/wanliu/flow/builtin/config"
	"github.com/wanliu/flow/builtin/resolves"
	"github.com/wanliu/flow/context"
)

type AddressConfirm struct {
	Values []string
	// order  *resolves.OrderResolve
}

func (ac AddressConfirm) SetUp(ctx context.Context) {
	ctx.SetValue(config.CtxKeyConfirm, ac)
}

func (ac AddressConfirm) ClearUp(ctx context.Context) {
	ctx.SetValue(config.CtxKeyConfirm, nil)
}

func (ac AddressConfirm) Notice(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		if len(ac.Values) > 0 {
			return fmt.Sprintf("是否将 “%v” 做为收货地址?", ac.Values[0])
		} else {
			// ctx.SetValue(config.CtxKeyConfirm, nil)
			ac.ClearUp(ctx)

			if order.Address == "" {
				return "取消操作完成，当前订单收货地址尚未确认，请输入收货地址"
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return ""
}

func (ac AddressConfirm) Cancel(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		if len(ac.Values) > 1 {
			newValues := ac.Values[1:]
			ac.Values = newValues
			// ctx.SetValue(config.CtxKeyConfirm, ac)
			ac.SetUp(ctx)

			return fmt.Sprintf("是否将 “%v” 做为收货地址?", newValues[0])
		} else {
			// ctx.SetValue(config.CtxKeyConfirm, nil)
			ac.ClearUp(ctx)

			if order.Address == "" {
				return "取消操作完成，当前订单收货地址尚未确认，请输入收货地址"
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return ""
}

func (ac AddressConfirm) Confirm(ctx context.Context) string {
	oIn := ctx.Value(config.CtxKeyOrder)
	// confirm := ctx.Value(config.CtxKeyConfirm)

	if oIn != nil {
		order := oIn.(resolves.OrderResolve)

		if order.Expired(config.SesssionExpiredMinutes) {
			return "当前没有正在进行中的订单"
		}

		// cConfirm := confirm.(AddressConfirm)

		if order.Address == "" {
			if len(ac.Values) > 0 {
				order.Address = ac.Values[0]

				if order.Fulfiled() {
					ctx.SetValue(config.CtxKeyOrder, nil)
					ctx.SetValue(config.CtxKeyLastOrder, order)
				} else {
					ctx.SetValue(config.CtxKeyOrder, order)
				}

				return fmt.Sprintf("已经确认\"%v\"为收货地址\n%v", ac.Values[0], order.Answer())

			} else {
				// ctx.SetValue(config.CtxKeyConfirm, nil)
				ac.ClearUp(ctx)
			}
		}
	} else {
		return "当前没有正在进行中的订单"
	}

	return "确认操作已经过期"
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/appcelerator/amp/cluster/agent/admin"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker/stack"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/compose/convert"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/cobra"
)

const (
	TARGET_SINGLE  = "single"
	TARGET_CLUSTER = "cluster"
)

func NewInstallCommand() *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install",
		Short: "Set up amp services in swarm environment",
		RunE:  install,
	}
	return installCmd
}

func install(cmd *cobra.Command, args []string) error {
	stdin, stdout, stderr := term.StdStreams()
	dockerCli := docker.NewDockerCli(stdin, stdout, stderr)

	// Create initial secrets
	createInitialSecrets()

	// Create initial configs
	createInitialConfigs()

	// Create initial networks
	createInitialNetworks()

	namespace := "amp"
	if len(args) > 0 {
		namespace = args[0]
	}

	etcdClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.kv", "true")
	if err != nil {
		return err
	}
	elasticsearchClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.search", "true")
	if err != nil {
		return err
	}
	clusterMode := map[string]string{"elasticsearch": elasticsearchClusterMode, "etcd": etcdClusterMode}
	files, err := getStackFiles("./stacks", clusterMode)
	if err != nil {
		return err
	}

	for _, f := range files {
		log.Println(f)
		if strings.Contains(f, "test") {
			err := deployTest(dockerCli, f, "test", 60 /* timeout in seconds */)
			stack.Remove(dockerCli, stack.RemoveOptions{Namespaces: []string{"test"}})
			if err != nil {
				return err
			}
		} else {
			err := deploy(dockerCli, f, namespace)
			if err != nil {
				return err
			}
			time.Sleep(10 * time.Second)
		}
	}
	return nil
}

// returns the deployment mode
// based on the number of nodes with the label passed as argument
// if number of nodes > 2, mode = cluster, else mode = single
func serviceDeploymentMode(c client.APIClient, labelKey string, labelValue string) (string, error) {
	// unfortunately filtering labels on NodeList won't work as expected, Cf. https://github.com/moby/moby/issues/27231
	nodes, err := c.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		return "", err
	}
	matchingNodes := 0
	for _, node := range nodes {
		// node is a swarm.Node
		for k, v := range node.Spec.Labels {
			if k == labelKey {
				if labelValue == "" || labelValue == v {
					matchingNodes++
				}
			}
		}
	}
	switch matchingNodes {
	case 0:
		return "", fmt.Errorf("can't find a node with label %s", labelKey)
	case 1:
		fallthrough
	case 2:
		return TARGET_SINGLE, nil
	default:
		return TARGET_CLUSTER, nil
	}
}

// returns sorted list of yaml file pathnames
func getStackFiles(path string, clusterMode map[string]string) ([]string, error) {
	if path == "" {
		path = "./stacks"
	}

	// a bit more work but we can't just use filepath.Glob
	// since we need to match both *.yml and *.yaml
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	stackfiles := []string{}
	for _, f := range files {
		name := f.Name()
		// not compiling regex since only expecting less than a dozen stackfiles
		matched, err := regexp.MatchString("\\.ya?ml$", name)
		if err != nil {
			log.Println(err)
		} else if matched {
			// looking for the service name, in case there's an indication for the cluster mode (single vs cluster)
			// expecting a file with a name NN-SERVICENAME-mode.*
			split := strings.Split(name, "-")
			if len(split) == 3 {
				serviceName := split[1]
				if strings.Contains(name, TARGET_SINGLE) && clusterMode[serviceName] != TARGET_SINGLE {
					continue
				}
				if strings.Contains(name, TARGET_CLUSTER) && clusterMode[serviceName] != TARGET_CLUSTER {
					continue
				}
			}
			stackfiles = append(stackfiles, filepath.Join(path, name))
		}
	}
	return stackfiles, nil
}

func deploy(d *command.DockerCli, stackfile string, namespace string) error {
	return deployExpectingState(d, stackfile, namespace, swarm.TaskStateRunning)
}

func deployExpectingState(d *command.DockerCli, stackfile string, namespace string, expectedState swarm.TaskState) error {
	if namespace == "" {
		// use the stackfile basename as the default stack namespace
		namespace = filepath.Base(stackfile)
		namespace = strings.TrimSuffix(namespace, filepath.Ext(namespace))
	}

	opts := stack.DeployOptions{
		Namespace:        namespace,
		Composefile:      stackfile,
		ResolveImage:     stack.ResolveImageNever,
		SendRegistryAuth: false,
		Prune:            false,
		ExpectedState:    expectedState,
	}

	return stack.Deploy(context.Background(), d, opts)
}

func deployTest(d *command.DockerCli, stackfile string, namespace string, timeout int) error {
	// Deploy the test stack
	if err := deployExpectingState(d, stackfile, namespace, swarm.TaskStateComplete); err != nil {
		return err
	}

	// Create a docker client
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}

	// List stack tasks
	options := types.TaskListOptions{Filters: filters.NewArgs()}
	options.Filters.Add("label", convert.LabelNamespace+"="+namespace)
	tasks, err := stack.ListTasks(context.Background(), c, options)
	if err != nil {
		return err
	}

	// Assert we have at least one task
	if len(tasks) == 0 {
		return fmt.Errorf("no task for test")
	}

	// Assert we have only one task
	if len(tasks) != 1 {
		return fmt.Errorf("too many tasks for test: %d", len(tasks))
	}

	// If the task has an error, the test has failed
	task := tasks[0]
	if task.Status.Err != "" {
		return fmt.Errorf("test failed with status: %s", task.Status.Err)
	}

	log.Println("Test successful")
	return nil
}

func ListSecrets() ([]swarm.Secret, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.SecretList(context.Background(), types.SecretListOptions{})
}

func SecretExists(name string) (bool, error) {
	secrets, err := ListSecrets()
	if err != nil {
		return false, err
	}
	for _, secret := range secrets {
		if secret.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateSecret(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.SecretSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.SecretCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP secrets map: Secret name paired to secret file in ./defaults
var ampSecrets = map[string]string{
	"alertmanager_yml": "alertmanager.yml",
	"amplifier_yml":    "amplifier.yml",
	"certificate_amp":  "certificate.amp",
}

// This is the default secrets path
const defaultSecretsPath = "defaults"

// exists returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

func createInitialSecrets() error {
	// Computing secret path
	secretPath := path.Join("/", defaultSecretsPath)
	pe, err := pathExists(secretPath)
	if err != nil {
		return err
	}
	if !pe {
		secretPath = defaultSecretsPath
	}
	secretPath, err = filepath.Abs(secretPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for secrets:", secretPath)

	// Creating secrets
	for secret, filename := range ampSecrets {
		// Check if secret already exists
		exists, err := SecretExists(secret)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing secret:", secret)
			continue
		}

		// Load secret data
		data, err := ioutil.ReadFile(path.Join(secretPath, filename))
		if err != nil {
			return err
		}

		// Create secret
		if err := CreateSecret(secret, data); err != nil {
			return err
		}
		log.Println("Successfully created secret:", secret)
	}
	return nil
}

func ListNetworks() ([]types.NetworkResource, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.NetworkList(context.Background(), types.NetworkListOptions{})
}

func NetworkExists(name string) (bool, error) {
	networks, err := ListNetworks()
	if err != nil {
		return false, err
	}
	for _, network := range networks {
		if network.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateNetwork(name string, overlay bool, attachable bool) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := types.NetworkCreate{
		CheckDuplicate: true,
		Attachable:     attachable,
	}
	if overlay {
		spec.Driver = "overlay"
	}
	_, err = c.NetworkCreate(context.Background(), name, spec)
	if err != nil {
		return err
	}
	return nil
}

const ampnet = "ampnet"

func createInitialNetworks() error {
	// Check if network already exists
	exists, err := NetworkExists(ampnet)
	if err != nil {
		return err
	}
	if exists {
		log.Println("Skipping already existing network:", ampnet)
		return nil
	}

	// Create network
	if err := CreateNetwork(ampnet, true, true); err != nil {
		return err
	}
	log.Println("Successfully created network:", ampnet)
	return nil
}

func ListConfigs() ([]swarm.Config, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.ConfigList(context.Background(), types.ConfigListOptions{})
}

func ConfigExists(name string) (bool, error) {
	configs, err := ListConfigs()
	if err != nil {
		return false, err
	}
	for _, config := range configs {
		if config.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateConfig(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.ConfigSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.ConfigCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP configs map: Config name paired to config file in ./defaults
var ampConfigs = map[string]string{
	"prometheus_alerts_rules":  "prometheus_alerts.rules",
}

// This is the default configs path
const defaultConfigsPath = "defaults"

func createInitialConfigs() error {
	// Computing config path
	configPath := path.Join("/", defaultConfigsPath)
	pe, err := pathExists(configPath)
	if err != nil {
		return err
	}
	if !pe {
		configPath = defaultConfigsPath
	}
	configPath, err = filepath.Abs(configPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for configs:", configPath)

	// Creating configs
	for config, filename := range ampConfigs {
		// Check if config already exists
		exists, err := ConfigExists(config)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing config:", config)
			continue
		}

		// Load config data
		data, err := ioutil.ReadFile(path.Join(configPath, filename))
		if err != nil {
			return err
		}

		// Create config
		if err := CreateConfig(config, data); err != nil {
			return err
		}
		log.Println("Successfully created config:", config)
	}
	return nil
}
<commit_msg>Using environment variable to determine version<commit_after>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/appcelerator/amp/cluster/agent/admin"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker/stack"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/compose/convert"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/cobra"
)

const (
	TARGET_SINGLE  = "single"
	TARGET_CLUSTER = "cluster"
)

func NewInstallCommand() *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install",
		Short: "Set up amp services in swarm environment",
		RunE:  install,
	}
	return installCmd
}

func install(cmd *cobra.Command, args []string) error {
	stdin, stdout, stderr := term.StdStreams()
	dockerCli := docker.NewDockerCli(stdin, stdout, stderr)

	// Create initial secrets
	createInitialSecrets()

	// Create initial configs
	createInitialConfigs()

	// Create initial networks
	createInitialNetworks()

	// Environment variables
	if os.Getenv("TAG") == "" { // If TAG is undefined, use the current project version
		os.Setenv("TAG", Version)
	}

	namespace := "amp"
	if len(args) > 0 {
		namespace = args[0]
	}

	etcdClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.kv", "true")
	if err != nil {
		return err
	}
	elasticsearchClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.search", "true")
	if err != nil {
		return err
	}
	clusterMode := map[string]string{"elasticsearch": elasticsearchClusterMode, "etcd": etcdClusterMode}
	files, err := getStackFiles("./stacks", clusterMode)
	if err != nil {
		return err
	}

	for _, f := range files {
		log.Println(f)
		if strings.Contains(f, "test") {
			err := deployTest(dockerCli, f, "test", 60 /* timeout in seconds */)
			stack.Remove(dockerCli, stack.RemoveOptions{Namespaces: []string{"test"}})
			if err != nil {
				return err
			}
		} else {
			err := deploy(dockerCli, f, namespace)
			if err != nil {
				return err
			}
			time.Sleep(10 * time.Second)
		}
	}
	return nil
}

// returns the deployment mode
// based on the number of nodes with the label passed as argument
// if number of nodes > 2, mode = cluster, else mode = single
func serviceDeploymentMode(c client.APIClient, labelKey string, labelValue string) (string, error) {
	// unfortunately filtering labels on NodeList won't work as expected, Cf. https://github.com/moby/moby/issues/27231
	nodes, err := c.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		return "", err
	}
	matchingNodes := 0
	for _, node := range nodes {
		// node is a swarm.Node
		for k, v := range node.Spec.Labels {
			if k == labelKey {
				if labelValue == "" || labelValue == v {
					matchingNodes++
				}
			}
		}
	}
	switch matchingNodes {
	case 0:
		return "", fmt.Errorf("can't find a node with label %s", labelKey)
	case 1:
		fallthrough
	case 2:
		return TARGET_SINGLE, nil
	default:
		return TARGET_CLUSTER, nil
	}
}

// returns sorted list of yaml file pathnames
func getStackFiles(path string, clusterMode map[string]string) ([]string, error) {
	if path == "" {
		path = "./stacks"
	}

	// a bit more work but we can't just use filepath.Glob
	// since we need to match both *.yml and *.yaml
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	stackfiles := []string{}
	for _, f := range files {
		name := f.Name()
		// not compiling regex since only expecting less than a dozen stackfiles
		matched, err := regexp.MatchString("\\.ya?ml$", name)
		if err != nil {
			log.Println(err)
		} else if matched {
			// looking for the service name, in case there's an indication for the cluster mode (single vs cluster)
			// expecting a file with a name NN-SERVICENAME-mode.*
			split := strings.Split(name, "-")
			if len(split) == 3 {
				serviceName := split[1]
				if strings.Contains(name, TARGET_SINGLE) && clusterMode[serviceName] != TARGET_SINGLE {
					continue
				}
				if strings.Contains(name, TARGET_CLUSTER) && clusterMode[serviceName] != TARGET_CLUSTER {
					continue
				}
			}
			stackfiles = append(stackfiles, filepath.Join(path, name))
		}
	}
	return stackfiles, nil
}

func deploy(d *command.DockerCli, stackfile string, namespace string) error {
	return deployExpectingState(d, stackfile, namespace, swarm.TaskStateRunning)
}

func deployExpectingState(d *command.DockerCli, stackfile string, namespace string, expectedState swarm.TaskState) error {
	if namespace == "" {
		// use the stackfile basename as the default stack namespace
		namespace = filepath.Base(stackfile)
		namespace = strings.TrimSuffix(namespace, filepath.Ext(namespace))
	}

	opts := stack.DeployOptions{
		Namespace:        namespace,
		Composefile:      stackfile,
		ResolveImage:     stack.ResolveImageNever,
		SendRegistryAuth: false,
		Prune:            false,
		ExpectedState:    expectedState,
	}

	return stack.Deploy(context.Background(), d, opts)
}

func deployTest(d *command.DockerCli, stackfile string, namespace string, timeout int) error {
	// Deploy the test stack
	if err := deployExpectingState(d, stackfile, namespace, swarm.TaskStateComplete); err != nil {
		return err
	}

	// Create a docker client
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}

	// List stack tasks
	options := types.TaskListOptions{Filters: filters.NewArgs()}
	options.Filters.Add("label", convert.LabelNamespace+"="+namespace)
	tasks, err := stack.ListTasks(context.Background(), c, options)
	if err != nil {
		return err
	}

	// Assert we have at least one task
	if len(tasks) == 0 {
		return fmt.Errorf("no task for test")
	}

	// Assert we have only one task
	if len(tasks) != 1 {
		return fmt.Errorf("too many tasks for test: %d", len(tasks))
	}

	// If the task has an error, the test has failed
	task := tasks[0]
	if task.Status.Err != "" {
		return fmt.Errorf("test failed with status: %s", task.Status.Err)
	}

	log.Println("Test successful")
	return nil
}

func ListSecrets() ([]swarm.Secret, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.SecretList(context.Background(), types.SecretListOptions{})
}

func SecretExists(name string) (bool, error) {
	secrets, err := ListSecrets()
	if err != nil {
		return false, err
	}
	for _, secret := range secrets {
		if secret.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateSecret(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.SecretSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.SecretCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP secrets map: Secret name paired to secret file in ./defaults
var ampSecrets = map[string]string{
	"alertmanager_yml": "alertmanager.yml",
	"amplifier_yml":    "amplifier.yml",
	"certificate_amp":  "certificate.amp",
}

// This is the default secrets path
const defaultSecretsPath = "defaults"

// exists returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

func createInitialSecrets() error {
	// Computing secret path
	secretPath := path.Join("/", defaultSecretsPath)
	pe, err := pathExists(secretPath)
	if err != nil {
		return err
	}
	if !pe {
		secretPath = defaultSecretsPath
	}
	secretPath, err = filepath.Abs(secretPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for secrets:", secretPath)

	// Creating secrets
	for secret, filename := range ampSecrets {
		// Check if secret already exists
		exists, err := SecretExists(secret)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing secret:", secret)
			continue
		}

		// Load secret data
		data, err := ioutil.ReadFile(path.Join(secretPath, filename))
		if err != nil {
			return err
		}

		// Create secret
		if err := CreateSecret(secret, data); err != nil {
			return err
		}
		log.Println("Successfully created secret:", secret)
	}
	return nil
}

func ListNetworks() ([]types.NetworkResource, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.NetworkList(context.Background(), types.NetworkListOptions{})
}

func NetworkExists(name string) (bool, error) {
	networks, err := ListNetworks()
	if err != nil {
		return false, err
	}
	for _, network := range networks {
		if network.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateNetwork(name string, overlay bool, attachable bool) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := types.NetworkCreate{
		CheckDuplicate: true,
		Attachable:     attachable,
	}
	if overlay {
		spec.Driver = "overlay"
	}
	_, err = c.NetworkCreate(context.Background(), name, spec)
	if err != nil {
		return err
	}
	return nil
}

const ampnet = "ampnet"

func createInitialNetworks() error {
	// Check if network already exists
	exists, err := NetworkExists(ampnet)
	if err != nil {
		return err
	}
	if exists {
		log.Println("Skipping already existing network:", ampnet)
		return nil
	}

	// Create network
	if err := CreateNetwork(ampnet, true, true); err != nil {
		return err
	}
	log.Println("Successfully created network:", ampnet)
	return nil
}

func ListConfigs() ([]swarm.Config, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.ConfigList(context.Background(), types.ConfigListOptions{})
}

func ConfigExists(name string) (bool, error) {
	configs, err := ListConfigs()
	if err != nil {
		return false, err
	}
	for _, config := range configs {
		if config.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateConfig(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.ConfigSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.ConfigCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP configs map: Config name paired to config file in ./defaults
var ampConfigs = map[string]string{
	"prometheus_alerts_rules":  "prometheus_alerts.rules",
}

// This is the default configs path
const defaultConfigsPath = "defaults"

func createInitialConfigs() error {
	// Computing config path
	configPath := path.Join("/", defaultConfigsPath)
	pe, err := pathExists(configPath)
	if err != nil {
		return err
	}
	if !pe {
		configPath = defaultConfigsPath
	}
	configPath, err = filepath.Abs(configPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for configs:", configPath)

	// Creating configs
	for config, filename := range ampConfigs {
		// Check if config already exists
		exists, err := ConfigExists(config)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing config:", config)
			continue
		}

		// Load config data
		data, err := ioutil.ReadFile(path.Join(configPath, filename))
		if err != nil {
			return err
		}

		// Create config
		if err := CreateConfig(config, data); err != nil {
			return err
		}
		log.Println("Successfully created config:", config)
	}
	return nil
}
<|endoftext|>


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

package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"runtime"
	"time"

	"github.com/gorilla/mux"
	"github.com/minio/minio-go/v7/pkg/set"
	xhttp "github.com/minio/minio/internal/http"
	"github.com/minio/minio/internal/logger"
	"github.com/minio/minio/internal/rest"
	"github.com/minio/pkg/env"
)

const (
	bootstrapRESTVersion       = "v1"
	bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
	bootstrapRESTPrefix        = minioReservedBucketPath + "/bootstrap"
	bootstrapRESTPath          = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
)

const (
	bootstrapRESTMethodHealth = "/health"
	bootstrapRESTMethodVerify = "/verify"
)

// To abstract a node over network.
type bootstrapRESTServer struct{}

// ServerSystemConfig - captures information about server configuration.
type ServerSystemConfig struct {
	MinioPlatform  string
	MinioEndpoints EndpointServerPools
	MinioEnv       map[string]string
}

// Diff - returns error on first difference found in two configs.
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
	if s1.MinioPlatform != s2.MinioPlatform {
		return fmt.Errorf("Expected platform '%s', found to be running '%s'",
			s1.MinioPlatform, s2.MinioPlatform)
	}
	if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
		return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
			s2.MinioEndpoints.NEndpoints())
	}

	for i, ep := range s1.MinioEndpoints {
		if ep.SetCount != s2.MinioEndpoints[i].SetCount {
			return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
				s2.MinioEndpoints[i].SetCount)
		}
		if ep.DrivesPerSet != s2.MinioEndpoints[i].DrivesPerSet {
			return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
				s2.MinioEndpoints[i].DrivesPerSet)
		}
		for j, endpoint := range ep.Endpoints {
			if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() {
				return fmt.Errorf("Expected endpoint %s, seen %s", endpoint,
					s2.MinioEndpoints[i].Endpoints[j])
			}
		}
	}
	if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
		return fmt.Errorf("Expected same MINIO_ environment variables and values")
	}
	return nil
}

var skipEnvs = map[string]struct{}{
	"MINIO_OPTS": {},
}

func getServerSystemCfg() ServerSystemConfig {
	envs := env.List("MINIO_")
	envValues := make(map[string]string, len(envs))
	for _, envK := range envs {
		// skip certain environment variables as part
		// of the whitelist and could be configured
		// differently on each nodes, update skipEnvs()
		// map if there are such environment values
		if _, ok := skipEnvs[envK]; ok {
			continue
		}
		envValues[envK] = env.Get(envK, "")
	}
	return ServerSystemConfig{
		MinioPlatform:  fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
		MinioEndpoints: globalEndpoints,
		MinioEnv:       envValues,
	}
}

// HealthHandler returns success if request is valid
func (b *bootstrapRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {}

func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
	ctx := newContext(r, w, "VerifyHandler")
	cfg := getServerSystemCfg()
	logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
	w.(http.Flusher).Flush()
}

// registerBootstrapRESTHandlers - register bootstrap rest router.
func registerBootstrapRESTHandlers(router *mux.Router) {
	server := &bootstrapRESTServer{}
	subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()

	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
		httpTraceHdrs(server.HealthHandler))

	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
		httpTraceHdrs(server.VerifyHandler))
}

// client to talk to bootstrap NEndpoints.
type bootstrapRESTClient struct {
	endpoint   Endpoint
	restClient *rest.Client
}

// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *bootstrapRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
	if values == nil {
		values = make(url.Values)
	}

	respBody, err = client.restClient.Call(ctx, method, values, body, length)
	if err == nil {
		return respBody, nil
	}

	return nil, err
}

// Stringer provides a canonicalized representation of node.
func (client *bootstrapRESTClient) String() string {
	return client.endpoint.String()
}

// Verify - fetches system server config.
func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSystemConfig) (err error) {
	if newObjectLayerFn() != nil {
		return nil
	}
	respBody, err := client.callWithContext(ctx, bootstrapRESTMethodVerify, nil, nil, -1)
	if err != nil {
		return
	}
	defer xhttp.DrainBody(respBody)
	recvCfg := ServerSystemConfig{}
	if err = json.NewDecoder(respBody).Decode(&recvCfg); err != nil {
		return err
	}
	return srcCfg.Diff(recvCfg)
}

func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools) error {
	srcCfg := getServerSystemCfg()
	clnts := newBootstrapRESTClients(endpointServerPools)
	var onlineServers int
	var offlineEndpoints []string
	var retries int
	for onlineServers < len(clnts)/2 {
		for _, clnt := range clnts {
			if err := clnt.Verify(ctx, srcCfg); err != nil {
				if isNetworkError(err) {
					offlineEndpoints = append(offlineEndpoints, clnt.String())
					continue
				}
				return fmt.Errorf("%s as has incorrect configuration: %w", clnt.String(), err)
			}
			onlineServers++
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			// Sleep for a while - so that we don't go into
			// 100% CPU when half the endpoints are offline.
			time.Sleep(100 * time.Millisecond)
			retries++
			// after 5 retries start logging that servers are not reachable yet
			if retries >= 5 {
				logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2))
				if len(offlineEndpoints) > 0 {
					logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
				}
				retries = 0 // reset to log again after 5 retries.
			}
			offlineEndpoints = nil
		}
	}
	return nil
}

func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstrapRESTClient {
	seenHosts := set.NewStringSet()
	var clnts []*bootstrapRESTClient
	for _, ep := range endpointServerPools {
		for _, endpoint := range ep.Endpoints {
			if seenHosts.Contains(endpoint.Host) {
				continue
			}
			seenHosts.Add(endpoint.Host)

			// Only proceed for remote endpoints.
			if !endpoint.IsLocal {
				clnts = append(clnts, newBootstrapRESTClient(endpoint))
			}
		}
	}
	return clnts
}

// Returns a new bootstrap client.
func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
	serverURL := &url.URL{
		Scheme: endpoint.Scheme,
		Host:   endpoint.Host,
		Path:   bootstrapRESTPath,
	}

	restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
	restClient.HealthCheckFn = nil

	return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
}
<commit_msg>ignore MINIO_CERT_PASSWD to be same on all instances<commit_after>// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"runtime"
	"time"

	"github.com/gorilla/mux"
	"github.com/minio/minio-go/v7/pkg/set"
	xhttp "github.com/minio/minio/internal/http"
	"github.com/minio/minio/internal/logger"
	"github.com/minio/minio/internal/rest"
	"github.com/minio/pkg/env"
)

const (
	bootstrapRESTVersion       = "v1"
	bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
	bootstrapRESTPrefix        = minioReservedBucketPath + "/bootstrap"
	bootstrapRESTPath          = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
)

const (
	bootstrapRESTMethodHealth = "/health"
	bootstrapRESTMethodVerify = "/verify"
)

// To abstract a node over network.
type bootstrapRESTServer struct{}

// ServerSystemConfig - captures information about server configuration.
type ServerSystemConfig struct {
	MinioPlatform  string
	MinioEndpoints EndpointServerPools
	MinioEnv       map[string]string
}

// Diff - returns error on first difference found in two configs.
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
	if s1.MinioPlatform != s2.MinioPlatform {
		return fmt.Errorf("Expected platform '%s', found to be running '%s'",
			s1.MinioPlatform, s2.MinioPlatform)
	}
	if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
		return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
			s2.MinioEndpoints.NEndpoints())
	}

	for i, ep := range s1.MinioEndpoints {
		if ep.SetCount != s2.MinioEndpoints[i].SetCount {
			return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
				s2.MinioEndpoints[i].SetCount)
		}
		if ep.DrivesPerSet != s2.MinioEndpoints[i].DrivesPerSet {
			return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
				s2.MinioEndpoints[i].DrivesPerSet)
		}
		for j, endpoint := range ep.Endpoints {
			if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() {
				return fmt.Errorf("Expected endpoint %s, seen %s", endpoint,
					s2.MinioEndpoints[i].Endpoints[j])
			}
		}
	}
	if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
		return fmt.Errorf("Expected same MINIO_ environment variables and values")
	}
	return nil
}

var skipEnvs = map[string]struct{}{
	"MINIO_OPTS":        {},
	"MINIO_CERT_PASSWD": {},
}

func getServerSystemCfg() ServerSystemConfig {
	envs := env.List("MINIO_")
	envValues := make(map[string]string, len(envs))
	for _, envK := range envs {
		// skip certain environment variables as part
		// of the whitelist and could be configured
		// differently on each nodes, update skipEnvs()
		// map if there are such environment values
		if _, ok := skipEnvs[envK]; ok {
			continue
		}
		envValues[envK] = env.Get(envK, "")
	}
	return ServerSystemConfig{
		MinioPlatform:  fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
		MinioEndpoints: globalEndpoints,
		MinioEnv:       envValues,
	}
}

// HealthHandler returns success if request is valid
func (b *bootstrapRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {}

func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
	ctx := newContext(r, w, "VerifyHandler")
	cfg := getServerSystemCfg()
	logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
	w.(http.Flusher).Flush()
}

// registerBootstrapRESTHandlers - register bootstrap rest router.
func registerBootstrapRESTHandlers(router *mux.Router) {
	server := &bootstrapRESTServer{}
	subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()

	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
		httpTraceHdrs(server.HealthHandler))

	subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
		httpTraceHdrs(server.VerifyHandler))
}

// client to talk to bootstrap NEndpoints.
type bootstrapRESTClient struct {
	endpoint   Endpoint
	restClient *rest.Client
}

// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *bootstrapRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
	if values == nil {
		values = make(url.Values)
	}

	respBody, err = client.restClient.Call(ctx, method, values, body, length)
	if err == nil {
		return respBody, nil
	}

	return nil, err
}

// Stringer provides a canonicalized representation of node.
func (client *bootstrapRESTClient) String() string {
	return client.endpoint.String()
}

// Verify - fetches system server config.
func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSystemConfig) (err error) {
	if newObjectLayerFn() != nil {
		return nil
	}
	respBody, err := client.callWithContext(ctx, bootstrapRESTMethodVerify, nil, nil, -1)
	if err != nil {
		return
	}
	defer xhttp.DrainBody(respBody)
	recvCfg := ServerSystemConfig{}
	if err = json.NewDecoder(respBody).Decode(&recvCfg); err != nil {
		return err
	}
	return srcCfg.Diff(recvCfg)
}

func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools) error {
	srcCfg := getServerSystemCfg()
	clnts := newBootstrapRESTClients(endpointServerPools)
	var onlineServers int
	var offlineEndpoints []string
	var retries int
	for onlineServers < len(clnts)/2 {
		for _, clnt := range clnts {
			if err := clnt.Verify(ctx, srcCfg); err != nil {
				if isNetworkError(err) {
					offlineEndpoints = append(offlineEndpoints, clnt.String())
					continue
				}
				return fmt.Errorf("%s as has incorrect configuration: %w", clnt.String(), err)
			}
			onlineServers++
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			// Sleep for a while - so that we don't go into
			// 100% CPU when half the endpoints are offline.
			time.Sleep(100 * time.Millisecond)
			retries++
			// after 5 retries start logging that servers are not reachable yet
			if retries >= 5 {
				logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers to be online for bootstrap check", len(clnts)/2))
				if len(offlineEndpoints) > 0 {
					logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
				}
				retries = 0 // reset to log again after 5 retries.
			}
			offlineEndpoints = nil
		}
	}
	return nil
}

func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstrapRESTClient {
	seenHosts := set.NewStringSet()
	var clnts []*bootstrapRESTClient
	for _, ep := range endpointServerPools {
		for _, endpoint := range ep.Endpoints {
			if seenHosts.Contains(endpoint.Host) {
				continue
			}
			seenHosts.Add(endpoint.Host)

			// Only proceed for remote endpoints.
			if !endpoint.IsLocal {
				clnts = append(clnts, newBootstrapRESTClient(endpoint))
			}
		}
	}
	return clnts
}

// Returns a new bootstrap client.
func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
	serverURL := &url.URL{
		Scheme: endpoint.Scheme,
		Host:   endpoint.Host,
		Path:   bootstrapRESTPath,
	}

	restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
	restClient.HealthCheckFn = nil

	return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 cmd

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

	"github.com/spf13/cobra"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
	"k8s.io/kubernetes/pkg/util/initsystem"
)

// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
	var skipPreFlight, removeNode bool
	cmd := &cobra.Command{
		Use:   "reset",
		Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.",
		Run: func(cmd *cobra.Command, args []string) {
			r, err := NewReset(skipPreFlight, removeNode)
			kubeadmutil.CheckErr(err)
			kubeadmutil.CheckErr(r.Run(out))
		},
	}

	cmd.PersistentFlags().BoolVar(
		&skipPreFlight, "skip-preflight-checks", false,
		"Skip preflight checks normally run before modifying the system",
	)

	cmd.PersistentFlags().BoolVar(
		&removeNode, "remove-node", true,
		"Remove this node from the pool of nodes in this cluster",
	)

	return cmd
}

type Reset struct {
	removeNode bool
}

func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
	if !skipPreFlight {
		fmt.Println("[preflight] Running pre-flight checks")

		if err := preflight.RunRootCheckOnly(); err != nil {
			return nil, err
		}
	} else {
		fmt.Println("[preflight] Skipping pre-flight checks")
	}

	return &Reset{
		removeNode: removeNode,
	}, nil
}

// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer) error {

	// Try to drain and remove the node from the cluster
	err := drainAndRemoveNode(r.removeNode)
	if err != nil {
		fmt.Printf("[reset] Failed to cleanup node: [%v]\n", err)
	}

	// Try to stop the kubelet service
	initSystem, err := initsystem.GetInitSystem()
	if err != nil {
		fmt.Println("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm because no supported init system was detected.")
		fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
	} else {
		fmt.Println("[reset] Stopping the kubelet service")
		if err := initSystem.ServiceStop("kubelet"); err != nil {
			fmt.Printf("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm: [%v]\n", err)
			fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
		}
	}

	// Try to unmount mounted directories under /var/lib/kubelet in order to be able to remove the /var/lib/kubelet directory later
	fmt.Printf("[reset] Unmounting mounted directories in %q\n", "/var/lib/kubelet")
	umountDirsCmd := "cat /proc/mounts | awk '{print $2}' | grep '/var/lib/kubelet' | xargs -r umount"
	umountOutputBytes, err := exec.Command("sh", "-c", umountDirsCmd).Output()
	if err != nil {
		fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
	}

	dockerCheck := preflight.ServiceCheck{Service: "docker"}
	if warnings, errors := dockerCheck.Check(); len(warnings) == 0 && len(errors) == 0 {
		fmt.Println("[reset] Removing kubernetes-managed containers")
		if err := exec.Command("sh", "-c", "docker ps | grep 'k8s_' | awk '{print $1}' | xargs -r docker rm --force --volumes").Run(); err != nil {
			fmt.Println("[reset] Failed to stop the running containers")
		}
	} else {
		fmt.Println("[reset] docker doesn't seem to be running, skipping the removal of running kubernetes containers")
	}

	dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d"}

	// Only clear etcd data when the etcd manifest is found. In case it is not found, we must assume that the user
	// provided external etcd endpoints. In that case, it is his own responsibility to reset etcd
	etcdManifestPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests/etcd.json")
	if _, err := os.Stat(etcdManifestPath); err == nil {
		dirsToClean = append(dirsToClean, "/var/lib/etcd")
	} else {
		fmt.Printf("[reset] No etcd manifest found in %q, assuming external etcd.\n", etcdManifestPath)
	}

	// Then clean contents from the stateful kubelet, etcd and cni directories
	fmt.Printf("[reset] Deleting contents of stateful directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		cleanDir(dir)
	}

	// Remove contents from the config and pki directories
	resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmapi.GlobalEnvParams.HostPKIPath)

	return nil
}

func drainAndRemoveNode(removeNode bool) error {

	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("failed to detect node hostname")
	}
	hostname = strings.ToLower(hostname)

	// TODO: Use the "native" k8s client for this once we're confident the versioned is working
	kubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")

	getNodesCmd := fmt.Sprintf("kubectl --kubeconfig %s get nodes | grep %s", kubeConfigPath, hostname)
	output, err := exec.Command("sh", "-c", getNodesCmd).Output()
	if err != nil || len(output) == 0 {
		// kubeadm shouldn't drain and/or remove the node when it doesn't exist anymore
		return nil
	}

	fmt.Printf("[reset] Draining node: %q\n", hostname)

	output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "drain", hostname, "--delete-local-data", "--force", "--ignore-daemonsets").Output()
	if err != nil {
		return fmt.Errorf("failed to drain node %q [%s]", hostname, output)
	}

	if removeNode {
		fmt.Printf("[reset] Removing node: %q\n", hostname)

		output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "delete", "node", hostname).Output()
		if err != nil {
			return fmt.Errorf("failed to remove node %q [%s]", hostname, output)
		}
	}

	return nil
}

// cleanDir removes everything in a directory, but not the directory itself
func cleanDir(filepath string) error {
	// If the directory doesn't even exist there's nothing to do, and we do
	// not consider this an error
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		return nil
	}

	d, err := os.Open(filepath)
	if err != nil {
		return err
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}
	for _, name := range names {
		err = os.RemoveAll(path.Join(filepath, name))
		if err != nil {
			return err
		}
	}
	return nil
}

// resetConfigDir is used to cleanup the files kubeadm writes in /etc/kubernetes/.
func resetConfigDir(configPathDir, pkiPathDir string) {
	dirsToClean := []string{
		path.Join(configPathDir, "manifests"),
		pkiPathDir,
	}
	fmt.Printf("[reset] Deleting contents of config directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		err := cleanDir(dir)
		if err != nil {
			fmt.Printf("[reset] Failed to remove directory: %q [%v]\n", dir, err)
		}
	}

	filesToClean := []string{
		path.Join(configPathDir, "admin.conf"),
		path.Join(configPathDir, "kubelet.conf"),
	}
	fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
	for _, path := range filesToClean {
		err := os.RemoveAll(path)
		if err != nil {
			fmt.Printf("[reset] Failed to remove file: %q [%v]\n", path, err)
		}
	}
}
<commit_msg>resetting cluster should check whether docker service is active<commit_after>/*
Copyright 2016 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 cmd

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

	"github.com/spf13/cobra"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
	"k8s.io/kubernetes/pkg/util/initsystem"
)

// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
	var skipPreFlight, removeNode bool
	cmd := &cobra.Command{
		Use:   "reset",
		Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.",
		Run: func(cmd *cobra.Command, args []string) {
			r, err := NewReset(skipPreFlight, removeNode)
			kubeadmutil.CheckErr(err)
			kubeadmutil.CheckErr(r.Run(out))
		},
	}

	cmd.PersistentFlags().BoolVar(
		&skipPreFlight, "skip-preflight-checks", false,
		"Skip preflight checks normally run before modifying the system",
	)

	cmd.PersistentFlags().BoolVar(
		&removeNode, "remove-node", true,
		"Remove this node from the pool of nodes in this cluster",
	)

	return cmd
}

type Reset struct {
	removeNode bool
}

func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
	if !skipPreFlight {
		fmt.Println("[preflight] Running pre-flight checks")

		if err := preflight.RunRootCheckOnly(); err != nil {
			return nil, err
		}
	} else {
		fmt.Println("[preflight] Skipping pre-flight checks")
	}

	return &Reset{
		removeNode: removeNode,
	}, nil
}

// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer) error {

	// Try to drain and remove the node from the cluster
	err := drainAndRemoveNode(r.removeNode)
	if err != nil {
		fmt.Printf("[reset] Failed to cleanup node: [%v]\n", err)
	}

	// Try to stop the kubelet service
	initSystem, err := initsystem.GetInitSystem()
	if err != nil {
		fmt.Println("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm because no supported init system was detected.")
		fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
	} else {
		fmt.Println("[reset] Stopping the kubelet service")
		if err := initSystem.ServiceStop("kubelet"); err != nil {
			fmt.Printf("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm: [%v]\n", err)
			fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
		}
	}

	// Try to unmount mounted directories under /var/lib/kubelet in order to be able to remove the /var/lib/kubelet directory later
	fmt.Printf("[reset] Unmounting mounted directories in %q\n", "/var/lib/kubelet")
	umountDirsCmd := "cat /proc/mounts | awk '{print $2}' | grep '/var/lib/kubelet' | xargs -r umount"
	umountOutputBytes, err := exec.Command("sh", "-c", umountDirsCmd).Output()
	if err != nil {
		fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
	}

	dockerCheck := preflight.ServiceCheck{Service: "docker", CheckIfActive: true}
	if warnings, errors := dockerCheck.Check(); len(warnings) == 0 && len(errors) == 0 {
		fmt.Println("[reset] Removing kubernetes-managed containers")
		if err := exec.Command("sh", "-c", "docker ps | grep 'k8s_' | awk '{print $1}' | xargs -r docker rm --force --volumes").Run(); err != nil {
			fmt.Println("[reset] Failed to stop the running containers")
		}
	} else {
		fmt.Println("[reset] docker doesn't seem to be running, skipping the removal of running kubernetes containers")
	}

	dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d"}

	// Only clear etcd data when the etcd manifest is found. In case it is not found, we must assume that the user
	// provided external etcd endpoints. In that case, it is his own responsibility to reset etcd
	etcdManifestPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests/etcd.json")
	if _, err := os.Stat(etcdManifestPath); err == nil {
		dirsToClean = append(dirsToClean, "/var/lib/etcd")
	} else {
		fmt.Printf("[reset] No etcd manifest found in %q, assuming external etcd.\n", etcdManifestPath)
	}

	// Then clean contents from the stateful kubelet, etcd and cni directories
	fmt.Printf("[reset] Deleting contents of stateful directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		cleanDir(dir)
	}

	// Remove contents from the config and pki directories
	resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmapi.GlobalEnvParams.HostPKIPath)

	return nil
}

func drainAndRemoveNode(removeNode bool) error {

	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("failed to detect node hostname")
	}
	hostname = strings.ToLower(hostname)

	// TODO: Use the "native" k8s client for this once we're confident the versioned is working
	kubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")

	getNodesCmd := fmt.Sprintf("kubectl --kubeconfig %s get nodes | grep %s", kubeConfigPath, hostname)
	output, err := exec.Command("sh", "-c", getNodesCmd).Output()
	if err != nil || len(output) == 0 {
		// kubeadm shouldn't drain and/or remove the node when it doesn't exist anymore
		return nil
	}

	fmt.Printf("[reset] Draining node: %q\n", hostname)

	output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "drain", hostname, "--delete-local-data", "--force", "--ignore-daemonsets").Output()
	if err != nil {
		return fmt.Errorf("failed to drain node %q [%s]", hostname, output)
	}

	if removeNode {
		fmt.Printf("[reset] Removing node: %q\n", hostname)

		output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "delete", "node", hostname).Output()
		if err != nil {
			return fmt.Errorf("failed to remove node %q [%s]", hostname, output)
		}
	}

	return nil
}

// cleanDir removes everything in a directory, but not the directory itself
func cleanDir(filepath string) error {
	// If the directory doesn't even exist there's nothing to do, and we do
	// not consider this an error
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		return nil
	}

	d, err := os.Open(filepath)
	if err != nil {
		return err
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}
	for _, name := range names {
		err = os.RemoveAll(path.Join(filepath, name))
		if err != nil {
			return err
		}
	}
	return nil
}

// resetConfigDir is used to cleanup the files kubeadm writes in /etc/kubernetes/.
func resetConfigDir(configPathDir, pkiPathDir string) {
	dirsToClean := []string{
		path.Join(configPathDir, "manifests"),
		pkiPathDir,
	}
	fmt.Printf("[reset] Deleting contents of config directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		err := cleanDir(dir)
		if err != nil {
			fmt.Printf("[reset] Failed to remove directory: %q [%v]\n", dir, err)
		}
	}

	filesToClean := []string{
		path.Join(configPathDir, "admin.conf"),
		path.Join(configPathDir, "kubelet.conf"),
	}
	fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
	for _, path := range filesToClean {
		err := os.RemoveAll(path)
		if err != nil {
			fmt.Printf("[reset] Failed to remove file: %q [%v]\n", path, err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"net/http"
	"path/filepath"

	"github.com/omeid/go-livereload"
	"github.com/omeid/log"
	"gopkg.in/fsnotify.v1"
)

var (
	livereloadAddr = flag.String("livereload", ":35729", "liverloead servera addr.")
	serverAddr     = flag.String("server", ":8082", "static server addr. Requires -serve ")
	serve          = flag.String("serve", "", "static files folders.")
	strip          = flag.String("strip", "", "path to strip from static files.")
)

func main() {
	flag.Parse()
	log := log.New("livereload ")

	globs := flag.Args()
	if len(globs) == 0 {
		log.Fatalf("No Files To Watch.")
	}

	var files []string

	for _, glob := range globs {

		matches, err := filepath.Glob(glob)
		if err != nil {
			log.Fatal(err)
		}
		files = append(files, matches...)
	}

	if len(files) == 0 {
		log.Fatalf("No Files Matched The Globs.")
	}

	watch, err := fsnotify.NewWatcher()
	if err != nil {
		log.Error(err)
	}
	defer watch.Close()

	for _, file := range files {
		err = watch.Add(file)
		if err != nil {
			log.Error(err)
			return
		}
	}

	if *serve != "" {
		go func() {
			static := http.StripPrefix(*strip, http.FileServer(http.Dir(*serve)))
			log.Infof("Serving '%s' at %s", *serve, *serverAddr)
			log.Fatal(http.ListenAndServe(*serverAddr, static))
		}()
	}
	lr := livereload.New("go-livereload")
	go func() {
		mux := http.NewServeMux()
		mux.HandleFunc("/livereload.js", livereload.LivereloadScript)
		mux.Handle("/", lr)
		log.Infof("Serving livereload at %s", *livereloadAddr)
		log.Fatal(http.ListenAndServe(*livereloadAddr, mux))
	}()

	for {
		select {
		case event := <-watch.Events:
			if event.Op&(fsnotify.Rename|fsnotify.Create|fsnotify.Write) > 0 {
			  log.Infof("Reloading %s", event.Name)
				lr.Reload(event.Name, true)
			}
		case err := <-watch.Errors:
			if err != nil {
				log.Error(err)
			}
		}
	}
}
<commit_msg>  Update fsnotify import url.<commit_after>package main

import (
	"flag"
	"net/http"
	"path/filepath"

	"github.com/fsnotify/fsnotify"
	"github.com/omeid/go-livereload"
	"github.com/omeid/log"
)

var (
	livereloadAddr = flag.String("livereload", ":35729", "liverloead servera addr.")
	serverAddr     = flag.String("server", ":8082", "static server addr. Requires -serve ")
	serve          = flag.String("serve", "", "static files folders.")
	strip          = flag.String("strip", "", "path to strip from static files.")
)

func main() {
	flag.Parse()
	log := log.New("livereload ")

	globs := flag.Args()
	if len(globs) == 0 {
		log.Fatalf("No Files To Watch.")
	}

	var files []string

	for _, glob := range globs {

		matches, err := filepath.Glob(glob)
		if err != nil {
			log.Fatal(err)
		}
		files = append(files, matches...)
	}

	if len(files) == 0 {
		log.Fatalf("No Files Matched The Globs.")
	}

	watch, err := fsnotify.NewWatcher()
	if err != nil {
		log.Error(err)
	}
	defer watch.Close()

	for _, file := range files {
		err = watch.Add(file)
		if err != nil {
			log.Error(err)
			return
		}
	}

	if *serve != "" {
		go func() {
			static := http.StripPrefix(*strip, http.FileServer(http.Dir(*serve)))
			log.Infof("Serving '%s' at %s", *serve, *serverAddr)
			log.Fatal(http.ListenAndServe(*serverAddr, static))
		}()
	}
	lr := livereload.New("go-livereload")
	go func() {
		mux := http.NewServeMux()
		mux.HandleFunc("/livereload.js", livereload.LivereloadScript)
		mux.Handle("/", lr)
		log.Infof("Serving livereload at %s", *livereloadAddr)
		log.Fatal(http.ListenAndServe(*livereloadAddr, mux))
	}()

	for {
		select {
		case event := <-watch.Events:
			if event.Op&(fsnotify.Rename|fsnotify.Create|fsnotify.Write) > 0 {
				log.Infof("Reloading %s", event.Name)
				lr.Reload(event.Name, true)
			}
		case err := <-watch.Errors:
			if err != nil {
				log.Error(err)
			}
		}
	}
}
<|endoftext|>