<commit_before>/*
Copyright 2015 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"
	"math/rand"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	discovery "k8s.io/apimachinery/pkg/version"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	certutil "k8s.io/client-go/util/cert"
	"k8s.io/klog/v2"

	"k8s.io/ingress-nginx/internal/file"
	"k8s.io/ingress-nginx/internal/ingress/annotations/class"
	"k8s.io/ingress-nginx/internal/ingress/controller"
	"k8s.io/ingress-nginx/internal/ingress/metric"
	"k8s.io/ingress-nginx/internal/k8s"
	"k8s.io/ingress-nginx/internal/net/ssl"
	"k8s.io/ingress-nginx/internal/nginx"
	"k8s.io/ingress-nginx/version"
)

func main() {
	klog.InitFlags(nil)

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

	fmt.Println(version.String())

	showVersion, conf, err := parseFlags()
	if showVersion {
		os.Exit(0)
	}

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

	err = file.CreateRequiredDirectories()
	if err != nil {
		klog.Fatal(err)
	}

	kubeClient, err := createApiserverClient(conf.APIServerHost, conf.RootCAFile, conf.KubeConfigFile)
	if err != nil {
		handleFatalInitError(err)
	}

	if len(conf.DefaultService) > 0 {
		err := checkService(conf.DefaultService, kubeClient)
		if err != nil {
			klog.Fatal(err)
		}

		klog.Infof("Validated %v as the default backend.", conf.DefaultService)
	}

	if len(conf.PublishService) > 0 {
		err := checkService(conf.PublishService, kubeClient)
		if err != nil {
			klog.Fatal(err)
		}
	}

	if conf.Namespace != "" {
		_, err = kubeClient.CoreV1().Namespaces().Get(context.TODO(), conf.Namespace, metav1.GetOptions{})
		if err != nil {
			klog.Fatalf("No namespace with name %v found: %v", conf.Namespace, err)
		}
	}

	conf.FakeCertificate = ssl.GetFakeSSLCert()
	klog.Infof("SSL fake certificate created %v", conf.FakeCertificate.PemFileName)

	k8s.IsNetworkingIngressAvailable, k8s.IsIngressV1Ready = k8s.NetworkingIngressAvailable(kubeClient)
	if !k8s.IsNetworkingIngressAvailable {
		klog.Warningf("Using deprecated \"k8s.io/api/extensions/v1beta1\" package because Kubernetes version is < v1.14.0")
	}

	if k8s.IsIngressV1Ready {
		klog.Infof("Enabling new Ingress features available since Kubernetes v1.18")
		k8s.IngressClass, err = kubeClient.NetworkingV1beta1().IngressClasses().
			Get(context.TODO(), class.IngressClass, metav1.GetOptions{})
		if err != nil {
			if !errors.IsNotFound(err) {
				if !errors.IsUnauthorized(err) && !errors.IsForbidden(err) {
					klog.Fatalf("Error searching IngressClass: %v", err)
				}

				klog.Errorf("Unexpected error searching IngressClass: %v", err)
			}

			klog.Warningf("No IngressClass resource with name %v found. Only annotation will be used.", class.IngressClass)

			// TODO: remove once this is fixed in client-go
			k8s.IngressClass = nil
		}

		if k8s.IngressClass != nil && k8s.IngressClass.Spec.Controller != k8s.IngressNGINXController {
			klog.Fatalf("IngressClass with name %v is not valid for ingress-nginx (invalid Spec.Controller)", class.IngressClass)
		}
	}

	conf.Client = kubeClient

	reg := prometheus.NewRegistry()

	reg.MustRegister(prometheus.NewGoCollector())
	reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{
		PidFn:        func() (int, error) { return os.Getpid(), nil },
		ReportErrors: true,
	}))

	mc := metric.NewDummyCollector()
	if conf.EnableMetrics {
		mc, err = metric.NewCollector(conf.MetricsPerHost, reg)
		if err != nil {
			klog.Fatalf("Error creating prometheus collector:  %v", err)
		}
	}
	mc.Start()

	if conf.EnableProfiling {
		go registerProfiler()
	}

	ngx := controller.NewNGINXController(conf, mc)

	mux := http.NewServeMux()
	registerHealthz(nginx.HealthPath, ngx, mux)
	registerMetrics(reg, mux)

	go startHTTPServer(conf.ListenPorts.Health, mux)
	go ngx.Start()

	handleSigterm(ngx, func(code int) {
		os.Exit(code)
	})
}

type exiter func(code int)

func handleSigterm(ngx *controller.NGINXController, exit exiter) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGTERM)
	<-signalChan
	klog.Info("Received SIGTERM, shutting down")

	exitCode := 0
	if err := ngx.Stop(); err != nil {
		klog.Infof("Error during shutdown: %v", err)
		exitCode = 1
	}

	klog.Info("Handled quit, awaiting Pod deletion")
	time.Sleep(10 * time.Second)

	klog.Infof("Exiting with %v", exitCode)
	exit(exitCode)
}

// createApiserverClient creates a new Kubernetes REST client. apiserverHost is
// the URL of the API server in the format protocol://address:port/pathPrefix,
// kubeConfig is the location of a kubeconfig file. If defined, the kubeconfig
// file is loaded first, the URL of the API server read from the file is then
// optionally overridden by the value of apiserverHost.
// If neither apiserverHost nor kubeConfig is passed in, we assume the
// controller runs inside Kubernetes and fallback to the in-cluster config. If
// the in-cluster config is missing or fails, we fallback to the default config.
func createApiserverClient(apiserverHost, rootCAFile, kubeConfig string) (*kubernetes.Clientset, error) {
	cfg, err := clientcmd.BuildConfigFromFlags(apiserverHost, kubeConfig)

	if err != nil {
		return nil, err
	}

	// Configure the User-Agent used for the HTTP requests made to the API server.
	cfg.UserAgent = fmt.Sprintf(
		"%s/%s (%s/%s) ingress-nginx/%s",
		filepath.Base(os.Args[0]),
		version.RELEASE,
		runtime.GOOS,
		runtime.GOARCH,
		version.COMMIT,
	)

	if apiserverHost != "" && rootCAFile != "" {
		tlsClientConfig := rest.TLSClientConfig{}

		if _, err := certutil.NewPool(rootCAFile); err != nil {
			klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
		} else {
			tlsClientConfig.CAFile = rootCAFile
		}

		cfg.TLSClientConfig = tlsClientConfig
	}

	klog.Infof("Creating API client for %s", cfg.Host)

	client, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, err
	}

	var v *discovery.Info

	// The client may fail to connect to the API server in the first request.
	// https://github.com/kubernetes/ingress-nginx/issues/1968
	defaultRetry := wait.Backoff{
		Steps:    10,
		Duration: 1 * time.Second,
		Factor:   1.5,
		Jitter:   0.1,
	}

	var lastErr error
	retries := 0
	klog.V(2).Info("Trying to discover Kubernetes version")
	err = wait.ExponentialBackoff(defaultRetry, func() (bool, error) {
		v, err = client.Discovery().ServerVersion()

		if err == nil {
			return true, nil
		}

		lastErr = err
		klog.V(2).Infof("Unexpected error discovering Kubernetes version (attempt %v): %v", retries, err)
		retries++
		return false, nil
	})

	// err is returned in case of timeout in the exponential backoff (ErrWaitTimeout)
	if err != nil {
		return nil, lastErr
	}

	// this should not happen, warn the user
	if retries > 0 {
		klog.Warningf("Initial connection to the Kubernetes API server was retried %d times.", retries)
	}

	klog.Infof("Running in Kubernetes cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
		v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)

	return client, nil
}

// Handler for fatal init errors. Prints a verbose error message and exits.
func handleFatalInitError(err error) {
	klog.Fatalf("Error while initiating a connection to the Kubernetes API server. "+
		"This could mean the cluster is misconfigured (e.g. it has invalid API server certificates "+
		"or Service Accounts configuration). Reason: %s\n"+
		"Refer to the troubleshooting guide for more information: "+
		"https://kubernetes.github.io/ingress-nginx/troubleshooting/",
		err)
}

func registerHealthz(healthPath string, ic *controller.NGINXController, mux *http.ServeMux) {
	// expose health check endpoint (/healthz)
	healthz.InstallPathHandler(mux,
		healthPath,
		healthz.PingHealthz,
		ic,
	)
}

func registerMetrics(reg *prometheus.Registry, mux *http.ServeMux) {
	mux.Handle(
		"/metrics",
		promhttp.InstrumentMetricHandler(
			reg,
			promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
		),
	)

}

func registerProfiler() {
	mux := http.NewServeMux()

	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/heap", pprof.Index)
	mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
	mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
	mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
	mux.HandleFunc("/debug/pprof/block", pprof.Index)
	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

	server := &http.Server{
		Addr:    fmt.Sprintf("127.0.0.1:%v", nginx.ProfilerPort),
		Handler: mux,
	}
	klog.Fatal(server.ListenAndServe())
}

func startHTTPServer(port int, mux *http.ServeMux) {
	server := &http.Server{
		Addr:              fmt.Sprintf(":%v", port),
		Handler:           mux,
		ReadTimeout:       10 * time.Second,
		ReadHeaderTimeout: 10 * time.Second,
		WriteTimeout:      300 * time.Second,
		IdleTimeout:       120 * time.Second,
	}
	klog.Fatal(server.ListenAndServe())
}

func checkService(key string, kubeClient *kubernetes.Clientset) error {
	ns, name, err := k8s.ParseNameNS(key)
	if err != nil {
		return err
	}

	_, err = kubeClient.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		if errors.IsUnauthorized(err) || errors.IsForbidden(err) {
			return fmt.Errorf("✖ The cluster seems to be running with a restrictive Authorization mode and the Ingress controller does not have the required permissions to operate normally.")
		}

		if errors.IsNotFound(err) {
			return fmt.Errorf("No service with name %v found in namespace %v: %v", ns, name, err)
		}

		return fmt.Errorf("Unexpected error searching service with name %v in namespace %v: %v", ns, name, err)
	}

	return nil
}
<commit_msg>fix variable ordering in error message<commit_after>/*
Copyright 2015 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"
	"math/rand"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	discovery "k8s.io/apimachinery/pkg/version"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	certutil "k8s.io/client-go/util/cert"
	"k8s.io/klog/v2"

	"k8s.io/ingress-nginx/internal/file"
	"k8s.io/ingress-nginx/internal/ingress/annotations/class"
	"k8s.io/ingress-nginx/internal/ingress/controller"
	"k8s.io/ingress-nginx/internal/ingress/metric"
	"k8s.io/ingress-nginx/internal/k8s"
	"k8s.io/ingress-nginx/internal/net/ssl"
	"k8s.io/ingress-nginx/internal/nginx"
	"k8s.io/ingress-nginx/version"
)

func main() {
	klog.InitFlags(nil)

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

	fmt.Println(version.String())

	showVersion, conf, err := parseFlags()
	if showVersion {
		os.Exit(0)
	}

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

	err = file.CreateRequiredDirectories()
	if err != nil {
		klog.Fatal(err)
	}

	kubeClient, err := createApiserverClient(conf.APIServerHost, conf.RootCAFile, conf.KubeConfigFile)
	if err != nil {
		handleFatalInitError(err)
	}

	if len(conf.DefaultService) > 0 {
		err := checkService(conf.DefaultService, kubeClient)
		if err != nil {
			klog.Fatal(err)
		}

		klog.Infof("Validated %v as the default backend.", conf.DefaultService)
	}

	if len(conf.PublishService) > 0 {
		err := checkService(conf.PublishService, kubeClient)
		if err != nil {
			klog.Fatal(err)
		}
	}

	if conf.Namespace != "" {
		_, err = kubeClient.CoreV1().Namespaces().Get(context.TODO(), conf.Namespace, metav1.GetOptions{})
		if err != nil {
			klog.Fatalf("No namespace with name %v found: %v", conf.Namespace, err)
		}
	}

	conf.FakeCertificate = ssl.GetFakeSSLCert()
	klog.Infof("SSL fake certificate created %v", conf.FakeCertificate.PemFileName)

	k8s.IsNetworkingIngressAvailable, k8s.IsIngressV1Ready = k8s.NetworkingIngressAvailable(kubeClient)
	if !k8s.IsNetworkingIngressAvailable {
		klog.Warningf("Using deprecated \"k8s.io/api/extensions/v1beta1\" package because Kubernetes version is < v1.14.0")
	}

	if k8s.IsIngressV1Ready {
		klog.Infof("Enabling new Ingress features available since Kubernetes v1.18")
		k8s.IngressClass, err = kubeClient.NetworkingV1beta1().IngressClasses().
			Get(context.TODO(), class.IngressClass, metav1.GetOptions{})
		if err != nil {
			if !errors.IsNotFound(err) {
				if !errors.IsUnauthorized(err) && !errors.IsForbidden(err) {
					klog.Fatalf("Error searching IngressClass: %v", err)
				}

				klog.Errorf("Unexpected error searching IngressClass: %v", err)
			}

			klog.Warningf("No IngressClass resource with name %v found. Only annotation will be used.", class.IngressClass)

			// TODO: remove once this is fixed in client-go
			k8s.IngressClass = nil
		}

		if k8s.IngressClass != nil && k8s.IngressClass.Spec.Controller != k8s.IngressNGINXController {
			klog.Fatalf("IngressClass with name %v is not valid for ingress-nginx (invalid Spec.Controller)", class.IngressClass)
		}
	}

	conf.Client = kubeClient

	reg := prometheus.NewRegistry()

	reg.MustRegister(prometheus.NewGoCollector())
	reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{
		PidFn:        func() (int, error) { return os.Getpid(), nil },
		ReportErrors: true,
	}))

	mc := metric.NewDummyCollector()
	if conf.EnableMetrics {
		mc, err = metric.NewCollector(conf.MetricsPerHost, reg)
		if err != nil {
			klog.Fatalf("Error creating prometheus collector:  %v", err)
		}
	}
	mc.Start()

	if conf.EnableProfiling {
		go registerProfiler()
	}

	ngx := controller.NewNGINXController(conf, mc)

	mux := http.NewServeMux()
	registerHealthz(nginx.HealthPath, ngx, mux)
	registerMetrics(reg, mux)

	go startHTTPServer(conf.ListenPorts.Health, mux)
	go ngx.Start()

	handleSigterm(ngx, func(code int) {
		os.Exit(code)
	})
}

type exiter func(code int)

func handleSigterm(ngx *controller.NGINXController, exit exiter) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGTERM)
	<-signalChan
	klog.Info("Received SIGTERM, shutting down")

	exitCode := 0
	if err := ngx.Stop(); err != nil {
		klog.Infof("Error during shutdown: %v", err)
		exitCode = 1
	}

	klog.Info("Handled quit, awaiting Pod deletion")
	time.Sleep(10 * time.Second)

	klog.Infof("Exiting with %v", exitCode)
	exit(exitCode)
}

// createApiserverClient creates a new Kubernetes REST client. apiserverHost is
// the URL of the API server in the format protocol://address:port/pathPrefix,
// kubeConfig is the location of a kubeconfig file. If defined, the kubeconfig
// file is loaded first, the URL of the API server read from the file is then
// optionally overridden by the value of apiserverHost.
// If neither apiserverHost nor kubeConfig is passed in, we assume the
// controller runs inside Kubernetes and fallback to the in-cluster config. If
// the in-cluster config is missing or fails, we fallback to the default config.
func createApiserverClient(apiserverHost, rootCAFile, kubeConfig string) (*kubernetes.Clientset, error) {
	cfg, err := clientcmd.BuildConfigFromFlags(apiserverHost, kubeConfig)

	if err != nil {
		return nil, err
	}

	// Configure the User-Agent used for the HTTP requests made to the API server.
	cfg.UserAgent = fmt.Sprintf(
		"%s/%s (%s/%s) ingress-nginx/%s",
		filepath.Base(os.Args[0]),
		version.RELEASE,
		runtime.GOOS,
		runtime.GOARCH,
		version.COMMIT,
	)

	if apiserverHost != "" && rootCAFile != "" {
		tlsClientConfig := rest.TLSClientConfig{}

		if _, err := certutil.NewPool(rootCAFile); err != nil {
			klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
		} else {
			tlsClientConfig.CAFile = rootCAFile
		}

		cfg.TLSClientConfig = tlsClientConfig
	}

	klog.Infof("Creating API client for %s", cfg.Host)

	client, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, err
	}

	var v *discovery.Info

	// The client may fail to connect to the API server in the first request.
	// https://github.com/kubernetes/ingress-nginx/issues/1968
	defaultRetry := wait.Backoff{
		Steps:    10,
		Duration: 1 * time.Second,
		Factor:   1.5,
		Jitter:   0.1,
	}

	var lastErr error
	retries := 0
	klog.V(2).Info("Trying to discover Kubernetes version")
	err = wait.ExponentialBackoff(defaultRetry, func() (bool, error) {
		v, err = client.Discovery().ServerVersion()

		if err == nil {
			return true, nil
		}

		lastErr = err
		klog.V(2).Infof("Unexpected error discovering Kubernetes version (attempt %v): %v", retries, err)
		retries++
		return false, nil
	})

	// err is returned in case of timeout in the exponential backoff (ErrWaitTimeout)
	if err != nil {
		return nil, lastErr
	}

	// this should not happen, warn the user
	if retries > 0 {
		klog.Warningf("Initial connection to the Kubernetes API server was retried %d times.", retries)
	}

	klog.Infof("Running in Kubernetes cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
		v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)

	return client, nil
}

// Handler for fatal init errors. Prints a verbose error message and exits.
func handleFatalInitError(err error) {
	klog.Fatalf("Error while initiating a connection to the Kubernetes API server. "+
		"This could mean the cluster is misconfigured (e.g. it has invalid API server certificates "+
		"or Service Accounts configuration). Reason: %s\n"+
		"Refer to the troubleshooting guide for more information: "+
		"https://kubernetes.github.io/ingress-nginx/troubleshooting/",
		err)
}

func registerHealthz(healthPath string, ic *controller.NGINXController, mux *http.ServeMux) {
	// expose health check endpoint (/healthz)
	healthz.InstallPathHandler(mux,
		healthPath,
		healthz.PingHealthz,
		ic,
	)
}

func registerMetrics(reg *prometheus.Registry, mux *http.ServeMux) {
	mux.Handle(
		"/metrics",
		promhttp.InstrumentMetricHandler(
			reg,
			promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
		),
	)

}

func registerProfiler() {
	mux := http.NewServeMux()

	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/heap", pprof.Index)
	mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
	mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
	mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
	mux.HandleFunc("/debug/pprof/block", pprof.Index)
	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

	server := &http.Server{
		Addr:    fmt.Sprintf("127.0.0.1:%v", nginx.ProfilerPort),
		Handler: mux,
	}
	klog.Fatal(server.ListenAndServe())
}

func startHTTPServer(port int, mux *http.ServeMux) {
	server := &http.Server{
		Addr:              fmt.Sprintf(":%v", port),
		Handler:           mux,
		ReadTimeout:       10 * time.Second,
		ReadHeaderTimeout: 10 * time.Second,
		WriteTimeout:      300 * time.Second,
		IdleTimeout:       120 * time.Second,
	}
	klog.Fatal(server.ListenAndServe())
}

func checkService(key string, kubeClient *kubernetes.Clientset) error {
	ns, name, err := k8s.ParseNameNS(key)
	if err != nil {
		return err
	}

	_, err = kubeClient.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		if errors.IsUnauthorized(err) || errors.IsForbidden(err) {
			return fmt.Errorf("✖ The cluster seems to be running with a restrictive Authorization mode and the Ingress controller does not have the required permissions to operate normally.")
		}

		if errors.IsNotFound(err) {
			return fmt.Errorf("No service with name %v found in namespace %v: %v", name, ns, err)
		}

		return fmt.Errorf("Unexpected error searching service with name %v in namespace %v: %v", name, ns, err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Reborndb Org. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"path"
	"runtime"
	"strconv"

	"github.com/reborndb/reborn/pkg/proxy/router"
	"github.com/reborndb/reborn/pkg/utils"

	"github.com/docopt/docopt-go"
	log "github.com/ngaut/logging"
)

var (
	cpus       = 2
	addr       = ":9000"
	httpAddr   = ":9001"
	proxyID    = ""
	configFile = "config.ini"
	pidfile    = ""
)

var usage = `usage: proxy [options]

options:
   -c <config_file>               set config file
   -L <log_file>                  set output log file, default is stdout
   --log-level=<loglevel>         set log level: info, warn, error, debug [default: info]
   --cpu=<cpu_num>                num of cpu cores that proxy can use
   --addr=<proxy_listen_addr>     proxy listen address, example: 0.0.0.0:9000
   --id=<proxy_id>                proxy id, global unique, can not be empty
   --http-addr=<debug_http_addr>  debug vars http server
   --dump-path=<path>             dump path to log crash error
   --pidfile=<path>               proxy pid file
`

var banner string = `
    ____       __                     ____  ____
   / __ \___  / /_  ____  _________  / __ \/ __ )
  / /_/ / _ \/ __ \/ __ \/ ___/ __ \/ / / / __  |
 / _, _/  __/ /_/ / /_/ / /  / / / / /_/ / /_/ /
/_/ |_|\___/_.___/\____/_/  /_/ /_/_____/_____/

`

func handleSetLogLevel(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	level := r.Form.Get("level")
	log.SetLevelByString(level)
	log.Info("set log level to", level)
}

func main() {
	fmt.Print(banner)
	log.SetLevelByString("info")

	args, err := docopt.Parse(usage, nil, true, "reborn proxy v0.1", true)
	if err != nil {
		log.Error(err)
	}

	// set config file
	if args["-c"] != nil {
		configFile = args["-c"].(string)
	}

	// set output log file
	if args["-L"] != nil {
		log.SetOutputByName(args["-L"].(string))
	}

	// set log level
	if args["--log-level"] != nil {
		log.SetLevelByString(args["--log-level"].(string))
	}

	// set cpu
	if args["--cpu"] != nil {
		cpus, err = strconv.Atoi(args["--cpu"].(string))
		if err != nil {
			log.Fatal(err)
		}
	}

	// set addr
	if args["--addr"] != nil {
		addr = args["--addr"].(string)
	}

	// set http addr
	if args["--http-addr"] != nil {
		httpAddr = args["--http-addr"].(string)
	}

	if args["--id"] != nil {
		proxyID = args["--id"].(string)
	} else {
		log.Fatalf("invalid empty proxy id")
	}

	dumppath := utils.GetExecutorPath()
	if args["--dump-path"] != nil {
		dumppath = args["--dump-path"].(string)
	}

	if args["--pidfile"] != nil {
		pidfile = args["--pidfile"].(string)
	}

	log.Info("dump file path:", dumppath)
	log.CrashLog(path.Join(dumppath, "reborn-proxy.dump"))

	router.CheckUlimit(1024)
	runtime.GOMAXPROCS(cpus)

	http.HandleFunc("/setloglevel", handleSetLogLevel)
	go http.ListenAndServe(httpAddr, nil)
	log.Info("running on ", addr)
	conf, err := router.LoadConf(configFile)
	if err != nil {
		log.Fatal(err)
	}

	conf.Addr = addr
	conf.HTTPAddr = httpAddr
	conf.ProxyID = proxyID
	conf.PidFile = pidfile

	if err := utils.CreatePidFile(conf.PidFile); err != nil {
		log.Fatal(err)
	}

	s := router.NewServer(conf)
	s.Run()
	log.Warning("exit")
}
<commit_msg>add password cmd flag<commit_after>// Copyright 2015 Reborndb Org. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"path"
	"runtime"
	"strconv"

	"github.com/reborndb/reborn/pkg/proxy/router"
	"github.com/reborndb/reborn/pkg/utils"

	"github.com/docopt/docopt-go"
	log "github.com/ngaut/logging"
)

var (
	cpus       = 2
	addr       = ":9000"
	httpAddr   = ":9001"
	proxyID    = ""
	configFile = "config.ini"
	pidfile    = ""
)

var usage = `usage: proxy [options]

options:
   -c <config_file>               set config file
   -L <log_file>                  set output log file, default is stdout
   --log-level=<loglevel>         set log level: info, warn, error, debug [default: info]
   --cpu=<cpu_num>                num of cpu cores that proxy can use
   --addr=<proxy_listen_addr>     proxy listen address, example: 0.0.0.0:9000
   --id=<proxy_id>                proxy id, global unique, can not be empty
   --http-addr=<debug_http_addr>  debug vars http server
   --dump-path=<path>             dump path to log crash error
   --pidfile=<path>               proxy pid file
   --proxy-pass=PASSWORD          proxy password
   --server-pass=PASSWORD         backend server password
`

var banner string = `
    ____       __                     ____  ____
   / __ \___  / /_  ____  _________  / __ \/ __ )
  / /_/ / _ \/ __ \/ __ \/ ___/ __ \/ / / / __  |
 / _, _/  __/ /_/ / /_/ / /  / / / / /_/ / /_/ /
/_/ |_|\___/_.___/\____/_/  /_/ /_/_____/_____/

`

func handleSetLogLevel(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	level := r.Form.Get("level")
	log.SetLevelByString(level)
	log.Info("set log level to", level)
}

func setStringFromOpt(dest *string, args map[string]interface{}, key string) {
	if s, ok := args[key].(string); ok && len(s) != 0 {
		*dest = s
	}
}

func main() {
	fmt.Print(banner)
	log.SetLevelByString("info")

	args, err := docopt.Parse(usage, nil, true, "reborn proxy v0.1", true)
	if err != nil {
		log.Error(err)
	}

	// set config file
	if args["-c"] != nil {
		configFile = args["-c"].(string)
	}

	// set output log file
	if args["-L"] != nil {
		log.SetOutputByName(args["-L"].(string))
	}

	// set log level
	if args["--log-level"] != nil {
		log.SetLevelByString(args["--log-level"].(string))
	}

	// set cpu
	if args["--cpu"] != nil {
		cpus, err = strconv.Atoi(args["--cpu"].(string))
		if err != nil {
			log.Fatal(err)
		}
	}

	// set addr
	if args["--addr"] != nil {
		addr = args["--addr"].(string)
	}

	// set http addr
	if args["--http-addr"] != nil {
		httpAddr = args["--http-addr"].(string)
	}

	if args["--id"] != nil {
		proxyID = args["--id"].(string)
	} else {
		log.Fatalf("invalid empty proxy id")
	}

	dumppath := utils.GetExecutorPath()
	if args["--dump-path"] != nil {
		dumppath = args["--dump-path"].(string)
	}

	if args["--pidfile"] != nil {
		pidfile = args["--pidfile"].(string)
	}

	log.Info("dump file path:", dumppath)
	log.CrashLog(path.Join(dumppath, "reborn-proxy.dump"))

	router.CheckUlimit(1024)
	runtime.GOMAXPROCS(cpus)

	http.HandleFunc("/setloglevel", handleSetLogLevel)
	go http.ListenAndServe(httpAddr, nil)
	log.Info("running on ", addr)
	conf, err := router.LoadConf(configFile)
	if err != nil {
		log.Fatal(err)
	}

	conf.Addr = addr
	conf.HTTPAddr = httpAddr
	conf.ProxyID = proxyID
	conf.PidFile = pidfile

	setStringFromOpt(&conf.ProxyPassword, args, "--proxy-pass")
	setStringFromOpt(&conf.ServerPassword, args, "--server-pass")

	if err := utils.CreatePidFile(conf.PidFile); err != nil {
		log.Fatal(err)
	}

	s := router.NewServer(conf)
	s.Run()
	log.Warning("exit")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/burntsushi/toml"
	"github.com/veoo/go-smpp/smpp"
	"github.com/veoo/go-smpp/smpp/pdu"
	"github.com/veoo/go-smpp/smpp/pdu/pdufield"
	"github.com/veoo/go-smpp/smpp/pdu/pdutext"
)

type Config struct {
	Address  string
	User     string
	Password string
	From     string
	To       string
	Content  string
	Encoding string
}

func main() {

	argsWithoutProg := os.Args[1:]
	config := ReadConfig(argsWithoutProg[0])

	transceiverHandler := func(p pdu.Body) {
		fmt.Println("RECEIVED SOMETHING", p.Header().ID.String())
		switch p.Header().ID {
		case pdu.DeliverSMID:
			f := p.Fields()
			src := f[pdufield.SourceAddr]
			dst := f[pdufield.DestinationAddr]
			txt := f[pdufield.ShortMessage]
			fmt.Println("TLVs:", p.TLVFields())
			fmt.Println(string(p.TLVFields()[pdufield.MessageStateOption].Bytes()))
			fmt.Println(string(p.TLVFields()[pdufield.ReceiptedMessageID].Bytes()))
			log.Info(fmt.Sprintf("Client: (DeliverSMID) Short message from=%s to=%s: %s", src, dst, txt))
		case pdu.EnquireLinkID:
			log.Info("Enquire link received, sending an enquire link resp")
			// TODO: Implement sending an enquire_link_resp here
		}
	}

	transceiver := &smpp.Transceiver{
		Addr:        config.Address,
		User:        config.User,
		Passwd:      config.Password,
		Handler:     transceiverHandler,
		RespTimeout: 10 * time.Second,
	}

	connA := transceiver.Bind() // make persistent connection.
	go func() {
		for c := range connA {
			log.Info("SMPP connection status: ", c.Status(), c.Error())
		}
	}()

	var text pdutext.Codec
	switch config.Encoding {
	case "ucs2":
		text = pdutext.UCS2(config.Content)
	case "latin1":
		text = pdutext.Latin1(config.Content)
	default:
		text = pdutext.Raw(config.Content)
	}

	time.Sleep(2 * time.Second)
	req := &smpp.ShortMessage{
		Src:      config.From,
		Dst:      config.To,
		Text:     text,
		Register: smpp.FinalDeliveryReceipt,
	}
	sm, err := transceiver.Submit(req)
	if err != nil {
		log.Fatal("Error:", err)
	}

	log.Info(sm)

	// Add signal interrupt here, currently need to confirm an unbund is sent
	// on CTRL - C
	sigs := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigs
		log.Info("Received interrupt or sigterm unbinding.")
		transceiver.Close()
		done <- true
	}()

	<-done
	log.Info("Exited cleanly, all done!")
}

func ReadConfig(configfile string) Config {
	configLocation := "./" + configfile
	_, err := os.Stat(configLocation)
	if err != nil {
		log.Fatal("Config file is missing: ", configLocation)
	}

	var config Config
	if _, err := toml.DecodeFile(configLocation, &config); err != nil {
		log.Fatal(err)
	}

	return config
}
<commit_msg>Adding functionality for concatenated messages as well.<commit_after>package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/burntsushi/toml"
	"github.com/veoo/go-smpp/smpp"
	"github.com/veoo/go-smpp/smpp/pdu"
	"github.com/veoo/go-smpp/smpp/pdu/pdufield"
	"github.com/veoo/go-smpp/smpp/pdu/pdutext"
)

type Config struct {
	Address  string
	User     string
	Password string
	From     string
	To       string
	Content  string
	Encoding string
}

func main() {

	argsWithoutProg := os.Args[1:]
	config := ReadConfig(argsWithoutProg[0])

	transceiverHandler := func(p pdu.Body) {
		fmt.Println("RECEIVED SOMETHING", p.Header().ID.String())
		switch p.Header().ID {
		case pdu.DeliverSMID:
			f := p.Fields()
			src := f[pdufield.SourceAddr]
			dst := f[pdufield.DestinationAddr]
			txt := f[pdufield.ShortMessage]
			fmt.Println("TLVs:", p.TLVFields())
			fmt.Println(string(p.TLVFields()[pdufield.MessageStateOption].Bytes()))
			fmt.Println(string(p.TLVFields()[pdufield.ReceiptedMessageID].Bytes()))
			log.Info(fmt.Sprintf("Client: (DeliverSMID) Short message from=%s to=%s: %s", src, dst, txt))
		case pdu.EnquireLinkID:
			log.Info("Enquire link received, sending an enquire link resp")
			// TODO: Implement sending an enquire_link_resp here
		}
	}

	transceiver := &smpp.Transceiver{
		Addr:        config.Address,
		User:        config.User,
		Passwd:      config.Password,
		Handler:     transceiverHandler,
		RespTimeout: 10 * time.Second,
	}

	connA := transceiver.Bind() // make persistent connection.
	go func() {
		for c := range connA {
			log.Info("SMPP connection status: ", c.Status(), c.Error())
		}
	}()

	var text pdutext.Codec
	switch config.Encoding {
	case "ucs2":
		text = pdutext.UCS2(config.Content)
	case "latin1":
		text = pdutext.Latin1(config.Content)
	default:
		text = pdutext.Raw(config.Content)
	}

	time.Sleep(2 * time.Second)
	req := &smpp.ShortMessage{
		Src:      config.From,
		Dst:      config.To,
		Text:     text,
		Register: smpp.FinalDeliveryReceipt,
	}

	var sm *smpp.ShortMessage
	var err error
	if len(config.Content) < 120 {
		log.Info("Submitting standard message")
		sm, err = transceiver.Submit(req)
	} else {
		log.Info("Submitting long message")
		sm, err = transceiver.SubmitLongMsg(req)
	}

	if err != nil {
		log.Fatal("Error:", err)
	}

	log.Info(sm)

	// Add signal interrupt here, currently need to confirm an unbund is sent
	// on CTRL - C
	sigs := make(chan os.Signal, 1)
	done := make(chan bool, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigs
		log.Info("Received interrupt or sigterm unbinding.")
		transceiver.Close()
		done <- true
	}()

	<-done
	log.Info("Exited cleanly, all done!")
}

func ReadConfig(configfile string) Config {
	configLocation := "./" + configfile
	_, err := os.Stat(configLocation)
	if err != nil {
		log.Fatal("Config file is missing: ", configLocation)
	}

	var config Config
	if _, err := toml.DecodeFile(configLocation, &config); err != nil {
		log.Fatal(err)
	}

	return config
}
<|endoftext|>


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

package main

import (
	"github.com/globocom/tsuru/cmd"
	"github.com/globocom/tsuru/cmd/tsuru-base"
	"os"
)

const (
	version = "0.6"
	header  = "Supported-Tsuru"
)

func buildManager(name string) *cmd.Manager {
	m := cmd.BuildBaseManager(name, version, header)
	m.Register(&tsuru.AppRun{})
	m.Register(&tsuru.AppInfo{})
	m.Register(&AppCreate{})
	m.Register(&AppRemove{})
	m.Register(&UnitAdd{})
	m.Register(&UnitRemove{})
	m.Register(tsuru.AppList{})
	m.Register(&tsuru.AppLog{})
	m.Register(&tsuru.AppGrant{})
	m.Register(&tsuru.AppRevoke{})
	m.Register(&tsuru.AppRestart{})
	m.Register(&tsuru.SetCName{})
	m.Register(&tsuru.UnsetCName{})
	m.Register(&tsuru.EnvGet{})
	m.Register(&tsuru.EnvSet{})
	m.Register(&tsuru.EnvUnset{})
	m.Register(&KeyAdd{})
	m.Register(&KeyRemove{})
	m.Register(tsuru.ServiceList{})
	m.Register(tsuru.ServiceAdd{})
	m.Register(tsuru.ServiceRemove{})
	m.Register(tsuru.ServiceDoc{})
	m.Register(tsuru.ServiceInfo{})
	m.Register(tsuru.ServiceInstanceStatus{})
	m.Register(&tsuru.ServiceBind{})
	m.Register(&tsuru.ServiceUnbind{})
	return m
}

func main() {
	name := cmd.ExtractProgramName(os.Args[0])
	manager := buildManager(name)
	manager.Run(os.Args[1:])
}
<commit_msg>cmd/tsuru: version 0.7<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"github.com/globocom/tsuru/cmd"
	"github.com/globocom/tsuru/cmd/tsuru-base"
	"os"
)

const (
	version = "0.7"
	header  = "Supported-Tsuru"
)

func buildManager(name string) *cmd.Manager {
	m := cmd.BuildBaseManager(name, version, header)
	m.Register(&tsuru.AppRun{})
	m.Register(&tsuru.AppInfo{})
	m.Register(&AppCreate{})
	m.Register(&AppRemove{})
	m.Register(&UnitAdd{})
	m.Register(&UnitRemove{})
	m.Register(tsuru.AppList{})
	m.Register(&tsuru.AppLog{})
	m.Register(&tsuru.AppGrant{})
	m.Register(&tsuru.AppRevoke{})
	m.Register(&tsuru.AppRestart{})
	m.Register(&tsuru.SetCName{})
	m.Register(&tsuru.UnsetCName{})
	m.Register(&tsuru.EnvGet{})
	m.Register(&tsuru.EnvSet{})
	m.Register(&tsuru.EnvUnset{})
	m.Register(&KeyAdd{})
	m.Register(&KeyRemove{})
	m.Register(tsuru.ServiceList{})
	m.Register(tsuru.ServiceAdd{})
	m.Register(tsuru.ServiceRemove{})
	m.Register(tsuru.ServiceDoc{})
	m.Register(tsuru.ServiceInfo{})
	m.Register(tsuru.ServiceInstanceStatus{})
	m.Register(&tsuru.ServiceBind{})
	m.Register(&tsuru.ServiceUnbind{})
	return m
}

func main() {
	name := cmd.ExtractProgramName(os.Args[0])
	manager := buildManager(name)
	manager.Run(os.Args[1:])
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/dustin/go-humanize"
)

const (
	RELEASE_LIST_URI    = "/repos/%s/%s/releases%s"
	RELEASE_LATEST_URI  = "/repos/%s/%s/releases/latest%s"
	RELEASE_DATE_FORMAT = "02/01/2006 at 15:04"
)

type Release struct {
	Url         string     `json:"url"`
	PageUrl     string     `json:"html_url"`
	UploadUrl   string     `json:"upload_url"`
	Id          int        `json:"id"`
	Name        string     `json:"name"`
	Description string     `json:"body"`
	TagName     string     `json:"tag_name"`
	Draft       bool       `json:"draft"`
	Prerelease  bool       `json:"prerelease"`
	Created     *time.Time `json:"created_at"`
	Published   *time.Time `json:"published_at"`
	Assets      []Asset    `json:"assets"`
}

func (r *Release) CleanUploadUrl() string {
	bracket := strings.Index(r.UploadUrl, "{")

	if bracket == -1 {
		return r.UploadUrl
	}

	return r.UploadUrl[0:bracket]
}

func (r *Release) String() string {
	str := make([]string, len(r.Assets)+1)
	str[0] = fmt.Sprintf(
		"%s, name: '%s', description: '%s', id: %d, tagged: %s, published: %s, draft: %v, prerelease: %v",
		r.TagName, r.Name, r.Description, r.Id,
		timeFmtOr(r.Created, RELEASE_DATE_FORMAT, ""),
		timeFmtOr(r.Published, RELEASE_DATE_FORMAT, ""),
		Mark(r.Draft), Mark(r.Prerelease))

	for idx, asset := range r.Assets {
		str[idx+1] = fmt.Sprintf("  - artifact: %s, downloads: %d, state: %s, type: %s, size: %s, id: %d",
			asset.Name, asset.Downloads, asset.State, asset.ContentType,
			humanize.Bytes(asset.Size), asset.Id)
	}

	return strings.Join(str, "\n")
}

type ReleaseCreate struct {
	TagName         string `json:"tag_name"`
	TargetCommitish string `json:"target_commitish,omitempty"`
	Name            string `json:"name"`
	Body            string `json:"body"`
	Draft           bool   `json:"draft"`
	Prerelease      bool   `json:"prerelease"`
}

func Releases(user, repo, token string) ([]Release, error) {
	if token != "" {
		token = "?access_token=" + token
	}
	var releases []Release
	err := GithubGet(fmt.Sprintf(RELEASE_LIST_URI, user, repo, token), &releases)
	if err != nil {
		return nil, err
	}

	return releases, nil
}

func LatestReleaseApi(user, repo, token string) (*Release, error) {
	if token != "" {
		token = "?access_token=" + token
	}
	var release Release
	err := GithubGet(fmt.Sprintf(RELEASE_LATEST_URI, user, repo, token), &release)
	if err != nil {
		return nil, err
	}
	return &release, nil
}

func LatestRelease(user, repo, token string) (*Release, error) {
	latestRelease, err := LatestReleaseApi(user, repo, token)

	// enterprise api doesnt support the latest release endpoint
	// get all releases and see published date to get the latest
	if err != nil {
		releases, err := Releases(user, repo, token)
		if err != nil {
			return nil, err
		}
		var latestRelIndex = -1
		var maxDate time.Time = time.Time{}
		for i, release := range releases {
			var relDate = *(release.Published)
			if relDate.After(maxDate) {
				maxDate = relDate
				latestRelIndex = i
			}
		}
		if latestRelIndex != -1 {
			vprintln("latest release is ->", &releases[latestRelIndex])
			return &releases[latestRelIndex], nil
		}
		return nil, fmt.Errorf("could not find the latest release")

	}
	return latestRelease, nil
}

func ReleaseOfTag(user, repo, tag, token string) (*Release, error) {
	releases, err := Releases(user, repo, token)
	if err != nil {
		return nil, err
	}

	for _, release := range releases {
		if release.TagName == tag {
			return &release, nil
		}
	}

	return nil, fmt.Errorf("could not find the release corresponding to tag %s", tag)
}

/* find the release-id of the specified tag */
func IdOfTag(user, repo, tag, token string) (int, error) {
	release, err := ReleaseOfTag(user, repo, tag, token)
	if err != nil {
		return 0, err
	}

	return release.Id, nil
}
<commit_msg>cmd/download: cleanup latest release code<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/dustin/go-humanize"
)

