<commit_before>package main

import (
	"context"
	goflag "flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"sort"
	"strings"
	"time"

	_ "github.com/containers/libpod/pkg/hooks/0.1.0"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/containers/storage/pkg/reexec"
	"github.com/cri-o/cri-o/internal/criocli"
	"github.com/cri-o/cri-o/internal/log"
	"github.com/cri-o/cri-o/internal/signals"
	"github.com/cri-o/cri-o/internal/version"
	libconfig "github.com/cri-o/cri-o/pkg/config"
	"github.com/cri-o/cri-o/server"
	"github.com/cri-o/cri-o/server/metrics"
	"github.com/cri-o/cri-o/utils"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/sirupsen/logrus"
	"github.com/soheilhy/cmux"
	"github.com/urfave/cli/v2"
	"golang.org/x/sys/unix"
	"google.golang.org/grpc"
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
	"k8s.io/klog"
)

func writeCrioGoroutineStacks() {
	path := filepath.Join("/tmp", fmt.Sprintf(
		"crio-goroutine-stacks-%s.log",
		strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", ""),
	))
	if err := utils.WriteGoroutineStacksToFile(path); err != nil {
		logrus.Warnf("Failed to write goroutine stacks: %s", err)
	}
}

func catchShutdown(ctx context.Context, cancel context.CancelFunc, gserver *grpc.Server, sserver *server.Server, hserver *http.Server, signalled *bool) {
	sig := make(chan os.Signal, 2048)
	signal.Notify(sig, signals.Interrupt, signals.Term, unix.SIGUSR1, unix.SIGPIPE, signals.Hup)
	go func() {
		for s := range sig {
			logrus.WithFields(logrus.Fields{
				"signal": s,
			}).Debug("received signal")
			switch s {
			case unix.SIGUSR1:
				writeCrioGoroutineStacks()
				continue
			case unix.SIGPIPE:
				continue
			case signals.Interrupt:
				logrus.Debugf("Caught SIGINT")
			case signals.Term:
				logrus.Debugf("Caught SIGTERM")
			default:
				continue
			}
			*signalled = true
			gserver.GracefulStop()
			hserver.Shutdown(ctx) // nolint: errcheck
			if err := sserver.StopStreamServer(); err != nil {
				logrus.Warnf("error shutting down streaming server: %v", err)
			}
			sserver.StopMonitors()
			cancel()
			if err := sserver.Shutdown(ctx); err != nil {
				logrus.Warnf("error shutting down main service %v", err)
			}
			return
		}
	}()
}

const usage = `OCI-based implementation of Kubernetes Container Runtime Interface Daemon

crio is meant to provide an integration path between OCI conformant runtimes
and the kubelet. Specifically, it implements the Kubelet Container Runtime
Interface (CRI) using OCI conformant runtimes. The scope of crio is tied to the
scope of the CRI.

1. Support multiple image formats including the existing Docker and OCI image formats.
2. Support for multiple means to download images including trust & image verification.
3. Container image management (managing image layers, overlay filesystems, etc).
4. Container process lifecycle management.
5. Monitoring and logging required to satisfy the CRI.
6. Resource isolation as required by the CRI.`

func main() {
	// Unfortunately, there's no way to ask klog to not write to tmp without this kludge.
	// Until something like https://github.com/kubernetes/klog/pull/100 is merged, this will have to do.
	klogFlagSet := goflag.NewFlagSet("klog", goflag.ExitOnError)
	klog.InitFlags(klogFlagSet)

	if err := klogFlagSet.Set("logtostderr", "false"); err != nil {
		fmt.Fprintf(os.Stderr, "unable to set logtostderr for klog: %v\n", err)
	}
	if err := klogFlagSet.Set("alsologtostderr", "false"); err != nil {
		fmt.Fprintf(os.Stderr, "unable to set alsologtostderr for klog: %v\n", err)
	}

	klog.SetOutput(ioutil.Discard)

	// This must be done before reexec.Init()
	ioutils.SetDefaultOptions(ioutils.AtomicFileWriterOptions{NoSync: true})

	if reexec.Init() {
		fmt.Fprintf(os.Stderr, "unable to initialize container storage\n")
		os.Exit(-1)
	}
	app := cli.NewApp()

	app.Name = "crio"
	app.Usage = "OCI-based implementation of Kubernetes Container Runtime Interface"
	app.Authors = []*cli.Author{{Name: "The CRI-O Maintainers"}}
	app.UsageText = usage
	app.Description = app.Usage
	app.Version = version.Version + "\n" + version.Get().String()

	var err error
	app.Flags, app.Metadata, err = criocli.GetFlagsAndMetadata()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	sort.Sort(cli.FlagsByName(app.Flags))
	sort.Sort(cli.FlagsByName(configCommand.Flags))

	app.Commands = criocli.DefaultCommands
	app.Commands = append(app.Commands, []*cli.Command{
		configCommand,
		versionCommand,
		wipeCommand,
	}...)

	app.Before = func(c *cli.Context) (err error) {
		config, err := criocli.GetAndMergeConfigFromContext(c)
		if err != nil {
			return err
		}

		cf := &logrus.TextFormatter{
			TimestampFormat: "2006-01-02 15:04:05.000000000Z07:00",
			FullTimestamp:   true,
		}

		logrus.SetFormatter(cf)

		level, err := logrus.ParseLevel(config.LogLevel)
		if err != nil {
			return err
		}
		logrus.SetLevel(level)
		logrus.AddHook(log.NewFilenameHook())

		filterHook, err := log.NewFilterHook(config.LogFilter)
		if err != nil {
			return err
		}
		logrus.AddHook(filterHook)

		if path := c.String("log"); path != "" {
			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o666)
			if err != nil {
				return err
			}
			logrus.SetOutput(f)
		}

		switch c.String("log-format") {
		case "text":
			// retain logrus's default.
		case "json":
			logrus.SetFormatter(new(logrus.JSONFormatter))
		default:
			return fmt.Errorf("unknown log-format %q", c.String("log-format"))
		}

		return nil
	}

	app.Action = func(c *cli.Context) error {
		ctx, cancel := context.WithCancel(context.Background())
		if c.Bool("profile") {
			profilePort := c.Int("profile-port")
			profileEndpoint := fmt.Sprintf("localhost:%v", profilePort)
			go func() {
				logrus.Debugf("starting profiling server on %v", profileEndpoint)
				if err := http.ListenAndServe(profileEndpoint, nil); err != nil {
					logrus.Fatalf("unable to run profiling server: %v", err)
				}
			}()
		}

		if c.Args().Len() > 0 {
			cancel()
			return fmt.Errorf("command %q not supported", c.Args().Get(0))
		}

		config, ok := c.App.Metadata["config"].(*libconfig.Config)
		if !ok {
			cancel()
			return fmt.Errorf("type assertion error when accessing server config")
		}

		// Validate the configuration during runtime
		if err := config.Validate(true); err != nil {
			cancel()
			return err
		}

		lis, err := server.Listen("unix", config.Listen)
		if err != nil {
			logrus.Fatalf("failed to listen: %v", err)
		}

		grpcServer := grpc.NewServer(
			grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
				metrics.UnaryInterceptor(),
				log.UnaryInterceptor(),
			)),
			grpc.StreamInterceptor(log.StreamInterceptor()),
			grpc.MaxSendMsgSize(config.GRPCMaxSendMsgSize),
			grpc.MaxRecvMsgSize(config.GRPCMaxRecvMsgSize),
		)

		service, err := server.New(ctx, config)
		if err != nil {
			logrus.Fatal(err)
		}

		// Immediately upon start up, write our new version files
		// we write one to a tmpfs, so we can detect when a node rebooted.
		// in this sitaution, we want to wipe containers
		if err := version.WriteVersionFile(config.VersionFile); err != nil {
			logrus.Fatal(err)
		}
		// we then write to a persistent directory. This is to check if crio has upgraded
		// if it has, we should wipe images
		if err := version.WriteVersionFile(config.VersionFilePersist); err != nil {
			logrus.Fatal(err)
		}

		runtime.RegisterRuntimeServiceServer(grpcServer, service)
		runtime.RegisterImageServiceServer(grpcServer, service)

		// after the daemon is done setting up we can notify systemd api
		notifySystem()

		go func() {
			service.StartExitMonitor()
		}()
		hookSync := make(chan error, 2)
		if service.ContainerServer.Hooks == nil {
			hookSync <- err // so we don't block during cleanup
		} else {
			go service.ContainerServer.Hooks.Monitor(ctx, hookSync)
			err = <-hookSync
			if err != nil {
				cancel()
				logrus.Fatal(err)
			}
		}

		m := cmux.New(lis)
		grpcL := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
		httpL := m.Match(cmux.HTTP1Fast())

		infoMux := service.GetInfoMux()
		httpServer := &http.Server{
			Handler:     infoMux,
			ReadTimeout: 5 * time.Second,
		}

		graceful := false
		catchShutdown(ctx, cancel, grpcServer, service, httpServer, &graceful)

		go func() {
			if err := grpcServer.Serve(grpcL); err != nil {
				logrus.Errorf("unable to run GRPC server: %v", err)
			}
		}()
		go func() {
			if err := httpServer.Serve(httpL); err != nil {
				logrus.Debugf("closed http server")
			}
		}()

		serverCloseCh := make(chan struct{})
		go func() {
			defer close(serverCloseCh)
			if err := m.Serve(); err != nil {
				if graceful && strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
					err = nil
				} else {
					logrus.Errorf("Failed to serve grpc request: %v", err)
				}
			}
		}()

		streamServerCloseCh := service.StreamingServerCloseChan()
		serverMonitorsCh := service.MonitorsCloseChan()
		select {
		case <-streamServerCloseCh:
		case <-serverMonitorsCh:
		case <-serverCloseCh:
		}

		if err := service.Shutdown(ctx); err != nil {
			logrus.Warnf("error shutting down service: %v", err)
		}
		cancel()

		<-streamServerCloseCh
		logrus.Debug("closed stream server")
		<-serverMonitorsCh
		logrus.Debug("closed monitors")
		err = <-hookSync
		if err == nil || err == context.Canceled {
			logrus.Debug("closed hook monitor")
		} else {
			logrus.Errorf("hook monitor failed: %v", err)
		}
		<-serverCloseCh
		logrus.Debug("closed main server")

		return nil
	}

	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}
<commit_msg>Bring back pprof<commit_after>package main

import (
	"context"
	goflag "flag"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"path/filepath"
	"sort"
	"strings"
	"time"

	_ "github.com/containers/libpod/pkg/hooks/0.1.0"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/containers/storage/pkg/reexec"
	"github.com/cri-o/cri-o/internal/criocli"
	"github.com/cri-o/cri-o/internal/log"
	"github.com/cri-o/cri-o/internal/signals"
	"github.com/cri-o/cri-o/internal/version"
	libconfig "github.com/cri-o/cri-o/pkg/config"
	"github.com/cri-o/cri-o/server"
	"github.com/cri-o/cri-o/server/metrics"
	"github.com/cri-o/cri-o/utils"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/sirupsen/logrus"
	"github.com/soheilhy/cmux"
	"github.com/urfave/cli/v2"
	"golang.org/x/sys/unix"
	"google.golang.org/grpc"
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
	"k8s.io/klog"
)

