<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/klog"
	"k8s.io/klog/klogr"
	"k8s.io/kops/cmd/kops-controller/controllers"
	"k8s.io/kops/cmd/kops-controller/pkg/config"
	"k8s.io/kops/pkg/nodeidentity"
	nodeidentityaws "k8s.io/kops/pkg/nodeidentity/aws"
	nodeidentitygce "k8s.io/kops/pkg/nodeidentity/gce"
	nodeidentityos "k8s.io/kops/pkg/nodeidentity/openstack"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/yaml"
	// +kubebuilder:scaffold:imports
)

var (
	scheme   = runtime.NewScheme()
	setupLog = ctrl.Log.WithName("setup")
)

func init() {

	// +kubebuilder:scaffold:scheme
}

func main() {
	klog.InitFlags(nil)

	// Disable metrics by default (avoid port conflicts, also risky because we are host network)
	metricsAddress := ":0"
	//flag.StringVar(&metricsAddr, "metrics-addr", metricsAddress, "The address the metric endpoint binds to.")

	configPath := "/etc/kubernetes/kops-controller/config.yaml"
	flag.StringVar(&configPath, "conf", configPath, "Location of yaml configuration file")

	flag.Parse()

	if configPath == "" {
		klog.Fatalf("must specify --conf")
	}

	var opt config.Options
	opt.PopulateDefaults()

	{
		b, err := ioutil.ReadFile(configPath)
		if err != nil {
			klog.Fatalf("failed to read configuration file %q: %v", configPath, err)
		}

		if err := yaml.Unmarshal(b, &opt); err != nil {
			klog.Fatalf("failed to parse configuration file %q: %v", configPath, err)
		}
	}

	ctrl.SetLogger(klogr.New())

	if err := buildScheme(); err != nil {
		setupLog.Error(err, "error building scheme")
		os.Exit(1)
	}

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:             scheme,
		MetricsBindAddress: metricsAddress,
		LeaderElection:     true,
		LeaderElectionID:   "kops-controller-leader",
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}

	if err := addNodeController(mgr, &opt); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "NodeController")
		os.Exit(1)
	}
	// +kubebuilder:scaffold:builder

	setupLog.Info("starting manager")
	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

func buildScheme() error {
	if err := corev1.AddToScheme(scheme); err != nil {
		return fmt.Errorf("error registering corev1: %v", err)
	}
	return nil
}

func addNodeController(mgr manager.Manager, opt *config.Options) error {
	var identifier nodeidentity.Identifier
	var err error
	switch opt.Cloud {
	case "aws":
		identifier, err = nodeidentityaws.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}
	case "gce":
		identifier, err = nodeidentitygce.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}

	case "openstack":
		identifier, err = nodeidentityos.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}

	case "":
		return fmt.Errorf("must specify cloud")

	default:
		return fmt.Errorf("identifier for cloud %q not implemented", opt.Cloud)
	}

	if opt.ConfigBase == "" {
		return fmt.Errorf("must specify configBase")
	}

	nodeController, err := controllers.NewNodeReconciler(mgr, opt.ConfigBase, identifier)
	if err != nil {
		return err
	}
	if err := nodeController.SetupWithManager(mgr); err != nil {
		return err
	}

	return nil
}
<commit_msg>Initial work<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/klog"
	"k8s.io/klog/klogr"
	"k8s.io/kops/cmd/kops-controller/controllers"
	"k8s.io/kops/cmd/kops-controller/pkg/config"
	"k8s.io/kops/pkg/nodeidentity"
	nodeidentityaws "k8s.io/kops/pkg/nodeidentity/aws"
	nodeidentitygce "k8s.io/kops/pkg/nodeidentity/gce"
	nodeidentityos "k8s.io/kops/pkg/nodeidentity/openstack"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/yaml"
	// +kubebuilder:scaffold:imports
)

var (
	scheme   = runtime.NewScheme()
	setupLog = ctrl.Log.WithName("setup")
)

func init() {

	// +kubebuilder:scaffold:scheme
}

func main() {
	klog.InitFlags(nil)

	klog.Infof("sri testing")

	// Disable metrics by default (avoid port conflicts, also risky because we are host network)
	metricsAddress := ":0"
	//flag.StringVar(&metricsAddr, "metrics-addr", metricsAddress, "The address the metric endpoint binds to.")

	configPath := "/etc/kubernetes/kops-controller/config.yaml"
	flag.StringVar(&configPath, "conf", configPath, "Location of yaml configuration file")

	flag.Parse()

	if configPath == "" {
		klog.Fatalf("must specify --conf")
	}

	var opt config.Options
	opt.PopulateDefaults()

	{
		b, err := ioutil.ReadFile(configPath)
		if err != nil {
			klog.Fatalf("failed to read configuration file %q: %v", configPath, err)
		}

		if err := yaml.Unmarshal(b, &opt); err != nil {
			klog.Fatalf("failed to parse configuration file %q: %v", configPath, err)
		}
	}

	ctrl.SetLogger(klogr.New())

	if err := buildScheme(); err != nil {
		setupLog.Error(err, "error building scheme")
		os.Exit(1)
	}

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:             scheme,
		MetricsBindAddress: metricsAddress,
		LeaderElection:     true,
		LeaderElectionID:   "kops-controller-leader",
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}

	if err := addNodeController(mgr, &opt); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "NodeController")
		os.Exit(1)
	}
	// +kubebuilder:scaffold:builder

	setupLog.Info("starting manager")
	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

func buildScheme() error {
	if err := corev1.AddToScheme(scheme); err != nil {
		return fmt.Errorf("error registering corev1: %v", err)
	}
	return nil
}

func addNodeController(mgr manager.Manager, opt *config.Options) error {
	var identifier nodeidentity.Identifier
	var err error
	switch opt.Cloud {
	case "aws":
		identifier, err = nodeidentityaws.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}
	case "gce":
		identifier, err = nodeidentitygce.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}

	case "openstack":
		identifier, err = nodeidentityos.New()
		if err != nil {
			return fmt.Errorf("error building identifier: %v", err)
		}

	case "":
		return fmt.Errorf("must specify cloud")

	default:
		return fmt.Errorf("identifier for cloud %q not implemented", opt.Cloud)
	}

	if opt.ConfigBase == "" {
		return fmt.Errorf("must specify configBase")
	}

	nodeController, err := controllers.NewNodeReconciler(mgr, opt.ConfigBase, identifier)
	if err != nil {
		return err
	}
	if err := nodeController.SetupWithManager(mgr); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/spf13/cobra"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/kops/cmd/kops/util"
	kopsapi "k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/cloudinstances"
	"k8s.io/kops/pkg/commands/commandutils"
	"k8s.io/kops/pkg/instancegroups"
	"k8s.io/kops/pkg/validation"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

// DeleteInstanceOptions is the command Object for an instance deletion.
type DeleteInstanceOptions struct {
	Yes       bool
	CloudOnly bool

	// The following two variables are when kOps is validating a cluster
	// between detach and deletion.

	// FailOnDrainError fail deletion if drain errors.
	FailOnDrainError bool

	// FailOnValidate fail the deletion when the cluster
	// does not validate, after a validation period.
	FailOnValidate bool

	// PostDrainDelay is the duration of a pause after a drain operation
	PostDrainDelay time.Duration

	// ValidationTimeout is the timeout for validation to succeed after the drain and pause
	ValidationTimeout time.Duration

	// ValidateCount is the amount of time that a cluster needs to be validated between drain and deletion
	ValidateCount int32

	ClusterName string

	InstanceID string

	Surge bool
}

func (o *DeleteInstanceOptions) initDefaults() {
	d := &RollingUpdateOptions{}
	d.InitDefaults()

	o.CloudOnly = false
	o.FailOnDrainError = false
	o.FailOnValidate = true

	o.PostDrainDelay = d.PostDrainDelay
	o.ValidationTimeout = d.ValidationTimeout
	o.ValidateCount = d.ValidateCount

	o.Surge = true
}

func NewCmdDeleteInstance(f *util.Factory, out io.Writer) *cobra.Command {
	deleteInstanceLong := templates.LongDesc(i18n.T(`
		Delete an instance. By default, it will detach the instance from
		the instance group, drain it, then terminate it.`))

	deleteInstanceExample := templates.Examples(i18n.T(`
		# Delete an instance from the currently active cluster.
		kops delete instance i-0a5ed581b862d3425 --yes

		# Delete an instance from the currently active cluster using node name.
		kops delete instance ip-xx.xx.xx.xx.ec2.internal --yes

		# Delete an instance from the currently active cluster without
		validation or draining.
		kops delete instance --cloudonly i-0a5ed581b862d3425 --yes
		`))

	deleteInstanceShort := i18n.T(`Delete an instance.`)

	var options DeleteInstanceOptions
	options.initDefaults()

	cmd := &cobra.Command{
		Use:     "instance INSTANCE|NODE",
		Short:   deleteInstanceShort,
		Long:    deleteInstanceLong,
		Example: deleteInstanceExample,
		Args: func(cmd *cobra.Command, args []string) error {
			options.ClusterName = rootCommand.ClusterName(true)
			if options.ClusterName == "" {
				return fmt.Errorf("--name is required")
			}

			if len(args) == 0 {
				return fmt.Errorf("must specify ID of instance or name of node to delete")
			}
			options.InstanceID = args[0]

			if len(args) != 1 {
				return fmt.Errorf("can only delete one instance at a time")
			}

			return nil
		},
		ValidArgsFunction: completeInstanceOrNode(f, &options),
		RunE: func(cmd *cobra.Command, args []string) error {
			return RunDeleteInstance(context.TODO(), f, out, &options)
		},
	}

	cmd.Flags().BoolVar(&options.CloudOnly, "cloudonly", options.CloudOnly, "Perform deletion update without confirming progress with Kubernetes")
	cmd.Flags().BoolVar(&options.Surge, "surge", options.Surge, "Surge by detaching the node from the ASG before deletion")

	cmd.Flags().DurationVar(&options.ValidationTimeout, "validation-timeout", options.ValidationTimeout, "Maximum time to wait for a cluster to validate")
	cmd.Flags().Int32Var(&options.ValidateCount, "validate-count", options.ValidateCount, "Number of times that a cluster needs to be validated after single node update")
	cmd.Flags().DurationVar(&options.PostDrainDelay, "post-drain-delay", options.PostDrainDelay, "Time to wait after draining each node")

	cmd.Flags().BoolVar(&options.FailOnDrainError, "fail-on-drain-error", true, "Fail if draining a node fails")
	cmd.Flags().BoolVar(&options.FailOnValidate, "fail-on-validate-error", true, "Fail if the cluster fails to validate")

	cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Specify --yes to immediately delete the instance")

	return cmd
}