const (
	RELEASE_LIST_URI    = "/repos/%s/%s/releases%s"
	RELEASE_LATEST_URI  = "/repos/%s/%s/releases/latest%s"
	RELEASE_DATE_FORMAT = "02/01/2006 at 15:04"
)

type Release struct {
	Url         string     `json:"url"`
	PageUrl     string     `json:"html_url"`
	UploadUrl   string     `json:"upload_url"`
	Id          int        `json:"id"`
	Name        string     `json:"name"`
	Description string     `json:"body"`
	TagName     string     `json:"tag_name"`
	Draft       bool       `json:"draft"`
	Prerelease  bool       `json:"prerelease"`
	Created     *time.Time `json:"created_at"`
	Published   *time.Time `json:"published_at"`
	Assets      []Asset    `json:"assets"`
}

func (r *Release) CleanUploadUrl() string {
	bracket := strings.Index(r.UploadUrl, "{")

	if bracket == -1 {
		return r.UploadUrl
	}

	return r.UploadUrl[0:bracket]
}

func (r *Release) String() string {
	str := make([]string, len(r.Assets)+1)
	str[0] = fmt.Sprintf(
		"%s, name: '%s', description: '%s', id: %d, tagged: %s, published: %s, draft: %v, prerelease: %v",
		r.TagName, r.Name, r.Description, r.Id,
		timeFmtOr(r.Created, RELEASE_DATE_FORMAT, ""),
		timeFmtOr(r.Published, RELEASE_DATE_FORMAT, ""),
		Mark(r.Draft), Mark(r.Prerelease))

	for idx, asset := range r.Assets {
		str[idx+1] = fmt.Sprintf("  - artifact: %s, downloads: %d, state: %s, type: %s, size: %s, id: %d",
			asset.Name, asset.Downloads, asset.State, asset.ContentType,
			humanize.Bytes(asset.Size), asset.Id)
	}

	return strings.Join(str, "\n")
}