func writeCrioGoroutineStacks() {
	path := filepath.Join("/tmp", fmt.Sprintf(
		"crio-goroutine-stacks-%s.log",
		strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", ""),
	))
	if err := utils.WriteGoroutineStacksToFile(path); err != nil {
		logrus.Warnf("Failed to write goroutine stacks: %s", err)
	}
}

func catchShutdown(ctx context.Context, cancel context.CancelFunc, gserver *grpc.Server, sserver *server.Server, hserver *http.Server, signalled *bool) {
	sig := make(chan os.Signal, 2048)
	signal.Notify(sig, signals.Interrupt, signals.Term, unix.SIGUSR1, unix.SIGPIPE, signals.Hup)
	go func() {
		for s := range sig {
			logrus.WithFields(logrus.Fields{
				"signal": s,
			}).Debug("received signal")
			switch s {
			case unix.SIGUSR1:
				writeCrioGoroutineStacks()
				continue
			case unix.SIGPIPE:
				continue
			case signals.Interrupt:
				logrus.Debugf("Caught SIGINT")
			case signals.Term:
				logrus.Debugf("Caught SIGTERM")
			default:
				continue
			}
			*signalled = true
			gserver.GracefulStop()
			hserver.Shutdown(ctx) // nolint: errcheck
			if err := sserver.StopStreamServer(); err != nil {
				logrus.Warnf("error shutting down streaming server: %v", err)
			}
			sserver.StopMonitors()
			cancel()
			if err := sserver.Shutdown(ctx); err != nil {
				logrus.Warnf("error shutting down main service %v", err)
			}
			return
		}
	}()
}

const usage = `OCI-based implementation of Kubernetes Container Runtime Interface Daemon

crio is meant to provide an integration path between OCI conformant runtimes
and the kubelet. Specifically, it implements the Kubelet Container Runtime
Interface (CRI) using OCI conformant runtimes. The scope of crio is tied to the
scope of the CRI.

1. Support multiple image formats including the existing Docker and OCI image formats.
2. Support for multiple means to download images including trust & image verification.
3. Container image management (managing image layers, overlay filesystems, etc).
4. Container process lifecycle management.
5. Monitoring and logging required to satisfy the CRI.
6. Resource isolation as required by the CRI.`

func main() {
	// Unfortunately, there's no way to ask klog to not write to tmp without this kludge.
	// Until something like https://github.com/kubernetes/klog/pull/100 is merged, this will have to do.
	klogFlagSet := goflag.NewFlagSet("klog", goflag.ExitOnError)
	klog.InitFlags(klogFlagSet)

	if err := klogFlagSet.Set("logtostderr", "false"); err != nil {
		fmt.Fprintf(os.Stderr, "unable to set logtostderr for klog: %v\n", err)
	}
	if err := klogFlagSet.Set("alsologtostderr", "false"); err != nil {
		fmt.Fprintf(os.Stderr, "unable to set alsologtostderr for klog: %v\n", err)
	}

	klog.SetOutput(ioutil.Discard)

	// This must be done before reexec.Init()
	ioutils.SetDefaultOptions(ioutils.AtomicFileWriterOptions{NoSync: true})

	if reexec.Init() {
		fmt.Fprintf(os.Stderr, "unable to initialize container storage\n")
		os.Exit(-1)
	}
	app := cli.NewApp()

	app.Name = "crio"
	app.Usage = "OCI-based implementation of Kubernetes Container Runtime Interface"
	app.Authors = []*cli.Author{{Name: "The CRI-O Maintainers"}}
	app.UsageText = usage
	app.Description = app.Usage
	app.Version = version.Version + "\n" + version.Get().String()

	var err error
	app.Flags, app.Metadata, err = criocli.GetFlagsAndMetadata()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	sort.Sort(cli.FlagsByName(app.Flags))
	sort.Sort(cli.FlagsByName(configCommand.Flags))

	app.Commands = criocli.DefaultCommands
	app.Commands = append(app.Commands, []*cli.Command{
		configCommand,
		versionCommand,
		wipeCommand,
	}...)

	app.Before = func(c *cli.Context) (err error) {
		config, err := criocli.GetAndMergeConfigFromContext(c)
		if err != nil {
			return err
		}

		cf := &logrus.TextFormatter{
			TimestampFormat: "2006-01-02 15:04:05.000000000Z07:00",
			FullTimestamp:   true,
		}

		logrus.SetFormatter(cf)

		level, err := logrus.ParseLevel(config.LogLevel)
		if err != nil {
			return err
		}
		logrus.SetLevel(level)
		logrus.AddHook(log.NewFilenameHook())

		filterHook, err := log.NewFilterHook(config.LogFilter)
		if err != nil {
			return err
		}
		logrus.AddHook(filterHook)

		if path := c.String("log"); path != "" {
			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o666)
			if err != nil {
				return err
			}
			logrus.SetOutput(f)
		}

		switch c.String("log-format") {
		case "text":
			// retain logrus's default.
		case "json":
			logrus.SetFormatter(new(logrus.JSONFormatter))
		default:
			return fmt.Errorf("unknown log-format %q", c.String("log-format"))
		}

		return nil
	}

	app.Action = func(c *cli.Context) error {
		ctx, cancel := context.WithCancel(context.Background())
		if c.Bool("profile") {
			profilePort := c.Int("profile-port")
			profileEndpoint := fmt.Sprintf("localhost:%v", profilePort)
			go func() {
				logrus.Debugf("starting profiling server on %v", profileEndpoint)
				if err := http.ListenAndServe(profileEndpoint, nil); err != nil {
					logrus.Fatalf("unable to run profiling server: %v", err)
				}
			}()
		}

		if c.Args().Len() > 0 {
			cancel()
			return fmt.Errorf("command %q not supported", c.Args().Get(0))
		}

		config, ok := c.App.Metadata["config"].(*libconfig.Config)
		if !ok {
			cancel()
			return fmt.Errorf("type assertion error when accessing server config")
		}

		// Validate the configuration during runtime
		if err := config.Validate(true); err != nil {
			cancel()
			return err
		}

		lis, err := server.Listen("unix", config.Listen)
		if err != nil {
			logrus.Fatalf("failed to listen: %v", err)
		}

		grpcServer := grpc.NewServer(
			grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
				metrics.UnaryInterceptor(),
				log.UnaryInterceptor(),
			)),
			grpc.StreamInterceptor(log.StreamInterceptor()),
			grpc.MaxSendMsgSize(config.GRPCMaxSendMsgSize),
			grpc.MaxRecvMsgSize(config.GRPCMaxRecvMsgSize),
		)

		service, err := server.New(ctx, config)
		if err != nil {
			logrus.Fatal(err)
		}

		// Immediately upon start up, write our new version files
		// we write one to a tmpfs, so we can detect when a node rebooted.
		// in this sitaution, we want to wipe containers
		if err := version.WriteVersionFile(config.VersionFile); err != nil {
			logrus.Fatal(err)
		}
		// we then write to a persistent directory. This is to check if crio has upgraded
		// if it has, we should wipe images
		if err := version.WriteVersionFile(config.VersionFilePersist); err != nil {
			logrus.Fatal(err)
		}

		runtime.RegisterRuntimeServiceServer(grpcServer, service)
		runtime.RegisterImageServiceServer(grpcServer, service)

		// after the daemon is done setting up we can notify systemd api
		notifySystem()

		go func() {
			service.StartExitMonitor()
		}()
		hookSync := make(chan error, 2)
		if service.ContainerServer.Hooks == nil {
			hookSync <- err // so we don't block during cleanup
		} else {
			go service.ContainerServer.Hooks.Monitor(ctx, hookSync)
			err = <-hookSync
			if err != nil {
				cancel()
				logrus.Fatal(err)
			}
		}

		m := cmux.New(lis)
		grpcL := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
		httpL := m.Match(cmux.HTTP1Fast())

		infoMux := service.GetInfoMux()
		httpServer := &http.Server{
			Handler:     infoMux,
			ReadTimeout: 5 * time.Second,
		}

		graceful := false
		catchShutdown(ctx, cancel, grpcServer, service, httpServer, &graceful)

		go func() {
			if err := grpcServer.Serve(grpcL); err != nil {
				logrus.Errorf("unable to run GRPC server: %v", err)
			}
		}()
		go func() {
			if err := httpServer.Serve(httpL); err != nil {
				logrus.Debugf("closed http server")
			}
		}()

		serverCloseCh := make(chan struct{})
		go func() {
			defer close(serverCloseCh)
			if err := m.Serve(); err != nil {
				if graceful && strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
					err = nil
				} else {
					logrus.Errorf("Failed to serve grpc request: %v", err)
				}
			}
		}()

		streamServerCloseCh := service.StreamingServerCloseChan()
		serverMonitorsCh := service.MonitorsCloseChan()
		select {
		case <-streamServerCloseCh:
		case <-serverMonitorsCh:
		case <-serverCloseCh:
		}

		if err := service.Shutdown(ctx); err != nil {
			logrus.Warnf("error shutting down service: %v", err)
		}
		cancel()

		<-streamServerCloseCh
		logrus.Debug("closed stream server")
		<-serverMonitorsCh
		logrus.Debug("closed monitors")
		err = <-hookSync
		if err == nil || err == context.Canceled {
			logrus.Debug("closed hook monitor")
		} else {
			logrus.Errorf("hook monitor failed: %v", err)
		}
		<-serverCloseCh
		logrus.Debug("closed main server")

		return nil
	}

	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/require"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

func cleanupFork(t *testing.T, project string) {
	// Failing to find the project will fail the test and is a legit
	// failure case since its the only thing asserting the project exists
	// (was forked)
	p, err := lab.FindProject(project)
	if err != nil {
		t.Fatal(errors.Wrap(err, "failed to find project "+project+" for cleanup"))
	}
	err = lab.ProjectDelete(p.ID)
	if err != nil {
		t.Fatal(errors.Wrap(err, "failed to delete project "+project+" during cleanup"))
	}
}