func RunDeleteInstance(ctx context.Context, f *util.Factory, out io.Writer, options *DeleteInstanceOptions) error {
	clientSet, err := f.Clientset()
	if err != nil {
		return err
	}

	cluster, err := GetCluster(ctx, f, options.ClusterName)
	if err != nil {
		return err
	}

	var nodes []v1.Node
	var k8sClient kubernetes.Interface
	var host string
	if !options.CloudOnly {
		k8sClient, host, nodes, err = getNodes(ctx, cluster, true)
		if err != nil {
			return err
		}
	}

	list, err := clientSet.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	var instanceGroups []*kopsapi.InstanceGroup
	for i := range list.Items {
		instanceGroups = append(instanceGroups, &list.Items[i])
	}

	cloud, err := cloudup.BuildCloud(cluster)
	if err != nil {
		return err
	}

	groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
	if err != nil {
		return err
	}

	cloudMember := findDeletionNode(groups, options)

	if cloudMember == nil {
		return fmt.Errorf("could not find instance %v", options.InstanceID)
	}

	if options.CloudOnly {
		fmt.Fprintf(out, "Instance %v found for deletion\n", cloudMember.ID)
	} else {
		if cloudMember.Node != nil {
			fmt.Fprintf(out, "Instance %v (%v) found for deletion\n", cloudMember.ID, cloudMember.Node.Name)
		} else {
			fmt.Fprintf(os.Stderr, "Instance is not a member of the cluster\n")
			fmt.Fprintf(os.Stderr, "Use --cloudonly to do a deletion without confirming progress with the k8s API\n\n")
			return fmt.Errorf("error finding node name for instance: %v", cloudMember.ID)
		}
	}

	if !options.Yes {
		fmt.Fprintf(out, "\nMust specify --yes to delete instance\n")
		return nil
	}

	d := &instancegroups.RollingUpdateCluster{
		Cluster:           cluster,
		Ctx:               ctx,
		MasterInterval:    0,
		NodeInterval:      0,
		BastionInterval:   0,
		Interactive:       false,
		Force:             true,
		Cloud:             cloud,
		K8sClient:         k8sClient,
		FailOnDrainError:  options.FailOnDrainError,
		FailOnValidate:    options.FailOnValidate,
		CloudOnly:         options.CloudOnly,
		ClusterName:       options.ClusterName,
		PostDrainDelay:    options.PostDrainDelay,
		ValidationTimeout: options.ValidationTimeout,
		ValidateCount:     int(options.ValidateCount),
		// TODO should we expose this to the UI?
		ValidateTickDuration:    30 * time.Second,
		ValidateSuccessDuration: 10 * time.Second,
	}

	var clusterValidator validation.ClusterValidator
	if !options.CloudOnly {
		clusterValidator, err = validation.NewClusterValidator(cluster, cloud, list, host, k8sClient)
		if err != nil {
			return fmt.Errorf("cannot create cluster validator: %v", err)
		}
	}
	d.ClusterValidator = clusterValidator

	return d.UpdateSingleInstance(cloudMember, options.Surge)
}

func getNodes(ctx context.Context, cluster *kopsapi.Cluster, verbose bool) (kubernetes.Interface, string, []v1.Node, error) {
	var nodes []v1.Node
	var k8sClient kubernetes.Interface

	contextName := cluster.ObjectMeta.Name
	clientGetter := genericclioptions.NewConfigFlags(true)
	clientGetter.Context = &contextName

	config, err := clientGetter.ToRESTConfig()
	if err != nil {
		return nil, "", nil, fmt.Errorf("cannot load kubecfg settings for %q: %v", contextName, err)
	}

	k8sClient, err = kubernetes.NewForConfig(config)
	if err != nil {
		return nil, "", nil, fmt.Errorf("cannot build kube client for %q: %v", contextName, err)
	}

	nodeList, err := k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
	if err != nil {
		if verbose {
			fmt.Fprintf(os.Stderr, "Unable to reach the kubernetes API.\n")
			fmt.Fprintf(os.Stderr, "Use --cloudonly to do a deletion without confirming progress with the k8s API\n\n")
		}
		return nil, "", nil, fmt.Errorf("listing nodes in cluster: %v", err)
	}

	if nodeList != nil {
		nodes = nodeList.Items
	}
	return k8sClient, config.Host, nodes, nil
}

func deleteNodeMatch(cloudMember *cloudinstances.CloudInstance, options *DeleteInstanceOptions) bool {
	return cloudMember.ID == options.InstanceID ||
		(!options.CloudOnly && cloudMember.Node != nil && cloudMember.Node.Name == options.InstanceID)
}

func findDeletionNode(groups map[string]*cloudinstances.CloudInstanceGroup, options *DeleteInstanceOptions) *cloudinstances.CloudInstance {
	for _, group := range groups {
		for _, r := range group.Ready {
			if deleteNodeMatch(r, options) {
				return r
			}
		}
		for _, r := range group.NeedUpdate {
			if deleteNodeMatch(r, options) {
				return r
			}
		}
	}
	return nil
}

func completeInstanceOrNode(f commandutils.Factory, options *DeleteInstanceOptions) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) > 0 {
			return nil, cobra.ShellCompDirectiveNoFileComp
		}

		commandutils.ConfigureKlogForCompletion()
		ctx := context.TODO()

		cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, f, nil)
		if cluster == nil {
			return completions, directive
		}

		var nodes []v1.Node
		var err error
		if !options.CloudOnly {
			_, _, nodes, err = getNodes(ctx, cluster, false)
			if err != nil {
				cobra.CompErrorln(err.Error())
			}
		}

		list, err := clientSet.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
		if err != nil {
			return commandutils.CompletionError("listing instance groups", err)
		}

		var instanceGroups []*kopsapi.InstanceGroup
		for i := range list.Items {
			instanceGroups = append(instanceGroups, &list.Items[i])
		}

		cloud, err := cloudup.BuildCloud(cluster)
		if err != nil {
			return commandutils.CompletionError("initializing cloud", err)
		}

		groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
		if err != nil {
			return commandutils.CompletionError("listing instances", err)
		}

		completions = nil
		longestGroup := 0
		for _, group := range groups {
			if group.InstanceGroup != nil && longestGroup < len(group.InstanceGroup.Name) {
				longestGroup = len(group.InstanceGroup.Name)
			}
		}
		for _, group := range groups {
			for _, instance := range group.Ready {
				completions = appendInstance(completions, instance, longestGroup)
			}
			for _, instance := range group.NeedUpdate {
				completions = appendInstance(completions, instance, longestGroup)
			}
		}

		return completions, cobra.ShellCompDirectiveNoFileComp
	}
}

func appendInstance(completions []string, instance *cloudinstances.CloudInstance, longestGroup int) []string {
	completion := instance.ID
	if instance.CloudInstanceGroup.InstanceGroup != nil {
		completion += "\t" + instance.CloudInstanceGroup.InstanceGroup.Name

		if instance.Node != nil {
			padding := strings.Repeat(" ", longestGroup+1-len(instance.CloudInstanceGroup.InstanceGroup.Name))
			completion += padding + instance.Node.Name
			completions = append(completions, instance.Node.Name+"\t"+instance.CloudInstanceGroup.InstanceGroup.Name+padding+instance.ID)
		}
	}
	return append(completions, completion)
}
<commit_msg>Fix SIGSEGV when deleting a Hetzner instance<commit_after>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/spf13/cobra"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/kops/cmd/kops/util"
	kopsapi "k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/cloudinstances"
	"k8s.io/kops/pkg/commands/commandutils"
	"k8s.io/kops/pkg/instancegroups"
	"k8s.io/kops/pkg/validation"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

// DeleteInstanceOptions is the command Object for an instance deletion.
type DeleteInstanceOptions struct {
	Yes       bool
	CloudOnly bool

	// The following two variables are when kOps is validating a cluster
	// between detach and deletion.

	// FailOnDrainError fail deletion if drain errors.
	FailOnDrainError bool

	// FailOnValidate fail the deletion when the cluster
	// does not validate, after a validation period.
	FailOnValidate bool

	// PostDrainDelay is the duration of a pause after a drain operation
	PostDrainDelay time.Duration

	// ValidationTimeout is the timeout for validation to succeed after the drain and pause
	ValidationTimeout time.Duration

	// ValidateCount is the amount of time that a cluster needs to be validated between drain and deletion
	ValidateCount int32

	ClusterName string

	InstanceID string

	Surge bool
}

func (o *DeleteInstanceOptions) initDefaults() {
	d := &RollingUpdateOptions{}
	d.InitDefaults()

	o.CloudOnly = false
	o.FailOnDrainError = false
	o.FailOnValidate = true

	o.PostDrainDelay = d.PostDrainDelay
	o.ValidationTimeout = d.ValidationTimeout
	o.ValidateCount = d.ValidateCount

	o.Surge = true
}

func NewCmdDeleteInstance(f *util.Factory, out io.Writer) *cobra.Command {
	deleteInstanceLong := templates.LongDesc(i18n.T(`
		Delete an instance. By default, it will detach the instance from
		the instance group, drain it, then terminate it.`))

	deleteInstanceExample := templates.Examples(i18n.T(`
		# Delete an instance from the currently active cluster.
		kops delete instance i-0a5ed581b862d3425 --yes

		# Delete an instance from the currently active cluster using node name.
		kops delete instance ip-xx.xx.xx.xx.ec2.internal --yes

		# Delete an instance from the currently active cluster without
		validation or draining.
		kops delete instance --cloudonly i-0a5ed581b862d3425 --yes
		`))

	deleteInstanceShort := i18n.T(`Delete an instance.`)

	var options DeleteInstanceOptions
	options.initDefaults()

	cmd := &cobra.Command{
		Use:     "instance INSTANCE|NODE",
		Short:   deleteInstanceShort,
		Long:    deleteInstanceLong,
		Example: deleteInstanceExample,
		Args: func(cmd *cobra.Command, args []string) error {
			options.ClusterName = rootCommand.ClusterName(true)
			if options.ClusterName == "" {
				return fmt.Errorf("--name is required")
			}

			if len(args) == 0 {
				return fmt.Errorf("must specify ID of instance or name of node to delete")
			}
			options.InstanceID = args[0]

			if len(args) != 1 {
				return fmt.Errorf("can only delete one instance at a time")
			}

			return nil
		},
		ValidArgsFunction: completeInstanceOrNode(f, &options),
		RunE: func(cmd *cobra.Command, args []string) error {
			return RunDeleteInstance(context.TODO(), f, out, &options)
		},
	}

	cmd.Flags().BoolVar(&options.CloudOnly, "cloudonly", options.CloudOnly, "Perform deletion update without confirming progress with Kubernetes")
	cmd.Flags().BoolVar(&options.Surge, "surge", options.Surge, "Surge by detaching the node from the ASG before deletion")

	cmd.Flags().DurationVar(&options.ValidationTimeout, "validation-timeout", options.ValidationTimeout, "Maximum time to wait for a cluster to validate")
	cmd.Flags().Int32Var(&options.ValidateCount, "validate-count", options.ValidateCount, "Number of times that a cluster needs to be validated after single node update")
	cmd.Flags().DurationVar(&options.PostDrainDelay, "post-drain-delay", options.PostDrainDelay, "Time to wait after draining each node")

	cmd.Flags().BoolVar(&options.FailOnDrainError, "fail-on-drain-error", true, "Fail if draining a node fails")
	cmd.Flags().BoolVar(&options.FailOnValidate, "fail-on-validate-error", true, "Fail if the cluster fails to validate")

	cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Specify --yes to immediately delete the instance")

	return cmd
}

func RunDeleteInstance(ctx context.Context, f *util.Factory, out io.Writer, options *DeleteInstanceOptions) error {
	clientSet, err := f.Clientset()
	if err != nil {
		return err
	}

	cluster, err := GetCluster(ctx, f, options.ClusterName)
	if err != nil {
		return err
	}

	var nodes []v1.Node
	var k8sClient kubernetes.Interface
	var host string
	if !options.CloudOnly {
		k8sClient, host, nodes, err = getNodes(ctx, cluster, true)
		if err != nil {
			return err
		}
	}

	list, err := clientSet.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	var instanceGroups []*kopsapi.InstanceGroup
	for i := range list.Items {
		instanceGroups = append(instanceGroups, &list.Items[i])
	}

	cloud, err := cloudup.BuildCloud(cluster)
	if err != nil {
		return err
	}

	groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
	if err != nil {
		return err
	}

	cloudMember := findDeletionNode(groups, options)

	if cloudMember == nil {
		return fmt.Errorf("could not find instance %v", options.InstanceID)
	}

	if options.CloudOnly {
		fmt.Fprintf(out, "Instance %v found for deletion\n", cloudMember.ID)
	} else {
		if cloudMember.Node != nil {
			fmt.Fprintf(out, "Instance %v (%v) found for deletion\n", cloudMember.ID, cloudMember.Node.Name)
		} else {
			fmt.Fprintf(os.Stderr, "Instance is not a member of the cluster\n")
			fmt.Fprintf(os.Stderr, "Use --cloudonly to do a deletion without confirming progress with the k8s API\n\n")
			return fmt.Errorf("error finding node name for instance: %v", cloudMember.ID)
		}
	}

	if !options.Yes {
		fmt.Fprintf(out, "\nMust specify --yes to delete instance\n")
		return nil
	}

	d := &instancegroups.RollingUpdateCluster{
		Clientset:         clientSet,
		Cluster:           cluster,
		Ctx:               ctx,
		MasterInterval:    0,
		NodeInterval:      0,
		BastionInterval:   0,
		Interactive:       false,
		Force:             true,
		Cloud:             cloud,
		K8sClient:         k8sClient,
		FailOnDrainError:  options.FailOnDrainError,
		FailOnValidate:    options.FailOnValidate,
		CloudOnly:         options.CloudOnly,
		ClusterName:       options.ClusterName,
		PostDrainDelay:    options.PostDrainDelay,
		ValidationTimeout: options.ValidationTimeout,
		ValidateCount:     int(options.ValidateCount),
		// TODO should we expose this to the UI?
		ValidateTickDuration:    30 * time.Second,
		ValidateSuccessDuration: 10 * time.Second,
	}

	var clusterValidator validation.ClusterValidator
	if !options.CloudOnly {
		clusterValidator, err = validation.NewClusterValidator(cluster, cloud, list, host, k8sClient)
		if err != nil {
			return fmt.Errorf("cannot create cluster validator: %v", err)
		}
	}
	d.ClusterValidator = clusterValidator

	return d.UpdateSingleInstance(cloudMember, options.Surge)
}