type ReleaseCreate struct {
	TagName         string `json:"tag_name"`
	TargetCommitish string `json:"target_commitish,omitempty"`
	Name            string `json:"name"`
	Body            string `json:"body"`
	Draft           bool   `json:"draft"`
	Prerelease      bool   `json:"prerelease"`
}

func Releases(user, repo, token string) ([]Release, error) {
	if token != "" {
		token = "?access_token=" + token
	}
	var releases []Release
	err := GithubGet(fmt.Sprintf(RELEASE_LIST_URI, user, repo, token), &releases)
	if err != nil {
		return nil, err
	}

	return releases, nil
}

func latestReleaseApi(user, repo, token string) (*Release, error) {
	if token != "" {
		token = "?access_token=" + token
	}
	var release Release
	return &release, GithubGet(fmt.Sprintf(RELEASE_LATEST_URI, user, repo, token), &release)
}

func LatestRelease(user, repo, token string) (*Release, error) {
	// If latestReleaseApi DOESN'T give an error, return the release.
	if latestRelease, err := latestReleaseApi(user, repo, token); err == nil {
		return latestRelease, nil
	}

	// The enterprise api doesnt support the latest release endpoint. Get
	// all releases and compare the published date to get the latest.
	releases, err := Releases(user, repo, token)
	if err != nil {
		return nil, err
	}

	var latestRelIndex = -1
	maxDate := time.Time{}
	for i, release := range releases {
		if relDate := *release.Published; relDate.After(maxDate) {
			maxDate = relDate
			latestRelIndex = i
		}
	}
	if latestRelIndex == -1 {
		return nil, fmt.Errorf("could not find the latest release")
	}

	vprintln("Scanning ", len(releases), "releases, latest release is", releases[latestRelIndex])
	return &releases[latestRelIndex], nil
}