func Test_fork(t *testing.T) {
	tests := []struct {
		desc      string
		args      []string
		name      string
		path      string
		namespace string
	}{
		{
			desc:      "do_fork",
			args:      []string{},
			name:      "fork_test",
			path:      "",
			namespace: "",
		},
		{
			desc:      "do_fork_name",
			args:      []string{"-n", "fork_test_name"},
			name:      "fork_test_name",
			path:      "",
			namespace: "",
		},
		{
			desc:      "do_fork_path",
			args:      []string{"-p", "fork_test_path"},
			name:      "fork_test",
			path:      "fork_test_path",
			namespace: "",
		},
		{
			desc:      "do_fork_name_path",
			args:      []string{"-n", "fork_test_name_1", "-p", "fork_test_path_1"},
			name:      "fork_test_name_1",
			path:      "fork_test_path_1",
			namespace: "",
		},
		{
			desc:      "do_fork_namespace",
			args:      []string{"-m", "lab-testing-test-group"},
			name:      "fork_test",
			path:      "",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_name",
			args:      []string{"-m", "lab-testing-test-group", "-n", "fork_test_name"},
			name:      "fork_test_name",
			path:      "",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_path",
			args:      []string{"-m", "lab-testing-test-group", "-p", "fork_test_path"},
			name:      "fork_test",
			path:      "fork_test_path",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_name_path",
			args:      []string{"-m", "lab-testing-test-group", "-n", "fork_test_name_1", "-p", "fork_test_path_1"},
			name:      "fork_test_name_1",
			path:      "fork_test_path_1",
			namespace: "lab-testing-test-group",
		},
	}

	t.Parallel()

	for _, test := range tests {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			// remove the .git/config so no remotes exist
			repo := copyTestRepo(t)
			os.Remove(path.Join(repo, ".git/config"))

			cmd := exec.Command("git", "remote", "add", "origin", "git@gitlab.com:zaquestion/fork_test")
			cmd.Dir = repo
			err := cmd.Run()
			if err != nil {
				t.Fatal(err)
			}

			args := []string{"fork"}
			if len(test.args) > 0 {
				args = append(args, test.args...)
			}
			cmd = exec.Command(labBinaryPath, args...)
			cmd.Dir = repo
			b, err := cmd.CombinedOutput()
			out := string(b)
			if err != nil {
				t.Log(out)
				t.Fatal(err)
			}

			namespace := "lab-testing"
			if test.namespace != "" {
				namespace = test.namespace
			}
			name := test.name
			if test.path != "" {
				name = test.path
			}
			project := namespace + "/" + name

			require.Contains(t, out, "From gitlab.com:"+project)
			require.Contains(t, out, "new remote: "+namespace)

			cmd = exec.Command("git", "remote", "-v")
			cmd.Dir = repo

			b, err = cmd.CombinedOutput()
			if err != nil {
				t.Fatal(err)
			}
			require.Contains(t, string(b), namespace)
			time.Sleep(2 * time.Second)
			cleanupFork(t, project)
		})
	}
}

func Test_determineForkRemote(t *testing.T) {
	tests := []struct {
		desc     string
		project  string
		expected string
	}{
		{"project is forked from repo", "zaquestion", "lab-testing"},
		{"project is user", "lab-testing", "upstream"},
	}

	for _, test := range tests {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			require.Equal(t, test.expected, determineForkRemote(test.project))
		})
	}
}
<commit_msg>fork: add test for --no-wait option<commit_after>package cmd

import (
	"os"
	"os/exec"
	"path"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/require"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

func cleanupFork(t *testing.T, project string) {
	// Failing to find the project will fail the test and is a legit
	// failure case since its the only thing asserting the project exists
	// (was forked)
	p, err := lab.FindProject(project)
	if err != nil {
		t.Fatal(errors.Wrap(err, "failed to find project "+project+" for cleanup"))
	}
	err = lab.ProjectDelete(p.ID)
	if err != nil {
		t.Fatal(errors.Wrap(err, "failed to delete project "+project+" during cleanup"))
	}
}

func Test_fork(t *testing.T) {
	tests := []struct {
		desc      string
		args      []string
		name      string
		path      string
		namespace string
	}{
		{
			desc:      "do_fork",
			args:      []string{},
			name:      "fork_test",
			path:      "",
			namespace: "",
		},
		{
			desc:      "do_fork_name",
			args:      []string{"-n", "fork_test_name"},
			name:      "fork_test_name",
			path:      "",
			namespace: "",
		},
		{
			desc:      "do_fork_path",
			args:      []string{"-p", "fork_test_path"},
			name:      "fork_test",
			path:      "fork_test_path",
			namespace: "",
		},
		{
			desc:      "do_fork_name_path",
			args:      []string{"-n", "fork_test_name_1", "-p", "fork_test_path_1"},
			name:      "fork_test_name_1",
			path:      "fork_test_path_1",
			namespace: "",
		},
		{
			desc:      "do_fork_namespace",
			args:      []string{"-m", "lab-testing-test-group"},
			name:      "fork_test",
			path:      "",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_name",
			args:      []string{"-m", "lab-testing-test-group", "-n", "fork_test_name"},
			name:      "fork_test_name",
			path:      "",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_path",
			args:      []string{"-m", "lab-testing-test-group", "-p", "fork_test_path"},
			name:      "fork_test",
			path:      "fork_test_path",
			namespace: "lab-testing-test-group",
		},
		{
			desc:      "do_fork_namespace_name_path",
			args:      []string{"-m", "lab-testing-test-group", "-n", "fork_test_name_1", "-p", "fork_test_path_1"},
			name:      "fork_test_name_1",
			path:      "fork_test_path_1",
			namespace: "lab-testing-test-group",
		},
	}

	t.Parallel()

	for _, test := range tests {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			// remove the .git/config so no remotes exist
			repo := copyTestRepo(t)
			os.Remove(path.Join(repo, ".git/config"))

			cmd := exec.Command("git", "remote", "add", "origin", "git@gitlab.com:zaquestion/fork_test")
			cmd.Dir = repo
			err := cmd.Run()
			if err != nil {
				t.Fatal(err)
			}

			args := []string{"fork"}
			if len(test.args) > 0 {
				args = append(args, test.args...)
			}
			cmd = exec.Command(labBinaryPath, args...)
			cmd.Dir = repo
			b, err := cmd.CombinedOutput()
			out := string(b)
			if err != nil {
				t.Log(out)
				t.Fatal(err)
			}

			namespace := "lab-testing"
			if test.namespace != "" {
				namespace = test.namespace
			}
			name := test.name
			if test.path != "" {
				name = test.path
			}
			project := namespace + "/" + name

			require.Contains(t, out, "From gitlab.com:"+project)
			require.Contains(t, out, "new remote: "+namespace)

			cmd = exec.Command("git", "remote", "-v")
			cmd.Dir = repo

			b, err = cmd.CombinedOutput()
			if err != nil {
				t.Fatal(err)
			}
			require.Contains(t, string(b), namespace)
			time.Sleep(2 * time.Second)
			cleanupFork(t, project)
		})
	}
}

func Test_forkWait(t *testing.T) {
	repo := copyTestRepo(t)
	os.Remove(path.Join(repo, ".git/config"))

	cmd := exec.Command("git", "remote", "add", "origin", "git@gitlab.com:zaquestion/fork_test")
	cmd.Dir = repo
	err := cmd.Run()
	if err != nil {
		t.Fatal(err)
	}

	// The default behavior is to "wait" for the fork and it's already being
	// tested in the Test_fork() test. Here we only test the --no-wait
	// option, which we can't effectively test because we don't have a big
	// enough repo.
	t.Run("fork_nowait", func(t *testing.T) {
		cmd = exec.Command(labBinaryPath, []string{"fork", "--no-wait"}...)
		cmd.Dir = repo
		b, err := cmd.CombinedOutput()
		out := string(b)
		if err != nil {
			t.Log(out)
			t.Fatal(err)
		}
		require.Contains(t, out, "From gitlab.com:lab-testing/fork_test")
		require.Contains(t, out, "new remote: lab-testing")
		cleanupFork(t, "fork_test")
	})
}

func Test_determineForkRemote(t *testing.T) {
	tests := []struct {
		desc     string
		project  string
		expected string
	}{
		{"project is forked from repo", "zaquestion", "lab-testing"},
		{"project is user", "lab-testing", "upstream"},
	}

	for _, test := range tests {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			require.Equal(t, test.expected, determineForkRemote(test.project))
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/ghodss/yaml"
	"github.com/spf13/cobra"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/lint"
	"k8s.io/helm/pkg/lint/support"
	"k8s.io/helm/pkg/strvals"
)

var longLintHelp = `
This command takes a path to a chart and runs a series of tests to verify that
the chart is well-formed.

If the linter encounters things that will cause the chart to fail installation,
it will emit [ERROR] messages. If it encounters issues that break with convention
or recommendation, it will emit [WARNING] messages.
`

type lintCmd struct {
	valueFiles valueFiles
	values     []string
	sValues    []string
	namespace  string
	strict     bool
	paths      []string
	out        io.Writer
}

func newLintCmd(out io.Writer) *cobra.Command {
	l := &lintCmd{
		paths: []string{"."},
		out:   out,
	}
	cmd := &cobra.Command{
		Use:   "lint [flags] PATH",
		Short: "examines a chart for possible issues",
		Long:  longLintHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) > 0 {
				l.paths = args
			}
			return l.run()
		},
	}

	cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
	cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
	cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
	cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)")
	cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings")

	return cmd
}

var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)")

func (l *lintCmd) run() error {
	var lowestTolerance int
	if l.strict {
		lowestTolerance = support.WarningSev
	} else {
		lowestTolerance = support.ErrorSev
	}

	// Get the raw values
	rvals, err := l.vals()
	if err != nil {
		return err
	}

	var total int
	var failures int
	rc := 0
	for _, path := range l.paths {
		if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
			fmt.Println("==> Skipping", path)
			fmt.Println(err)
			if err == errLintNoChart {
				rc = 1
			}
		} else {
			fmt.Println("==> Linting", path)

			if len(linter.Messages) == 0 {
				fmt.Println("Lint OK")
			}

			for _, msg := range linter.Messages {
				fmt.Println(msg)
			}

			total = total + 1
			if linter.HighestSeverity >= lowestTolerance {
				failures = failures + 1
			}
		}
		fmt.Println("")
	}

	msg := fmt.Sprintf("%d chart(s) linted", total)
	if failures > 0 {
		return fmt.Errorf("%s, %d chart(s) failed", msg, failures)
	}

	fmt.Fprintf(l.out, "%s, no failures\n", msg)

	if rc != 0 {
		os.Exit(rc)
	}

	return nil
}

func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) {
	var chartPath string
	linter := support.Linter{}

	if strings.HasSuffix(path, ".tgz") {
		tempDir, err := ioutil.TempDir("", "helm-lint")
		if err != nil {
			return linter, err
		}
		defer os.RemoveAll(tempDir)

		file, err := os.Open(path)
		if err != nil {
			return linter, err
		}
		defer file.Close()

		if err = chartutil.Expand(tempDir, file); err != nil {
			return linter, err
		}

		lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-")
		if lastHyphenIndex <= 0 {
			return linter, fmt.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path))
		}
		base := filepath.Base(path)[:lastHyphenIndex]
		chartPath = filepath.Join(tempDir, base)
	} else {
		chartPath = path
	}

	// Guard: Error out of this is not a chart.
	if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
		return linter, errLintNoChart
	}

	return lint.All(chartPath, vals, namespace, strict), nil
}

func (l *lintCmd) vals() ([]byte, error) {
	base := map[string]interface{}{}

	// User specified a values files via -f/--values
	for _, filePath := range l.valueFiles {
		currentMap := map[string]interface{}{}
		bytes, err := ioutil.ReadFile(filePath)
		if err != nil {
			return []byte{}, err
		}

		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
			return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
		}
		// Merge with the previous map
		base = mergeValues(base, currentMap)
	}

	// User specified a value via --set
	for _, value := range l.values {
		if err := strvals.ParseInto(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
		}
	}

	// User specified a value via --set-string
	for _, value := range l.sValues {
		if err := strvals.ParseIntoString(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
		}
	}

	return yaml.Marshal(base)
}
<commit_msg>using existing mechanism to flag for failures<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/ghodss/yaml"
	"github.com/spf13/cobra"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/lint"
	"k8s.io/helm/pkg/lint/support"
	"k8s.io/helm/pkg/strvals"
)

var longLintHelp = `
This command takes a path to a chart and runs a series of tests to verify that
the chart is well-formed.

If the linter encounters things that will cause the chart to fail installation,
it will emit [ERROR] messages. If it encounters issues that break with convention
or recommendation, it will emit [WARNING] messages.
`