func getNodes(ctx context.Context, cluster *kopsapi.Cluster, verbose bool) (kubernetes.Interface, string, []v1.Node, error) {
	var nodes []v1.Node
	var k8sClient kubernetes.Interface

	contextName := cluster.ObjectMeta.Name
	clientGetter := genericclioptions.NewConfigFlags(true)
	clientGetter.Context = &contextName

	config, err := clientGetter.ToRESTConfig()
	if err != nil {
		return nil, "", nil, fmt.Errorf("cannot load kubecfg settings for %q: %v", contextName, err)
	}

	k8sClient, err = kubernetes.NewForConfig(config)
	if err != nil {
		return nil, "", nil, fmt.Errorf("cannot build kube client for %q: %v", contextName, err)
	}

	nodeList, err := k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
	if err != nil {
		if verbose {
			fmt.Fprintf(os.Stderr, "Unable to reach the kubernetes API.\n")
			fmt.Fprintf(os.Stderr, "Use --cloudonly to do a deletion without confirming progress with the k8s API\n\n")
		}
		return nil, "", nil, fmt.Errorf("listing nodes in cluster: %v", err)
	}

	if nodeList != nil {
		nodes = nodeList.Items
	}
	return k8sClient, config.Host, nodes, nil
}

func deleteNodeMatch(cloudMember *cloudinstances.CloudInstance, options *DeleteInstanceOptions) bool {
	return cloudMember.ID == options.InstanceID ||
		(!options.CloudOnly && cloudMember.Node != nil && cloudMember.Node.Name == options.InstanceID)
}

func findDeletionNode(groups map[string]*cloudinstances.CloudInstanceGroup, options *DeleteInstanceOptions) *cloudinstances.CloudInstance {
	for _, group := range groups {
		for _, r := range group.Ready {
			if deleteNodeMatch(r, options) {
				return r
			}
		}
		for _, r := range group.NeedUpdate {
			if deleteNodeMatch(r, options) {
				return r
			}
		}
	}
	return nil
}

func completeInstanceOrNode(f commandutils.Factory, options *DeleteInstanceOptions) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if len(args) > 0 {
			return nil, cobra.ShellCompDirectiveNoFileComp
		}

		commandutils.ConfigureKlogForCompletion()
		ctx := context.TODO()

		cluster, clientSet, completions, directive := GetClusterForCompletion(ctx, f, nil)
		if cluster == nil {
			return completions, directive
		}

		var nodes []v1.Node
		var err error
		if !options.CloudOnly {
			_, _, nodes, err = getNodes(ctx, cluster, false)
			if err != nil {
				cobra.CompErrorln(err.Error())
			}
		}

		list, err := clientSet.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
		if err != nil {
			return commandutils.CompletionError("listing instance groups", err)
		}

		var instanceGroups []*kopsapi.InstanceGroup
		for i := range list.Items {
			instanceGroups = append(instanceGroups, &list.Items[i])
		}

		cloud, err := cloudup.BuildCloud(cluster)
		if err != nil {
			return commandutils.CompletionError("initializing cloud", err)
		}

		groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
		if err != nil {
			return commandutils.CompletionError("listing instances", err)
		}

		completions = nil
		longestGroup := 0
		for _, group := range groups {
			if group.InstanceGroup != nil && longestGroup < len(group.InstanceGroup.Name) {
				longestGroup = len(group.InstanceGroup.Name)
			}
		}
		for _, group := range groups {
			for _, instance := range group.Ready {
				completions = appendInstance(completions, instance, longestGroup)
			}
			for _, instance := range group.NeedUpdate {
				completions = appendInstance(completions, instance, longestGroup)
			}
		}

		return completions, cobra.ShellCompDirectiveNoFileComp
	}
}

func appendInstance(completions []string, instance *cloudinstances.CloudInstance, longestGroup int) []string {
	completion := instance.ID
	if instance.CloudInstanceGroup.InstanceGroup != nil {
		completion += "\t" + instance.CloudInstanceGroup.InstanceGroup.Name

		if instance.Node != nil {
			padding := strings.Repeat(" ", longestGroup+1-len(instance.CloudInstanceGroup.InstanceGroup.Name))
			completion += padding + instance.Node.Name
			completions = append(completions, instance.Node.Name+"\t"+instance.CloudInstanceGroup.InstanceGroup.Name+padding+instance.ID)
		}
	}
	return append(completions, completion)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 Skippbox, Ltd.

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 (
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/kubeless/kubeless/pkg/utils"
	"github.com/spf13/cobra"

	k8scmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var topicCreateCmd = &cobra.Command{
	Use:   "create <topic_name> FLAG",
	Short: "create a topic to Kubeless",
	Long:  `create a topic to Kubeless`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) != 1 {
			logrus.Fatal("Need exactly one argument - topic name")
		}
		ctlNamespace, err := cmd.Flags().GetString("kafka-namespace")
		if err != nil {
			logrus.Fatal(err)
		}

		topicName := args[0]
		command := []string{"bash", "/opt/bitnami/kafka/bin/kafka-topics.sh", "--zookeeper", "zookeeper." + ctlNamespace + ":2181", "--replication-factor", "1", "--partitions", "1", "--create", "--topic", topicName}

		execCommand(command, ctlNamespace)
	},
}