func ReleaseOfTag(user, repo, tag, token string) (*Release, error) {
	releases, err := Releases(user, repo, token)
	if err != nil {
		return nil, err
	}

	for _, release := range releases {
		if release.TagName == tag {
			return &release, nil
		}
	}

	return nil, fmt.Errorf("could not find the release corresponding to tag %s", tag)
}

/* find the release-id of the specified tag */
func IdOfTag(user, repo, tag, token string) (int, error) {
	release, err := ReleaseOfTag(user, repo, tag, token)
	if err != nil {
		return 0, err
	}

	return release.Id, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 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 logs

import (
	"flag"
	"log"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/pflag"
	"k8s.io/apimachinery/pkg/util/wait"
)

const logFlushFreqFlagName = "log-flush-frequency"

var logFlushFreq = pflag.Duration(logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes")

// TODO(thockin): This is temporary until we agree on log dirs and put those into each cmd.
func init() {
	flag.Set("logtostderr", "true")
}

// AddFlags registers this package's flags on arbitrary FlagSets, such that they point to the
// same value as the global flags.
func AddFlags(fs *pflag.FlagSet) {
	fs.AddFlag(pflag.Lookup(logFlushFreqFlagName))
}

// GlogWriter serves as a bridge between the standard log package and the glog package.
type GlogWriter struct{}

// Write implements the io.Writer interface.
func (writer GlogWriter) Write(data []byte) (n int, err error) {
	glog.Info(string(data))
	return len(data), nil
}

// InitLogs initializes logs the way we want for kubernetes.
func InitLogs() {
	log.SetOutput(GlogWriter{})
	log.SetFlags(0)
	// The default glog flush interval is 5 seconds.
	go wait.Forever(glog.Flush, *logFlushFreq)
}

// FlushLogs flushes logs immediately.
func FlushLogs() {
	glog.Flush()
}

// NewLogger creates a new log.Logger which sends logs to glog.Info.
func NewLogger(prefix string) *log.Logger {
	return log.New(GlogWriter{}, prefix, 0)
}
<commit_msg>move glog setter to util/logs<commit_after>/*
Copyright 2014 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 logs

import (
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/pflag"
	"k8s.io/apimachinery/pkg/util/wait"
)

const logFlushFreqFlagName = "log-flush-frequency"

var logFlushFreq = pflag.Duration(logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes")

// TODO(thockin): This is temporary until we agree on log dirs and put those into each cmd.
func init() {
	flag.Set("logtostderr", "true")
}

// AddFlags registers this package's flags on arbitrary FlagSets, such that they point to the
// same value as the global flags.
func AddFlags(fs *pflag.FlagSet) {
	fs.AddFlag(pflag.Lookup(logFlushFreqFlagName))
}

// GlogWriter serves as a bridge between the standard log package and the glog package.
type GlogWriter struct{}

// Write implements the io.Writer interface.
func (writer GlogWriter) Write(data []byte) (n int, err error) {
	glog.Info(string(data))
	return len(data), nil
}

// InitLogs initializes logs the way we want for kubernetes.
func InitLogs() {
	log.SetOutput(GlogWriter{})
	log.SetFlags(0)
	// The default glog flush interval is 5 seconds.
	go wait.Forever(glog.Flush, *logFlushFreq)
}

// FlushLogs flushes logs immediately.
func FlushLogs() {
	glog.Flush()
}

// NewLogger creates a new log.Logger which sends logs to glog.Info.
func NewLogger(prefix string) *log.Logger {
	return log.New(GlogWriter{}, prefix, 0)
}

// GlogSetter is a setter to set glog level.
func GlogSetter(val string) (string, error) {
	var level glog.Level
	if err := level.Set(val); err != nil {
		return "", fmt.Errorf("failed set glog.logging.verbosity %s: %v", val, err)
	}
	return fmt.Sprintf("successfully set glog.logging.verbosity to %s", val), nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/gnuflag"
	"launchpad.net/juju/go/log"
	stdlog "log"
	"os"
	"path/filepath"
)

// Context represents the run context of a Command. Command implementations
// should interpret file names relative to Dir (see AbsPath below), and print
// output and errors to Stdout and Stderr respectively.
type Context struct {
	Dir    string
	Stdout io.Writer
	Stderr io.Writer
}

// DefaultContext returns a Context suitable for use in non-hosted situations.
func DefaultContext() *Context {
	dir, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		panic(err)
	}
	return &Context{abs, os.Stdout, os.Stderr}
}

// AbsPath returns an absolute representation of path, with relative paths
// interpreted as relative to ctx.Dir.
func (ctx *Context) AbsPath(path string) string {
	if filepath.IsAbs(path) {
		return path
	}
	return filepath.Join(ctx.Dir, path)
}

// InitLog sets up logging to a file or to ctx.Stderr as directed.
func (ctx *Context) InitLog(verbose bool, debug bool, logfile string) (err error) {
	log.Debug = debug
	var target io.Writer
	if logfile != "" {
		path := ctx.AbsPath(logfile)
		target, err = os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
		if err != nil {
			return
		}
	} else if verbose || debug {
		target = ctx.Stderr
	}
	if target != nil {
		log.Target = stdlog.New(target, "", stdlog.LstdFlags)
	} else {
		log.Target = nil
	}
	return
}

// Main runs the given Command in the supplied Context with the given
// arguments, which should not include the command name. It returns a code
// suitable for passing to os.Exit.
func Main(c Command, ctx *Context, args []string) int {
	f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError)
	f.SetOutput(ioutil.Discard)
	if err := c.Init(f, args); err != nil {
		help := c.Info().help(f)
		if err == gnuflag.ErrHelp {
			ctx.Stderr.Write(help)
			return 0
		}
		fmt.Fprintf(ctx.Stderr, "ERROR: %v\n", err)
		ctx.Stderr.Write(help)
		return 2
	}
	if err := c.Run(ctx); err != nil {
		log.Debugf("%s command failed: %s\n", c.Info().Name, err)
		fmt.Fprintf(ctx.Stderr, "ERROR: %v\n", err)
		return 1
	}
	return 0
}
<commit_msg>review point<commit_after>package cmd

import (
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/gnuflag"
	"launchpad.net/juju/go/log"
	stdlog "log"
	"os"
	"path/filepath"
)

// Context represents the run context of a Command. Command implementations
// should interpret file names relative to Dir (see AbsPath below), and print
// output and errors to Stdout and Stderr respectively.
type Context struct {
	Dir    string
	Stdout io.Writer
	Stderr io.Writer
}

// DefaultContext returns a Context suitable for use in non-hosted situations.
func DefaultContext() *Context {
	dir, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		panic(err)
	}
	return &Context{abs, os.Stdout, os.Stderr}
}

// AbsPath returns an absolute representation of path, with relative paths
// interpreted as relative to ctx.Dir.
func (ctx *Context) AbsPath(path string) string {
	if filepath.IsAbs(path) {
		return path
	}
	return filepath.Join(ctx.Dir, path)
}

// InitLog sets up logging to a file or to ctx.Stderr as directed.
func (ctx *Context) InitLog(verbose bool, debug bool, logfile string) (err error) {
	log.Debug = debug
	var target io.Writer
	if logfile != "" {
		path := ctx.AbsPath(logfile)
		target, err = os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
		if err != nil {
			return
		}
	} else if verbose || debug {
		target = ctx.Stderr
	}
	if target != nil {
		log.Target = stdlog.New(target, "", stdlog.LstdFlags)
	} else {
		log.Target = nil
	}
	return
}

// Main runs the given Command in the supplied Context with the given
// arguments, which should not include the command name. It returns a code
// suitable for passing to os.Exit.
func Main(c Command, ctx *Context, args []string) int {
	f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError)
	f.SetOutput(ioutil.Discard)
	if err := c.Init(f, args); err != nil {
		help := c.Info().help(f)
		if err == gnuflag.ErrHelp {
			ctx.Stderr.Write(help)
			return 0
		}
		fmt.Fprintf(ctx.Stderr, "ERROR: %v\n%s", err, help)
		return 2
	}
	if err := c.Run(ctx); err != nil {
		log.Debugf("%s command failed: %s\n", c.Info().Name, err)
		fmt.Fprintf(ctx.Stderr, "ERROR: %v\n", err)
		return 1
	}
	return 0
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	gocontext "context"

	"github.com/docker/containerd/api/execution"
	execEvents "github.com/docker/containerd/execution"
	"github.com/nats-io/go-nats"
	"github.com/urfave/cli"
)

type runConfig struct {
	Image   string `toml:"image"`
	Process struct {
		Args []string `toml:"args"`
		Env  []string `toml:"env"`
		Cwd  string   `toml:"cwd"`
		Uid  int      `toml:"uid"`
		Gid  int      `toml:"gid"`
		Tty  bool     `toml:"tty"`
	} `toml:"process"`
	Network struct {
		Type    string `toml:"type"`
		IP      string `toml:"ip"`
		Gateway string `toml:"gateway"`
	} `toml:"network"`
}

var runCommand = cli.Command{
	Name:  "run",
	Usage: "run a container",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "bundle, b",
			Usage: "path to the container's bundle",
		},
		cli.BoolFlag{
			Name:  "tty, t",
			Usage: "allocate a TTY for the container",
		},
	},
	Action: func(context *cli.Context) error {
		// var config runConfig
		// if _, err := toml.DecodeFile(context.Args().First(), &config); err != nil {
		// 	return err
		// }
		id := context.Args().First()
		if id == "" {
			return fmt.Errorf("container id must be provided")
		}
		executionService, err := getExecutionService(context)
		if err != nil {
			return err
		}

		// setup our event subscriber
		nc, err := nats.Connect(nats.DefaultURL)
		if err != nil {
			return err
		}
		nec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
		if err != nil {
			nc.Close()
			return err
		}
		defer nec.Close()

		evCh := make(chan *execEvents.ContainerExitEvent, 64)
		sub, err := nec.Subscribe(execEvents.ContainersEventsSubjectSubscriber, func(e *execEvents.ContainerExitEvent) {
			evCh <- e
		})
		if err != nil {
			return err
		}
		defer sub.Unsubscribe()

		tmpDir, err := getTempDir(id)
		if err != nil {
			return err
		}
		defer os.RemoveAll(tmpDir)

		bundle, err := filepath.Abs(context.String("bundle"))
		if err != nil {
			return err
		}
		crOpts := &execution.CreateContainerRequest{
			ID:         id,
			BundlePath: bundle,
			Console:    context.Bool("tty"),
			Stdin:      filepath.Join(tmpDir, "stdin"),
			Stdout:     filepath.Join(tmpDir, "stdout"),
			Stderr:     filepath.Join(tmpDir, "stderr"),
		}

		fwg, err := prepareStdio(crOpts.Stdin, crOpts.Stdout, crOpts.Stderr, crOpts.Console)
		if err != nil {
			return err
		}

		cr, err := executionService.Create(gocontext.Background(), crOpts)
		if err != nil {
			return err
		}

		if _, err := executionService.Start(gocontext.Background(), &execution.StartContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		var ec uint32
	eventLoop:
		for {
			select {
			case e, more := <-evCh:
				if !more {
					break eventLoop
				}

				if e.ID == cr.Container.ID && e.PID == cr.InitProcess.ID {
					ec = e.StatusCode
					break eventLoop
				}
			case <-time.After(1 * time.Second):
				if nec.Conn.Status() != nats.CONNECTED {
					break eventLoop
				}
			}
		}

		if _, err := executionService.Delete(gocontext.Background(), &execution.DeleteContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		// Ensure we read all io
		fwg.Wait()

		os.Exit(int(ec))

		return nil
	},
}
<commit_msg>ctr: set raw mode when using --tty<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	gocontext "context"

	"github.com/docker/containerd/api/execution"
	execEvents "github.com/docker/containerd/execution"
	"github.com/docker/docker/pkg/term"
	"github.com/nats-io/go-nats"
	"github.com/urfave/cli"
)

type runConfig struct {
	Image   string `toml:"image"`
	Process struct {
		Args []string `toml:"args"`
		Env  []string `toml:"env"`
		Cwd  string   `toml:"cwd"`
		Uid  int      `toml:"uid"`
		Gid  int      `toml:"gid"`
		Tty  bool     `toml:"tty"`
	} `toml:"process"`
	Network struct {
		Type    string `toml:"type"`
		IP      string `toml:"ip"`
		Gateway string `toml:"gateway"`
	} `toml:"network"`
}

var runCommand = cli.Command{
	Name:  "run",
	Usage: "run a container",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "bundle, b",
			Usage: "path to the container's bundle",
		},
		cli.BoolFlag{
			Name:  "tty, t",
			Usage: "allocate a TTY for the container",
		},
	},
	Action: func(context *cli.Context) error {
		// var config runConfig
		// if _, err := toml.DecodeFile(context.Args().First(), &config); err != nil {
		// 	return err
		// }
		id := context.Args().First()
		if id == "" {
			return fmt.Errorf("container id must be provided")
		}
		executionService, err := getExecutionService(context)
		if err != nil {
			return err
		}

		// setup our event subscriber
		nc, err := nats.Connect(nats.DefaultURL)
		if err != nil {
			return err
		}
		nec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
		if err != nil {
			nc.Close()
			return err
		}
		defer nec.Close()

		evCh := make(chan *execEvents.ContainerExitEvent, 64)
		sub, err := nec.Subscribe(execEvents.ContainersEventsSubjectSubscriber, func(e *execEvents.ContainerExitEvent) {
			evCh <- e
		})
		if err != nil {
			return err
		}
		defer sub.Unsubscribe()

		tmpDir, err := getTempDir(id)
		if err != nil {
			return err
		}
		defer os.RemoveAll(tmpDir)

		bundle, err := filepath.Abs(context.String("bundle"))
		if err != nil {
			return err
		}
		crOpts := &execution.CreateContainerRequest{
			ID:         id,
			BundlePath: bundle,
			Console:    context.Bool("tty"),
			Stdin:      filepath.Join(tmpDir, "stdin"),
			Stdout:     filepath.Join(tmpDir, "stdout"),
			Stderr:     filepath.Join(tmpDir, "stderr"),
		}

		var oldState *term.State
		restoreTerm := func() {
			if oldState != nil {
				term.RestoreTerminal(os.Stdin.Fd(), oldState)
			}
		}

		if crOpts.Console {
			oldState, err = term.SetRawTerminal(os.Stdin.Fd())
			if err != nil {
				return err
			}
			defer restoreTerm()
		}

		fwg, err := prepareStdio(crOpts.Stdin, crOpts.Stdout, crOpts.Stderr, crOpts.Console)
		if err != nil {
			return err
		}

		cr, err := executionService.Create(gocontext.Background(), crOpts)
		if err != nil {
			return err
		}

		if _, err := executionService.Start(gocontext.Background(), &execution.StartContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		var ec uint32
	eventLoop:
		for {
			select {
			case e, more := <-evCh:
				if !more {
					break eventLoop
				}

				if e.ID == cr.Container.ID && e.PID == cr.InitProcess.ID {
					ec = e.StatusCode
					break eventLoop
				}
			case <-time.After(1 * time.Second):
				if nec.Conn.Status() != nats.CONNECTED {
					break eventLoop
				}
			}
		}

		if _, err := executionService.Delete(gocontext.Background(), &execution.DeleteContainerRequest{
			ID: cr.Container.ID,
		}); err != nil {
			return err
		}

		// Ensure we read all io
		fwg.Wait()

		restoreTerm()
		os.Exit(int(ec))

		return nil
	},
}
<|endoftext|>


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

package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"

	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/pkg"
	"github.com/kris-nova/kubicorn/pkg/cli"
	"github.com/kris-nova/kubicorn/pkg/initapi"

	"github.com/kris-nova/kubicorn/pkg/logger"
	"github.com/kris-nova/kubicorn/state"
	"github.com/kris-nova/kubicorn/state/fs"
	"github.com/kris-nova/kubicorn/state/git"
	"github.com/kris-nova/kubicorn/state/jsonfs"
	"github.com/spf13/cobra"
	gg "github.com/tcnksm/go-gitconfig"
)

type OutputData struct {
	Actual   *cluster.Cluster
	Expected *cluster.Cluster
}

var exo = &cli.ExplainOptions{}

// ExplainCmd represents the explain command
func ExplainCmd() *cobra.Command {
	var cmd = &cobra.Command{
		Use:   "explain",
		Short: "Explain cluster",
		Long:  `Output expected and actual state of the given cluster`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				exo.Name = cli.StrEnvDef("KUBICORN_NAME", "")
			} else if len(args) > 1 {
				logger.Critical("Too many arguments.")
				os.Exit(1)
			} else {
				exo.Name = args[0]
			}

			err := RunExplain(exo)
			if err != nil {
				logger.Critical(err.Error())
				os.Exit(1)
			}
		},
	}

	cmd.Flags().StringVarP(&exo.StateStore, "state-store", "s", cli.StrEnvDef("KUBICORN_STATE_STORE", "fs"), "The state store type to use for the cluster")
	cmd.Flags().StringVarP(&exo.StateStorePath, "state-store-path", "S", cli.StrEnvDef("KUBICORN_STATE_STORE_PATH", "./_state"), "The state store path to use")
	cmd.Flags().StringVarP(&exo.Output, "output", "o", cli.StrEnvDef("KUBICORN_OUTPUT", "json"), "Output format (currently only JSON supported)")

	return cmd
}

func RunExplain(options *cli.ExplainOptions) error {

	// Ensure we have a name
	name := options.Name
	if name == "" {
		return errors.New("Empty name. Must specify the name of the cluster to apply")
	}

	// Expand state store path
	options.StateStorePath = cli.ExpandPath(options.StateStorePath)

	// Register state store
	var stateStore state.ClusterStorer
	switch options.StateStore {
	case "fs":
		stateStore = fs.NewFileSystemStore(&fs.FileSystemStoreOptions{
			BasePath:    options.StateStorePath,
			ClusterName: name,
		})
	case "git":
		if options.GitRemote == "" {
			return errors.New("Empty GitRemote url. Must specify the link to the remote git repo.")
		}
		user, _ := gg.Global("user.name")
		email, _ := gg.Email()

		stateStore = git.NewJSONGitStore(&git.JSONGitStoreOptions{
			BasePath:    options.StateStorePath,
			ClusterName: name,
			CommitConfig: &git.JSONGitCommitConfig{
				Name:   user,
				Email:  email,
				Remote: options.GitRemote,
			},
		})
	case "jsonfs":
		stateStore = jsonfs.NewJSONFileSystemStore(&jsonfs.JSONFileSystemStoreOptions{
			BasePath:    options.StateStorePath,
			ClusterName: name,
		})
	}

	cluster, err := stateStore.GetCluster()
	if err != nil {
		return fmt.Errorf("Unable to get cluster [%s]: %v", name, err)
	}

	cluster, err = initapi.InitCluster(cluster)
	if err != nil {
		return err
	}

	runtimeParams := &pkg.RuntimeParameters{}

	if len(ao.AwsProfile) > 0 {
		runtimeParams.AwsProfile = ao.AwsProfile
	}

	reconciler, err := pkg.GetReconciler(cluster, runtimeParams)
	if err != nil {
		return fmt.Errorf("Unable to get reconciler: %v", err)
	}

	var d OutputData
	d.Actual, err = reconciler.Actual(cluster)
	if err != nil {
		return fmt.Errorf("Unable to get actual cluster: %v", err)
	}
	d.Expected, err = reconciler.Expected(cluster)
	if err != nil {
		return fmt.Errorf("Unable to get expected cluster: %v", err)
	}

	if exo.Output == "json" {
		o, err := json.MarshalIndent(d, "", "\t")
		if err != nil {
			return fmt.Errorf("Unable to parse cluster: %v", err)
		}
		fmt.Printf("%s\n", o)
	} else {
		return fmt.Errorf("Unsupported output format")
	}

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

package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"

	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/pkg"
	"github.com/kris-nova/kubicorn/pkg/cli"
	"github.com/kris-nova/kubicorn/pkg/initapi"

	"github.com/kris-nova/kubicorn/pkg/logger"
	"github.com/spf13/cobra"
)

type OutputData struct {
	Actual   *cluster.Cluster
	Expected *cluster.Cluster
}

var exo = &cli.ExplainOptions{}

// ExplainCmd represents the explain command
func ExplainCmd() *cobra.Command {
	var cmd = &cobra.Command{
		Use:   "explain",
		Short: "Explain cluster",
		Long:  `Output expected and actual state of the given cluster`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				exo.Name = cli.StrEnvDef("KUBICORN_NAME", "")
			} else if len(args) > 1 {
				logger.Critical("Too many arguments.")
				os.Exit(1)
			} else {
				exo.Name = args[0]
			}

			err := RunExplain(exo)
			if err != nil {
				logger.Critical(err.Error())
				os.Exit(1)
			}
		},
	}

	cmd.Flags().StringVarP(&exo.StateStore, "state-store", "s", cli.StrEnvDef("KUBICORN_STATE_STORE", "fs"), "The state store type to use for the cluster")
	cmd.Flags().StringVarP(&exo.StateStorePath, "state-store-path", "S", cli.StrEnvDef("KUBICORN_STATE_STORE_PATH", "./_state"), "The state store path to use")
	cmd.Flags().StringVarP(&exo.Output, "output", "o", cli.StrEnvDef("KUBICORN_OUTPUT", "json"), "Output format (currently only JSON supported)")

	// git flags
	cmd.Flags().StringVar(&exo.GitRemote, "git-config", cli.StrEnvDef("KUBICORN_GIT_CONFIG", "git"), "The git remote url to use")

	// s3 flags
	cmd.Flags().StringVar(&exo.S3AccessKey, "s3-access", cli.StrEnvDef("KUBICORN_S3_ACCESS_KEY", ""), "The s3 access key.")
	cmd.Flags().StringVar(&exo.S3SecretKey, "s3-secret", cli.StrEnvDef("KUBICORN_S3_SECRET_KEY", ""), "The s3 secret key.")
	cmd.Flags().StringVar(&exo.BucketEndpointURL, "s3-endpoint", cli.StrEnvDef("KUBICORN_S3_ENDPOINT", ""), "The s3 endpoint url.")
	cmd.Flags().BoolVar(&exo.BucketSSL, "s3-ssl", cli.BoolEnvDef("KUBICORN_S3_SSL", true), "The s3 bucket name to be used for saving the git state for the cluster.")
	cmd.Flags().StringVar(&exo.BucketName, "s3-bucket", cli.StrEnvDef("KUBICORN_S3_BUCKET", ""), "The s3 bucket name to be used for saving the s3 state for the cluster.")

	return cmd
}

func RunExplain(options *cli.ExplainOptions) error {

	// Ensure we have a name
	name := options.Name
	if name == "" {
		return errors.New("Empty name. Must specify the name of the cluster to apply")
	}

	// Expand state store path
	options.StateStorePath = cli.ExpandPath(options.StateStorePath)

	// Register state store
	stateStore, err := options.NewStateStore()
	if err != nil {
		return err
	} else if !stateStore.Exists() {
		return fmt.Errorf("State store [%s] does not exists, can't edit", name)
	}

	cluster, err := stateStore.GetCluster()
	if err != nil {
		return fmt.Errorf("Unable to get cluster [%s]: %v", name, err)
	}

	cluster, err = initapi.InitCluster(cluster)
	if err != nil {
		return err
	}

	runtimeParams := &pkg.RuntimeParameters{}

	if len(ao.AwsProfile) > 0 {
		runtimeParams.AwsProfile = ao.AwsProfile
	}

	reconciler, err := pkg.GetReconciler(cluster, runtimeParams)
	if err != nil {
		return fmt.Errorf("Unable to get reconciler: %v", err)
	}

	var d OutputData
	d.Actual, err = reconciler.Actual(cluster)
	if err != nil {
		return fmt.Errorf("Unable to get actual cluster: %v", err)
	}
	d.Expected, err = reconciler.Expected(cluster)
	if err != nil {
		return fmt.Errorf("Unable to get expected cluster: %v", err)
	}

	if exo.Output == "json" {
		o, err := json.MarshalIndent(d, "", "\t")
		if err != nil {
			return fmt.Errorf("Unable to parse cluster: %v", err)
		}
		fmt.Printf("%s\n", o)
	} else {
		return fmt.Errorf("Unsupported output format")
	}

	return nil
}
<|endoftext|>


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

package cmd

import (
	"context"

	"github.com/fatih/color"
	"github.com/minio/cli"
	json "github.com/minio/colorjson"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/pkg/console"
)

var mbFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "region",
		Value: "us-east-1",
		Usage: "specify bucket region; defaults to 'us-east-1'",
	},
	cli.BoolFlag{
		Name:  "ignore-existing, p",
		Usage: "ignore if bucket/directory already exists",
	},
	cli.BoolFlag{
		Name:  "with-lock, l",
		Usage: "enable object lock",
	},
	cli.BoolFlag{
		Name:  "with-versioning",
		Usage: "enable versioned bucket",
	},
}

// make a bucket.
var mbCmd = cli.Command{
	Name:         "mb",
	Usage:        "make a bucket",
	Action:       mainMakeBucket,
	Before:       setGlobalsFromContext,
	OnUsageError: onUsageError,
	Flags:        append(mbFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] TARGET [TARGET...]
{{if .VisibleFlags}}
FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
EXAMPLES:
  1. Create a bucket on Amazon S3 cloud storage.
     {{.Prompt}} {{.HelpName}} s3/mynewbucket

  2. Create a new bucket on Google Cloud Storage.
     {{.Prompt}} {{.HelpName}} gcs/miniocloud

  3. Create a new bucket on Amazon S3 cloud storage in region 'us-west-2'.
     {{.Prompt}} {{.HelpName}} --region=us-west-2 s3/myregionbucket

  4. Create a new directory including its missing parents (equivalent to 'mkdir -p').
     {{.Prompt}} {{.HelpName}} /tmp/this/new/dir1

  5. Create multiple directories including its missing parents (behavior similar to 'mkdir -p').
     {{.Prompt}} {{.HelpName}} /mnt/sdb/mydisk /mnt/sdc/mydisk /mnt/sdd/mydisk

  6. Ignore if bucket/directory already exists.
     {{.Prompt}} {{.HelpName}} --ignore-existing myminio/mynewbucket

  7. Create a new bucket on Amazon S3 cloud storage in region 'us-west-2' with object lock enabled.
     {{.Prompt}} {{.HelpName}} --with-lock --region=us-west-2 s3/myregionbucket

  8. Create a new bucket on MinIO with versioning enabled.
     {{.Prompt}} {{.HelpName}} --with-versioning myminio/myversionedbucket
`,
}

// makeBucketMessage is container for make bucket success and failure messages.
type makeBucketMessage struct {
	Status string `json:"status"`
	Bucket string `json:"bucket"`
	Region string `json:"region"`
}

// String colorized make bucket message.
func (s makeBucketMessage) String() string {
	return console.Colorize("MakeBucket", "Bucket created successfully `"+s.Bucket+"`.")
}

// JSON jsonified make bucket message.
func (s makeBucketMessage) JSON() string {
	makeBucketJSONBytes, e := json.MarshalIndent(s, "", " ")
	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")

	return string(makeBucketJSONBytes)
}

// Validate command line arguments.
func checkMakeBucketSyntax(cliCtx *cli.Context) {
	if !cliCtx.Args().Present() {
		cli.ShowCommandHelpAndExit(cliCtx, "mb", 1) // last argument is exit code
	}
}

// mainMakeBucket is entry point for mb command.
func mainMakeBucket(cliCtx *cli.Context) error {
	// check 'mb' cli arguments.
	checkMakeBucketSyntax(cliCtx)

	// Additional command speific theme customization.
	console.SetColor("MakeBucket", color.New(color.FgGreen, color.Bold))

	// Save region.
	region := cliCtx.String("region")
	ignoreExisting := cliCtx.Bool("p")
	withLock := cliCtx.Bool("l")

	var cErr error
	for _, targetURL := range cliCtx.Args() {
		// Instantiate client for URL.
		clnt, err := newClient(targetURL)
		if err != nil {
			errorIf(err.Trace(targetURL), "Invalid target `"+targetURL+"`.")
			cErr = exitStatus(globalErrorExitStatus)
			continue
		}

		ctx, cancelMakeBucket := context.WithCancel(globalContext)
		defer cancelMakeBucket()

		// Make bucket.
		if err = clnt.MakeBucket(ctx, region, ignoreExisting, withLock); err != nil {
			switch err.ToGoError().(type) {
			case BucketNameEmpty:
				errorIf(err.Trace(targetURL), "Unable to make bucket, please use `mc mb %s`.", urlJoinPath(targetURL, "your-bucket-name"))
			default:
				errorIf(err.Trace(targetURL), "Unable to make bucket `"+targetURL+"`.")
			}
			cErr = exitStatus(globalErrorExitStatus)
			continue
		}

		if cliCtx.Bool("s") {
			fatalIf(clnt.SetVersion(ctx, "enable", []string{}, false), "Unable to enable versioning")
		}

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

package cmd

import (
	"context"

	"github.com/fatih/color"
	"github.com/minio/cli"
	json "github.com/minio/colorjson"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/pkg/console"
)

var mbFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "region",
		Value: "us-east-1",
		Usage: "specify bucket region; defaults to 'us-east-1'",
	},
	cli.BoolFlag{
		Name:  "ignore-existing, p",
		Usage: "ignore if bucket/directory already exists",
	},
	cli.BoolFlag{
		Name:  "with-lock, l",
		Usage: "enable object lock",
	},
	cli.BoolFlag{
		Name:  "with-versioning",
		Usage: "enable versioned bucket",
	},
}

// make a bucket.
var mbCmd = cli.Command{
	Name:         "mb",
	Usage:        "make a bucket",
	Action:       mainMakeBucket,
	Before:       setGlobalsFromContext,
	OnUsageError: onUsageError,
	Flags:        append(mbFlags, globalFlags...),
	CustomHelpTemplate: `NAME:
  {{.HelpName}} - {{.Usage}}

USAGE:
  {{.HelpName}} [FLAGS] TARGET [TARGET...]
{{if .VisibleFlags}}
FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
EXAMPLES:
  1. Create a bucket on Amazon S3 cloud storage.
     {{.Prompt}} {{.HelpName}} s3/mynewbucket

  2. Create a new bucket on Google Cloud Storage.
     {{.Prompt}} {{.HelpName}} gcs/miniocloud

  3. Create a new bucket on Amazon S3 cloud storage in region 'us-west-2'.
     {{.Prompt}} {{.HelpName}} --region=us-west-2 s3/myregionbucket

  4. Create a new directory including its missing parents (equivalent to 'mkdir -p').
     {{.Prompt}} {{.HelpName}} /tmp/this/new/dir1

  5. Create multiple directories including its missing parents (behavior similar to 'mkdir -p').
     {{.Prompt}} {{.HelpName}} /mnt/sdb/mydisk /mnt/sdc/mydisk /mnt/sdd/mydisk

  6. Ignore if bucket/directory already exists.
     {{.Prompt}} {{.HelpName}} --ignore-existing myminio/mynewbucket

  7. Create a new bucket on Amazon S3 cloud storage in region 'us-west-2' with object lock enabled.
     {{.Prompt}} {{.HelpName}} --with-lock --region=us-west-2 s3/myregionbucket

  8. Create a new bucket on MinIO with versioning enabled.
     {{.Prompt}} {{.HelpName}} --with-versioning myminio/myversionedbucket
`,
}

// makeBucketMessage is container for make bucket success and failure messages.
type makeBucketMessage struct {
	Status string `json:"status"`
	Bucket string `json:"bucket"`
	Region string `json:"region"`
}

// String colorized make bucket message.
func (s makeBucketMessage) String() string {
	return console.Colorize("MakeBucket", "Bucket created successfully `"+s.Bucket+"`.")
}

// JSON jsonified make bucket message.
func (s makeBucketMessage) JSON() string {
	makeBucketJSONBytes, e := json.MarshalIndent(s, "", " ")
	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")

	return string(makeBucketJSONBytes)
}

// Validate command line arguments.
func checkMakeBucketSyntax(cliCtx *cli.Context) {
	if !cliCtx.Args().Present() {
		cli.ShowCommandHelpAndExit(cliCtx, "mb", 1) // last argument is exit code
	}
}

// mainMakeBucket is entry point for mb command.
func mainMakeBucket(cliCtx *cli.Context) error {
	// check 'mb' cli arguments.
	checkMakeBucketSyntax(cliCtx)

	// Additional command speific theme customization.
	console.SetColor("MakeBucket", color.New(color.FgGreen, color.Bold))

	// Save region.
	region := cliCtx.String("region")
	ignoreExisting := cliCtx.Bool("p")
	withLock := cliCtx.Bool("l")

	var cErr error
	for _, targetURL := range cliCtx.Args() {
		// Instantiate client for URL.
		clnt, err := newClient(targetURL)
		if err != nil {
			errorIf(err.Trace(targetURL), "Invalid target `"+targetURL+"`.")
			cErr = exitStatus(globalErrorExitStatus)
			continue
		}

		ctx, cancelMakeBucket := context.WithCancel(globalContext)
		defer cancelMakeBucket()

		// Make bucket.
		if err = clnt.MakeBucket(ctx, region, ignoreExisting, withLock); err != nil {
			switch err.ToGoError().(type) {
			case BucketNameEmpty:
				errorIf(err.Trace(targetURL), "Unable to make bucket, please use `mc mb %s`.", urlJoinPath(targetURL, "your-bucket-name"))
			default:
				errorIf(err.Trace(targetURL), "Unable to make bucket `"+targetURL+"`.")
			}
			cErr = exitStatus(globalErrorExitStatus)
			continue
		}

		if cliCtx.Bool("with-versioning") {
			fatalIf(clnt.SetVersion(ctx, "enable", []string{}, false), "Unable to enable versioning")
		}

		// Successfully created a bucket.
		printMsg(makeBucketMessage{Status: "success", Bucket: targetURL})
	}
	return cErr
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"
	"path"
	"path/filepath"
	"strconv"

	"errors"

	"github.com/rainforestapp/rainforest-cli/rainforest"
	"github.com/urfave/cli"
)

// Maximum concurrency for multithreaded HTTP requests
const reporterConcurrency = 4

// resourceAPI is part of the API connected to available resources
type reporterAPI interface {
	GetRunTestDetails(int, int) (*rainforest.RunTestDetails, error)
	GetRunDetails(int) (*rainforest.RunDetails, error)
}

type reporter struct {
	getRunDetails           func(int, reporterAPI) (*rainforest.RunDetails, error)
	createOutputFile        func(string) (*os.File, error)
	createJUnitReportSchema func(*rainforest.RunDetails, reporterAPI) (*jUnitReportSchema, error)
	writeJUnitReport        func(*jUnitReportSchema, *os.File) error
}

func createReport(c cliContext) error {
	r := newReporter()
	return r.createReport(c)
}

func postRunJUnitReport(c cliContext, runID int) error {
	// Get the csv file path either and skip uploading if it's not present
	fileName := c.String("junit-file")
	if fileName == "" {
		return nil
	}
	rerunAttempt := c.Uint("rerun-attempt")

	r := newReporter()
	return r.createJUnitReport(runID, augmentJunitFileName(fileName, rerunAttempt))
}

func newReporter() *reporter {
	return &reporter{
		getRunDetails:           getRunDetails,
		createOutputFile:        os.Create,
		createJUnitReportSchema: createJUnitReportSchema,
		writeJUnitReport:        writeJUnitReport,
	}
}

func (r *reporter) createReport(c cliContext) error {
	var runID int
	var err error

	if runIDArg := c.Args().Get(0); runIDArg != "" {
		runID, err = strconv.Atoi(runIDArg)
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}
	} else if deprecatedRunIDArg := c.String("run-id"); deprecatedRunIDArg != "" {
		runID, err = strconv.Atoi(deprecatedRunIDArg)
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}

		log.Println("Warning: `run-id` flag is deprecated. Please provide Run ID as an argument.")
	} else {
		return cli.NewExitError("No run ID argument found.", 1)
	}

	if junitFile := c.String("junit-file"); junitFile != "" {
		rerunAttempt := c.Uint("rerun-attempt")
		err = r.createJUnitReport(runID, augmentJunitFileName(junitFile, rerunAttempt))
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}
	} else {
		return cli.NewExitError("Output file not specified", 1)
	}

	return nil
}

func augmentJunitFileName(junitFile string, rerunAttempt uint) string {
	if rerunAttempt > 0 {
		junitFile = fmt.Sprintf("%v.%v", junitFile, rerunAttempt)
	}
	return junitFile
}

func (r *reporter) createJUnitReport(runID int, junitFile string) error {
	log.Print("Creating JUnit report for run #" + strconv.Itoa(runID) + ": " + junitFile)

	filepath, err := filepath.Abs(junitFile)
	if err != nil {
		return err
	}

	var runDetails *rainforest.RunDetails
	runDetails, err = r.getRunDetails(runID, api)
	if err != nil {
		return err
	}

	var outputFile *os.File
	outputFile, err = r.createOutputFile(filepath)
	defer outputFile.Close()
	if err != nil {
		return err
	}

	var reportSchema *jUnitReportSchema
	reportSchema, err = r.createJUnitReportSchema(runDetails, api)
	if err != nil {
		return err
	}

	err = r.writeJUnitReport(reportSchema, outputFile)
	if err != nil {
		return err
	}

	return nil
}

func getRunDetails(runID int, client reporterAPI) (*rainforest.RunDetails, error) {
	var runDetails *rainforest.RunDetails
	var err error

	log.Printf("Fetching details for run #" + strconv.Itoa(runID))
	if runDetails, err = client.GetRunDetails(runID); err != nil {
		return runDetails, err
	}

	if !runDetails.StateDetails.IsFinalState {
		err = errors.New("Report cannot be created for an incomplete run")
	}

	return runDetails, err
}

type jUnitTestReportFailure struct {
	XMLName xml.Name `xml:"failure"`
	Type    string   `xml:"type,attr"`
	Message string   `xml:"message,attr"`
}

type jUnitTestReportSchema struct {
	XMLName  xml.Name `xml:"testcase"`
	ID       int      `xml:"id,attr"`
	Name     string   `xml:"name,attr"`
	Time     float64  `xml:"time,attr"`
	Failures []jUnitTestReportFailure
}

type jUnitReportSchema struct {
	XMLName   xml.Name `xml:"testsuite"`
	ID        int      `xml:"id,attr"`
	Name      string   `xml:"name,attr"`
	Tests     int      `xml:"tests,attr"`
	Errors    int      `xml:"errors,attr"`
	Failures  int      `xml:"failures,attr"`
	Time      float64  `xml:"time,attr"`
	TestCases []jUnitTestReportSchema
}

func testURL(runID int, testID int) string {
	u := *api.BaseURL
	u.Path = path.Join("runs", strconv.Itoa(runID), "tests", strconv.Itoa(testID))
	return u.String()
}

func createJUnitReportSchema(runDetails *rainforest.RunDetails, api reporterAPI) (*jUnitReportSchema, error) {
	type processedTestCase struct {
		TestCase jUnitTestReportSchema
		Error    error
	}

	// Create channels for work to be done and results
	testCount := len(runDetails.Tests)
	processedTestChan := make(chan processedTestCase, testCount)
	testsChan := make(chan rainforest.RunTestDetails, testCount)

	processTestWorker := func(testsChan <-chan rainforest.RunTestDetails) {
		for test := range testsChan {
			testCase := jUnitTestReportSchema{}

			testDuration := test.UpdatedAt.Sub(test.CreatedAt).Seconds()

			testCase.ID = test.ID
			testCase.Name = test.Title
			testCase.Time = testDuration

			if test.Result == "failed" {
				log.Printf("Fetching information for failed test #" + strconv.Itoa(test.ID))
				testDetails, err := api.GetRunTestDetails(runDetails.ID, test.ID)

				if err != nil {
					processedTestChan <- processedTestCase{TestCase: jUnitTestReportSchema{}, Error: err}
					return
				}

				if testDetails.HasRfaResults == true {
					for _, browser := range testDetails.Browsers {
						if browser.Result == "failed" {
							url := testURL(runDetails.ID, test.ID)
							reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url}
							testCase.Failures = append(testCase.Failures, reportFailure)
						}
					}
				} else {
					for _, step := range testDetails.Steps {
						for _, browser := range step.Browsers {
							log.Println(testDetails)
							for _, feedback := range browser.Feedback {

								if feedback.Result != "failed" || feedback.JobState != "approved" {
									continue
								}

								if feedback.FailureNote != "" {
									url := testURL(runDetails.ID, test.ID)
									reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url + " - " + feedback.FailureNote}
									testCase.Failures = append(testCase.Failures, reportFailure)
								} else if feedback.CommentReason != "" {
									// The step failed due to a special comment type being selected
									message := feedback.CommentReason

									if feedback.Comment != "" {
										message += ": " + feedback.Comment
									}
									url := testURL(runDetails.ID, test.ID)
									reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url + " - " + message}
									testCase.Failures = append(testCase.Failures, reportFailure)
								}
							}
						}
					}
				}
			}

			processedTestChan <- processedTestCase{TestCase: testCase}
		}
	}

	// spawn workers
	for i := 0; i < reporterConcurrency; i++ {
		go processTestWorker(testsChan)
	}

	// give them work
	for _, test := range runDetails.Tests {
		testsChan <- test
	}
	close(testsChan)

	// and collect the results
	testCases := make([]jUnitTestReportSchema, testCount)
	for i := 0; i < testCount; i++ {
		processed := <-processedTestChan

		if processed.Error != nil {
			return &jUnitReportSchema{}, processed.Error
		}

		testCases[i] = processed.TestCase
	}

	finalStateName := runDetails.StateDetails.Name
	runDuration := runDetails.Timestamps[finalStateName].Sub(runDetails.Timestamps["created_at"]).Seconds()
	report := &jUnitReportSchema{
		ID:        runDetails.ID,
		Errors:    runDetails.TotalNoResultTests,
		Failures:  runDetails.TotalFailedTests,
		Tests:     runDetails.TotalTests,
		TestCases: testCases,
		Time:      runDuration,
	}

	if runDesc := runDetails.Description; runDesc != "" {
		report.Name = runDesc
	} else {
		report.Name = fmt.Sprintf("Run #%v", runDetails.ID)
	}

	return report, nil
}

func writeJUnitReport(reportSchema *jUnitReportSchema, file *os.File) error {
	enc := xml.NewEncoder(file)

	file.Write([]byte(xml.Header))

	enc.Indent("", "  ")
	err := enc.Encode(reportSchema)
	if err != nil {
		return err
	}

	log.Printf("JUnit report successfully written to %v", file.Name())
	return nil
}
<commit_msg>RF-22011 Do not clobber main.api<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"
	"path"
	"path/filepath"
	"strconv"

	"errors"

	"github.com/rainforestapp/rainforest-cli/rainforest"
	"github.com/urfave/cli"
)

// Maximum concurrency for multithreaded HTTP requests
const reporterConcurrency = 4

// resourceAPI is part of the API connected to available resources
type reporterAPI interface {
	GetRunTestDetails(int, int) (*rainforest.RunTestDetails, error)
	GetRunDetails(int) (*rainforest.RunDetails, error)
}

type reporter struct {
	getRunDetails           func(int, reporterAPI) (*rainforest.RunDetails, error)
	createOutputFile        func(string) (*os.File, error)
	createJUnitReportSchema func(*rainforest.RunDetails, reporterAPI) (*jUnitReportSchema, error)
	writeJUnitReport        func(*jUnitReportSchema, *os.File) error
}

func createReport(c cliContext) error {
	r := newReporter()
	return r.createReport(c)
}

func postRunJUnitReport(c cliContext, runID int) error {
	// Get the csv file path either and skip uploading if it's not present
	fileName := c.String("junit-file")
	if fileName == "" {
		return nil
	}
	rerunAttempt := c.Uint("rerun-attempt")

	r := newReporter()
	return r.createJUnitReport(runID, augmentJunitFileName(fileName, rerunAttempt))
}

func newReporter() *reporter {
	return &reporter{
		getRunDetails:           getRunDetails,
		createOutputFile:        os.Create,
		createJUnitReportSchema: createJUnitReportSchema,
		writeJUnitReport:        writeJUnitReport,
	}
}

func (r *reporter) createReport(c cliContext) error {
	var runID int
	var err error

	if runIDArg := c.Args().Get(0); runIDArg != "" {
		runID, err = strconv.Atoi(runIDArg)
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}
	} else if deprecatedRunIDArg := c.String("run-id"); deprecatedRunIDArg != "" {
		runID, err = strconv.Atoi(deprecatedRunIDArg)
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}

		log.Println("Warning: `run-id` flag is deprecated. Please provide Run ID as an argument.")
	} else {
		return cli.NewExitError("No run ID argument found.", 1)
	}

	if junitFile := c.String("junit-file"); junitFile != "" {
		rerunAttempt := c.Uint("rerun-attempt")
		err = r.createJUnitReport(runID, augmentJunitFileName(junitFile, rerunAttempt))
		if err != nil {
			return cli.NewExitError(err.Error(), 1)
		}
	} else {
		return cli.NewExitError("Output file not specified", 1)
	}

	return nil
}

func augmentJunitFileName(junitFile string, rerunAttempt uint) string {
	if rerunAttempt > 0 {
		junitFile = fmt.Sprintf("%v.%v", junitFile, rerunAttempt)
	}
	return junitFile
}

func (r *reporter) createJUnitReport(runID int, junitFile string) error {
	log.Print("Creating JUnit report for run #" + strconv.Itoa(runID) + ": " + junitFile)

	filepath, err := filepath.Abs(junitFile)
	if err != nil {
		return err
	}

	var runDetails *rainforest.RunDetails
	runDetails, err = r.getRunDetails(runID, api)
	if err != nil {
		return err
	}

	var outputFile *os.File
	outputFile, err = r.createOutputFile(filepath)
	defer outputFile.Close()
	if err != nil {
		return err
	}

	var reportSchema *jUnitReportSchema
	reportSchema, err = r.createJUnitReportSchema(runDetails, api)
	if err != nil {
		return err
	}

	err = r.writeJUnitReport(reportSchema, outputFile)
	if err != nil {
		return err
	}

	return nil
}

func getRunDetails(runID int, client reporterAPI) (*rainforest.RunDetails, error) {
	var runDetails *rainforest.RunDetails
	var err error

	log.Printf("Fetching details for run #" + strconv.Itoa(runID))
	if runDetails, err = client.GetRunDetails(runID); err != nil {
		return runDetails, err
	}

	if !runDetails.StateDetails.IsFinalState {
		err = errors.New("Report cannot be created for an incomplete run")
	}

	return runDetails, err
}

type jUnitTestReportFailure struct {
	XMLName xml.Name `xml:"failure"`
	Type    string   `xml:"type,attr"`
	Message string   `xml:"message,attr"`
}

type jUnitTestReportSchema struct {
	XMLName  xml.Name `xml:"testcase"`
	ID       int      `xml:"id,attr"`
	Name     string   `xml:"name,attr"`
	Time     float64  `xml:"time,attr"`
	Failures []jUnitTestReportFailure
}

type jUnitReportSchema struct {
	XMLName   xml.Name `xml:"testsuite"`
	ID        int      `xml:"id,attr"`
	Name      string   `xml:"name,attr"`
	Tests     int      `xml:"tests,attr"`
	Errors    int      `xml:"errors,attr"`
	Failures  int      `xml:"failures,attr"`
	Time      float64  `xml:"time,attr"`
	TestCases []jUnitTestReportSchema
}

func testURL(runID int, testID int) string {
	u := *api.BaseURL
	u.Path = path.Join("runs", strconv.Itoa(runID), "tests", strconv.Itoa(testID))
	return u.String()
}

func createJUnitReportSchema(runDetails *rainforest.RunDetails, apiReporter reporterAPI) (*jUnitReportSchema, error) {
	type processedTestCase struct {
		TestCase jUnitTestReportSchema
		Error    error
	}

	// Create channels for work to be done and results
	testCount := len(runDetails.Tests)
	processedTestChan := make(chan processedTestCase, testCount)
	testsChan := make(chan rainforest.RunTestDetails, testCount)

	processTestWorker := func(testsChan <-chan rainforest.RunTestDetails) {
		for test := range testsChan {
			testCase := jUnitTestReportSchema{}

			testDuration := test.UpdatedAt.Sub(test.CreatedAt).Seconds()

			testCase.ID = test.ID
			testCase.Name = test.Title
			testCase.Time = testDuration

			if test.Result == "failed" {
				log.Printf("Fetching information for failed test #" + strconv.Itoa(test.ID))
				testDetails, err := apiReporter.GetRunTestDetails(runDetails.ID, test.ID)

				if err != nil {
					processedTestChan <- processedTestCase{TestCase: jUnitTestReportSchema{}, Error: err}
					return
				}

				if testDetails.HasRfaResults == true {
					for _, browser := range testDetails.Browsers {
						if browser.Result == "failed" {
							url := testURL(runDetails.ID, test.ID)
							reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url}
							testCase.Failures = append(testCase.Failures, reportFailure)
						}
					}
				} else {
					for _, step := range testDetails.Steps {
						for _, browser := range step.Browsers {
							log.Println(testDetails)
							for _, feedback := range browser.Feedback {

								if feedback.Result != "failed" || feedback.JobState != "approved" {
									continue
								}

								if feedback.FailureNote != "" {
									url := testURL(runDetails.ID, test.ID)
									reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url + " - " + feedback.FailureNote}
									testCase.Failures = append(testCase.Failures, reportFailure)
								} else if feedback.CommentReason != "" {
									// The step failed due to a special comment type being selected
									message := feedback.CommentReason

									if feedback.Comment != "" {
										message += ": " + feedback.Comment
									}
									url := testURL(runDetails.ID, test.ID)
									reportFailure := jUnitTestReportFailure{Type: browser.Name, Message: url + " - " + message}
									testCase.Failures = append(testCase.Failures, reportFailure)
								}
							}
						}
					}
				}
			}

			processedTestChan <- processedTestCase{TestCase: testCase}
		}
	}

	// spawn workers
	for i := 0; i < reporterConcurrency; i++ {
		go processTestWorker(testsChan)
	}

	// give them work
	for _, test := range runDetails.Tests {
		testsChan <- test
	}
	close(testsChan)

	// and collect the results
	testCases := make([]jUnitTestReportSchema, testCount)
	for i := 0; i < testCount; i++ {
		processed := <-processedTestChan

		if processed.Error != nil {
			return &jUnitReportSchema{}, processed.Error
		}

		testCases[i] = processed.TestCase
	}

	finalStateName := runDetails.StateDetails.Name
	runDuration := runDetails.Timestamps[finalStateName].Sub(runDetails.Timestamps["created_at"]).Seconds()
	report := &jUnitReportSchema{
		ID:        runDetails.ID,
		Errors:    runDetails.TotalNoResultTests,
		Failures:  runDetails.TotalFailedTests,
		Tests:     runDetails.TotalTests,
		TestCases: testCases,
		Time:      runDuration,
	}

	if runDesc := runDetails.Description; runDesc != "" {
		report.Name = runDesc
	} else {
		report.Name = fmt.Sprintf("Run #%v", runDetails.ID)
	}

	return report, nil
}

func writeJUnitReport(reportSchema *jUnitReportSchema, file *os.File) error {
	enc := xml.NewEncoder(file)

	file.Write([]byte(xml.Header))

	enc.Indent("", "  ")
	err := enc.Encode(reportSchema)
	if err != nil {
		return err
	}

	log.Printf("JUnit report successfully written to %v", file.Name())
	return nil
}
<|endoftext|>