type lintCmd struct {
	valueFiles valueFiles
	values     []string
	sValues    []string
	namespace  string
	strict     bool
	paths      []string
	out        io.Writer
}

func newLintCmd(out io.Writer) *cobra.Command {
	l := &lintCmd{
		paths: []string{"."},
		out:   out,
	}
	cmd := &cobra.Command{
		Use:   "lint [flags] PATH",
		Short: "examines a chart for possible issues",
		Long:  longLintHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) > 0 {
				l.paths = args
			}
			return l.run()
		},
	}

	cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
	cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
	cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
	cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)")
	cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings")

	return cmd
}

var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)")

func (l *lintCmd) run() error {
	var lowestTolerance int
	if l.strict {
		lowestTolerance = support.WarningSev
	} else {
		lowestTolerance = support.ErrorSev
	}

	// Get the raw values
	rvals, err := l.vals()
	if err != nil {
		return err
	}

	var total int
	var failures int
	for _, path := range l.paths {
		if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
			fmt.Println("==> Skipping", path)
			fmt.Println(err)
			if err == errLintNoChart {
				failures = failures + 1
			}
		} else {
			fmt.Println("==> Linting", path)

			if len(linter.Messages) == 0 {
				fmt.Println("Lint OK")
			}

			for _, msg := range linter.Messages {
				fmt.Println(msg)
			}

			total = total + 1
			if linter.HighestSeverity >= lowestTolerance {
				failures = failures + 1
			}
		}
		fmt.Println("")
	}

	msg := fmt.Sprintf("%d chart(s) linted", total)
	if failures > 0 {
		return fmt.Errorf("%s, %d chart(s) failed", msg, failures)
	}

	fmt.Fprintf(l.out, "%s, no failures\n", msg)

	return nil
}

func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) {
	var chartPath string
	linter := support.Linter{}

	if strings.HasSuffix(path, ".tgz") {
		tempDir, err := ioutil.TempDir("", "helm-lint")
		if err != nil {
			return linter, err
		}
		defer os.RemoveAll(tempDir)

		file, err := os.Open(path)
		if err != nil {
			return linter, err
		}
		defer file.Close()

		if err = chartutil.Expand(tempDir, file); err != nil {
			return linter, err
		}

		lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-")
		if lastHyphenIndex <= 0 {
			return linter, fmt.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path))
		}
		base := filepath.Base(path)[:lastHyphenIndex]
		chartPath = filepath.Join(tempDir, base)
	} else {
		chartPath = path
	}

	// Guard: Error out of this is not a chart.
	if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
		return linter, errLintNoChart
	}

	return lint.All(chartPath, vals, namespace, strict), nil
}

func (l *lintCmd) vals() ([]byte, error) {
	base := map[string]interface{}{}

	// User specified a values files via -f/--values
	for _, filePath := range l.valueFiles {
		currentMap := map[string]interface{}{}
		bytes, err := ioutil.ReadFile(filePath)
		if err != nil {
			return []byte{}, err
		}

		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
			return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
		}
		// Merge with the previous map
		base = mergeValues(base, currentMap)
	}

	// User specified a value via --set
	for _, value := range l.values {
		if err := strvals.ParseInto(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
		}
	}

	// User specified a value via --set-string
	for _, value := range l.sValues {
		if err := strvals.ParseIntoString(value, base); err != nil {
			return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
		}
	}

	return yaml.Marshal(base)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"fmt"
	"strings"

	"github.com/gosuri/uitable"
	"github.com/spf13/cobra"

	"k8s.io/helm/pkg/helm"
	"k8s.io/helm/pkg/proto/hapi/release"
	"k8s.io/helm/pkg/proto/hapi/services"
	"k8s.io/helm/pkg/timeconv"
)

var listHelp = `
This command lists all of the currently deployed releases.

By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date.

If an argument is provided, it will be treated as a filter. Filters are
regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned.

	$ helm list -l 'ara[a-z]+'
	NAME            	UPDATED                 	CHART
	maudlin-arachnid	Mon May  9 16:07:08 2016	alpine-0.1.0

If no results are found, 'helm list' will exit 0, but with no output (or in
the case of '-l', only headers).

By default, up to 256 items may be returned. To limit this, use the '--max' flag.
Setting '--max' to 0 will not return all results. Rather, it will return the
server's default, which may be much higher than 256. Pairing the '--max'
flag with the '--offset' flag allows you to page through results.
`

var listCommand = &cobra.Command{
	Use:               "list [flags] [FILTER]",
	Short:             "list releases",
	Long:              listHelp,
	RunE:              listCmd,
	Aliases:           []string{"ls"},
	PersistentPreRunE: setupConnection,
}

var (
	listLong     bool
	listMax      int
	listOffset   string
	listByDate   bool
	listSortDesc bool
)

func init() {
	f := listCommand.Flags()
	f.BoolVarP(&listLong, "long", "l", false, "output long listing format")
	f.BoolVarP(&listByDate, "date", "d", false, "sort by release date")
	f.BoolVarP(&listSortDesc, "reverse", "r", false, "reverse the sort order")
	f.IntVarP(&listMax, "max", "m", 256, "maximum number of releases to fetch")
	f.StringVarP(&listOffset, "offset", "o", "", "the next release name in the list, used to offset from start value")

	RootCommand.AddCommand(listCommand)
}

func listCmd(cmd *cobra.Command, args []string) error {
	var filter string
	if len(args) > 0 {
		filter = strings.Join(args, " ")
	}

	sortBy := services.ListSort_NAME
	if listByDate {
		sortBy = services.ListSort_LAST_RELEASED
	}

	sortOrder := services.ListSort_ASC
	if listSortDesc {
		sortOrder = services.ListSort_DESC
	}

	res, err := helm.ListReleases(listMax, listOffset, sortBy, sortOrder, filter)
	if err != nil {
		return prettyError(err)
	}

	if len(res.Releases) == 0 {
		return nil
	}

	if res.Next != "" {
		fmt.Printf("\tnext: %s", res.Next)
	}

	rels := res.Releases
	if listLong {
		return formatList(rels)
	}
	for _, r := range rels {
		fmt.Println(r.Name)
	}

	return nil
}

func formatList(rels []*release.Release) error {
	table := uitable.New()
	table.MaxColWidth = 30
	table.AddRow("NAME", "UPDATED", "STATUS", "CHART")
	for _, r := range rels {
		c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)
		t := timeconv.String(r.Info.LastDeployed)
		s := r.Info.Status.Code.String()
		table.AddRow(r.Name, t, s, c)
	}
	fmt.Println(table)

	return nil
}
<commit_msg>ref(cmd): refactor out globals and init()<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/gosuri/uitable"
	"github.com/spf13/cobra"

	"k8s.io/helm/pkg/helm"
	"k8s.io/helm/pkg/proto/hapi/release"
	"k8s.io/helm/pkg/proto/hapi/services"
	"k8s.io/helm/pkg/timeconv"
)

var listHelp = `
This command lists all of the currently deployed releases.

By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date.

If an argument is provided, it will be treated as a filter. Filters are
regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned.

	$ helm list -l 'ara[a-z]+'
	NAME            	UPDATED                 	CHART
	maudlin-arachnid	Mon May  9 16:07:08 2016	alpine-0.1.0

If no results are found, 'helm list' will exit 0, but with no output (or in
the case of '-l', only headers).

By default, up to 256 items may be returned. To limit this, use the '--max' flag.
Setting '--max' to 0 will not return all results. Rather, it will return the
server's default, which may be much higher than 256. Pairing the '--max'
flag with the '--offset' flag allows you to page through results.
`

type lister struct {
	long     bool
	max      int
	offset   string
	byDate   bool
	sortDesc bool
	out      io.Writer
}

func newListCmd(out io.Writer) *cobra.Command {
	list := &lister{
		out: out,
	}
	cmd := &cobra.Command{
		Use:               "list [flags] [FILTER]",
		Short:             "list releases",
		Long:              listHelp,
		Aliases:           []string{"ls"},
		PersistentPreRunE: setupConnection,
		RunE: func(cmd *cobra.Command, args []string) error {
			return list.run(args)
		},
	}
	f := cmd.Flags()
	f.BoolVarP(&list.long, "long", "l", false, "output long listing format")
	f.BoolVarP(&list.byDate, "date", "d", false, "sort by release date")
	f.BoolVarP(&list.sortDesc, "reverse", "r", false, "reverse the sort order")
	f.IntVarP(&list.max, "max", "m", 256, "maximum number of releases to fetch")
	f.StringVarP(&list.offset, "offset", "o", "", "the next release name in the list, used to offset from start value")
	return cmd
}

func init() {
	RootCommand.AddCommand(newListCmd(os.Stdout))
}

func (l *lister) run(args []string) error {
	var filter string
	if len(args) > 0 {
		filter = strings.Join(args, " ")
	}

	sortBy := services.ListSort_NAME
	if l.byDate {
		sortBy = services.ListSort_LAST_RELEASED
	}

	sortOrder := services.ListSort_ASC
	if l.sortDesc {
		sortOrder = services.ListSort_DESC
	}

	res, err := helm.ListReleases(l.max, l.offset, sortBy, sortOrder, filter)
	if err != nil {
		return prettyError(err)
	}

	if len(res.Releases) == 0 {
		return nil
	}

	if res.Next != "" {
		fmt.Fprintf(l.out, "\tnext: %s", res.Next)
	}

	rels := res.Releases

	if l.long {
		fmt.Fprintln(l.out, formatList(rels))
		return nil
	}
	for _, r := range rels {
		fmt.Fprintln(l.out, r.Name)
	}

	return nil
}

func formatList(rels []*release.Release) string {
	table := uitable.New()
	table.MaxColWidth = 30
	table.AddRow("NAME", "UPDATED", "STATUS", "CHART")
	for _, r := range rels {
		c := fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version)
		t := timeconv.String(r.Info.LastDeployed)
		s := r.Info.Status.Code.String()
		table.AddRow(r.Name, t, s, c)
	}
	return table.String()
}
<|endoftext|>


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

// +build ignore

package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/doc"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"sort"
	"strings"

	"github.com/vdobler/ht/gui"
	"github.com/vdobler/ht/ht"
)

var godoc map[string]*doc.Package