// wrapper of kubectl exec
// execCommand executes a command to kafka pod
func execCommand(command []string, ctlNamespace string) {
	f := cmdutil.NewFactory(nil)

	k8sClientSet := utils.GetClientOutOfCluster()
	pods, _ := utils.GetPodsByLabel(k8sClientSet, ctlNamespace, "kubeless", "kafka")
	params := &k8scmd.ExecOptions{
		StreamOptions: k8scmd.StreamOptions{
			Namespace:     ctlNamespace,
			PodName:       pods.Items[0].Name,
			ContainerName: "broker",
			In:            nil,
			Out:           os.Stdout,
			Err:           os.Stderr,
			TTY:           false,
		},
		Executor: &k8scmd.DefaultRemoteExecutor{},
		Command:  command,
	}
	config, err := f.ClientConfig()
	if err != nil {
		logrus.Fatalln(err)
	}
	params.Config = config

	fClientset, err := f.ClientSet()
	if err != nil {
		logrus.Fatalln(err)
	}
	params.PodClient = fClientset.Core()

	if err := params.Run(); err != nil {
		logrus.Fatalln(err)
	}
}
<commit_msg>cli: improve error message when kafka pod is not found<commit_after>/*
Copyright 2016 Skippbox, Ltd.

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 (
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/kubeless/kubeless/pkg/utils"
	"github.com/spf13/cobra"

	k8scmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var topicCreateCmd = &cobra.Command{
	Use:   "create <topic_name> FLAG",
	Short: "create a topic to Kubeless",
	Long:  `create a topic to Kubeless`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) != 1 {
			logrus.Fatal("Need exactly one argument - topic name")
		}
		ctlNamespace, err := cmd.Flags().GetString("kafka-namespace")
		if err != nil {
			logrus.Fatal(err)
		}

		topicName := args[0]
		command := []string{"bash", "/opt/bitnami/kafka/bin/kafka-topics.sh", "--zookeeper", "zookeeper." + ctlNamespace + ":2181", "--replication-factor", "1", "--partitions", "1", "--create", "--topic", topicName}

		execCommand(command, ctlNamespace)
	},
}

// wrapper of kubectl exec
// execCommand executes a command to kafka pod
func execCommand(command []string, ctlNamespace string) {
	f := cmdutil.NewFactory(nil)

	k8sClientSet := utils.GetClientOutOfCluster()
	pods, err := utils.GetPodsByLabel(k8sClientSet, ctlNamespace, "kubeless", "kafka")
	if err != nil {
		logrus.Fatalf("Can't find the kafka pod: %v", err)
	} else if len(pods.Items) == 0 {
		logrus.Fatalln("Can't find any kafka pod")
	}
	params := &k8scmd.ExecOptions{
		StreamOptions: k8scmd.StreamOptions{
			Namespace:     ctlNamespace,
			PodName:       pods.Items[0].Name,
			ContainerName: "broker",
			In:            nil,
			Out:           os.Stdout,
			Err:           os.Stderr,
			TTY:           false,
		},
		Executor: &k8scmd.DefaultRemoteExecutor{},
		Command:  command,
	}
	config, err := f.ClientConfig()
	if err != nil {
		logrus.Fatalln(err)
	}
	params.Config = config

	fClientset, err := f.ClientSet()
	if err != nil {
		logrus.Fatalln(err)
	}
	params.PodClient = fClientset.Core()

	if err := params.Run(); err != nil {
		logrus.Fatalln(err)
	}
}
<|endoftext|>


<commit_before>package operate

import (
	"github.com/itchio/butler/butlerd"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
)

func MakeSourceURL(client *itchio.Client, consumer *state.Consumer, sessionID string, params *InstallParams, fileType string) string {
	build := params.Build
	if build != nil {
		if fileType == "" {
			fileType = "archive"
			if FindBuildFile(build.Files, itchio.BuildFileTypeUnpacked, itchio.BuildFileSubTypeDefault) != nil {
				fileType = "unpacked"
			}
		}

		return client.MakeBuildDownloadURL(itchio.MakeBuildDownloadURLParams{
			BuildID:     build.ID,
			UUID:        sessionID,
			Credentials: params.Access.Credentials,
			Type:        itchio.BuildFileType(fileType),
		})
	}

	return client.MakeUploadDownloadURL(itchio.MakeUploadDownloadURLParams{
		UploadID:    params.Upload.ID,
		UUID:        sessionID,
		Credentials: params.Access.Credentials,
	})
}

type ItchfsURLParams struct {
	Credentials *butlerd.GameCredentials
	UploadID    int64
	BuildID     int64
	FileType    string
	UUID        string
}
<commit_msg>Don't prefer unpacked when installing.<commit_after>package operate

import (
	"github.com/itchio/butler/butlerd"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
)

func MakeSourceURL(client *itchio.Client, consumer *state.Consumer, sessionID string, params *InstallParams, fileType string) string {
	build := params.Build
	if build != nil {
		if fileType == "" {
			fileType = "archive"
		}

		return client.MakeBuildDownloadURL(itchio.MakeBuildDownloadURLParams{
			BuildID:     build.ID,
			UUID:        sessionID,
			Credentials: params.Access.Credentials,
			Type:        itchio.BuildFileType(fileType),
		})
	}

	return client.MakeUploadDownloadURL(itchio.MakeUploadDownloadURLParams{
		UploadID:    params.Upload.ID,
		UUID:        sessionID,
		Credentials: params.Access.Credentials,
	})
}

type ItchfsURLParams struct {
	Credentials *butlerd.GameCredentials
	UploadID    int64
	BuildID     int64
	FileType    string
	UUID        string
}
<|endoftext|>


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

package main

import (
	"context"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/prometheus/prometheus/notifier"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/util/testutil"
)

var promPath string
var promConfig = filepath.Join("..", "..", "documentation", "examples", "prometheus.yml")
var promData = filepath.Join(os.TempDir(), "data")

func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Short() {
		os.Exit(m.Run())
	}
	// On linux with a global proxy the tests will fail as the go client(http,grpc) tries to connect through the proxy.
	os.Setenv("no_proxy", "localhost,127.0.0.1,0.0.0.0,:")

	var err error
	promPath, err = os.Getwd()
	if err != nil {
		fmt.Printf("can't get current dir :%s \n", err)
		os.Exit(1)
	}
	promPath = filepath.Join(promPath, "prometheus")

	build := exec.Command("go", "build", "-o", promPath)
	output, err := build.CombinedOutput()
	if err != nil {
		fmt.Printf("compilation error :%s \n", output)
		os.Exit(1)
	}

	exitCode := m.Run()
	os.Remove(promPath)
	os.RemoveAll(promData)
	os.Exit(exitCode)
}

// As soon as prometheus starts responding to http request should be able to accept Interrupt signals for a graceful shutdown.
func TestStartupInterrupt(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	prom := exec.Command(promPath, "--config.file="+promConfig, "--storage.tsdb.path="+promData)
	err := prom.Start()
	if err != nil {
		t.Errorf("execution error: %v", err)
		return
	}

	done := make(chan error)
	go func() {
		done <- prom.Wait()
	}()

	var startedOk bool
	var stoppedErr error

Loop:
	for x := 0; x < 10; x++ {
		// error=nil means prometheus has started so can send the interrupt signal and wait for the grace shutdown.
		if _, err := http.Get("http://localhost:9090/graph"); err == nil {
			startedOk = true
			prom.Process.Signal(os.Interrupt)
			select {
			case stoppedErr = <-done:
				break Loop
			case <-time.After(10 * time.Second):
			}
			break Loop
		}
		time.Sleep(500 * time.Millisecond)
	}

	if !startedOk {
		t.Errorf("prometheus didn't start in the specified timeout")
		return
	}
	if err := prom.Process.Kill(); err == nil {
		t.Errorf("prometheus didn't shutdown gracefully after sending the Interrupt signal")
	} else if stoppedErr != nil && stoppedErr.Error() != "signal: interrupt" { // TODO - find a better way to detect when the process didn't exit as expected!
		t.Errorf("prometheus exited with an unexpected error:%v", stoppedErr)
	}
}

func TestComputeExternalURL(t *testing.T) {
	tests := []struct {
		input string
		valid bool
	}{
		{
			input: "",
			valid: true,
		},
		{
			input: "http://proxy.com/prometheus",
			valid: true,
		},
		{
			input: "'https://url/prometheus'",
			valid: false,
		},
		{
			input: "'relative/path/with/quotes'",
			valid: false,
		},
		{
			input: "http://alertmanager.company.com",
			valid: true,
		},
		{
			input: "https://double--dash.de",
			valid: true,
		},
		{
			input: "'http://starts/with/quote",
			valid: false,
		},
		{
			input: "ends/with/quote\"",
			valid: false,
		},
	}

	for _, test := range tests {
		_, err := computeExternalURL(test.input, "0.0.0.0:9090")
		if test.valid {
			testutil.Ok(t, err)
		} else {
			testutil.NotOk(t, err, "input=%q", test.input)
		}
	}
}

// Let's provide an invalid configuration file and verify the exit status indicates the error.
func TestFailedStartupExitCode(t *testing.T) {
	fakeInputFile := "fake-input-file"
	expectedExitStatus := 1

	prom := exec.Command(promPath, "--config.file="+fakeInputFile)
	err := prom.Run()
	testutil.NotOk(t, err, "")

	if exitError, ok := err.(*exec.ExitError); ok {
		status := exitError.Sys().(syscall.WaitStatus)
		testutil.Equals(t, expectedExitStatus, status.ExitStatus())
	} else {
		t.Errorf("unable to retrieve the exit status for prometheus: %v", err)
	}
}

type senderFunc func(alerts ...*notifier.Alert)

func (s senderFunc) Send(alerts ...*notifier.Alert) {
	s(alerts...)
}

func TestSendAlerts(t *testing.T) {
	testCases := []struct {
		in  []*rules.Alert
		exp []*notifier.Alert
	}{
		{
			in: []*rules.Alert{
				&rules.Alert{
					Labels:      []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations: []labels.Label{{Name: "a2", Value: "v2"}},
					ActiveAt:    time.Unix(1, 0),
					FiredAt:     time.Unix(2, 0),
					ValidUntil:  time.Unix(3, 0),
				},
			},
			exp: []*notifier.Alert{
				&notifier.Alert{
					Labels:       []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations:  []labels.Label{{Name: "a2", Value: "v2"}},
					StartsAt:     time.Unix(2, 0),
					EndsAt:       time.Unix(3, 0),
					GeneratorURL: "http://localhost:9090/graph?g0.expr=up&g0.tab=1",
				},
			},
		},
		{
			in: []*rules.Alert{
				&rules.Alert{
					Labels:      []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations: []labels.Label{{Name: "a2", Value: "v2"}},
					ActiveAt:    time.Unix(1, 0),
					FiredAt:     time.Unix(2, 0),
					ResolvedAt:  time.Unix(4, 0),
				},
			},
			exp: []*notifier.Alert{
				&notifier.Alert{
					Labels:       []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations:  []labels.Label{{Name: "a2", Value: "v2"}},
					StartsAt:     time.Unix(2, 0),
					EndsAt:       time.Unix(4, 0),
					GeneratorURL: "http://localhost:9090/graph?g0.expr=up&g0.tab=1",
				},
			},
		},
		{
			in: []*rules.Alert{},
		},
	}

	for i, tc := range testCases {
		tc := tc
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			senderFunc := senderFunc(func(alerts ...*notifier.Alert) {
				if len(tc.in) == 0 {
					t.Fatalf("sender called with 0 alert")
				}
				testutil.Equals(t, tc.exp, alerts)
			})
			sendAlerts(senderFunc, "http://localhost:9090")(context.TODO(), "up", tc.in...)
		})
	}
}

func TestWALSegmentSizeBounds(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	for size, expectedExitStatus := range map[string]int{"9MB": 1, "257MB": 1, "10": 2, "1GB": 1, "12MB": 0} {
		prom := exec.Command(promPath, "--storage.tsdb.wal-segment-size="+size, "--config.file="+promConfig)
		err := prom.Start()
		testutil.Ok(t, err)

		if expectedExitStatus == 0 {
			done := make(chan error, 1)
			go func() { done <- prom.Wait() }()
			select {
			case err := <-done:
				t.Errorf("prometheus should be still running: %v", err)
			case <-time.After(5 * time.Second):
				prom.Process.Signal(os.Interrupt)
			}
			continue
		}

		err = prom.Wait()
		testutil.NotOk(t, err, "")
		if exitError, ok := err.(*exec.ExitError); ok {
			status := exitError.Sys().(syscall.WaitStatus)
			testutil.Equals(t, expectedExitStatus, status.ExitStatus())
		} else {
			t.Errorf("unable to retrieve the exit status for prometheus: %v", err)
		}
	}
}
<commit_msg>fix TestFailedStartupExitCode (#5076)<commit_after>// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/prometheus/prometheus/notifier"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/util/testutil"
)

var promPath string
var promConfig = filepath.Join("..", "..", "documentation", "examples", "prometheus.yml")
var promData = filepath.Join(os.TempDir(), "data")

func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Short() {
		os.Exit(m.Run())
	}
	// On linux with a global proxy the tests will fail as the go client(http,grpc) tries to connect through the proxy.
	os.Setenv("no_proxy", "localhost,127.0.0.1,0.0.0.0,:")

	var err error
	promPath, err = os.Getwd()
	if err != nil {
		fmt.Printf("can't get current dir :%s \n", err)
		os.Exit(1)
	}
	promPath = filepath.Join(promPath, "prometheus")

	build := exec.Command("go", "build", "-o", promPath)
	output, err := build.CombinedOutput()
	if err != nil {
		fmt.Printf("compilation error :%s \n", output)
		os.Exit(1)
	}

	exitCode := m.Run()
	os.Remove(promPath)
	os.RemoveAll(promData)
	os.Exit(exitCode)
}

// As soon as prometheus starts responding to http request should be able to accept Interrupt signals for a graceful shutdown.
func TestStartupInterrupt(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	prom := exec.Command(promPath, "--config.file="+promConfig, "--storage.tsdb.path="+promData)
	err := prom.Start()
	if err != nil {
		t.Errorf("execution error: %v", err)
		return
	}

	done := make(chan error)
	go func() {
		done <- prom.Wait()
	}()

	var startedOk bool
	var stoppedErr error

Loop:
	for x := 0; x < 10; x++ {
		// error=nil means prometheus has started so can send the interrupt signal and wait for the grace shutdown.
		if _, err := http.Get("http://localhost:9090/graph"); err == nil {
			startedOk = true
			prom.Process.Signal(os.Interrupt)
			select {
			case stoppedErr = <-done:
				break Loop
			case <-time.After(10 * time.Second):
			}
			break Loop
		}
		time.Sleep(500 * time.Millisecond)
	}

	if !startedOk {
		t.Errorf("prometheus didn't start in the specified timeout")
		return
	}
	if err := prom.Process.Kill(); err == nil {
		t.Errorf("prometheus didn't shutdown gracefully after sending the Interrupt signal")
	} else if stoppedErr != nil && stoppedErr.Error() != "signal: interrupt" { // TODO - find a better way to detect when the process didn't exit as expected!
		t.Errorf("prometheus exited with an unexpected error:%v", stoppedErr)
	}
}

func TestComputeExternalURL(t *testing.T) {
	tests := []struct {
		input string
		valid bool
	}{
		{
			input: "",
			valid: true,
		},
		{
			input: "http://proxy.com/prometheus",
			valid: true,
		},
		{
			input: "'https://url/prometheus'",
			valid: false,
		},
		{
			input: "'relative/path/with/quotes'",
			valid: false,
		},
		{
			input: "http://alertmanager.company.com",
			valid: true,
		},
		{
			input: "https://double--dash.de",
			valid: true,
		},
		{
			input: "'http://starts/with/quote",
			valid: false,
		},
		{
			input: "ends/with/quote\"",
			valid: false,
		},
	}

	for _, test := range tests {
		_, err := computeExternalURL(test.input, "0.0.0.0:9090")
		if test.valid {
			testutil.Ok(t, err)
		} else {
			testutil.NotOk(t, err, "input=%q", test.input)
		}
	}
}

// Let's provide an invalid configuration file and verify the exit status indicates the error.
func TestFailedStartupExitCode(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	fakeInputFile := "fake-input-file"
	expectedExitStatus := 1

	prom := exec.Command(promPath, "--config.file="+fakeInputFile)
	err := prom.Run()
	testutil.NotOk(t, err, "")

	if exitError, ok := err.(*exec.ExitError); ok {
		status := exitError.Sys().(syscall.WaitStatus)
		testutil.Equals(t, expectedExitStatus, status.ExitStatus())
	} else {
		t.Errorf("unable to retrieve the exit status for prometheus: %v", err)
	}
}

type senderFunc func(alerts ...*notifier.Alert)

func (s senderFunc) Send(alerts ...*notifier.Alert) {
	s(alerts...)
}

func TestSendAlerts(t *testing.T) {
	testCases := []struct {
		in  []*rules.Alert
		exp []*notifier.Alert
	}{
		{
			in: []*rules.Alert{
				&rules.Alert{
					Labels:      []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations: []labels.Label{{Name: "a2", Value: "v2"}},
					ActiveAt:    time.Unix(1, 0),
					FiredAt:     time.Unix(2, 0),
					ValidUntil:  time.Unix(3, 0),
				},
			},
			exp: []*notifier.Alert{
				&notifier.Alert{
					Labels:       []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations:  []labels.Label{{Name: "a2", Value: "v2"}},
					StartsAt:     time.Unix(2, 0),
					EndsAt:       time.Unix(3, 0),
					GeneratorURL: "http://localhost:9090/graph?g0.expr=up&g0.tab=1",
				},
			},
		},
		{
			in: []*rules.Alert{
				&rules.Alert{
					Labels:      []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations: []labels.Label{{Name: "a2", Value: "v2"}},
					ActiveAt:    time.Unix(1, 0),
					FiredAt:     time.Unix(2, 0),
					ResolvedAt:  time.Unix(4, 0),
				},
			},
			exp: []*notifier.Alert{
				&notifier.Alert{
					Labels:       []labels.Label{{Name: "l1", Value: "v1"}},
					Annotations:  []labels.Label{{Name: "a2", Value: "v2"}},
					StartsAt:     time.Unix(2, 0),
					EndsAt:       time.Unix(4, 0),
					GeneratorURL: "http://localhost:9090/graph?g0.expr=up&g0.tab=1",
				},
			},
		},
		{
			in: []*rules.Alert{},
		},
	}

	for i, tc := range testCases {
		tc := tc
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			senderFunc := senderFunc(func(alerts ...*notifier.Alert) {
				if len(tc.in) == 0 {
					t.Fatalf("sender called with 0 alert")
				}
				testutil.Equals(t, tc.exp, alerts)
			})
			sendAlerts(senderFunc, "http://localhost:9090")(context.TODO(), "up", tc.in...)
		})
	}
}

func TestWALSegmentSizeBounds(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}

	for size, expectedExitStatus := range map[string]int{"9MB": 1, "257MB": 1, "10": 2, "1GB": 1, "12MB": 0} {
		prom := exec.Command(promPath, "--storage.tsdb.wal-segment-size="+size, "--config.file="+promConfig)
		err := prom.Start()
		testutil.Ok(t, err)

		if expectedExitStatus == 0 {
			done := make(chan error, 1)
			go func() { done <- prom.Wait() }()
			select {
			case err := <-done:
				t.Errorf("prometheus should be still running: %v", err)
			case <-time.After(5 * time.Second):
				prom.Process.Signal(os.Interrupt)
			}
			continue
		}

		err = prom.Wait()
		testutil.NotOk(t, err, "")
		if exitError, ok := err.(*exec.ExitError); ok {
			status := exitError.Sys().(syscall.WaitStatus)
			testutil.Equals(t, expectedExitStatus, status.ExitStatus())
		} else {
			t.Errorf("unable to retrieve the exit status for prometheus: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/signal"
	"runtime"
	"syscall"

	"shadowsocks-go/cmd/shadowss"
	"shadowsocks-go/pkg/proxyserver"
	"shadowsocks-go/pkg/util/flag"

	"github.com/golang/glog"
	"github.com/spf13/pflag"
)

func waitSignal() {
	var sigChan = make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGHUP)
	for sig := range sigChan {
		if sig == syscall.SIGHUP {
			//updatePasswd()
		} else {
			// is this going to happen?
			log.Printf("caught signal %v, exit", sig)
			os.Exit(0)
		}
	}
}

// func start(udp bool) {
// 	for _, v := range config.ServerCfg.Clients {
// 		glog.V(5).Infof("listen pair(%v:%v) with auth:%v", v.Port, v.Password, v.EnableOTA)
// 		go shadowss.Run(v.Password, v.EncryptMethod, v.Port, v.EnableOTA, time.Duration(v.Timeout)*time.Second)
// 		if udp {
// 			go shadowss.RunUDP(v.Password, v.EncryptMethod, v.Port, time.Duration(v.Timeout)*time.Second)
// 		}
// 	}
// 	//
// 	// if udp {
// 	// 	go shadowss.RunUDP(string("barfoo"), string("aes-128-cfb"), 53, time.Duration(200)*time.Second)
// 	// }
// }

func main() {
	serverRunOptions := shadowss.NewServerOption()

	// Parse command line flags.
	serverRunOptions.AddFlags(pflag.CommandLine)
	flag.InitFlags()

	if serverRunOptions.CpuCoreNum > 0 {
		runtime.GOMAXPROCS(serverRunOptions.CpuCoreNum)
	}

	glog.V(5).Infoln("tset")
	err := serverRunOptions.LoadConfigFile()
	if err != nil {
		glog.Fatalln("load user configure error:\r\n", err)
	}

	pxy := proxyserver.NewServers(serverRunOptions.EnableUDPRelay)
	pxy.Start()

	waitSignal()
}
<commit_msg>raise user module when startup<commit_after>package main

import (
	"log"
	"os"
	"os/signal"
	"runtime"
	"syscall"

	"shadowsocks-go/cmd/shadowss"
	"shadowsocks-go/pkg/proxyserver"
	"shadowsocks-go/pkg/users"
	"shadowsocks-go/pkg/util/flag"

	"github.com/golang/glog"
	"github.com/spf13/pflag"
)

func waitSignal() {
	var sigChan = make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGHUP)
	for sig := range sigChan {
		if sig == syscall.SIGHUP {
			//updatePasswd()
		} else {
			// is this going to happen?
			log.Printf("caught signal %v, exit", sig)
			os.Exit(0)
		}
	}
}

// func start(udp bool) {
// 	for _, v := range config.ServerCfg.Clients {
// 		glog.V(5).Infof("listen pair(%v:%v) with auth:%v", v.Port, v.Password, v.EnableOTA)
// 		go shadowss.Run(v.Password, v.EncryptMethod, v.Port, v.EnableOTA, time.Duration(v.Timeout)*time.Second)
// 		if udp {
// 			go shadowss.RunUDP(v.Password, v.EncryptMethod, v.Port, time.Duration(v.Timeout)*time.Second)
// 		}
// 	}
// 	//
// 	// if udp {
// 	// 	go shadowss.RunUDP(string("barfoo"), string("aes-128-cfb"), 53, time.Duration(200)*time.Second)
// 	// }
// }

func main() {
	serverRunOptions := shadowss.NewServerOption()
	// Parse command line flags.
	serverRunOptions.AddFlags(pflag.CommandLine)

	users := users.NewUsers()
	users.AddFlags(pflag.CommandLine)

	flag.InitFlags()

	if serverRunOptions.CpuCoreNum > 0 {
		runtime.GOMAXPROCS(serverRunOptions.CpuCoreNum)
	}

	glog.V(5).Infoln("tset")
	err := serverRunOptions.LoadConfigFile()
	if err != nil {
		glog.Fatalln("load user configure error:\r\n", err)
	}

	pxy := proxyserver.NewServers(serverRunOptions.EnableUDPRelay)
	pxy.Start()

	users.CreateUsersSync(pxy)
	waitSignal()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"os"
	"sync"
	"time"

	"github.com/nsf/termbox-go"
)

var (
	m      *sync.Mutex
	meY    = 12
	enemyY = 12
	ball   = []int{40, 12}
	vector = []int{-1, 1}
	level  = 0
	score  = []int{0, 0}
	shadow [][]int
	ipAddr string
	clear  = false
)

const (
	WallLeft   = 0
	WallRight  = 79
	WallTop    = 1
	WallBottom = 23
	MeX        = 76
	EnemyX     = 2
	Bar        = 4
)

func drawLine(x, y int, str string) {
	runes := []rune(str)
	for i := 0; i < len(runes); i++ {
		termbox.SetCell(x+i, y, runes[i], termbox.ColorDefault, termbox.ColorDefault)
	}
}

func draw() {

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	if clear == true {
		return
	}
	drawLine(WallLeft, WallTop-1, fmt.Sprintf("                                                                     %03d - %03d", score[0], score[1]))
	drawLine(WallLeft, WallTop, fmt.Sprintf("--------------------------------------------------------------------------------"))
	drawLine(WallLeft, WallBottom, fmt.Sprintf("--------------------------------------------------------------------------------"))
	drawLine(WallLeft, WallBottom+1, fmt.Sprintf("EXIT : ESC KEY"))
	drawLine(ball[0], ball[1], fmt.Sprintf("*"))

	for i := range shadow {
		drawLine(shadow[i][0], shadow[i][1], fmt.Sprintf(string(ipAddr[len(ipAddr)-i-1])))

	}

	for i := 0; i < Bar; i++ {
		drawLine(MeX, meY+i, fmt.Sprintf("||"))
		drawLine(EnemyX, enemyY+i, fmt.Sprintf("||"))
	}
	m.Lock()
	defer m.Unlock()
	termbox.Flush()
}

func keyEvent() {
	draw()
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			switch ev.Key {
			case termbox.KeyEsc:
				clear = true
				draw()
				return
			case termbox.KeyArrowUp:
				if meY > WallTop+1 {
					meY--
				}
			case termbox.KeyArrowDown:
				if meY < WallBottom-Bar {
					meY++
				}
			default:
			}
			draw()
		default:
		}
		hitTest()
	}
}

func moveBall() {
	for {

		ball[0] += vector[0]
		ball[1] += vector[1]
		hitTest()
		draw()

		ball[0] += vector[0]
		hitTest()
		recMove()
		draw()

		if ball[1] <= WallTop+1 || ball[1] >= WallBottom-1 {
			vector[1] *= -1
		}

		if ball[0] <= WallLeft+1 || ball[0] >= WallRight-1 {
			vector[0] *= -1
			if ball[0] <= WallLeft+1 {
				score[1]++
			}
			if ball[0] >= WallRight-1 {
				score[0]++
			}
			initGame()
			draw()
			time.Sleep(time.Duration(500) * time.Millisecond)
		}

		time.Sleep(time.Duration(100-level*5) * time.Millisecond)
	}
}
func recMove() {
	shadow = append(shadow, []int{ball[0], ball[1]})
	shadow = shadow[1:]
}
func moveEnemy() {
	vec := 0
	for {

		vec = ball[1] - (enemyY + 2)

		if enemyY > WallTop+1 && vec < 0 {
			enemyY--
		}
		if enemyY < WallBottom-Bar && vec > 0 {
			enemyY++
		}
		hitTest()
		draw()

		switch true {
		case ball[0] < 30:
			time.Sleep(100 * time.Millisecond)
		case ball[0] < 50:
			time.Sleep(150 * time.Millisecond)
		case ball[0] < 80:
			time.Sleep(180 * time.Millisecond)
		default:
			time.Sleep(200 * time.Millisecond)
		}
	}
}

func hitTest() {
	if vector[0] == 1 && ball[0] > WallRight-10 {
		if (ball[0] == MeX || ball[0] == MeX-1) && ball[1] >= meY && ball[1] <= meY+Bar {
			vector[0] *= -1
			level = (level + 1) % 10
		}
	}
	if vector[0] == -1 && ball[0] < WallLeft+10 {
		if (ball[0] == EnemyX+1 || ball[0] == EnemyX+2) && ball[1] >= enemyY && ball[1] <= enemyY+Bar {
			vector[0] *= -1
			level = (level + 1) % 10
		}
	}
}

func initGame() {
	level = 0
	rand.Seed(time.Now().UnixNano())
	ball = []int{40, 5 + rand.Intn(15)}
	if len(os.Args) >= 2 {
		ipAddr = os.Args[1]
	} else {
		ipAddr = ""
	}
	shadow = make([][]int, len(ipAddr))
	for i := range shadow {
		shadow[i] = []int{ball[0], ball[1]}
	}
}

func main() {
	m = new(sync.Mutex)
	initGame()
	err := termbox.Init()
	if err != nil {
		panic(err)
	}

	go moveBall()
	go moveEnemy()
	defer termbox.Close()

	keyEvent()
}
<commit_msg>fix:crash<commit_after>package main

import (
	"fmt"
	"math/rand"
	"os"
	"time"

	"github.com/nsf/termbox-go"
)

var (
	flush  = make(chan int)
	meY    = 12
	enemyY = 12
	ball   = []int{40, 12}
	vector = []int{-1, 1}
	level  = 0
	score  = []int{0, 0}
	shadow [][]int
	ipAddr string
	clear  = false
)

const (
	wallLeft   = 0
	wallRight  = 79
	wallTop    = 1
	wallBottom = 23
	meX        = 76
	enemyX     = 2
	bar        = 4
)

func drawLine(x, y int, str string) {
	runes := []rune(str)
	for i := 0; i < len(runes); i++ {
		termbox.SetCell(x+i, y, runes[i], termbox.ColorDefault, termbox.ColorDefault)
	}
}

func draw() {

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	if clear == true {
		return
	}
	drawLine(wallLeft, wallTop-1, fmt.Sprintf("                                                                     %03d - %03d", score[0], score[1]))
	drawLine(wallLeft, wallTop, fmt.Sprintf("--------------------------------------------------------------------------------"))
	drawLine(wallLeft, wallBottom, fmt.Sprintf("--------------------------------------------------------------------------------"))
	drawLine(wallLeft, wallBottom+1, fmt.Sprintf("EXIT : ESC KEY"))
	drawLine(ball[0], ball[1], fmt.Sprintf("*"))

	for i := range shadow {
		drawLine(shadow[i][0], shadow[i][1], fmt.Sprintf(string(ipAddr[len(ipAddr)-i-1])))

	}

	for i := 0; i < bar; i++ {
		drawLine(meX, meY+i, fmt.Sprintf("||"))
		drawLine(enemyX, enemyY+i, fmt.Sprintf("||"))
	}
	flush <- 1
}

func termSync() {
	for {
		flg := <-flush
		if flg == -1 {
			break
		}
		termbox.Flush()
	}
}

func keyEvent() {
	draw()
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			switch ev.Key {
			case termbox.KeyEsc:
				clear = true
				draw()
				flush <- -1
				return
			case termbox.KeyArrowUp:
				if meY > wallTop+1 {
					meY--
				}
			case termbox.KeyArrowDown:
				if meY < wallBottom-bar {
					meY++
				}

			default:
			}
			draw()
		default:
		}
		hitTest()
	}
}

func moveBall() {
	for {

		ball[0] += vector[0]
		ball[1] += vector[1]
		hitTest()
		draw()

		ball[0] += vector[0]
		hitTest()
		recMove()
		draw()

		if ball[1] <= wallTop+1 || ball[1] >= wallBottom-1 {
			vector[1] *= -1
		}

		if ball[0] <= wallLeft+1 || ball[0] >= wallRight-1 {
			vector[0] *= -1
			if ball[0] <= wallLeft+1 {
				score[1]++
			}
			if ball[0] >= wallRight-1 {
				score[0]++
			}
			initGame()
			draw()
			time.Sleep(time.Duration(500) * time.Millisecond)
		}

		time.Sleep(time.Duration(100-level*5) * time.Millisecond)
	}
}
func recMove() {
	shadow = append(shadow, []int{ball[0], ball[1]})
	shadow = shadow[1:]
}
func moveEnemy() {
	vec := 0
	for {

		vec = ball[1] - (enemyY + 2)

		if enemyY > wallTop+1 && vec < 0 {
			enemyY--
		}
		if enemyY < wallBottom-bar && vec > 0 {
			enemyY++
		}
		hitTest()
		draw()

		switch true {
		case ball[0] < 30:
			time.Sleep(100 * time.Millisecond)
		case ball[0] < 50:
			time.Sleep(150 * time.Millisecond)
		case ball[0] < 80:
			time.Sleep(180 * time.Millisecond)
		default:
			time.Sleep(200 * time.Millisecond)
		}
	}
}

func hitTest() {
	if vector[0] == 1 && ball[0] > wallRight-10 {
		if (ball[0] == meX || ball[0] == meX-1) && ball[1] >= meY && ball[1] <= meY+bar {
			vector[0] *= -1
			level = (level + 1) % 10
		}
	}
	if vector[0] == -1 && ball[0] < wallLeft+10 {
		if (ball[0] == enemyX+1 || ball[0] == enemyX+2) && ball[1] >= enemyY && ball[1] <= enemyY+bar {
			vector[0] *= -1
			level = (level + 1) % 10
		}
	}
}

func initGame() {
	level = 0
	rand.Seed(time.Now().UnixNano())
	ball = []int{40, 5 + rand.Intn(15)}
	if len(os.Args) >= 2 {
		ipAddr = os.Args[1]
	} else {
		ipAddr = ""
	}
	shadow = make([][]int, len(ipAddr))
	for i := range shadow {
		shadow[i] = []int{ball[0], ball[1]}
	}
}

func main() {
	initGame()
	err := termbox.Init()
	if err != nil {
		panic(err)
	}

	go moveBall()
	go moveEnemy()
	go termSync()
	defer termbox.Close()

	keyEvent()
}
<|endoftext|>


<commit_before>package v1payload

//CreatePlacementInput is input for queue creation
type CreatePlacementInput struct {
	ProjectID string `json:"project_id" valid:"required"`
	Host      string `json:"host" valid:"required"`
	Token     string `json:"token" valid:"required"`
	CAPem     string `json:"ca_pem" valid:"required"`
}

//CreatePlacementOutput is output for queue creation
type CreatePlacementOutput struct {
}

//DeletePlacementInput is input for queue creation
type DeletePlacementInput struct {
	ProjectID string `json:"project_id" valid:"required"`
}

//DeletePlacementOutput is output for queue creation
type DeletePlacementOutput struct{}
<commit_msg>made ca cert optional<commit_after>package v1payload

//CreatePlacementInput is input for queue creation
type CreatePlacementInput struct {
	ProjectID string `json:"project_id" valid:"required"`
	Host      string `json:"host" valid:"required"`
	Token     string `json:"token" valid:"required"`
	CAPem     string `json:"ca_pem"`
}

//CreatePlacementOutput is output for queue creation
type CreatePlacementOutput struct {
}

//DeletePlacementInput is input for queue creation
type DeletePlacementInput struct {
	ProjectID string `json:"project_id" valid:"required"`
}

//DeletePlacementOutput is output for queue creation
type DeletePlacementOutput struct{}
<|endoftext|>


<commit_before>package discoverd

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	hh "github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/flynn/pkg/random"
)

// EnvInstanceMeta are environment variables which will be automatically added
// to instance metadata if present.
var EnvInstanceMeta = map[string]struct{}{
	"FLYNN_APP_ID":       {},
	"FLYNN_RELEASE_ID":   {},
	"FLYNN_PROCESS_TYPE": {},
	"FLYNN_JOB_ID":       {},
}

type Heartbeater interface {
	SetMeta(map[string]string) error
	Close() error
	Addr() string
	SetClient(*Client)
}

func (c *Client) maybeAddService(service string) error {
	if err := c.AddService(service, nil); err != nil {
		if !hh.IsObjectExistsError(err) {
			return err
		}
	}
	return nil
}

func (c *Client) AddServiceAndRegister(service, addr string) (Heartbeater, error) {
	if err := c.maybeAddService(service); err != nil {
		return nil, err
	}
	return c.Register(service, addr)
}

func (c *Client) AddServiceAndRegisterInstance(service string, inst *Instance) (Heartbeater, error) {
	if err := c.maybeAddService(service); err != nil {
		return nil, err
	}
	return c.RegisterInstance(service, inst)
}

func (c *Client) Register(service, addr string) (Heartbeater, error) {
	return c.RegisterInstance(service, &Instance{Addr: addr})
}

func (c *Client) RegisterInstance(service string, inst *Instance) (Heartbeater, error) {
	inst.Addr = expandAddr(inst.Addr)
	if inst.Proto == "" {
		inst.Proto = "tcp"
	}
	inst.ID = inst.id()
	// add EnvInstanceMeta if present
	for _, env := range os.Environ() {
		kv := strings.SplitN(env, "=", 2)
		if _, ok := EnvInstanceMeta[kv[0]]; !ok {
			continue
		}
		if inst.Meta == nil {
			inst.Meta = make(map[string]string)
		}
		inst.Meta[kv[0]] = kv[1]
	}
	h := newHeartbeater(c, service, inst)
	firstErr := make(chan error)
	go h.run(firstErr)
	if err := <-firstErr; err != nil {
		return nil, err
	}
	return h, nil
}

func newHeartbeater(c *Client, service string, inst *Instance) *heartbeater {
	h := &heartbeater{
		service: service,
		stop:    make(chan struct{}),
		done:    make(chan struct{}),
		inst:    inst.Clone(),
	}
	h.c.Store(c)
	return h
}

type heartbeater struct {
	c atomic.Value // *Client

	stop chan struct{}
	done chan struct{}

	// Mutex protects inst.Meta
	sync.Mutex
	inst *Instance

	service   string
	closeOnce sync.Once
}

func (h *heartbeater) Close() error {
	h.closeOnce.Do(func() {
		close(h.stop)
		<-h.done
	})
	return nil
}

func (h *heartbeater) SetMeta(meta map[string]string) error {
	h.Lock()
	defer h.Unlock()
	h.inst.Meta = meta
	return h.client().Put(fmt.Sprintf("/services/%s/instances/%s", h.service, h.inst.ID), h.inst, nil)
}

func (h *heartbeater) Addr() string {
	return h.inst.Addr
}

func (h *heartbeater) SetClient(c *Client) {
	h.c.Store(c)
}

func (h *heartbeater) client() *Client {
	return h.c.Load().(*Client)
}

const (
	heartbeatInterval        = 5 * time.Second
	heartbeatJitter          = 2 * time.Second
	heartbeatFailingInterval = 200 * time.Millisecond
)

func (h *heartbeater) run(firstErr chan<- error) {
	path := fmt.Sprintf("/services/%s/instances/%s", h.service, h.inst.ID)
	register := func() error {
		h.Lock()
		defer h.Unlock()
		return h.client().Put(path, h.inst, nil)
	}

	err := register()
	firstErr <- err
	if err != nil {
		return
	}
	timer := time.NewTimer(nextHeartbeat())
	for {
		select {
		case <-timer.C:
			if err := register(); err != nil {
				h.client().Logger.Error("heartbeat failed", "service", h.service, "addr", h.inst.Addr, "err", err)
				timer.Reset(nextHeartbeatFailing())
				break
			}
			timer.Reset(nextHeartbeat())
		case <-h.stop:
			h.client().Delete(path)
			close(h.done)
			return
		}
	}
}

func expandAddr(addr string) string {
	if addr[0] == ':' {
		return os.Getenv("EXTERNAL_IP") + addr
	}
	return addr
}

func nextHeartbeat() time.Duration {
	return heartbeatInterval - time.Duration(random.Math.Int63n(int64(heartbeatJitter)))
}

func nextHeartbeatFailing() time.Duration {
	return heartbeatFailingInterval + time.Duration(random.Math.Int63n(int64(heartbeatFailingInterval)))
}
<commit_msg>discoverd/client: Retry registering services and creating heartbeater<commit_after>package discoverd

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/flynn/flynn/pkg/attempt"
	hh "github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/flynn/pkg/random"
)

// EnvInstanceMeta are environment variables which will be automatically added
// to instance metadata if present.
var EnvInstanceMeta = map[string]struct{}{
	"FLYNN_APP_ID":       {},
	"FLYNN_RELEASE_ID":   {},
	"FLYNN_PROCESS_TYPE": {},
	"FLYNN_JOB_ID":       {},
}

type Heartbeater interface {
	SetMeta(map[string]string) error
	Close() error
	Addr() string
	SetClient(*Client)
}

var runAttempts = attempt.Strategy{
	Min:   5,
	Total: 60 * time.Second,
	Delay: 200 * time.Millisecond,
}

func (c *Client) maybeAddService(service string) error {
	return runAttempts.Run(func() error {
		if err := c.AddService(service, nil); err != nil {
			if !hh.IsObjectExistsError(err) {
				return err
			}
			return nil
		}
		return nil
	})
}

func (c *Client) AddServiceAndRegister(service, addr string) (Heartbeater, error) {
	if err := c.maybeAddService(service); err != nil {
		return nil, err
	}
	return c.Register(service, addr)
}

func (c *Client) AddServiceAndRegisterInstance(service string, inst *Instance) (Heartbeater, error) {
	if err := c.maybeAddService(service); err != nil {
		return nil, err
	}
	return c.RegisterInstance(service, inst)
}

func (c *Client) Register(service, addr string) (Heartbeater, error) {
	return c.RegisterInstance(service, &Instance{Addr: addr})
}

func (c *Client) RegisterInstance(service string, inst *Instance) (Heartbeater, error) {
	inst.Addr = expandAddr(inst.Addr)
	if inst.Proto == "" {
		inst.Proto = "tcp"
	}
	inst.ID = inst.id()
	// add EnvInstanceMeta if present
	for _, env := range os.Environ() {
		kv := strings.SplitN(env, "=", 2)
		if _, ok := EnvInstanceMeta[kv[0]]; !ok {
			continue
		}
		if inst.Meta == nil {
			inst.Meta = make(map[string]string)
		}
		inst.Meta[kv[0]] = kv[1]
	}
	h := newHeartbeater(c, service, inst)
	err := runAttempts.Run(func() error {
		firstErr := make(chan error)
		go h.run(firstErr)
		return <-firstErr
	})
	if err != nil {
		return nil, err
	}

	return h, nil
}

func newHeartbeater(c *Client, service string, inst *Instance) *heartbeater {
	h := &heartbeater{
		service: service,
		stop:    make(chan struct{}),
		done:    make(chan struct{}),
		inst:    inst.Clone(),
	}
	h.c.Store(c)
	return h
}

type heartbeater struct {
	c atomic.Value // *Client

	stop chan struct{}
	done chan struct{}

	// Mutex protects inst.Meta
	sync.Mutex
	inst *Instance

	service   string
	closeOnce sync.Once
}

func (h *heartbeater) Close() error {
	h.closeOnce.Do(func() {
		close(h.stop)
		<-h.done
	})
	return nil
}

func (h *heartbeater) SetMeta(meta map[string]string) error {
	h.Lock()
	defer h.Unlock()
	h.inst.Meta = meta
	return h.client().Put(fmt.Sprintf("/services/%s/instances/%s", h.service, h.inst.ID), h.inst, nil)
}

func (h *heartbeater) Addr() string {
	return h.inst.Addr
}

func (h *heartbeater) SetClient(c *Client) {
	h.c.Store(c)
}

func (h *heartbeater) client() *Client {
	return h.c.Load().(*Client)
}

const (
	heartbeatInterval        = 5 * time.Second
	heartbeatJitter          = 2 * time.Second
	heartbeatFailingInterval = 200 * time.Millisecond
)

func (h *heartbeater) run(firstErr chan<- error) {
	path := fmt.Sprintf("/services/%s/instances/%s", h.service, h.inst.ID)
	register := func() error {
		h.Lock()
		defer h.Unlock()
		return h.client().Put(path, h.inst, nil)
	}

	err := register()
	firstErr <- err
	if err != nil {
		return
	}
	timer := time.NewTimer(nextHeartbeat())
	for {
		select {
		case <-timer.C:
			if err := register(); err != nil {
				h.client().Logger.Error("heartbeat failed", "service", h.service, "addr", h.inst.Addr, "err", err)
				timer.Reset(nextHeartbeatFailing())
				break
			}
			timer.Reset(nextHeartbeat())
		case <-h.stop:
			h.client().Delete(path)
			close(h.done)
			return
		}
	}
}

func expandAddr(addr string) string {
	if addr[0] == ':' {
		return os.Getenv("EXTERNAL_IP") + addr
	}
	return addr
}

func nextHeartbeat() time.Duration {
	return heartbeatInterval - time.Duration(random.Math.Int63n(int64(heartbeatJitter)))
}

func nextHeartbeatFailing() time.Duration {
	return heartbeatFailingInterval + time.Duration(random.Math.Int63n(int64(heartbeatFailingInterval)))
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative Authors

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

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

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

package github

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/cloudevents/sdk-go"
	sourcesv1alpha1 "github.com/knative/eventing-sources/pkg/apis/sources/v1alpha1"
	"github.com/knative/eventing-sources/pkg/kncloudevents"
	gh "gopkg.in/go-playground/webhooks.v5/github"
)

const (
	GHHeaderEvent    = "GitHub-Event"
	GHHeaderDelivery = "GitHub-Delivery"
)

// Adapter converts incoming GitHub webhook events to CloudEvents
type Adapter struct {
	client cloudevents.Client
	source string
}

// New creates an adapter to convert incoming GitHub webhook events to CloudEvents and
// then sends them to the specified Sink
func New(sinkURI, ownerRepo string) (*Adapter, error) {
	a := new(Adapter)
	var err error
	a.client, err = kncloudevents.NewDefaultClient(sinkURI)
	if err != nil {
		return nil, err
	}
	source := sourcesv1alpha1.GitHubEventSource(ownerRepo)
	// Check at startup if it's not a URLRef and return an error in that case.
	src := cloudevents.ParseURLRef(source)
	if src == nil {
		return nil, fmt.Errorf("invalid source for github events: %s", source)
	}
	a.source = source
	return a, nil
}

// HandleEvent is invoked whenever an event comes in from GitHub
func (a *Adapter) HandleEvent(payload interface{}, header http.Header) {
	hdr := http.Header(header)
	err := a.handleEvent(payload, hdr)
	if err != nil {
		log.Printf("unexpected error handling GitHub event: %s", err)
	}
}

func (a *Adapter) handleEvent(payload interface{}, hdr http.Header) error {
	gitHubEventType := hdr.Get("X-" + GHHeaderEvent)
	eventID := hdr.Get("X-" + GHHeaderDelivery)

	log.Printf("Handling %s", gitHubEventType)

	cloudEventType := sourcesv1alpha1.GitHubEventType(gitHubEventType)
	subject := subjectFromGitHubEvent(gh.Event(gitHubEventType), payload)

	event := cloudevents.NewEvent(cloudevents.VersionV02)
	event.SetID(eventID)
	event.SetType(cloudEventType)
	event.SetSource(a.source)
	event.SetExtension(GHHeaderEvent, gitHubEventType)
	event.SetExtension(GHHeaderDelivery, eventID)
	event.SetSubject(subject)
	event.SetData(payload)

	_, err := a.client.Send(context.Background(), event)
	return err
}

func subjectFromGitHubEvent(gitHubEvent gh.Event, payload interface{}) string {
	// The decision of what to put in subject is somewhat arbitrary here (i.e., it's the author's opinion)
	// TODO check if we should be setting subject to these values.
	var subject string
	var ok bool
	switch gitHubEvent {
	case gh.CheckSuiteEvent:
		var cs gh.CheckSuitePayload
		if cs, ok = payload.(gh.CheckSuitePayload); ok {
			subject = strconv.FormatInt(cs.CheckSuite.ID, 10)
		}
	case gh.CommitCommentEvent:
		var cc gh.CommitCommentPayload
		if cc, ok = payload.(gh.CommitCommentPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/commit/a10867b14bb761a232cd80139fbd4c0d33264240#commitcomment-29186860
			// and we keep with a10867b14bb761a232cd80139fbd4c0d33264240#commitcomment-29186860
			subject = lastPathPortion(cc.Comment.HTMLURL)
		}
	case gh.CreateEvent:
		var c gh.CreatePayload
		if c, ok = payload.(gh.CreatePayload); ok {
			// The object that was created, can be repository, branch, or tag.
			subject = c.RefType
		}
	case gh.DeleteEvent:
		var d gh.DeletePayload
		if d, ok = payload.(gh.DeletePayload); ok {
			// The object that was deleted, can be branch or tag.
			subject = d.RefType
		}
	case gh.DeploymentEvent:
		var d gh.DeploymentPayload
		if d, ok = payload.(gh.DeploymentPayload); ok {
			subject = strconv.FormatInt(d.Deployment.ID, 10)
		}
	case gh.DeploymentStatusEvent:
		var d gh.DeploymentStatusPayload
		if d, ok = payload.(gh.DeploymentStatusPayload); ok {
			subject = strconv.FormatInt(d.Deployment.ID, 10)
		}
	case gh.ForkEvent:
		var f gh.ForkPayload
		if f, ok = payload.(gh.ForkPayload); ok {
			subject = strconv.FormatInt(f.Forkee.ID, 10)
		}
	case gh.GollumEvent:
		var g gh.GollumPayload
		if g, ok = payload.(gh.GollumPayload); ok {
			// The pages that were updated.
			// E.g., Home, Main.
			pages := make([]string, 0, len(g.Pages))
			for _, page := range g.Pages {
				pages = append(pages, page.PageName)
			}
			subject = strings.Join(pages, ",")
		}
	case gh.InstallationEvent, gh.IntegrationInstallationEvent:
		var i gh.InstallationPayload
		if i, ok = payload.(gh.InstallationPayload); ok {
			subject = strconv.FormatInt(i.Installation.ID, 10)
		}
	case gh.IssueCommentEvent:
		var i gh.IssueCommentPayload
		if i, ok = payload.(gh.IssueCommentPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/issues/2#issuecomment-393304133
			// and we keep with 2#issuecomment-393304133
			subject = lastPathPortion(i.Comment.HTMLURL)
		}
	case gh.IssuesEvent:
		var i gh.IssuesPayload
		if i, ok = payload.(gh.IssuesPayload); ok {
			subject = strconv.FormatInt(i.Issue.Number, 10)
		}
	case gh.LabelEvent:
		var l gh.LabelPayload
		if l, ok = payload.(gh.LabelPayload); ok {
			// E.g., :bug: Bugfix
			subject = l.Label.Name
		}
	case gh.MemberEvent:
		var m gh.MemberPayload
		if m, ok = payload.(gh.MemberPayload); ok {
			subject = strconv.FormatInt(m.Member.ID, 10)
		}
	case gh.MembershipEvent:
		var m gh.MembershipPayload
		if m, ok = payload.(gh.MembershipPayload); ok {
			subject = strconv.FormatInt(m.Member.ID, 10)
		}
	case gh.MilestoneEvent:
		var m gh.MilestonePayload
		if m, ok = payload.(gh.MilestonePayload); ok {
			subject = strconv.FormatInt(m.Milestone.Number, 10)
		}
	case gh.OrganizationEvent:
		var o gh.OrganizationPayload
		if o, ok = payload.(gh.OrganizationPayload); ok {
			// The action that was performed, can be member_added, member_removed, or member_invited.
			subject = o.Action
		}
	case gh.OrgBlockEvent:
		var o gh.OrgBlockPayload
		if o, ok = payload.(gh.OrgBlockPayload); ok {
			// The action performed, can be blocked or unblocked.
			subject = o.Action
		}
	case gh.PageBuildEvent:
		var p gh.PageBuildPayload
		if p, ok = payload.(gh.PageBuildPayload); ok {
			subject = strconv.FormatInt(p.ID, 10)
		}
	case gh.PingEvent:
		var p gh.PingPayload
		if p, ok = payload.(gh.PingPayload); ok {
			subject = strconv.Itoa(p.HookID)
		}
	case gh.ProjectCardEvent:
		var p gh.ProjectCardPayload
		if p, ok = payload.(gh.ProjectCardPayload); ok {
			// The action performed on the project card, can be created, edited, moved, converted, or deleted.
			subject = p.Action
		}
	case gh.ProjectColumnEvent:
		var p gh.ProjectColumnPayload
		if p, ok = payload.(gh.ProjectColumnPayload); ok {
			// The action performed on the project column, can be created, edited, moved, converted, or deleted.
			subject = p.Action
		}
	case gh.ProjectEvent:
		var p gh.ProjectPayload
		if p, ok = payload.(gh.ProjectPayload); ok {
			// The action that was performed on the project, can be created, edited, closed, reopened, or deleted.
			subject = p.Action
		}
	case gh.PublicEvent:
		var p gh.PublicPayload
		if p, ok = payload.(gh.PublicPayload); ok {
			subject = strconv.FormatInt(p.Repository.ID, 10)
		}
	case gh.PullRequestEvent:
		var p gh.PullRequestPayload
		if p, ok = payload.(gh.PullRequestPayload); ok {
			subject = strconv.FormatInt(p.PullRequest.Number, 10)
		}
	case gh.PullRequestReviewEvent:
		var p gh.PullRequestReviewPayload
		if p, ok = payload.(gh.PullRequestReviewPayload); ok {
			subject = strconv.FormatInt(p.Review.ID, 10)
		}
	case gh.PullRequestReviewCommentEvent:
		var p gh.PullRequestReviewCommentPayload
		if p, ok = payload.(gh.PullRequestReviewCommentPayload); ok {
			subject = strconv.FormatInt(p.Comment.ID, 10)
		}
	case gh.PushEvent:
		var p gh.PushPayload
		if p, ok = payload.(gh.PushPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/compare/a10867b14bb7...000000000000
			// and we keep with a10867b14bb7...000000000000.
			subject = lastPathPortion(p.Compare)
		}
	case gh.ReleaseEvent:
		var r gh.ReleasePayload
		if r, ok = payload.(gh.ReleasePayload); ok {
			subject = r.Release.TagName
		}
	case gh.RepositoryEvent:
		var r gh.RepositoryPayload
		if r, ok = payload.(gh.RepositoryPayload); ok {
			subject = strconv.FormatInt(r.Repository.ID, 10)
		}
	case gh.StatusEvent:
		var s gh.StatusPayload
		if s, ok = payload.(gh.StatusPayload); ok {
			subject = s.Sha
		}
	case gh.TeamEvent:
		var t gh.TeamPayload
		if t, ok = payload.(gh.TeamPayload); ok {
			subject = strconv.FormatInt(t.Team.ID, 10)
		}
	case gh.TeamAddEvent:
		var t gh.TeamAddPayload
		if t, ok = payload.(gh.TeamAddPayload); ok {
			subject = strconv.FormatInt(t.Repository.ID, 10)
		}
	case gh.WatchEvent:
		var w gh.WatchPayload
		if w, ok = payload.(gh.WatchPayload); ok {
			subject = strconv.FormatInt(w.Repository.ID, 10)
		}
	}
	if !ok {
		log.Printf("Invalid payload in github event %s", gitHubEvent)
	} else if subject == "" {
		log.Printf("No subject found in github event %s", gitHubEvent)
	}
	return subject
}

func lastPathPortion(url string) string {
	var subject string
	index := strings.LastIndex(url, "/")
	if index != -1 {
		// Keep the last part.
		subject = url[index+1:]
	}
	return subject
}
<commit_msg>golang format tools (#401)<commit_after>/*
Copyright 2018 The Knative Authors

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

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

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

package github

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	cloudevents "github.com/cloudevents/sdk-go"
	sourcesv1alpha1 "github.com/knative/eventing-sources/pkg/apis/sources/v1alpha1"
	"github.com/knative/eventing-sources/pkg/kncloudevents"
	gh "gopkg.in/go-playground/webhooks.v5/github"
)

const (
	GHHeaderEvent    = "GitHub-Event"
	GHHeaderDelivery = "GitHub-Delivery"
)

// Adapter converts incoming GitHub webhook events to CloudEvents
type Adapter struct {
	client cloudevents.Client
	source string
}

// New creates an adapter to convert incoming GitHub webhook events to CloudEvents and
// then sends them to the specified Sink
func New(sinkURI, ownerRepo string) (*Adapter, error) {
	a := new(Adapter)
	var err error
	a.client, err = kncloudevents.NewDefaultClient(sinkURI)
	if err != nil {
		return nil, err
	}
	source := sourcesv1alpha1.GitHubEventSource(ownerRepo)
	// Check at startup if it's not a URLRef and return an error in that case.
	src := cloudevents.ParseURLRef(source)
	if src == nil {
		return nil, fmt.Errorf("invalid source for github events: %s", source)
	}
	a.source = source
	return a, nil
}

// HandleEvent is invoked whenever an event comes in from GitHub
func (a *Adapter) HandleEvent(payload interface{}, header http.Header) {
	hdr := http.Header(header)
	err := a.handleEvent(payload, hdr)
	if err != nil {
		log.Printf("unexpected error handling GitHub event: %s", err)
	}
}

func (a *Adapter) handleEvent(payload interface{}, hdr http.Header) error {
	gitHubEventType := hdr.Get("X-" + GHHeaderEvent)
	eventID := hdr.Get("X-" + GHHeaderDelivery)

	log.Printf("Handling %s", gitHubEventType)

	cloudEventType := sourcesv1alpha1.GitHubEventType(gitHubEventType)
	subject := subjectFromGitHubEvent(gh.Event(gitHubEventType), payload)

	event := cloudevents.NewEvent(cloudevents.VersionV02)
	event.SetID(eventID)
	event.SetType(cloudEventType)
	event.SetSource(a.source)
	event.SetExtension(GHHeaderEvent, gitHubEventType)
	event.SetExtension(GHHeaderDelivery, eventID)
	event.SetSubject(subject)
	event.SetData(payload)

	_, err := a.client.Send(context.Background(), event)
	return err
}

func subjectFromGitHubEvent(gitHubEvent gh.Event, payload interface{}) string {
	// The decision of what to put in subject is somewhat arbitrary here (i.e., it's the author's opinion)
	// TODO check if we should be setting subject to these values.
	var subject string
	var ok bool
	switch gitHubEvent {
	case gh.CheckSuiteEvent:
		var cs gh.CheckSuitePayload
		if cs, ok = payload.(gh.CheckSuitePayload); ok {
			subject = strconv.FormatInt(cs.CheckSuite.ID, 10)
		}
	case gh.CommitCommentEvent:
		var cc gh.CommitCommentPayload
		if cc, ok = payload.(gh.CommitCommentPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/commit/a10867b14bb761a232cd80139fbd4c0d33264240#commitcomment-29186860
			// and we keep with a10867b14bb761a232cd80139fbd4c0d33264240#commitcomment-29186860
			subject = lastPathPortion(cc.Comment.HTMLURL)
		}
	case gh.CreateEvent:
		var c gh.CreatePayload
		if c, ok = payload.(gh.CreatePayload); ok {
			// The object that was created, can be repository, branch, or tag.
			subject = c.RefType
		}
	case gh.DeleteEvent:
		var d gh.DeletePayload
		if d, ok = payload.(gh.DeletePayload); ok {
			// The object that was deleted, can be branch or tag.
			subject = d.RefType
		}
	case gh.DeploymentEvent:
		var d gh.DeploymentPayload
		if d, ok = payload.(gh.DeploymentPayload); ok {
			subject = strconv.FormatInt(d.Deployment.ID, 10)
		}
	case gh.DeploymentStatusEvent:
		var d gh.DeploymentStatusPayload
		if d, ok = payload.(gh.DeploymentStatusPayload); ok {
			subject = strconv.FormatInt(d.Deployment.ID, 10)
		}
	case gh.ForkEvent:
		var f gh.ForkPayload
		if f, ok = payload.(gh.ForkPayload); ok {
			subject = strconv.FormatInt(f.Forkee.ID, 10)
		}
	case gh.GollumEvent:
		var g gh.GollumPayload
		if g, ok = payload.(gh.GollumPayload); ok {
			// The pages that were updated.
			// E.g., Home, Main.
			pages := make([]string, 0, len(g.Pages))
			for _, page := range g.Pages {
				pages = append(pages, page.PageName)
			}
			subject = strings.Join(pages, ",")
		}
	case gh.InstallationEvent, gh.IntegrationInstallationEvent:
		var i gh.InstallationPayload
		if i, ok = payload.(gh.InstallationPayload); ok {
			subject = strconv.FormatInt(i.Installation.ID, 10)
		}
	case gh.IssueCommentEvent:
		var i gh.IssueCommentPayload
		if i, ok = payload.(gh.IssueCommentPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/issues/2#issuecomment-393304133
			// and we keep with 2#issuecomment-393304133
			subject = lastPathPortion(i.Comment.HTMLURL)
		}
	case gh.IssuesEvent:
		var i gh.IssuesPayload
		if i, ok = payload.(gh.IssuesPayload); ok {
			subject = strconv.FormatInt(i.Issue.Number, 10)
		}
	case gh.LabelEvent:
		var l gh.LabelPayload
		if l, ok = payload.(gh.LabelPayload); ok {
			// E.g., :bug: Bugfix
			subject = l.Label.Name
		}
	case gh.MemberEvent:
		var m gh.MemberPayload
		if m, ok = payload.(gh.MemberPayload); ok {
			subject = strconv.FormatInt(m.Member.ID, 10)
		}
	case gh.MembershipEvent:
		var m gh.MembershipPayload
		if m, ok = payload.(gh.MembershipPayload); ok {
			subject = strconv.FormatInt(m.Member.ID, 10)
		}
	case gh.MilestoneEvent:
		var m gh.MilestonePayload
		if m, ok = payload.(gh.MilestonePayload); ok {
			subject = strconv.FormatInt(m.Milestone.Number, 10)
		}
	case gh.OrganizationEvent:
		var o gh.OrganizationPayload
		if o, ok = payload.(gh.OrganizationPayload); ok {
			// The action that was performed, can be member_added, member_removed, or member_invited.
			subject = o.Action
		}
	case gh.OrgBlockEvent:
		var o gh.OrgBlockPayload
		if o, ok = payload.(gh.OrgBlockPayload); ok {
			// The action performed, can be blocked or unblocked.
			subject = o.Action
		}
	case gh.PageBuildEvent:
		var p gh.PageBuildPayload
		if p, ok = payload.(gh.PageBuildPayload); ok {
			subject = strconv.FormatInt(p.ID, 10)
		}
	case gh.PingEvent:
		var p gh.PingPayload
		if p, ok = payload.(gh.PingPayload); ok {
			subject = strconv.Itoa(p.HookID)
		}
	case gh.ProjectCardEvent:
		var p gh.ProjectCardPayload
		if p, ok = payload.(gh.ProjectCardPayload); ok {
			// The action performed on the project card, can be created, edited, moved, converted, or deleted.
			subject = p.Action
		}
	case gh.ProjectColumnEvent:
		var p gh.ProjectColumnPayload
		if p, ok = payload.(gh.ProjectColumnPayload); ok {
			// The action performed on the project column, can be created, edited, moved, converted, or deleted.
			subject = p.Action
		}
	case gh.ProjectEvent:
		var p gh.ProjectPayload
		if p, ok = payload.(gh.ProjectPayload); ok {
			// The action that was performed on the project, can be created, edited, closed, reopened, or deleted.
			subject = p.Action
		}
	case gh.PublicEvent:
		var p gh.PublicPayload
		if p, ok = payload.(gh.PublicPayload); ok {
			subject = strconv.FormatInt(p.Repository.ID, 10)
		}
	case gh.PullRequestEvent:
		var p gh.PullRequestPayload
		if p, ok = payload.(gh.PullRequestPayload); ok {
			subject = strconv.FormatInt(p.PullRequest.Number, 10)
		}
	case gh.PullRequestReviewEvent:
		var p gh.PullRequestReviewPayload
		if p, ok = payload.(gh.PullRequestReviewPayload); ok {
			subject = strconv.FormatInt(p.Review.ID, 10)
		}
	case gh.PullRequestReviewCommentEvent:
		var p gh.PullRequestReviewCommentPayload
		if p, ok = payload.(gh.PullRequestReviewCommentPayload); ok {
			subject = strconv.FormatInt(p.Comment.ID, 10)
		}
	case gh.PushEvent:
		var p gh.PushPayload
		if p, ok = payload.(gh.PushPayload); ok {
			// E.g., https://github.com/Codertocat/Hello-World/compare/a10867b14bb7...000000000000
			// and we keep with a10867b14bb7...000000000000.
			subject = lastPathPortion(p.Compare)
		}
	case gh.ReleaseEvent:
		var r gh.ReleasePayload
		if r, ok = payload.(gh.ReleasePayload); ok {
			subject = r.Release.TagName
		}
	case gh.RepositoryEvent:
		var r gh.RepositoryPayload
		if r, ok = payload.(gh.RepositoryPayload); ok {
			subject = strconv.FormatInt(r.Repository.ID, 10)
		}
	case gh.StatusEvent:
		var s gh.StatusPayload
		if s, ok = payload.(gh.StatusPayload); ok {
			subject = s.Sha
		}
	case gh.TeamEvent:
		var t gh.TeamPayload
		if t, ok = payload.(gh.TeamPayload); ok {
			subject = strconv.FormatInt(t.Team.ID, 10)
		}
	case gh.TeamAddEvent:
		var t gh.TeamAddPayload
		if t, ok = payload.(gh.TeamAddPayload); ok {
			subject = strconv.FormatInt(t.Repository.ID, 10)
		}
	case gh.WatchEvent:
		var w gh.WatchPayload
		if w, ok = payload.(gh.WatchPayload); ok {
			subject = strconv.FormatInt(w.Repository.ID, 10)
		}
	}
	if !ok {
		log.Printf("Invalid payload in github event %s", gitHubEvent)
	} else if subject == "" {
		log.Printf("No subject found in github event %s", gitHubEvent)
	}
	return subject
}

func lastPathPortion(url string) string {
	var subject string
	index := strings.LastIndex(url, "/")
	if index != -1 {
		// Keep the last part.
		subject = url[index+1:]
	}
	return subject
}
<|endoftext|>