func main() {
	godoc = make(map[string]*doc.Package)

	buf := &bytes.Buffer{}
	dumpChecksAndExtr(buf)
	dumpCommonTypes(buf)

	fileBuf := &bytes.Buffer{}
	fileBuf.WriteString(`// generated by go run directgen.go; DO NOT EDIT

package main

import (
    "github.com/vdobler/ht/gui"
`)

	for path := range godoc {
		fmt.Fprintf(fileBuf, "    %q\n", path)
	}
	fileBuf.WriteString(`
)

func init() {
`)
	fileBuf.Write(buf.Bytes())

	fileBuf.WriteString("\n}\n")

	b, err := format.Source(fileBuf.Bytes())
	if err != nil {
		log.Fatalf("cannot format data: %s\n%s", err, fileBuf.Bytes())
	}

	err = ioutil.WriteFile("guidata.go", b, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func dumpCommonTypes(buf *bytes.Buffer) {
	dumpData(buf, reflect.TypeOf(ht.Test{}))
	dumpData(buf, reflect.TypeOf(ht.Request{}))
	dumpData(buf, reflect.TypeOf(ht.Response{}))
	dumpData(buf, reflect.TypeOf(ht.CheckList{}))
	dumpData(buf, reflect.TypeOf(ht.ExtractorMap{}))
	dumpData(buf, reflect.TypeOf(ht.Extraction{}))
	dumpData(buf, reflect.TypeOf(ht.Execution{}))
	dumpData(buf, reflect.TypeOf(ht.Cookie{}))
	dumpData(buf, reflect.TypeOf(ht.Condition{}))
	dumpData(buf, reflect.TypeOf(url.Values{}))
	dumpData(buf, reflect.TypeOf(http.Header{}))
	dumpData(buf, reflect.TypeOf(http.Response{}))
}

func dumpChecksAndExtr(buf *bytes.Buffer) {
	names := make([]string, 0, len(ht.CheckRegistry))
	for name := range ht.CheckRegistry {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		typ := ht.CheckRegistry[name]
		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
		}
		dumpData(buf, typ)
	}

	names = make([]string, 0, len(ht.ExtractorRegistry))
	for name := range ht.ExtractorRegistry {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		typ := ht.ExtractorRegistry[name]
		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
		}
		dumpData(buf, typ)
	}
}

func dumpData(buf *bytes.Buffer, t reflect.Type) {
	importPath, symbol := t.PkgPath(), t.Name()
	ti := infoFor(importPath, symbol)

	pkg := importPath[strings.LastIndex(importPath, "/")+1:]
	typeLit := fmt.Sprintf("%s.%s{}", pkg, symbol)
	infoLit := fmt.Sprintf(`gui.Typeinfo{
    Doc: %q,
    Field: map[string]gui.Fieldinfo{`, ti.Doc)
	warnIfTooBroad(symbol, ti.Doc)

	names := make([]string, 0, len(ti.Field))
	for name := range ti.Field {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, field := range names {
		infoLit += fmt.Sprintf(`
        %q: gui.Fieldinfo{
            Doc: %q,
        },`, field, ti.Field[field].Doc)
		warnIfTooBroad(symbol+"."+field, ti.Field[field].Doc)

	}
	infoLit += "}}"

	fmt.Fprintf(buf, "gui.RegisterType(%s, %s)\n\n", typeLit, infoLit)
}

func warnIfTooBroad(symbol, doc string) {
	for _, line := range strings.Split(doc, "\n") {
		if len(line) > 80 {
			fmt.Printf("Type Doc for %s too long:\n  %s\n",
				symbol, line)
		}
	}

}

func infoFor(importPath, symbol string) gui.Typeinfo {
	pkgDoc, ok := godoc[importPath]
	if !ok {
		pkg, err := build.Import(importPath, "", build.ImportComment)
		if err != nil {
			log.Fatal(err)
		}

		fs := token.NewFileSet()
		include := func(info os.FileInfo) bool {
			for _, name := range pkg.GoFiles {
				if name == info.Name() {
					return true
				}
			}
			return false
		}
		pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments)
		if err != nil {
			log.Fatal(err)
		}

		pkgAst := pkgs[pkg.Name]
		pkgDoc = doc.New(pkgAst, pkg.ImportPath, 0)
		godoc[importPath] = pkgDoc
	}

	return typeinfo(pkgDoc, symbol)
}

// typeDoc returns the Godoc for the type name and its fields in the given package.
func typeinfo(pkgDoc *doc.Package, typeName string) gui.Typeinfo {
	tinfo := gui.Typeinfo{}

	// find type
	var typ *doc.Type
	for i := range pkgDoc.Types {
		if typeName == pkgDoc.Types[i].Name {
			typ = pkgDoc.Types[i]
			break
		}
	}
	if typ == nil {
		log.Fatal("no such type", typeName)
	}

	buf := &bytes.Buffer{}
	doc.ToText(buf, typ.Doc, "", "    ", 80)
	tinfo.Doc = buf.String()

	spec := typeSpec(typ.Decl, typ.Name)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		fmt.Println(typeName, "is not a struct")
		return tinfo
	}

	tinfo.Field = make(map[string]gui.Fieldinfo)
	for _, field := range structType.Fields.List {
		fieldNames := []string{}

		if len(field.Names) == 0 {
			// Anonymous field
			ident, ok := field.Type.(*ast.Ident)
			if !ok {
				log.Fatalf("Strange anonymous field: %#v\n", field.Type)
			}
			fieldNames = []string{ident.Name}
		} else {
			for i := range field.Names {
				fieldNames = append(fieldNames, field.Names[i].Name)
			}
		}

		for _, fieldName := range fieldNames {
			buf.Reset()
			if field.Doc != nil {
				doc.ToText(buf, field.Doc.Text(), "", "    ", 80)
			}
			if field.Comment != nil {
				buf.WriteString(field.Comment.List[0].Text)
			}

			tinfo.Field[fieldName] = gui.Fieldinfo{
				Doc: buf.String(),
			}
		}
	}

	return tinfo
}

func typeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
	for _, spec := range decl.Specs {
		typeSpec := spec.(*ast.TypeSpec)
		if symbol == typeSpec.Name.Name {
			return typeSpec
		}
	}
	return nil
}
<commit_msg>cmd/ht: promote field tool tips for renamed types<commit_after>// Copyright 2016 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/doc"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"sort"
	"strings"

	"github.com/vdobler/ht/gui"
	"github.com/vdobler/ht/ht"
)

var godoc map[string]*doc.Package

func main() {
	godoc = make(map[string]*doc.Package)

	buf := &bytes.Buffer{}
	dumpChecksAndExtr(buf)
	dumpCommonTypes(buf)

	fileBuf := &bytes.Buffer{}
	fileBuf.WriteString(`// generated by go run directgen.go; DO NOT EDIT

package main

import (
    "github.com/vdobler/ht/gui"
`)

	for path := range godoc {
		fmt.Fprintf(fileBuf, "    %q\n", path)
	}
	fileBuf.WriteString(`
)

func init() {
`)
	fileBuf.Write(buf.Bytes())

	fileBuf.WriteString("\n}\n")

	b, err := format.Source(fileBuf.Bytes())
	if err != nil {
		log.Fatalf("cannot format data: %s\n%s", err, fileBuf.Bytes())
	}

	err = ioutil.WriteFile("guidata.go", b, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func dumpCommonTypes(buf *bytes.Buffer) {
	dumpData(buf, reflect.TypeOf(ht.Test{}))
	dumpData(buf, reflect.TypeOf(ht.Request{}))
	dumpData(buf, reflect.TypeOf(ht.Response{}))
	dumpData(buf, reflect.TypeOf(ht.CheckList{}))
	dumpData(buf, reflect.TypeOf(ht.ExtractorMap{}))
	dumpData(buf, reflect.TypeOf(ht.Extraction{}))
	dumpData(buf, reflect.TypeOf(ht.Execution{}))
	dumpData(buf, reflect.TypeOf(ht.Cookie{}))
	dumpData(buf, reflect.TypeOf(ht.Condition{}))
	dumpData(buf, reflect.TypeOf(url.Values{}))
	dumpData(buf, reflect.TypeOf(http.Header{}))
	dumpData(buf, reflect.TypeOf(http.Response{}))
}

func dumpChecksAndExtr(buf *bytes.Buffer) {
	names := make([]string, 0, len(ht.CheckRegistry))
	for name := range ht.CheckRegistry {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		typ := ht.CheckRegistry[name]
		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
		}
		dumpData(buf, typ)
	}

	names = make([]string, 0, len(ht.ExtractorRegistry))
	for name := range ht.ExtractorRegistry {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		typ := ht.ExtractorRegistry[name]
		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
		}
		dumpData(buf, typ)
	}
}

func dumpData(buf *bytes.Buffer, t reflect.Type) {
	importPath, symbol := t.PkgPath(), t.Name()
	ti := infoFor(importPath, symbol)

	pkg := importPath[strings.LastIndex(importPath, "/")+1:]
	typeLit := fmt.Sprintf("%s.%s{}", pkg, symbol)
	infoLit := fmt.Sprintf(`gui.Typeinfo{
    Doc: %q,
    Field: map[string]gui.Fieldinfo{`, ti.Doc)
	warnIfTooBroad(symbol, ti.Doc)

	names := make([]string, 0, len(ti.Field))
	for name := range ti.Field {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, field := range names {
		infoLit += fmt.Sprintf(`
        %q: gui.Fieldinfo{
            Doc: %q,
        },`, field, ti.Field[field].Doc)
		warnIfTooBroad(symbol+"."+field, ti.Field[field].Doc)

	}
	infoLit += "}}"

	fmt.Fprintf(buf, "gui.RegisterType(%s, %s)\n\n", typeLit, infoLit)
}

func warnIfTooBroad(symbol, doc string) {
	for _, line := range strings.Split(doc, "\n") {
		if len(line) > 80 {
			fmt.Printf("Type Doc for %s too long:\n  %s\n",
				symbol, line)
		}
	}

}

func infoFor(importPath, symbol string) gui.Typeinfo {
	pkgDoc, ok := godoc[importPath]
	if !ok {
		pkg, err := build.Import(importPath, "", build.ImportComment)
		if err != nil {
			log.Fatal(err)
		}

		fs := token.NewFileSet()
		include := func(info os.FileInfo) bool {
			for _, name := range pkg.GoFiles {
				if name == info.Name() {
					return true
				}
			}
			return false
		}
		pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments)
		if err != nil {
			log.Fatal(err)
		}

		pkgAst := pkgs[pkg.Name]
		pkgDoc = doc.New(pkgAst, pkg.ImportPath, 0)
		godoc[importPath] = pkgDoc
	}

	return typeinfo(pkgDoc, symbol)
}

// typeDoc returns the Godoc for the type name and its fields in the given package.
func typeinfo(pkgDoc *doc.Package, typeName string) gui.Typeinfo {
	tinfo := gui.Typeinfo{}

	// find type
	var typ *doc.Type
	for i := range pkgDoc.Types {
		if typeName == pkgDoc.Types[i].Name {
			typ = pkgDoc.Types[i]
			break
		}
	}
	if typ == nil {
		log.Fatal("no such type", typeName)
	}

	buf := &bytes.Buffer{}
	doc.ToText(buf, typ.Doc, "", "    ", 80)
	tinfo.Doc = buf.String()

	spec := typeSpec(typ.Decl, typ.Name)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		if ident, ok := spec.Type.(*ast.Ident); ok {
			ti := typeinfo(pkgDoc, ident.Name)
			tinfo.Field = ti.Field
		} else {
			fmt.Printf("%s is neither a struct nor a named type but a %T %#v\n",
				typeName, spec.Type, spec.Type)
		}
		return tinfo
	}

	tinfo.Field = make(map[string]gui.Fieldinfo)
	for _, field := range structType.Fields.List {
		fieldNames := []string{}

		if len(field.Names) == 0 {
			// Anonymous field
			ident, ok := field.Type.(*ast.Ident)
			if !ok {
				log.Fatalf("Strange anonymous field: %#v\n", field.Type)
			}
			fieldNames = []string{ident.Name}
		} else {
			for i := range field.Names {
				fieldNames = append(fieldNames, field.Names[i].Name)
			}
		}

		for _, fieldName := range fieldNames {
			buf.Reset()
			if field.Doc != nil {
				doc.ToText(buf, field.Doc.Text(), "", "    ", 80)
			}
			if field.Comment != nil {
				buf.WriteString(field.Comment.List[0].Text)
			}

			tinfo.Field[fieldName] = gui.Fieldinfo{
				Doc: buf.String(),
			}
		}
	}

	return tinfo
}

func typeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
	for _, spec := range decl.Specs {
		typeSpec := spec.(*ast.TypeSpec)
		if symbol == typeSpec.Name.Name {
			return typeSpec
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"runtime/pprof"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	"github.com/jbenet/go-ipfs/commands/cli"
	"github.com/jbenet/go-ipfs/core/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

const API_PATH = "/api/v0"

func main() {
	req, err := cli.Parse(os.Args[1:], commands.Root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// TODO: call command locally if option tells us to, or if command is CLI-only (e.g. ipfs init)

	cmd, err := commands.Root.Get(req.Path())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	res, err := sendRequest(req)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if debug, ok := req.Option("debug"); ok && debug.(bool) {
		u.Debug = true

		// if debugging, setup profiling.
		if u.Debug {
			ofi, err := os.Create("cpu.prof")
			if err != nil {
				fmt.Println(err)
				return
			}
			pprof.StartCPUProfile(ofi)
			defer ofi.Close()
			defer pprof.StopCPUProfile()
		}
	}

	//res := commands.Root.Call(req)

	if res.Error() != nil {
		fmt.Println(res.Error().Error())

		if cmd.Help != "" && res.Error().Code == cmds.ErrClient {
			// TODO: convert from markdown to ANSI terminal format?
			fmt.Println(cmd.Help)
		}

		os.Exit(1)
	}

	_, err = io.Copy(os.Stdout, res)
	if err != nil {
		fmt.Println(err.Error())
	}
}

func sendRequest(req cmds.Request) (cmds.Response, error) {
	// TODO: load RPC host from config
	url := "http://localhost:8080" + API_PATH
	url += "/" + strings.Join(req.Path(), "/")

	// TODO: support other encodings once we have multicodec to decode response
	//       (we shouldn't have to set this here)
	encoding := cmds.JSON
	req.SetOption(cmds.EncShort, encoding)

	query := "?"
	options := req.Options()
	for k, v := range options {
		query += "&" + k + "=" + v.(string)
	}

	httpRes, err := http.Post(url+query, "application/octet-stream", req.Stream())
	if err != nil {
		return nil, err
	}

	res := cmds.NewResponse(req)

	contentType := httpRes.Header["Content-Type"][0]
	contentType = strings.Split(contentType, ";")[0]

	if contentType == "application/octet-stream" {
		res.SetValue(httpRes.Body)
		return res, nil
	}

	// TODO: decode based on `encoding`, using multicodec
	dec := json.NewDecoder(httpRes.Body)

	if httpRes.StatusCode >= http.StatusBadRequest {
		e := cmds.Error{}
		err = dec.Decode(&e)
		if err != nil {
			fmt.Println(err)
			return nil, err
		}

		res.SetError(e, e.Code)

	} else {
		var v interface{}
		err = dec.Decode(&v)
		if err != nil {
			fmt.Println(err)
			return nil, err
		}

		res.SetValue(v)
	}

	return res, nil
}
<commit_msg>cmd/ipfs: Run CLI-specific subcommands locally in CLI entry point<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"runtime/pprof"
	"strings"

	cmds "github.com/jbenet/go-ipfs/commands"
	"github.com/jbenet/go-ipfs/commands/cli"
	"github.com/jbenet/go-ipfs/core/commands"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

const API_PATH = "/api/v0"

func main() {
	args := os.Args[1:]

	req, err := cli.Parse(args, Root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if len(req.Path()) == 0 {
		req, err = cli.Parse(args, commands.Root)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	var local bool // TODO: option to force local
	var root *cmds.Command
	cmd, err := Root.Get(req.Path())
	if err == nil {
		local = true
		root = Root

	} else if local {
		fmt.Println(err)
		os.Exit(1)

	} else {
		cmd, err = commands.Root.Get(req.Path())
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		local = false
		root = commands.Root
	}

	// TODO: get converted options so we can use them here (e.g. --debug, --config)

	if debug, ok := req.Option("debug"); ok && debug.(bool) {
		u.Debug = true

		// if debugging, setup profiling.
		if u.Debug {
			ofi, err := os.Create("cpu.prof")
			if err != nil {
				fmt.Println(err)
				return
			}
			pprof.StartCPUProfile(ofi)
			defer ofi.Close()
			defer pprof.StopCPUProfile()
		}
	}

	var res cmds.Response
	if local {
		// TODO: spin up node
		res = root.Call(req)
	} else {
		res, err = sendRequest(req)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	if res.Error() != nil {
		fmt.Println(res.Error().Error())

		if cmd.Help != "" && res.Error().Code == cmds.ErrClient {
			// TODO: convert from markdown to ANSI terminal format?
			fmt.Println(cmd.Help)
		}

		os.Exit(1)
	}

	_, err = io.Copy(os.Stdout, res)
	if err != nil {
		fmt.Println(err.Error())
	}
}

func sendRequest(req cmds.Request) (cmds.Response, error) {
	// TODO: load RPC host from config
	url := "http://localhost:8080" + API_PATH
	url += "/" + strings.Join(req.Path(), "/")

	// TODO: support other encodings once we have multicodec to decode response
	//       (we shouldn't have to set this here)
	encoding := cmds.JSON
	req.SetOption(cmds.EncShort, encoding)

	query := "?"
	options := req.Options()
	for k, v := range options {
		query += "&" + k + "=" + v.(string)
	}

	httpRes, err := http.Post(url+query, "application/octet-stream", req.Stream())
	if err != nil {
		return nil, err
	}

	res := cmds.NewResponse(req)

	contentType := httpRes.Header["Content-Type"][0]
	contentType = strings.Split(contentType, ";")[0]

	if contentType == "application/octet-stream" {
		res.SetValue(httpRes.Body)
		return res, nil
	}

	// TODO: decode based on `encoding`, using multicodec
	dec := json.NewDecoder(httpRes.Body)

	if httpRes.StatusCode >= http.StatusBadRequest {
		e := cmds.Error{}
		err = dec.Decode(&e)
		if err != nil {
			fmt.Println(err)
			return nil, err
		}

		res.SetError(e, e.Code)

	} else {
		var v interface{}
		err = dec.Decode(&v)
		if err != nil {
			fmt.Println(err)
			return nil, err
		}

		res.SetValue(v)
	}

	return res, nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/evandroflores/claimr/database"
	"github.com/evandroflores/claimr/messages"
	"github.com/shomali11/slacker"
	log "github.com/sirupsen/logrus"
)

func init() {
	Register("log-level <level>", "Change the current log level. admin-only", changeLogLevel)
}

func changeLogLevel(request *slacker.Request, response slacker.ResponseWriter) {
	response.Typing()

	event := getEvent(request)
	if !isAdmin(event.User) {
		response.Reply(messages.Get("admin-only"))
		return
	}

	currentLogLevel := log.GetLevel()
	newLogLevel := request.Param("level")

	if newLogLevel == "" {
		response.Reply(fmt.Sprintf(messages.Get("no-level-provided"), currentLogLevel))
		return
	}

	logrusLevel, err := log.ParseLevel(newLogLevel)

	if err != nil {
		response.Reply(err.Error())
		return
	}

	if currentLogLevel == logrusLevel {
		response.Reply(messages.Get("same-log-level"))
		return
	}

	database.DB.LogMode(logrusLevel == log.DebugLevel)

	log.SetLevel(logrusLevel)
	log.Printf("Log Test [%s -> %s]", currentLogLevel, newLogLevel)
	response.Reply(fmt.Sprintf(messages.Get("level-log-changed"), currentLogLevel, newLogLevel))
}
<commit_msg>Fixing log_level<commit_after>package cmd

import (
	"fmt"

	"github.com/evandroflores/claimr/database"
	"github.com/evandroflores/claimr/messages"
	"github.com/evandroflores/claimr/model"
	"github.com/shomali11/slacker"
	log "github.com/sirupsen/logrus"
)

func init() {
	Register("log-level <level>", "Change the current log level. admin-only", changeLogLevel)
}

func changeLogLevel(request *slacker.Request, response slacker.ResponseWriter) {
	response.Typing()

	event := getEvent(request)
	if !model.IsAdmin(event.User) {
		response.Reply(messages.Get("admin-only"))
		return
	}

	currentLogLevel := log.GetLevel()
	newLogLevel := request.Param("level")

	if newLogLevel == "" {
		response.Reply(fmt.Sprintf(messages.Get("no-level-provided"), currentLogLevel))
		return
	}

	logrusLevel, err := log.ParseLevel(newLogLevel)

	if err != nil {
		response.Reply(err.Error())
		return
	}

	if currentLogLevel == logrusLevel {
		response.Reply(messages.Get("same-log-level"))
		return
	}

	database.DB.LogMode(logrusLevel == log.DebugLevel)

	log.SetLevel(logrusLevel)
	log.Printf("Log Test [%s -> %s]", currentLogLevel, newLogLevel)
	response.Reply(fmt.Sprintf(messages.Get("level-log-changed"), currentLogLevel, newLogLevel))
}
<|endoftext|>


<commit_before>/*
 * Copyright 2018-2019 The original author or 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
 *
 *     https://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 main

import (
	"fmt"
	"os"

	"github.com/projectriff/riff/pkg/core"
	"github.com/projectriff/riff/pkg/riff/commands"
)

var (
	manifests = map[string]*core.Manifest{
		// validated, compatible versions of Knative
		"stable": {
			ManifestVersion: "0.1",
			Istio: []string{
				"https://storage.googleapis.com/knative-releases/serving/previous/v0.4.1/istio.yaml",
				"https://storage.googleapis.com/projectriff/istio/istio-riff-knative-serving-v0-4-1-patch.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-releases/build/previous/v0.4.0/build.yaml",
				"https://storage.googleapis.com/knative-releases/serving/previous/v0.4.1/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/v0.4.1/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/previous/v0.4.0/eventing.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/previous/v0.4.0/in-memory-channel.yaml",
				// TODO update to a release version before releasing riff
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate-0.2.0-snapshot-ci-c957e7e83d23.yaml",
			},
		},
		// most recent release of Knative. This manifest is not tested
		"latest": {
			ManifestVersion: "0.1",
			Istio: []string{
				"https://storage.googleapis.com/knative-releases/serving/latest/istio.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-releases/build/latest/build.yaml",
				"https://storage.googleapis.com/knative-releases/serving/latest/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/master/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/latest/eventing.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/latest/in-memory-channel.yaml",
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate.yaml",
			},
		},
		// most recent build of Knative from master. This manifest is not tested
		"nightly": {
			ManifestVersion: "0.1",
			Istio: []string{
				"https://storage.googleapis.com/knative-nightly/serving/latest/istio.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-nightly/build/latest/build.yaml",
				"https://storage.googleapis.com/knative-nightly/serving/latest/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/master/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-nightly/eventing/latest/eventing.yaml",
				"https://storage.googleapis.com/knative-nightly/eventing/latest/in-memory-channel.yaml",
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate.yaml",
			},
		},
	}
)

func main() {

	root := commands.CreateAndWireRootCommand(manifests)

	sub, err := root.ExecuteC()
	if err != nil {
		if !sub.SilenceUsage { // May have been switched to true once we're past PreRunE()
			sub.Help()
		}
		fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
		os.Exit(1)
	}
}
<commit_msg>Apply the Istio patch to istio.yaml<commit_after>/*
 * Copyright 2018-2019 The original author or 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
 *
 *     https://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 main

import (
	"fmt"
	"os"

	"github.com/projectriff/riff/pkg/core"
	"github.com/projectriff/riff/pkg/riff/commands"
)

var (
	manifests = map[string]*core.Manifest{
		// validated, compatible versions of Knative
		"stable": {
			ManifestVersion: "0.1",
			Istio: []string{
				// diff https://storage.googleapis.com/projectriff/istio/istio-riff-knative-serving-v0-4-1.diff
				"https://storage.googleapis.com/projectriff/istio/istio-riff-knative-serving-v0-4-1.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-releases/build/previous/v0.4.0/build.yaml",
				"https://storage.googleapis.com/knative-releases/serving/previous/v0.4.1/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/v0.4.1/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/previous/v0.4.0/eventing.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/previous/v0.4.0/in-memory-channel.yaml",
				// TODO update to a release version before releasing riff
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate-0.2.0-snapshot-ci-c957e7e83d23.yaml",
			},
		},
		// most recent release of Knative. This manifest is not tested
		"latest": {
			ManifestVersion: "0.1",
			Istio: []string{
				"https://storage.googleapis.com/knative-releases/serving/latest/istio.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-releases/build/latest/build.yaml",
				"https://storage.googleapis.com/knative-releases/serving/latest/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/master/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/latest/eventing.yaml",
				"https://storage.googleapis.com/knative-releases/eventing/latest/in-memory-channel.yaml",
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate.yaml",
			},
		},
		// most recent build of Knative from master. This manifest is not tested
		"nightly": {
			ManifestVersion: "0.1",
			Istio: []string{
				"https://storage.googleapis.com/knative-nightly/serving/latest/istio.yaml",
			},
			Knative: []string{
				"https://storage.googleapis.com/knative-nightly/build/latest/build.yaml",
				"https://storage.googleapis.com/knative-nightly/serving/latest/serving.yaml",
				"https://raw.githubusercontent.com/knative/serving/master/third_party/config/build/clusterrole.yaml",
				"https://storage.googleapis.com/knative-nightly/eventing/latest/eventing.yaml",
				"https://storage.googleapis.com/knative-nightly/eventing/latest/in-memory-channel.yaml",
				"https://storage.googleapis.com/projectriff/riff-buildtemplate/riff-cnb-clusterbuildtemplate.yaml",
			},
		},
	}
)

func main() {

	root := commands.CreateAndWireRootCommand(manifests)

	sub, err := root.ExecuteC()
	if err != nil {
		if !sub.SilenceUsage { // May have been switched to true once we're past PreRunE()
			sub.Help()
		}
		fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	etcdraft "github.com/coreos/etcd/raft"
	hashic "github.com/hashicorp/raft"
	"github.com/hashicorp/raft-boltdb"
	"github.com/relab/raft"
	etcd "github.com/relab/raft/etcd"
	hraft "github.com/relab/raft/hashicorp"
	"github.com/relab/raft/raftgorums"
	"github.com/relab/rkv/rkvpb"

	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
)

const (
	bgorums    = "gorums"
	betcd      = "etcd"
	bhashicorp = "hashicorp"
)

var (
	bench             = flag.Bool("quiet", false, "Silence log output")
	recover           = flag.Bool("recover", false, "Recover from stable storage")
	batch             = flag.Bool("batch", true, "enable batching")
	serverMetrics     = flag.Bool("servermetrics", true, "enable server-side metrics")
	electionTimeout   = flag.Duration("election", time.Second, "How long servers wait before starting an election")
	heartbeatTimeout  = flag.Duration("heartbeat", 20*time.Millisecond, "How often a heartbeat should be sent")
	entriesPerMsg     = flag.Uint64("entriespermsg", 64, "Entries per Appendentries message")
	catchupMultiplier = flag.Uint64("catchupmultiplier", 1024, "How many more times entries per message allowed during catch up")
)

func main() {
	var (
		id      = flag.Uint64("id", 0, "server ID")
		servers = flag.String("servers", ":9201,:9202,:9203,:9204,:9205,:9206,:9207", "comma separated list of server addresses")
		cluster = flag.String("cluster", "1,2,3", "comma separated list of server ids to form cluster with, [1 >= id <= len(servers)]")
		backend = flag.String("backend", "gorums", "Raft backend to use [gorums|etcd|hashicorp]")
	)

	flag.Parse()
	rand.Seed(time.Now().UnixNano())

	if *id == 0 {
		fmt.Print("-id argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	nodes := strings.Split(*servers, ",")

	if len(nodes) == 0 {
		fmt.Print("-server argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	selected := strings.Split(*cluster, ",")

	var ids []uint64

	for _, sid := range selected {
		id, err := strconv.ParseUint(sid, 10, 64)

		if err != nil {
			fmt.Print("could not parse -cluster argument\n\n")
			flag.Usage()
			os.Exit(1)
		}

		if id <= 0 || id > uint64(len(nodes)) {
			fmt.Print("invalid -cluster argument\n\n")
			flag.Usage()
			os.Exit(1)
		}

		ids = append(ids, id)
	}

	if len(ids) == 0 {
		fmt.Print("-cluster argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if len(ids) > len(nodes) {
		fmt.Print("-cluster specifies too many servers\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if *entriesPerMsg < 1 {
		fmt.Print("-entriespermsg must be atleast 1\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if *catchupMultiplier < 1 {
		fmt.Print("-catchupmultiplier must be atleast 1\n\n")
		flag.Usage()
		os.Exit(1)
	}

	logger := logrus.New()

	logFile, err := os.OpenFile(
		fmt.Sprintf("%s%sraft%.2d.log", os.TempDir(), string(filepath.Separator), *id),
		os.O_CREATE|os.O_TRUNC|os.O_APPEND|os.O_WRONLY, 0600,
	)

	if err != nil {
		logger.Fatal(err)
	}

	logger.Hooks.Add(NewLogToFileHook(logFile))

	if *bench {
		logger.Out = ioutil.Discard
		grpc.EnableTracing = false
	}

	grpclog.SetLogger(logger)

	lis, err := net.Listen("tcp", nodes[*id-1])

	if err != nil {
		logger.Fatal(err)
	}

	grpcServer := grpc.NewServer()

	if *serverMetrics {
		go func() {
			http.Handle("/metrics", promhttp.Handler())
			logger.Fatal(http.ListenAndServe(fmt.Sprintf(":590%d", *id), nil))
		}()
	}

	switch *backend {
	case bgorums:
		rungorums(logger, lis, grpcServer, *id, ids, nodes)
	case betcd:
		runetcd(logger, lis, grpcServer, *id, ids, nodes)
	case bhashicorp:
		runhashicorp(logger, lis, grpcServer, *id, ids, nodes)
	}

}

func runhashicorp(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	var selflis string
	selflis, ids, nodes = selfIDsNodes(logger, id, ids, nodes)

	servers := make([]hashic.Server, len(nodes)+1)
	servers[0] = hashic.Server{
		Suffrage: hashic.Voter,
		ID:       hashic.ServerID(selflis),
		Address:  hashic.ServerAddress(selflis),
	}
	for i, addr := range nodes {
		host, port, err := net.SplitHostPort(addr)
		if err != nil {
			logger.Fatal(err)
		}
		p, _ := strconv.Atoi(port)
		addr = host + ":" + strconv.Itoa(p-100)

		servers[i+1] = hashic.Server{
			Suffrage: hashic.Voter,
			ID:       hashic.ServerID(addr),
			Address:  hashic.ServerAddress(addr),
		}
	}

	addr, err := net.ResolveTCPAddr("tcp", selflis)
	if err != nil {
		logger.Fatal(err)
	}
	trans, err := hashic.NewTCPTransport(selflis, addr, len(ids), 10*time.Second, os.Stderr)
	if err != nil {
		logger.Fatal(err)
	}

	path := fmt.Sprintf("hashicorp%.2d.bolt", id)
	overwrite := !*recover
	// Check if file already exists.
	if _, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			// We don't need to overwrite a file that doesn't exist.
			overwrite = false
		} else {
			// If we are unable to verify the existence of the file,
			// there is probably a permission problem.
			logger.Fatal(err)
		}
	}
	if overwrite {
		if err := os.Remove(path); err != nil {
			logger.Fatal(err)
		}
	}
	logs, err := raftboltdb.NewBoltStore(path)
	if err != nil {
		logger.Fatal(err)
	}

	snaps := hashic.NewInmemSnapshotStore()

	cfg := &hashic.Config{
		LocalID:            hashic.ServerID(selflis),
		ProtocolVersion:    hashic.ProtocolVersionMax,
		HeartbeatTimeout:   *electionTimeout,
		ElectionTimeout:    *electionTimeout,
		CommitTimeout:      *heartbeatTimeout,
		MaxAppendEntries:   int(*entriesPerMsg),
		ShutdownOnRemove:   true,
		TrailingLogs:       math.MaxUint64,
		SnapshotInterval:   120 * time.Hour,
		SnapshotThreshold:  math.MaxUint64,
		LeaderLeaseTimeout: *electionTimeout / 2,
	}

	node := hraft.NewRaft(logger, NewStore(), cfg, servers, trans, logs, logs, snaps)

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	logger.Fatal(grpcServer.Serve(lis))
}

func runetcd(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	var selflis string
	selflis, ids, nodes = selfIDsNodes(logger, id, ids, nodes)

	peers := make([]etcdraft.Peer, len(nodes))

	for i, addr := range nodes {
		nid := ids[i]

		host, port, err := net.SplitHostPort(addr)
		if err != nil {
			logger.Fatal(err)
		}
		p, _ := strconv.Atoi(port)

		ur, err := url.Parse("http://" + addr)
		ur.Host = host + ":" + strconv.Itoa(p-100)
		if err != nil {
			logger.Fatal(err)
		}

		peers[i] = etcdraft.Peer{
			ID:      nid,
			Context: []byte(ur.String()),
		}
	}

	storage := etcdraft.NewMemoryStorage()
	node := etcd.NewRaft(
		logger,
		NewStore(),
		storage,
		&etcdraft.Config{
			ID:            id,
			ElectionTick:  int(*electionTimeout / *heartbeatTimeout),
			HeartbeatTick: 1,
			Storage:       storage,
			MaxSizePerMsg: *entriesPerMsg,
			// etcdserver says: Never overflow the rafthttp buffer,
			// which is 4096. We keep the same constant.
			MaxInflightMsgs: 4096 / 8,
			PreVote:         true,
			Logger:          logger,
		},
		peers,
		*heartbeatTimeout,
	)

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	go func() {
		logger.Fatal(grpcServer.Serve(lis))
	}()

	lishttp, err := net.Listen("tcp", selflis)

	if err != nil {
		logger.Fatal(err)
	}

	logger.Fatal(http.Serve(lishttp, node.Handler()))
}

func rungorums(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	storage, err := raft.NewFileStorage(fmt.Sprintf("db%.2d.bolt", id), !*recover)

	if err != nil {
		logger.Fatal(err)
	}

	storageWithCache := raft.NewCacheStorage(storage, 20000)

	node := raftgorums.NewRaft(NewStore(), &raftgorums.Config{
		ID:                id,
		Servers:           nodes,
		InitialCluster:    ids,
		Batch:             *batch,
		Storage:           storageWithCache,
		ElectionTimeout:   *electionTimeout,
		HeartbeatTimeout:  *heartbeatTimeout,
		EntriesPerMsg:     *entriesPerMsg,
		CatchupMultiplier: *catchupMultiplier,
		Logger:            logger,
		MetricsEnabled:    true,
	})

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	go func() {
		logger.Fatal(grpcServer.Serve(lis))
	}()

	logger.Fatal(node.Run(grpcServer))
}

func selfIDsNodes(logger logrus.FieldLogger, id uint64, ids []uint64, nodes []string) (string, []uint64, []string) {
	host, port, err := net.SplitHostPort(nodes[id-1])
	if err != nil {
		logger.Fatal(err)
	}
	p, _ := strconv.Atoi(port)
	selflis := host + ":" + strconv.Itoa(p-100)

	ids = append(ids[:id-1], ids[id:]...)
	nodes = append(nodes[:id-1], nodes[id:]...)

	return selflis, ids, nodes
}
<commit_msg>rkvd/main.go: Step down etcd leader on failed heartbeat<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	etcdraft "github.com/coreos/etcd/raft"
	hashic "github.com/hashicorp/raft"
	"github.com/hashicorp/raft-boltdb"
	"github.com/relab/raft"
	etcd "github.com/relab/raft/etcd"
	hraft "github.com/relab/raft/hashicorp"
	"github.com/relab/raft/raftgorums"
	"github.com/relab/rkv/rkvpb"

	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
)

const (
	bgorums    = "gorums"
	betcd      = "etcd"
	bhashicorp = "hashicorp"
)

var (
	bench             = flag.Bool("quiet", false, "Silence log output")
	recover           = flag.Bool("recover", false, "Recover from stable storage")
	batch             = flag.Bool("batch", true, "enable batching")
	serverMetrics     = flag.Bool("servermetrics", true, "enable server-side metrics")
	electionTimeout   = flag.Duration("election", time.Second, "How long servers wait before starting an election")
	heartbeatTimeout  = flag.Duration("heartbeat", 20*time.Millisecond, "How often a heartbeat should be sent")
	entriesPerMsg     = flag.Uint64("entriespermsg", 64, "Entries per Appendentries message")
	catchupMultiplier = flag.Uint64("catchupmultiplier", 1024, "How many more times entries per message allowed during catch up")
)

func main() {
	var (
		id      = flag.Uint64("id", 0, "server ID")
		servers = flag.String("servers", ":9201,:9202,:9203,:9204,:9205,:9206,:9207", "comma separated list of server addresses")
		cluster = flag.String("cluster", "1,2,3", "comma separated list of server ids to form cluster with, [1 >= id <= len(servers)]")
		backend = flag.String("backend", "gorums", "Raft backend to use [gorums|etcd|hashicorp]")
	)

	flag.Parse()
	rand.Seed(time.Now().UnixNano())

	if *id == 0 {
		fmt.Print("-id argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	nodes := strings.Split(*servers, ",")

	if len(nodes) == 0 {
		fmt.Print("-server argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	selected := strings.Split(*cluster, ",")

	var ids []uint64

	for _, sid := range selected {
		id, err := strconv.ParseUint(sid, 10, 64)

		if err != nil {
			fmt.Print("could not parse -cluster argument\n\n")
			flag.Usage()
			os.Exit(1)
		}

		if id <= 0 || id > uint64(len(nodes)) {
			fmt.Print("invalid -cluster argument\n\n")
			flag.Usage()
			os.Exit(1)
		}

		ids = append(ids, id)
	}

	if len(ids) == 0 {
		fmt.Print("-cluster argument is required\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if len(ids) > len(nodes) {
		fmt.Print("-cluster specifies too many servers\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if *entriesPerMsg < 1 {
		fmt.Print("-entriespermsg must be atleast 1\n\n")
		flag.Usage()
		os.Exit(1)
	}

	if *catchupMultiplier < 1 {
		fmt.Print("-catchupmultiplier must be atleast 1\n\n")
		flag.Usage()
		os.Exit(1)
	}

	logger := logrus.New()

	logFile, err := os.OpenFile(
		fmt.Sprintf("%s%sraft%.2d.log", os.TempDir(), string(filepath.Separator), *id),
		os.O_CREATE|os.O_TRUNC|os.O_APPEND|os.O_WRONLY, 0600,
	)

	if err != nil {
		logger.Fatal(err)
	}

	logger.Hooks.Add(NewLogToFileHook(logFile))

	if *bench {
		logger.Out = ioutil.Discard
		grpc.EnableTracing = false
	}

	grpclog.SetLogger(logger)

	lis, err := net.Listen("tcp", nodes[*id-1])

	if err != nil {
		logger.Fatal(err)
	}

	grpcServer := grpc.NewServer()

	if *serverMetrics {
		go func() {
			http.Handle("/metrics", promhttp.Handler())
			logger.Fatal(http.ListenAndServe(fmt.Sprintf(":590%d", *id), nil))
		}()
	}

	switch *backend {
	case bgorums:
		rungorums(logger, lis, grpcServer, *id, ids, nodes)
	case betcd:
		runetcd(logger, lis, grpcServer, *id, ids, nodes)
	case bhashicorp:
		runhashicorp(logger, lis, grpcServer, *id, ids, nodes)
	}

}

func runhashicorp(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	var selflis string
	selflis, ids, nodes = selfIDsNodes(logger, id, ids, nodes)

	servers := make([]hashic.Server, len(nodes)+1)
	servers[0] = hashic.Server{
		Suffrage: hashic.Voter,
		ID:       hashic.ServerID(selflis),
		Address:  hashic.ServerAddress(selflis),
	}
	for i, addr := range nodes {
		host, port, err := net.SplitHostPort(addr)
		if err != nil {
			logger.Fatal(err)
		}
		p, _ := strconv.Atoi(port)
		addr = host + ":" + strconv.Itoa(p-100)

		servers[i+1] = hashic.Server{
			Suffrage: hashic.Voter,
			ID:       hashic.ServerID(addr),
			Address:  hashic.ServerAddress(addr),
		}
	}

	addr, err := net.ResolveTCPAddr("tcp", selflis)
	if err != nil {
		logger.Fatal(err)
	}
	trans, err := hashic.NewTCPTransport(selflis, addr, len(ids), 10*time.Second, os.Stderr)
	if err != nil {
		logger.Fatal(err)
	}

	path := fmt.Sprintf("hashicorp%.2d.bolt", id)
	overwrite := !*recover
	// Check if file already exists.
	if _, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			// We don't need to overwrite a file that doesn't exist.
			overwrite = false
		} else {
			// If we are unable to verify the existence of the file,
			// there is probably a permission problem.
			logger.Fatal(err)
		}
	}
	if overwrite {
		if err := os.Remove(path); err != nil {
			logger.Fatal(err)
		}
	}
	logs, err := raftboltdb.NewBoltStore(path)
	if err != nil {
		logger.Fatal(err)
	}

	snaps := hashic.NewInmemSnapshotStore()

	cfg := &hashic.Config{
		LocalID:            hashic.ServerID(selflis),
		ProtocolVersion:    hashic.ProtocolVersionMax,
		HeartbeatTimeout:   *electionTimeout,
		ElectionTimeout:    *electionTimeout,
		CommitTimeout:      *heartbeatTimeout,
		MaxAppendEntries:   int(*entriesPerMsg),
		ShutdownOnRemove:   true,
		TrailingLogs:       math.MaxUint64,
		SnapshotInterval:   120 * time.Hour,
		SnapshotThreshold:  math.MaxUint64,
		LeaderLeaseTimeout: *electionTimeout / 2,
	}

	node := hraft.NewRaft(logger, NewStore(), cfg, servers, trans, logs, logs, snaps)

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	logger.Fatal(grpcServer.Serve(lis))
}

func runetcd(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	var selflis string
	selflis, ids, nodes = selfIDsNodes(logger, id, ids, nodes)

	peers := make([]etcdraft.Peer, len(nodes))

	for i, addr := range nodes {
		nid := ids[i]

		host, port, err := net.SplitHostPort(addr)
		if err != nil {
			logger.Fatal(err)
		}
		p, _ := strconv.Atoi(port)

		ur, err := url.Parse("http://" + addr)
		ur.Host = host + ":" + strconv.Itoa(p-100)
		if err != nil {
			logger.Fatal(err)
		}

		peers[i] = etcdraft.Peer{
			ID:      nid,
			Context: []byte(ur.String()),
		}
	}

	storage := etcdraft.NewMemoryStorage()
	node := etcd.NewRaft(
		logger,
		NewStore(),
		storage,
		&etcdraft.Config{
			ID:            id,
			ElectionTick:  int(*electionTimeout / *heartbeatTimeout),
			HeartbeatTick: 1,
			Storage:       storage,
			MaxSizePerMsg: *entriesPerMsg,
			// etcdserver says: Never overflow the rafthttp buffer,
			// which is 4096. We keep the same constant.
			MaxInflightMsgs: 4096 / 8,
			CheckQuorum:     true,
			PreVote:         true,
			Logger:          logger,
		},
		peers,
		*heartbeatTimeout,
	)

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	go func() {
		logger.Fatal(grpcServer.Serve(lis))
	}()

	lishttp, err := net.Listen("tcp", selflis)

	if err != nil {
		logger.Fatal(err)
	}

	logger.Fatal(http.Serve(lishttp, node.Handler()))
}

func rungorums(
	logger logrus.FieldLogger,
	lis net.Listener, grpcServer *grpc.Server,
	id uint64, ids []uint64, nodes []string,
) {
	storage, err := raft.NewFileStorage(fmt.Sprintf("db%.2d.bolt", id), !*recover)

	if err != nil {
		logger.Fatal(err)
	}

	storageWithCache := raft.NewCacheStorage(storage, 20000)

	node := raftgorums.NewRaft(NewStore(), &raftgorums.Config{
		ID:                id,
		Servers:           nodes,
		InitialCluster:    ids,
		Batch:             *batch,
		Storage:           storageWithCache,
		ElectionTimeout:   *electionTimeout,
		HeartbeatTimeout:  *heartbeatTimeout,
		EntriesPerMsg:     *entriesPerMsg,
		CatchupMultiplier: *catchupMultiplier,
		Logger:            logger,
		MetricsEnabled:    true,
	})

	service := NewService(node)
	rkvpb.RegisterRKVServer(grpcServer, service)

	go func() {
		logger.Fatal(grpcServer.Serve(lis))
	}()

	logger.Fatal(node.Run(grpcServer))
}

func selfIDsNodes(logger logrus.FieldLogger, id uint64, ids []uint64, nodes []string) (string, []uint64, []string) {
	host, port, err := net.SplitHostPort(nodes[id-1])
	if err != nil {
		logger.Fatal(err)
	}
	p, _ := strconv.Atoi(port)
	selflis := host + ":" + strconv.Itoa(p-100)

	ids = append(ids[:id-1], ids[id:]...)
	nodes = append(nodes[:id-1], nodes[id:]...)

	return selflis, ids, nodes
}
<|endoftext|>