<commit_before>package monitoring

import (
	"fmt"
	"reflect"
	"time"

	"github.com/pkg/errors"
	"github.com/rancher/norman/controller"
	"github.com/rancher/rancher/pkg/controllers/user/nslabels"
	"github.com/rancher/rancher/pkg/monitoring"
	"github.com/rancher/rancher/pkg/settings"
	mgmtv3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/apis/project.cattle.io/v3"
	k8scorev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

type projectHandler struct {
	clusterName         string
	cattleClusterClient mgmtv3.ClusterInterface
	cattleProjectClient mgmtv3.ProjectInterface
	app                 *appHandler
}

func (ph *projectHandler) sync(key string, project *mgmtv3.Project) (runtime.Object, error) {
	if project == nil || project.DeletionTimestamp != nil ||
		project.Spec.ClusterName != ph.clusterName {
		return project, nil
	}

	clusterID := project.Spec.ClusterName
	cluster, err := ph.cattleClusterClient.Get(clusterID, metav1.GetOptions{})
	if err != nil {
		return project, errors.Wrapf(err, "failed to find Cluster %s", clusterID)
	}
	if !cluster.Spec.EnableClusterMonitoring && project.Spec.EnableProjectMonitoring {
		return project, &controller.ForgetError{
			Err: errors.New("unable to operate Project Monitoring without enabling Cluster Monitoring"),
		}
	}

	projectTag := getProjectTag(project)
	src := project
	cpy := src.DeepCopy()

	err = ph.doSync(cpy, cluster.Spec.DisplayName)

	if !reflect.DeepEqual(cpy, src) {
		updated, updateErr := ph.cattleProjectClient.Update(cpy)
		if updateErr != nil {
			return project, errors.Wrapf(updateErr, "failed to update Project %s", projectTag)
		}

		cpy = updated
	}

	if err != nil {
		err = errors.Wrapf(err, "unable to sync Project %s", projectTag)
	}

	return cpy, err
}

func (ph *projectHandler) doSync(project *mgmtv3.Project, clusterName string) error {
	if !mgmtv3.NamespaceBackedResource.IsTrue(project) && !mgmtv3.ProjectConditionInitialRolesPopulated.IsTrue(project) {
		return nil
	}
	_, err := mgmtv3.ProjectConditionMetricExpressionDeployed.DoUntilTrue(project, func() (runtime.Object, error) {
		projectName := fmt.Sprintf("%s:%s", project.Spec.ClusterName, project.Name)

		for _, graph := range preDefinedProjectGraph {
			newObj := graph.DeepCopy()
			newObj.Namespace = project.Name
			newObj.Spec.ProjectName = projectName
			if _, err := ph.app.cattleProjectGraphClient.Create(newObj); err != nil && !apierrors.IsAlreadyExists(err) {
				return project, err
			}
		}

		return project, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to apply metric expression")
	}

	appName, appTargetNamespace := monitoring.ProjectMonitoringInfo(project.Name)

	if project.Spec.EnableProjectMonitoring {
		appProjectName, err := ph.ensureAppProjectName(appTargetNamespace, project)
		if err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to ensure monitoring project name")
		}

		if err := ph.deployApp(appName, appTargetNamespace, appProjectName, project, clusterName); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to deploy monitoring")
		}

		if err := ph.detectAppComponentsWhileInstall(appName, appTargetNamespace, project); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to detect the installation status of monitoring components")
		}

		mgmtv3.ProjectConditionMonitoringEnabled.True(project)
		mgmtv3.ProjectConditionMonitoringEnabled.Message(project, "")
	} else if project.Status.MonitoringStatus != nil {
		if err := ph.app.withdrawApp(project.Spec.ClusterName, appName, appTargetNamespace); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to withdraw monitoring")
		}

		if err := ph.detectAppComponentsWhileUninstall(appName, appTargetNamespace, project); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to detect the uninstallation status of monitoring components")
		}

		mgmtv3.ProjectConditionMonitoringEnabled.False(project)
		mgmtv3.ProjectConditionMonitoringEnabled.Message(project, "")
	}

	return nil
}

func (ph *projectHandler) ensureAppProjectName(appTargetNamespace string, project *mgmtv3.Project) (string, error) {
	appProjectName, err := monitoring.EnsureAppProjectName(ph.app.agentNamespaceClient, project.Name, project.Spec.ClusterName, appTargetNamespace)
	if err != nil {
		return "", err
	}

	return appProjectName, nil
}

func (ph *projectHandler) deployApp(appName, appTargetNamespace string, appProjectName string, project *mgmtv3.Project, clusterName string) error {
	appDeployProjectID := project.Name
	clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort := monitoring.ClusterPrometheusEndpoint()
	clusterAlertManagerSvcName, clusterAlertManagerSvcNamespaces, clusterAlertManagerPort := monitoring.ClusterAlertManagerEndpoint()

	optionalAppAnswers := map[string]string{
		"grafana.persistence.enabled": "false",

		"prometheus.persistence.enabled": "false",

		"prometheus.sync.mode": "remote",
	}

	mustAppAnswers := map[string]string{
		"enabled": "false",

		"exporter-coredns.enabled": "false",

		"exporter-kube-controller-manager.enabled": "false",

		"exporter-kube-dns.enabled": "false",

		"exporter-kube-scheduler.enabled": "false",

		"exporter-kube-state.enabled": "false",

		"exporter-kubelets.enabled": "false",

		"exporter-kubernetes.enabled": "false",

		"exporter-node.enabled": "false",

		"exporter-fluentd.enabled": "false",

		"grafana.enabled":            "true",
		"grafana.level":              "project",
		"grafana.apiGroup":           monitoring.APIVersion.Group,
		"grafana.serviceAccountName": appName,

		"prometheus.enabled":                          "true",
		"prometheus.externalLabels.prometheus_from":   clusterName,
		"prometheus.level":                            "project",
		"prometheus.apiGroup":                         monitoring.APIVersion.Group,
		"prometheus.serviceAccountNameOverride":       appName,
		"prometheus.additionalBindingClusterRoles[0]": fmt.Sprintf("%s-namespaces-readonly", appDeployProjectID),
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].targets[0]":          fmt.Sprintf("%s.%s:%s", clusterAlertManagerSvcName, clusterAlertManagerSvcNamespaces, clusterAlertManagerPort),
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.level":        "project",
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.project_id":   project.Name,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.project_name": project.Spec.DisplayName,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.cluster_id":   project.Spec.ClusterName,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.cluster_name": clusterName,
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].key":                nslabels.ProjectIDFieldLabel,
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].operator":           "In",
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].values[0]":          appDeployProjectID,
		"prometheus.ruleNamespaceSelector.matchExpressions[0].key":                          nslabels.ProjectIDFieldLabel,
		"prometheus.ruleNamespaceSelector.matchExpressions[0].operator":                     "In",
		"prometheus.ruleNamespaceSelector.matchExpressions[0].values[0]":                    appDeployProjectID,
		"prometheus.ruleSelector.matchExpressions[0].key":                                   monitoring.CattlePrometheusRuleLabelKey,
		"prometheus.ruleSelector.matchExpressions[0].operator":                              "In",
		"prometheus.ruleSelector.matchExpressions[0].values[0]":                             monitoring.CattleAlertingPrometheusRuleLabelValue,
	}

	appAnswers := monitoring.OverwriteAppAnswers(optionalAppAnswers, project.Annotations)

	// cannot overwrite mustAppAnswers
	for mustKey, mustVal := range mustAppAnswers {
		appAnswers[mustKey] = mustVal
	}

	// complete sync target & path
	if appAnswers["prometheus.sync.mode"] == "federate" {
		appAnswers["prometheus.sync.target"] = fmt.Sprintf("%s.%s:%s", clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort)
		appAnswers["prometheus.sync.path"] = "/federate"
	} else {
		appAnswers["prometheus.sync.target"] = fmt.Sprintf("http://%s.%s:%s", clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort)
		appAnswers["prometheus.sync.path"] = "/api/v1/read"
	}

	appCatalogID := settings.SystemMonitoringCatalogID.Get()
	app := &v3.App{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: monitoring.CopyCreatorID(nil, project.Annotations),
			Labels:      monitoring.OwnedLabels(appName, appTargetNamespace, appProjectName, monitoring.ProjectLevel),
			Name:        appName,
			Namespace:   appDeployProjectID,
		},
		Spec: v3.AppSpec{
			Answers:         appAnswers,
			Description:     "Rancher Project Monitoring",
			ExternalID:      appCatalogID,
			ProjectName:     appProjectName,
			TargetNamespace: appTargetNamespace,
		},
	}

	err := monitoring.DeployApp(ph.app.cattleAppClient, appDeployProjectID, app)
	if err != nil {
		return err
	}

	return nil
}

func (ph *projectHandler) detectAppComponentsWhileInstall(appName, appTargetNamespace string, project *mgmtv3.Project) error {
	if project.Status.MonitoringStatus == nil {
		project.Status.MonitoringStatus = &mgmtv3.MonitoringStatus{
			Conditions: []mgmtv3.MonitoringCondition{
				{Type: mgmtv3.ClusterConditionType(ConditionPrometheusDeployed), Status: k8scorev1.ConditionFalse},
				{Type: mgmtv3.ClusterConditionType(ConditionGrafanaDeployed), Status: k8scorev1.ConditionFalse},
			},
		}
	}
	monitoringStatus := project.Status.MonitoringStatus

	checkers := make([]func() error, 0, len(monitoringStatus.Conditions))
	if !ConditionGrafanaDeployed.IsTrue(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isGrafanaDeployed(ph.app.agentDeploymentClient, appTargetNamespace, appName, monitoringStatus, project.Spec.ClusterName)
		})
	}
	if !ConditionPrometheusDeployed.IsTrue(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isPrometheusDeployed(ph.app.agentStatefulSetClient, appTargetNamespace, appName, monitoringStatus)
		})
	}

	if len(checkers) == 0 {
		return nil
	}

	err := stream(checkers...)
	if err != nil {
		time.Sleep(5 * time.Second)
	}

	return err
}

func (ph *projectHandler) detectAppComponentsWhileUninstall(appName, appTargetNamespace string, project *mgmtv3.Project) error {
	if project.Status.MonitoringStatus == nil {
		return nil
	}
	monitoringStatus := project.Status.MonitoringStatus

	checkers := make([]func() error, 0, len(monitoringStatus.Conditions))
	if !ConditionGrafanaDeployed.IsFalse(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isGrafanaWithdrew(ph.app.agentDeploymentClient, appTargetNamespace, appName, monitoringStatus)
		})
	}
	if !ConditionPrometheusDeployed.IsFalse(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isPrometheusWithdrew(ph.app.agentStatefulSetClient, appTargetNamespace, appName, monitoringStatus)
		})
	}

	if len(checkers) == 0 {
		return nil
	}

	err := stream(checkers...)
	if err != nil {
		time.Sleep(5 * time.Second)
	}

	return err
}

func getProjectTag(project *mgmtv3.Project) string {
	return fmt.Sprintf("%s(%s)_%s", project.Name, project.Spec.DisplayName, project.Spec.ClusterName)
}
<commit_msg>Don't detect cluster-level enabling or not when enabling project-level Monitoring<commit_after>package monitoring

import (
	"fmt"
	"reflect"
	"time"

	"github.com/pkg/errors"
	"github.com/rancher/rancher/pkg/controllers/user/nslabels"
	"github.com/rancher/rancher/pkg/monitoring"
	"github.com/rancher/rancher/pkg/settings"
	mgmtv3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/apis/project.cattle.io/v3"
	k8scorev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

type projectHandler struct {
	clusterName         string
	cattleClusterClient mgmtv3.ClusterInterface
	cattleProjectClient mgmtv3.ProjectInterface
	app                 *appHandler
}

func (ph *projectHandler) sync(key string, project *mgmtv3.Project) (runtime.Object, error) {
	if project == nil || project.DeletionTimestamp != nil ||
		project.Spec.ClusterName != ph.clusterName {
		return project, nil
	}

	clusterID := project.Spec.ClusterName
	cluster, err := ph.cattleClusterClient.Get(clusterID, metav1.GetOptions{})
	if err != nil {
		return project, errors.Wrapf(err, "failed to find Cluster %s", clusterID)
	}

	clusterName := cluster.Spec.DisplayName
	projectTag := getProjectTag(project, clusterName)
	src := project
	cpy := src.DeepCopy()

	err = ph.doSync(cpy, clusterName)
	if !reflect.DeepEqual(cpy, src) {
		updated, updateErr := ph.cattleProjectClient.Update(cpy)
		if updateErr != nil {
			return project, errors.Wrapf(updateErr, "failed to update Project %s", projectTag)
		}

		cpy = updated
	}

	if err != nil {
		err = errors.Wrapf(err, "unable to sync Project %s", projectTag)
	}

	return cpy, err
}

func (ph *projectHandler) doSync(project *mgmtv3.Project, clusterName string) error {
	if !mgmtv3.NamespaceBackedResource.IsTrue(project) && !mgmtv3.ProjectConditionInitialRolesPopulated.IsTrue(project) {
		return nil
	}
	_, err := mgmtv3.ProjectConditionMetricExpressionDeployed.DoUntilTrue(project, func() (runtime.Object, error) {
		projectName := fmt.Sprintf("%s:%s", project.Spec.ClusterName, project.Name)

		for _, graph := range preDefinedProjectGraph {
			newObj := graph.DeepCopy()
			newObj.Namespace = project.Name
			newObj.Spec.ProjectName = projectName
			if _, err := ph.app.cattleProjectGraphClient.Create(newObj); err != nil && !apierrors.IsAlreadyExists(err) {
				return project, err
			}
		}

		return project, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to apply metric expression")
	}

	appName, appTargetNamespace := monitoring.ProjectMonitoringInfo(project.Name)

	if project.Spec.EnableProjectMonitoring {
		appProjectName, err := ph.ensureAppProjectName(appTargetNamespace, project)
		if err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to ensure monitoring project name")
		}

		if err := ph.deployApp(appName, appTargetNamespace, appProjectName, project, clusterName); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to deploy monitoring")
		}

		if err := ph.detectAppComponentsWhileInstall(appName, appTargetNamespace, project); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to detect the installation status of monitoring components")
		}

		mgmtv3.ProjectConditionMonitoringEnabled.True(project)
		mgmtv3.ProjectConditionMonitoringEnabled.Message(project, "")
	} else if project.Status.MonitoringStatus != nil {
		if err := ph.app.withdrawApp(project.Spec.ClusterName, appName, appTargetNamespace); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to withdraw monitoring")
		}

		if err := ph.detectAppComponentsWhileUninstall(appName, appTargetNamespace, project); err != nil {
			mgmtv3.ProjectConditionMonitoringEnabled.Unknown(project)
			mgmtv3.ProjectConditionMonitoringEnabled.Message(project, err.Error())
			return errors.Wrap(err, "failed to detect the uninstallation status of monitoring components")
		}

		mgmtv3.ProjectConditionMonitoringEnabled.False(project)
		mgmtv3.ProjectConditionMonitoringEnabled.Message(project, "")
	}

	return nil
}

func (ph *projectHandler) ensureAppProjectName(appTargetNamespace string, project *mgmtv3.Project) (string, error) {
	appProjectName, err := monitoring.EnsureAppProjectName(ph.app.agentNamespaceClient, project.Name, project.Spec.ClusterName, appTargetNamespace)
	if err != nil {
		return "", err
	}

	return appProjectName, nil
}

func (ph *projectHandler) deployApp(appName, appTargetNamespace string, appProjectName string, project *mgmtv3.Project, clusterName string) error {
	appDeployProjectID := project.Name
	clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort := monitoring.ClusterPrometheusEndpoint()
	clusterAlertManagerSvcName, clusterAlertManagerSvcNamespaces, clusterAlertManagerPort := monitoring.ClusterAlertManagerEndpoint()

	optionalAppAnswers := map[string]string{
		"grafana.persistence.enabled": "false",

		"prometheus.persistence.enabled": "false",

		"prometheus.sync.mode": "federate",
	}

	mustAppAnswers := map[string]string{
		"enabled": "false",

		"exporter-coredns.enabled": "false",

		"exporter-kube-controller-manager.enabled": "false",

		"exporter-kube-dns.enabled": "false",

		"exporter-kube-scheduler.enabled": "false",

		"exporter-kube-state.enabled": "false",

		"exporter-kubelets.enabled": "false",

		"exporter-kubernetes.enabled": "false",

		"exporter-node.enabled": "false",

		"exporter-fluentd.enabled": "false",

		"grafana.enabled":            "true",
		"grafana.level":              "project",
		"grafana.apiGroup":           monitoring.APIVersion.Group,
		"grafana.serviceAccountName": appName,

		"prometheus.enabled":                          "true",
		"prometheus.externalLabels.prometheus_from":   clusterName,
		"prometheus.level":                            "project",
		"prometheus.apiGroup":                         monitoring.APIVersion.Group,
		"prometheus.serviceAccountNameOverride":       appName,
		"prometheus.additionalBindingClusterRoles[0]": fmt.Sprintf("%s-namespaces-readonly", appDeployProjectID),
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].targets[0]":          fmt.Sprintf("%s.%s:%s", clusterAlertManagerSvcName, clusterAlertManagerSvcNamespaces, clusterAlertManagerPort),
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.level":        "project",
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.project_id":   project.Name,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.project_name": project.Spec.DisplayName,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.cluster_id":   project.Spec.ClusterName,
		"prometheus.additionalAlertManagerConfigs[0].static_configs[0].labels.cluster_name": clusterName,
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].key":                nslabels.ProjectIDFieldLabel,
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].operator":           "In",
		"prometheus.serviceMonitorNamespaceSelector.matchExpressions[0].values[0]":          appDeployProjectID,
		"prometheus.ruleNamespaceSelector.matchExpressions[0].key":                          nslabels.ProjectIDFieldLabel,
		"prometheus.ruleNamespaceSelector.matchExpressions[0].operator":                     "In",
		"prometheus.ruleNamespaceSelector.matchExpressions[0].values[0]":                    appDeployProjectID,
		"prometheus.ruleSelector.matchExpressions[0].key":                                   monitoring.CattlePrometheusRuleLabelKey,
		"prometheus.ruleSelector.matchExpressions[0].operator":                              "In",
		"prometheus.ruleSelector.matchExpressions[0].values[0]":                             monitoring.CattleAlertingPrometheusRuleLabelValue,
	}

	appAnswers := monitoring.OverwriteAppAnswers(optionalAppAnswers, project.Annotations)

	// cannot overwrite mustAppAnswers
	for mustKey, mustVal := range mustAppAnswers {
		appAnswers[mustKey] = mustVal
	}

	// complete sync target & path
	if appAnswers["prometheus.sync.mode"] == "federate" {
		appAnswers["prometheus.sync.target"] = fmt.Sprintf("%s.%s:%s", clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort)
		appAnswers["prometheus.sync.path"] = "/federate"
	} else {
		appAnswers["prometheus.sync.target"] = fmt.Sprintf("http://%s.%s:%s", clusterPrometheusSvcName, clusterPrometheusSvcNamespaces, clusterPrometheusPort)
		appAnswers["prometheus.sync.path"] = "/api/v1/read"
	}

	appCatalogID := settings.SystemMonitoringCatalogID.Get()
	app := &v3.App{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: monitoring.CopyCreatorID(nil, project.Annotations),
			Labels:      monitoring.OwnedLabels(appName, appTargetNamespace, appProjectName, monitoring.ProjectLevel),
			Name:        appName,
			Namespace:   appDeployProjectID,
		},
		Spec: v3.AppSpec{
			Answers:         appAnswers,
			Description:     "Rancher Project Monitoring",
			ExternalID:      appCatalogID,
			ProjectName:     appProjectName,
			TargetNamespace: appTargetNamespace,
		},
	}

	err := monitoring.DeployApp(ph.app.cattleAppClient, appDeployProjectID, app)
	if err != nil {
		return err
	}

	return nil
}

func (ph *projectHandler) detectAppComponentsWhileInstall(appName, appTargetNamespace string, project *mgmtv3.Project) error {
	if project.Status.MonitoringStatus == nil {
		project.Status.MonitoringStatus = &mgmtv3.MonitoringStatus{
			Conditions: []mgmtv3.MonitoringCondition{
				{Type: mgmtv3.ClusterConditionType(ConditionPrometheusDeployed), Status: k8scorev1.ConditionFalse},
				{Type: mgmtv3.ClusterConditionType(ConditionGrafanaDeployed), Status: k8scorev1.ConditionFalse},
			},
		}
	}
	monitoringStatus := project.Status.MonitoringStatus

	checkers := make([]func() error, 0, len(monitoringStatus.Conditions))
	if !ConditionGrafanaDeployed.IsTrue(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isGrafanaDeployed(ph.app.agentDeploymentClient, appTargetNamespace, appName, monitoringStatus, project.Spec.ClusterName)
		})
	}
	if !ConditionPrometheusDeployed.IsTrue(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isPrometheusDeployed(ph.app.agentStatefulSetClient, appTargetNamespace, appName, monitoringStatus)
		})
	}

	if len(checkers) == 0 {
		return nil
	}

	err := stream(checkers...)
	if err != nil {
		time.Sleep(5 * time.Second)
	}

	return err
}

func (ph *projectHandler) detectAppComponentsWhileUninstall(appName, appTargetNamespace string, project *mgmtv3.Project) error {
	if project.Status.MonitoringStatus == nil {
		return nil
	}
	monitoringStatus := project.Status.MonitoringStatus

	checkers := make([]func() error, 0, len(monitoringStatus.Conditions))
	if !ConditionGrafanaDeployed.IsFalse(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isGrafanaWithdrew(ph.app.agentDeploymentClient, appTargetNamespace, appName, monitoringStatus)
		})
	}
	if !ConditionPrometheusDeployed.IsFalse(monitoringStatus) {
		checkers = append(checkers, func() error {
			return isPrometheusWithdrew(ph.app.agentStatefulSetClient, appTargetNamespace, appName, monitoringStatus)
		})
	}

	if len(checkers) == 0 {
		return nil
	}

	err := stream(checkers...)
	if err != nil {
		time.Sleep(5 * time.Second)
	}

	return err
}

func getProjectTag(project *mgmtv3.Project, clusterName string) string {
	return fmt.Sprintf("%s(%s) of Cluster %s(%s)", project.Name, project.Spec.DisplayName, project.Spec.ClusterName, clusterName)
}
<|endoftext|>


<commit_before>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/core/invocation.go                                   |
|                                                          |
| LastModified: Feb 20, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package core

import (
	"context"
	"reflect"
	"strings"
)

type invocation struct {
	client    *Client
	namespace string
}

func (i invocation) Invoke(proxy interface{}, method reflect.StructField, name string, args []interface{}) (results []interface{}, err error) {
	var clientContext *ClientContext
	var ctx context.Context
	if len(args) > 0 {
		switch c := args[0].(type) {
		case *ClientContext:
			clientContext = c
			args = args[1:]
		case *rpcContext:
			clientContext = &ClientContext{Context: c}
			args = args[1:]
		case context.Context:
			ctx = c
			clientContext = GetClientContext(c)
			args = args[1:]
		}
	}
	if ctx == nil {
		ctx = context.Background()
	}
	if clientContext == nil {
		clientContext = NewClientContext()
	}
	if GetClientContext(ctx) == nil {
		ctx = WithContext(ctx, clientContext)
	}
	tagParser := parseTag(clientContext, method.Tag)
	if tagParser.Name != "" {
		name = tagParser.Name
	}
	name = strings.ReplaceAll(name, ".", "_")
	if i.namespace != "" {
		name = i.namespace + "_" + name
	}
	t := method.Type
	n := t.NumOut()
	clientContext.ReturnType = make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		clientContext.ReturnType = append(clientContext.ReturnType, t.Out(i))
	}
	if n > 0 && clientContext.ReturnType[n-1] == errorType {
		clientContext.ReturnType = clientContext.ReturnType[:n-1]
	}
	return i.client.InvokeContext(ctx, name, args)
}
<commit_msg>Fixed invocation on go 1.11<commit_after>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/core/invocation.go                                   |
|                                                          |
| LastModified: Feb 22, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package core

import (
	"context"
	"reflect"
	"strings"
)

type invocation struct {
	client    *Client
	namespace string
}

func (i invocation) Invoke(proxy interface{}, method reflect.StructField, name string, args []interface{}) (results []interface{}, err error) {
	var clientContext *ClientContext
	var ctx context.Context
	if len(args) > 0 {
		switch c := args[0].(type) {
		case *ClientContext:
			clientContext = c
			args = args[1:]
		case *rpcContext:
			clientContext = &ClientContext{Context: c}
			args = args[1:]
		case context.Context:
			ctx = c
			clientContext = GetClientContext(c)
			args = args[1:]
		}
	}
	if ctx == nil {
		ctx = context.Background()
	}
	if clientContext == nil {
		clientContext = NewClientContext()
	}
	if GetClientContext(ctx) == nil {
		ctx = WithContext(ctx, clientContext)
	}
	tagParser := parseTag(clientContext, method.Tag)
	if tagParser.Name != "" {
		name = tagParser.Name
	}
	name = strings.Replace(name, ".", "_", -1) //nolint:gocritic
	if i.namespace != "" {
		name = i.namespace + "_" + name
	}
	t := method.Type
	n := t.NumOut()
	clientContext.ReturnType = make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		clientContext.ReturnType = append(clientContext.ReturnType, t.Out(i))
	}
	if n > 0 && clientContext.ReturnType[n-1] == errorType {
		clientContext.ReturnType = clientContext.ReturnType[:n-1]
	}
	return i.client.InvokeContext(ctx, name, args)
}
<|endoftext|>


<commit_before>// Copyright 2015 The rkt 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 (
	"os"
	"strings"
	"text/template"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/pflag"
	"github.com/coreos/rkt/version"
)

var (
	commandUsageTemplate *template.Template
	templFuncs           = template.FuncMap{
		"descToLines": func(s string) []string {
			// trim leading/trailing whitespace and split into slice of lines
			return strings.Split(strings.Trim(s, "\n\t "), "\n")
		},
		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
			parts := []string{cmd.Name()}
			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
				cmd = cmd.Parent()
				parts = append([]string{cmd.Name()}, parts...)
			}
			return strings.Join(parts, " ")
		},
	}
)

func init() {
	commandUsage := `
{{ $cmd := .Cmd }}\
{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
NAME:
{{ if not .Cmd.HasParent }}\
{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
{{else}}\
{{printf "\t%s - %s" $cmdname .Cmd.Short}}
{{end}}\

USAGE:
{{printf "\t%s" .Cmd.UseLine}}
{{ if not .Cmd.HasParent }}\

VERSION:
{{printf "\t%s" .Version}}
{{end}}\
{{if .Cmd.HasSubCommands}}\

COMMANDS:
{{range .SubCommands}}\
{{ $cmdname := cmdName . $cmd }}\
{{ if .Runnable }}\
{{printf "\t%s\t%s" $cmdname .Short}}
{{end}}\
{{end}}\
{{end}}\
{{ if .Cmd.Long }}\

DESCRIPTION:
{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
{{end}}\
{{end}}\
{{if .Cmd.HasLocalFlags}}\

OPTIONS:
{{.Cmd.LocalFlags.FlagUsages}}\
{{end}}\
{{if .Cmd.HasInheritedFlags}}\

GLOBAL OPTIONS:
{{.Cmd.InheritedFlags.FlagUsages}}\
{{end}}
`[1:]

	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
}

func getSubCommands(cmd *cobra.Command) []*cobra.Command {
	subCommands := []*cobra.Command{}
	for _, subCmd := range cmd.Commands() {
		subCommands = append(subCommands, subCmd)
		subCommands = append(subCommands, getSubCommands(subCmd)...)
	}
	return subCommands
}

func usageFunc(cmd *cobra.Command) error {
	subCommands := getSubCommands(cmd)
	tabOut := getTabOutWithWriter(os.Stdout)
	commandUsageTemplate.Execute(tabOut, struct {
		Executable  string
		Cmd         *cobra.Command
		CmdFlags    *pflag.FlagSet
		SubCommands []*cobra.Command
		Version     string
	}{
		cliName,
		cmd,
		cmd.Flags(),
		subCommands,
		version.Version,
	})
	tabOut.Flush()
	return nil
}
<commit_msg>stage0: make options output nicer<commit_after>// Copyright 2015 The rkt 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 (
	"bytes"
	"fmt"
	"os"
	"strings"
	"text/template"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/pflag"
	"github.com/coreos/rkt/version"
)

var (
	commandUsageTemplate *template.Template
	templFuncs           = template.FuncMap{
		"descToLines": func(s string) []string {
			// trim leading/trailing whitespace and split into slice of lines
			return strings.Split(strings.Trim(s, "\n\t "), "\n")
		},
		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
			parts := []string{cmd.Name()}
			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
				cmd = cmd.Parent()
				parts = append([]string{cmd.Name()}, parts...)
			}
			return strings.Join(parts, " ")
		},
	}
)

func init() {
	commandUsage := `
{{ $cmd := .Cmd }}\
{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
NAME:
{{ if not .Cmd.HasParent }}\
{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
{{else}}\
{{printf "\t%s - %s" $cmdname .Cmd.Short}}
{{end}}\

USAGE:
{{printf "\t%s" .Cmd.UseLine}}
{{ if not .Cmd.HasParent }}\

VERSION:
{{printf "\t%s" .Version}}
{{end}}\
{{if .Cmd.HasSubCommands}}\

COMMANDS:
{{range .SubCommands}}\
{{ $cmdname := cmdName . $cmd }}\
{{ if .Runnable }}\
{{printf "\t%s\t%s" $cmdname .Short}}
{{end}}\
{{end}}\
{{end}}\
{{ if .Cmd.Long }}\

DESCRIPTION:
{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
{{end}}\
{{end}}\
{{if .Cmd.HasLocalFlags}}\

OPTIONS:
{{.LocalFlags}}\
{{end}}\
{{if .Cmd.HasInheritedFlags}}\

GLOBAL OPTIONS:
{{.GlobalFlags}}\
{{end}}
`[1:]

	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
}

func rktFlagUsages(flagSet *pflag.FlagSet) string {
	x := new(bytes.Buffer)

	flagSet.VisitAll(func(flag *pflag.Flag) {
		if len(flag.Deprecated) > 0 {
			return
		}
		format := ""
		if len(flag.Shorthand) > 0 {
			format = "  -%s, --%s"
		} else {
			format = "   %s   --%s"
		}
		if len(flag.NoOptDefVal) > 0 {
			format = format + "["
		}
		if flag.Value.Type() == "string" {
			// put quotes on the value
			format = format + "=%q"
		} else {
			format = format + "=%s"
		}
		if len(flag.NoOptDefVal) > 0 {
			format = format + "]"
		}
		format = format + "\t%s\n"
		shorthand := flag.Shorthand
		fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
	})

	return x.String()
}

func getSubCommands(cmd *cobra.Command) []*cobra.Command {
	subCommands := []*cobra.Command{}
	for _, subCmd := range cmd.Commands() {
		subCommands = append(subCommands, subCmd)
		subCommands = append(subCommands, getSubCommands(subCmd)...)
	}
	return subCommands
}

func usageFunc(cmd *cobra.Command) error {
	subCommands := getSubCommands(cmd)
	tabOut := getTabOutWithWriter(os.Stdout)
	commandUsageTemplate.Execute(tabOut, struct {
		Executable  string
		Cmd         *cobra.Command
		CmdFlags    *pflag.FlagSet
		LocalFlags  string
		GlobalFlags string
		SubCommands []*cobra.Command
		Version     string
	}{
		cliName,
		cmd,
		cmd.Flags(),
		rktFlagUsages(cmd.LocalFlags()),
		rktFlagUsages(cmd.InheritedFlags()),
		subCommands,
		version.Version,
	})
	tabOut.Flush()
	return nil
}
<|endoftext|>


<commit_before>package logwriter_test

import (
	"database/sql"

	testdb "github.com/erikstmartin/go-testdb"

	"io/ioutil"
	"os"

	"github.com/cloudfoundry-incubator/galera-healthcheck/cluster-health-logger/logwriter"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var (
	logFile *os.File
	err     error
)

var _ = Describe("Cluster Health Logger", func() {

	BeforeEach(func() {
		logFile, err = ioutil.TempFile(os.TempDir(), "cluster-health-logger")
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		err = os.Remove(logFile.Name())
		Expect(err).ToNot(HaveOccurred())
	})

	Context("when the log file does not exist", func() {
		BeforeEach(func() {
			err = os.Remove(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
		})

		It("writes headers to the file", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts := "happy-time"
			logWriter.Write(ts)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("timestamp,a,b,c,d,e,f,g,h,i\nhappy-time,1,2,3,4,5,6,7,8,9\n"))
		})
	})

	Context("when the log file exists", func() {

		It("writes only the rows to the file", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts := "happy-time"
			logWriter.Write(ts)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("happy-time,1,2,3,4,5,6,7,8,9\n"))
		})

		It("writes a new line", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts1 := "happy-time"
			logWriter.Write(ts1)
			ts2 := "sad-time"
			logWriter.Write(ts2)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("happy-time,1,2,3,4,5,6,7,8,9\nsad-time,1,2,3,4,5,6,7,8,9\n"))
		})

	})
})

func logWriterTestHelper(filePath string) logwriter.LogWriter {
	db, err := sql.Open("testdb", "")
	Expect(err).ToNot(HaveOccurred())

	sql := "SHOW STATUS WHERE Variable_name IN ('wsrep_ready','wsrep_cluster_conf_id','wsrep_cluster_status','wsrep_connected','wsrep_local_state_comment','wsrep_local_recv_queue_avg','wsrep_flow_control_paused','wsrep_cert_deps_distance','wsrep_local_send_queue_avg')"
	columns := []string{"Variable_name", "Value"}
	result := "a,1\nb,2\nc,3\nd,4\ne,5\nf,6\ng,7\nh,8\ni,9"
	testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result))

	return logwriter.New(db, filePath)
}
<commit_msg>Format cluster-health stubbed query for readability<commit_after>package logwriter_test

import (
	"database/sql"

	testdb "github.com/erikstmartin/go-testdb"

	"io/ioutil"
	"os"

	"github.com/cloudfoundry-incubator/galera-healthcheck/cluster-health-logger/logwriter"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var (
	logFile *os.File
	err     error
)

var _ = Describe("Cluster Health Logger", func() {

	BeforeEach(func() {
		logFile, err = ioutil.TempFile(os.TempDir(), "cluster-health-logger")
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		err = os.Remove(logFile.Name())
		Expect(err).ToNot(HaveOccurred())
	})

	Context("when the log file does not exist", func() {
		BeforeEach(func() {
			err = os.Remove(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
		})

		It("writes headers to the file", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts := "happy-time"
			logWriter.Write(ts)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("timestamp,a,b,c,d,e,f,g,h,i\nhappy-time,1,2,3,4,5,6,7,8,9\n"))
		})
	})

	Context("when the log file exists", func() {

		It("writes only the rows to the file", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts := "happy-time"
			logWriter.Write(ts)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("happy-time,1,2,3,4,5,6,7,8,9\n"))
		})

		It("writes a new line", func() {
			logWriter := logWriterTestHelper(logFile.Name())
			ts1 := "happy-time"
			logWriter.Write(ts1)
			ts2 := "sad-time"
			logWriter.Write(ts2)
			contents, err := ioutil.ReadFile(logFile.Name())
			Expect(err).ToNot(HaveOccurred())
			contentsStr := string(contents)
			Expect(contentsStr).To(Equal("happy-time,1,2,3,4,5,6,7,8,9\nsad-time,1,2,3,4,5,6,7,8,9\n"))
		})

	})
})

func logWriterTestHelper(filePath string) logwriter.LogWriter {
	db, err := sql.Open("testdb", "")
	Expect(err).ToNot(HaveOccurred())

	sql := `
		SHOW STATUS
		WHERE Variable_name IN (
			'wsrep_ready',
			'wsrep_cluster_conf_id',
			'wsrep_cluster_status',
			'wsrep_connected',
			'wsrep_local_state_comment',
			'wsrep_local_recv_queue_avg',
			'wsrep_flow_control_paused',
			'wsrep_cert_deps_distance',
			'wsrep_local_send_queue_avg'
		)`
	columns := []string{"Variable_name", "Value"}
	result := "a,1\nb,2\nc,3\nd,4\ne,5\nf,6\ng,7\nh,8\ni,9"
	testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result))

	return logwriter.New(db, filePath)
}
<|endoftext|>


<commit_before>// Copyright 2013 The go-github 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 github

import (
	"fmt"
	"time"
)

// PullRequestsService handles communication with the pull request related
// methods of the GitHub API.
//
// GitHub API docs: http://developer.github.com/v3/pulls/
type PullRequestsService struct {
	client *Client
}

// PullRequest represents a GitHub pull request on a repository.
type PullRequest struct {
	Number       *int       `json:"number,omitempty"`
	State        *string    `json:"state,omitempty"`
	Title        *string    `json:"title,omitempty"`
	Body         *string    `json:"body,omitempty"`
	CreatedAt    *time.Time `json:"created_at,omitempty"`
	UpdatedAt    *time.Time `json:"updated_at,omitempty"`
	ClosedAt     *time.Time `json:"closed_at,omitempty"`
	MergedAt     *time.Time `json:"merged_at,omitempty"`
	User         *User      `json:"user,omitempty"`
	Merged       *bool      `json:"merged,omitempty"`
	Mergeable    *bool      `json:"mergeable,omitempty"`
	MergedBy     *User      `json:"merged_by,omitempty"`
	Comments     *int       `json:"comments,omitempty"`
	Commits      *int       `json:"commits,omitempty"`
	Additions    *int       `json:"additions,omitempty"`
	Deletions    *int       `json:"deletions,omitempty"`
	ChangedFiles *int       `json:"changed_files,omitempty"`
	URL          *string    `json:"url,omitempty"`
	HTMLURL      *string    `json:"html_url,omitempty"`
	IssueURL     *string    `json:"issue_url,omitempty"`
	StatusesURL  *string    `json:"statuses_url,omitempty"`
	DiffURL      *string    `json:"diff_url,omitempty"`
	PatchURL     *string    `json:"patch_url,omitempty"`

	Head *PullRequestBranch `json:"head,omitempty"`
	Base *PullRequestBranch `json:"base,omitempty"`
}

func (p PullRequest) String() string {
	return Stringify(p)
}

// PullRequestBranch represents a base or head branch in a GitHub pull request.
type PullRequestBranch struct {
	Label *string     `json:"label,omitempty"`
	Ref   *string     `json:"ref,omitempty"`
	SHA   *string     `json:"sha,omitempty"`
	Repo  *Repository `json:"repo,omitempty"`
	User  *User       `json:"user,omitempty"`
}

// PullRequestListOptions specifies the optional parameters to the
// PullRequestsService.List method.
type PullRequestListOptions struct {
	// State filters pull requests based on their state.  Possible values are:
	// open, closed.  Default is "open".
	State string `url:"state,omitempty"`

	// Head filters pull requests by head user and branch name in the format of:
	// "user:ref-name".
	Head string `url:"head,omitempty"`

	// Base filters pull requests by base branch name.
	Base string `url:"base,omitempty"`

	// Sort specifies how to sort pull requests. Possible values are: created,
	// updated, popularity, long-running. Default is "created".
	Sort string `url:"sort,omitempty"`

	// Direction in which to sort pull requests. Possible values are: asc, desc.
	// If Sort is "created" or not specified, Default is "desc", otherwise Default
	// is "asc"
	Direction string `url:"direction,omitempty"`

	ListOptions
}

// List the pull requests for the specified repository.
//
// GitHub API docs: http://developer.github.com/v3/pulls/#list-pull-requests
func (s *PullRequestsService) List(owner string, repo string, opt *PullRequestListOptions) ([]PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	pulls := new([]PullRequest)
	resp, err := s.client.Do(req, pulls)
	if err != nil {
		return nil, resp, err
	}

	return *pulls, resp, err
}

// Get a single pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request
func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	pull := new(PullRequest)
	resp, err := s.client.Do(req, pull)
	if err != nil {
		return nil, resp, err
	}

	return pull, resp, err
}

// NewPullRequest represents a new pull request to be created.
type NewPullRequest struct {
	Title *string `json:"title,omitempty"`
	Head  *string `json:"head,omitempty"`
	Base  *string `json:"base,omitempty"`
	Body  *string `json:"body,omitempty"`
	Issue *int    `json:"issue,omitempty"`
}

// Create a new pull request on the specified repository.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request
func (s *PullRequestsService) Create(owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
	req, err := s.client.NewRequest("POST", u, pull)
	if err != nil {
		return nil, nil, err
	}

	p := new(PullRequest)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// Edit a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request
func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
	req, err := s.client.NewRequest("PATCH", u, pull)
	if err != nil {
		return nil, nil, err
	}

	p := new(PullRequest)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// ListCommits lists the commits in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) ([]RepositoryCommit, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	commits := new([]RepositoryCommit)
	resp, err := s.client.Do(req, commits)
	if err != nil {
		return nil, resp, err
	}

	return *commits, resp, err
}

// ListFiles lists the files in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files
func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) ([]CommitFile, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	commitFiles := new([]CommitFile)
	resp, err := s.client.Do(req, commitFiles)
	if err != nil {
		return nil, resp, err
	}

	return *commitFiles, resp, err
}

// IsMerged checks if a pull request has been merged.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged
func (s *PullRequestsService) IsMerged(owner string, repo string, number int) (bool, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return false, nil, err
	}

	resp, err := s.client.Do(req, nil)
	merged, err := parseBoolResponse(err)
	return merged, resp, err
}

// PullRequestMergeResult represents the result of merging a pull request.
type PullRequestMergeResult struct {
	SHA     *string `json:"sha,omitempty"`
	Merged  *bool   `json:"merged,omitempty"`
	Message *string `json:"message,omitempty"`
}

type pullRequestMergeRequest struct {
	CommitMessage *string `json:"commit_message"`
}

// Merge a pull request (Merge Button™).
//
// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade
func (s *PullRequestsService) Merge(owner string, repo string, number int, commitMessage string) (*PullRequestMergeResult, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)

	req, err := s.client.NewRequest("PUT", u, &pullRequestMergeRequest{
		CommitMessage: &commitMessage,
	})

	if err != nil {
		return nil, nil, err
	}

	mergeResult := new(PullRequestMergeResult)
	resp, err := s.client.Do(req, mergeResult)
	if err != nil {
		return nil, resp, err
	}

	return mergeResult, resp, err
}
<commit_msg>Add "assignee" field<commit_after>// Copyright 2013 The go-github 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 github

import (
	"fmt"
	"time"
)

// PullRequestsService handles communication with the pull request related
// methods of the GitHub API.
//
// GitHub API docs: http://developer.github.com/v3/pulls/
type PullRequestsService struct {
	client *Client
}

// PullRequest represents a GitHub pull request on a repository.
type PullRequest struct {
	Number       *int       `json:"number,omitempty"`
	State        *string    `json:"state,omitempty"`
	Title        *string    `json:"title,omitempty"`
	Body         *string    `json:"body,omitempty"`
	CreatedAt    *time.Time `json:"created_at,omitempty"`
	UpdatedAt    *time.Time `json:"updated_at,omitempty"`
	ClosedAt     *time.Time `json:"closed_at,omitempty"`
	MergedAt     *time.Time `json:"merged_at,omitempty"`
	User         *User      `json:"user,omitempty"`
	Merged       *bool      `json:"merged,omitempty"`
	Mergeable    *bool      `json:"mergeable,omitempty"`
	MergedBy     *User      `json:"merged_by,omitempty"`
	Comments     *int       `json:"comments,omitempty"`
	Commits      *int       `json:"commits,omitempty"`
	Additions    *int       `json:"additions,omitempty"`
	Deletions    *int       `json:"deletions,omitempty"`
	ChangedFiles *int       `json:"changed_files,omitempty"`
	URL          *string    `json:"url,omitempty"`
	HTMLURL      *string    `json:"html_url,omitempty"`
	IssueURL     *string    `json:"issue_url,omitempty"`
	StatusesURL  *string    `json:"statuses_url,omitempty"`
	DiffURL      *string    `json:"diff_url,omitempty"`
	PatchURL     *string    `json:"patch_url,omitempty"`
	Assignee     *User      `json:"assignee,omitempty"` // probably only in webhooks

	Head *PullRequestBranch `json:"head,omitempty"`
	Base *PullRequestBranch `json:"base,omitempty"`
}

func (p PullRequest) String() string {
	return Stringify(p)
}

// PullRequestBranch represents a base or head branch in a GitHub pull request.
type PullRequestBranch struct {
	Label *string     `json:"label,omitempty"`
	Ref   *string     `json:"ref,omitempty"`
	SHA   *string     `json:"sha,omitempty"`
	Repo  *Repository `json:"repo,omitempty"`
	User  *User       `json:"user,omitempty"`
}

// PullRequestListOptions specifies the optional parameters to the
// PullRequestsService.List method.
type PullRequestListOptions struct {
	// State filters pull requests based on their state.  Possible values are:
	// open, closed.  Default is "open".
	State string `url:"state,omitempty"`

	// Head filters pull requests by head user and branch name in the format of:
	// "user:ref-name".
	Head string `url:"head,omitempty"`

	// Base filters pull requests by base branch name.
	Base string `url:"base,omitempty"`

	// Sort specifies how to sort pull requests. Possible values are: created,
	// updated, popularity, long-running. Default is "created".
	Sort string `url:"sort,omitempty"`

	// Direction in which to sort pull requests. Possible values are: asc, desc.
	// If Sort is "created" or not specified, Default is "desc", otherwise Default
	// is "asc"
	Direction string `url:"direction,omitempty"`

	ListOptions
}

// List the pull requests for the specified repository.
//
// GitHub API docs: http://developer.github.com/v3/pulls/#list-pull-requests
func (s *PullRequestsService) List(owner string, repo string, opt *PullRequestListOptions) ([]PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	pulls := new([]PullRequest)
	resp, err := s.client.Do(req, pulls)
	if err != nil {
		return nil, resp, err
	}

	return *pulls, resp, err
}

// Get a single pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request
func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	pull := new(PullRequest)
	resp, err := s.client.Do(req, pull)
	if err != nil {
		return nil, resp, err
	}

	return pull, resp, err
}

// NewPullRequest represents a new pull request to be created.
type NewPullRequest struct {
	Title *string `json:"title,omitempty"`
	Head  *string `json:"head,omitempty"`
	Base  *string `json:"base,omitempty"`
	Body  *string `json:"body,omitempty"`
	Issue *int    `json:"issue,omitempty"`
}

// Create a new pull request on the specified repository.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request
func (s *PullRequestsService) Create(owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
	req, err := s.client.NewRequest("POST", u, pull)
	if err != nil {
		return nil, nil, err
	}

	p := new(PullRequest)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// Edit a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request
func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
	req, err := s.client.NewRequest("PATCH", u, pull)
	if err != nil {
		return nil, nil, err
	}

	p := new(PullRequest)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// ListCommits lists the commits in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) ([]RepositoryCommit, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	commits := new([]RepositoryCommit)
	resp, err := s.client.Do(req, commits)
	if err != nil {
		return nil, resp, err
	}

	return *commits, resp, err
}

// ListFiles lists the files in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files
func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) ([]CommitFile, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	commitFiles := new([]CommitFile)
	resp, err := s.client.Do(req, commitFiles)
	if err != nil {
		return nil, resp, err
	}

	return *commitFiles, resp, err
}

// IsMerged checks if a pull request has been merged.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged
func (s *PullRequestsService) IsMerged(owner string, repo string, number int) (bool, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return false, nil, err
	}

	resp, err := s.client.Do(req, nil)
	merged, err := parseBoolResponse(err)
	return merged, resp, err
}

// PullRequestMergeResult represents the result of merging a pull request.
type PullRequestMergeResult struct {
	SHA     *string `json:"sha,omitempty"`
	Merged  *bool   `json:"merged,omitempty"`
	Message *string `json:"message,omitempty"`
}

type pullRequestMergeRequest struct {
	CommitMessage *string `json:"commit_message"`
}

// Merge a pull request (Merge Button™).
//
// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade
func (s *PullRequestsService) Merge(owner string, repo string, number int, commitMessage string) (*PullRequestMergeResult, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)

	req, err := s.client.NewRequest("PUT", u, &pullRequestMergeRequest{
		CommitMessage: &commitMessage,
	})

	if err != nil {
		return nil, nil, err
	}

	mergeResult := new(PullRequestMergeResult)
	resp, err := s.client.Do(req, mergeResult)
	if err != nil {
		return nil, resp, err
	}

	return mergeResult, resp, err
}
<|endoftext|>


<commit_before>// See RpcService in interfaces.go for related details.

package raft

type RpcAppendEntries struct {
	// - leader's term
	Term TermNo

	// leaderId

	// - index of log entry immediately preceding new ones
	PrevLogIndex LogIndex

	// - term of prevLogIndex entry
	PrevLogTerm TermNo

	// - log entries to store (empty for heartbeat; may send more than one ...)
	// implied?: terms in these entries >= prevLogTerm
	// implied?: term in each new entry >= term of previous entry
	Entries []LogEntry

	// leader's commitIndex
	// implied?: LeaderCommit >= PrevLogIndex
	LeaderCommit LogIndex
}

type RpcAppendEntriesReply struct {
	Term    TermNo
	Success bool
}

type RpcRequestVote struct {
	// - candidate's term
	Term TermNo

	// candidateId

	// - index of candidate's last log entry
	LastLogIndex LogIndex

	// - term of candidate's last log entry
	LastLogTerm TermNo
}

type RpcRequestVoteReply struct {
	// - currentTerm, for candidate to update itself
	Term TermNo

	// - true means candidate receive vote
	VoteGranted bool
}
<commit_msg>Document RpcAppendEntriesReply fields<commit_after>// See RpcService in interfaces.go for related details.

package raft

type RpcAppendEntries struct {
	// - leader's term
	Term TermNo

	// leaderId

	// - index of log entry immediately preceding new ones
	PrevLogIndex LogIndex

	// - term of prevLogIndex entry
	PrevLogTerm TermNo

	// - log entries to store (empty for heartbeat; may send more than one ...)
	// implied?: terms in these entries >= prevLogTerm
	// implied?: term in each new entry >= term of previous entry
	Entries []LogEntry

	// leader's commitIndex
	// implied?: LeaderCommit >= PrevLogIndex
	LeaderCommit LogIndex
}

type RpcAppendEntriesReply struct {
	// - currentTerm, for candidate to update itself
	Term TermNo

	// - true if follower contained entry matching prevLogIndex and prevLogTerm
	Success bool
}

type RpcRequestVote struct {
	// - candidate's term
	Term TermNo

	// candidateId

	// - index of candidate's last log entry
	LastLogIndex LogIndex

	// - term of candidate's last log entry
	LastLogTerm TermNo
}

type RpcRequestVoteReply struct {
	// - currentTerm, for candidate to update itself
	Term TermNo

	// - true means candidate receive vote
	VoteGranted bool
}
<|endoftext|>


<commit_before>package metrics_buffer

import "github.com/cloudfoundry-community/stackdriver-tools/src/stackdriver-nozzle/stackdriver"

type MetricsBuffer interface {
	PostMetrics([]stackdriver.Metric) error
	IsEmpty() bool
}
<commit_msg>metrics_buffer: Explicitly implement stackdriver.MetricsAdapter<commit_after>package metrics_buffer

import "github.com/cloudfoundry-community/stackdriver-tools/src/stackdriver-nozzle/stackdriver"

type MetricsBuffer interface {
	stackdriver.MetricAdapter
	IsEmpty() bool
}
<|endoftext|>


<commit_before>package impl

import (
	"errors"
	"fmt"
	"time"

	"os"

	"github.com/ellcrys/util"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres" // gorm requires it
	"github.com/kr/pretty"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
)

var log = logging.MustGetLogger("postgres.store")

// ErrChainExist represents an error about an existing chain
var ErrChainExist = errors.New("chain already exists")

// LedgerTableName represents the name of the table where all
// known ledger info are stored.
const LedgerTableName = "ledgers"

// TransactionTableName represents the name of the table where all
// transactions are stored.
const TransactionTableName = "transactions"

// PostgresStore defines a store implementation
// on the postgres database. It implements the Store interface
type PostgresStore struct {
	db         *gorm.DB
	blockchain types.Blockchain
	locker     types.Lock
}

// SetBlockchainImplementation sets sets a reference of the blockchain implementation
func (s *PostgresStore) SetBlockchainImplementation(b types.Blockchain) {
	s.blockchain = b
}

// GetImplementationName returns the name of this store implementation
func (s *PostgresStore) GetImplementationName() string {
	return "postgres.store"
}

// Connect connects to a postgres server and returns a client
// or error if connection failed.
func (s *PostgresStore) Connect(dbAddr string) (interface{}, error) {

	var err error
	s.db, err = gorm.Open("postgres", dbAddr)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to store backend. %s", err)
	}

	s.db.LogMode(false)

	return s.db, nil
}

// MakeLegderHash takes a ledger and computes a hash
func (s *PostgresStore) MakeLegderHash(ledger *types.Ledger) string {
	return util.Sha256(fmt.Sprintf("%s;%t;%d", ledger.Name, ledger.Public, ledger.CreatedAt))
}

// Init initializes the types. Creates the necessary tables such as the
// the tables and public and private system ledgers
func (s *PostgresStore) Init(systemPublicLedgerName, systemPrivateLedgerName string) error {

	// create ledger table if not existing
	if !s.db.HasTable(LedgerTableName) {
		if err := s.db.CreateTable(&types.Ledger{}).Error; err != nil {
			return fmt.Errorf("failed to create `%s` table. %s", LedgerTableName, err)
		}
	}

	// create transaction table if not existing
	if !s.db.HasTable(TransactionTableName) {
		if err := s.db.CreateTable(&types.Transaction{}).
			AddIndex("idx_name_ledger_key_created_at", "ledger", "key", "created_at").Error; err != nil {
			return fmt.Errorf("failed to create `%s` table. %s", TransactionTableName, err)
		}
	}

	// create system ledgers
	var systemLedgers = [][]interface{}{
		[]interface{}{systemPublicLedgerName, true},   // public
		[]interface{}{systemPrivateLedgerName, false}, // private
	}
	for _, ledger := range systemLedgers {
		var c int
		if err := s.db.Model(&types.Ledger{}).Where("name = ?", ledger[0].(string)).Count(&c).Error; err != nil {
			return fmt.Errorf("failed to check existence of ledger named %s: %s", ledger[0].(string), err)
		}
		if c == 0 {
			_, err := s.CreateLedger(ledger[0].(string), true, ledger[1].(bool))
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// Destroy removes the database tables.
// Will only work in a test environment (Test Only!!!)
func Destroy(dbAddr string) error {

	if os.Getenv("APP_ENV") != "test" {
		return fmt.Errorf("Cowardly refusing to do it! Can only call Destroy() in test environment")
	}

	db, err := gorm.Open("postgres", dbAddr)
	if err != nil {
		return fmt.Errorf("failed to connect to store backend")
	}

	return db.DropTable(types.Ledger{}, types.Transaction{}).Error
}

// Clear the database tables.
// Will only work in a test environment (Test Only!!!)
func Clear(dbAddr string) error {

	if os.Getenv("APP_ENV") != "test" {
		return fmt.Errorf("Cowardly refusing to do it! Can only call Destroy() in test environment")
	}

	db, err := gorm.Open("postgres", dbAddr)
	if err != nil {
		return fmt.Errorf("failed to connect to store backend")
	}

	err = db.Delete(types.Transaction{}).Error
	if err != nil {
		return err
	}

	err = db.Delete(types.Ledger{}).Error
	if err != nil {
		return err
	}

	return nil
}

// CreateLedgerThen creates a new ledger and accepts an additional operation (via the thenFunc) to be
// executed before the ledger creation transaction is committed. If the thenFunc returns an error, the
// transaction is rolled back and error returned
func (s *PostgresStore) CreateLedgerThen(name string, chained, public bool, thenFunc func() error) (*types.Ledger, error) {

	tx := s.db.Begin()

	err := tx.Exec(`SET TRANSACTION isolation level repeatable read`).Error
	if err != nil {
		tx.Rollback()
		return nil, fmt.Errorf("failed to set transaction isolation level. %s", err)
	}

	newLedger := &types.Ledger{
		Name:      name,
		Public:    public,
		Chained:   chained,
		CreatedAt: time.Now().Unix(),
	}

	newLedger.Hash = s.MakeLegderHash(newLedger)

	if err := tx.Create(newLedger).Error; err != nil {
		tx.Rollback()
		if common.IsUniqueConstraintError(err, "name") {
			return nil, fmt.Errorf("ledger with matching name already exists")
		} else if common.IsUniqueConstraintError(err, "hash") {
			return nil, fmt.Errorf("hash is being used by another ledger")
		}
		return nil, err
	}

	// create chain
	if chained {
		_, err := s.blockchain.CreateChain(name, public)
		if err != nil {
			tx.Rollback()
			return nil, err
		}
	}

	// run the companion functions and Rollback
	// the transaction if error was returned
	if thenFunc != nil {
		if err = thenFunc(); err != nil {
			tx.Rollback()
			return nil, err
		}
	}

	tx.Commit()

	return newLedger, nil
}

// CreateLedger creates a new ledger.
func (s *PostgresStore) CreateLedger(name string, chained, public bool) (*types.Ledger, error) {
	return s.CreateLedgerThen(name, chained, public, nil)
}

// GetLedger fetches a ledger meta information
func (s *PostgresStore) GetLedger(name string) (*types.Ledger, error) {

	var l types.Ledger

	err := s.db.Where("name = ?", name).Last(&l).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get ledger. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return &l, nil
}

// makeTxLockKey constructs a lock key using a transaction key and ledger name
func makeTxLockKey(ledgerName, key string) string {
	return fmt.Sprintf("tx;key;%s;%s", ledgerName, key)
}

// PutThen adds transactions to the store and returns a list of transaction receipts.
// Any transaction that failed to be created will result in an error receipt being created
// and returned along with success receipts of they successfully added transactions.
// However, all transactions will be rolled back if the `thenFunc` returns error. Only the
// transaction that are successfully added will be passed to the thenFunc.
// Future work may allow the caller to determine the behaviour via an additional parameter.
func (s *PostgresStore) PutThen(ledgerName string, txs []*types.Transaction, thenFunc func(validTxss []*types.Transaction) error) ([]*types.TxReceipt, error) {

	var err error
	var validTxs []*types.Transaction
	txReceipts := []*types.TxReceipt{}

	dbTx := s.db.Begin()
	// err := dbTx.Exec(`SET TRANSACTION isolation level repeatable read`).Error
	// if err != nil {
	// 	dbTx.Rollback()
	// 	return nil, fmt.Errorf("failed to set transaction isolation level. %s", err)
	// }

	// create transactions and add transaction receipts for
	// successfully stored transactions
	for _, tx := range txs {
		// acquire lock on the transaction via its key
		lock := common.NewLock(makeTxLockKey(tx.LedgerInternal, tx.Key))
		if err := lock.Acquire(); err != nil {
			if err == types.ErrLockAlreadyAcquired {
				txReceipts = append(txReceipts, &types.TxReceipt{
					ID:  tx.ID,
					Err: "failed to acquire lock. object has been locked by another process",
				})
			} else {
				txReceipts = append(txReceipts, &types.TxReceipt{
					ID:  tx.ID,
					Err: err.Error(),
				})
			}
			continue
		}

		// ensure the current transaction is not a stale transaction
		// when checked with the valid transactions. If this is not done, before we call the tx.Create
		// the entire transaction will not be committed by the postgres driver
		isStale := false
		for _, vTx := range validTxs {
			if len(tx.RevisionTo) > 0 && tx.RevisionTo == vTx.RevisionTo && tx.KeyInternal == vTx.KeyInternal {
				isStale = true
				break
			}
		}
		if isStale {
			log.Info("Stale transaction (%s)", tx.ID)
			txReceipts = append(txReceipts, &types.TxReceipt{
				ID:  tx.ID,
				Err: "stale object",
			})
			lock.Release()
			continue
		}

		tx.Hash = tx.MakeHash()
		tx.Ledger = ledgerName
		txReceipt := &types.TxReceipt{ID: tx.ID}
		err = dbTx.Create(tx).Error
		if err != nil {
			log.Errorf("Failed to create transaction (%s): %s", tx.ID, err)
			txReceipt.Err = err.Error()
			if common.CompareErr(err, fmt.Errorf(`pq: duplicate key value violates unique constraint "idx_name_revision_to"`)) == 0 {
				txReceipt.Err = "stale object"
			}
		} else {
			fmt.Println("Added new valid transaction")
			validTxs = append(validTxs, tx)
		}

		if err = lock.Release(); err != nil {
			fmt.Println("Error releasing lock: ", err)
		}

		txReceipts = append(txReceipts, txReceipt)
	}

	// run the companion functions. Rollback
	// the transactions only if error was returned
	if thenFunc != nil {
		if err = thenFunc(validTxs); err != nil {
			return txReceipts, err
		}
	}

	if err := dbTx.Debug().Commit().Error; err != nil {
		pretty.Println(dbTx.GetErrors())
		return nil, err
	}

	return txReceipts, nil
}

// Put creates one or more transactions associated to a ledger.
// Returns a list of transaction receipts and a general error.
func (s *PostgresStore) Put(ledgerName string, txs []*types.Transaction) ([]*types.TxReceipt, error) {
	return s.PutThen(ledgerName, txs, nil)
}

// Get fetches a transaction by its ledger and key
func (s *PostgresStore) Get(ledger, key string) (*types.Transaction, error) {
	var tx types.Transaction

	// acquire lock on the transaction via its key
	lock := common.NewLock(makeTxLockKey(ledger, key))
	if err := lock.Acquire(); err != nil {
		if err == types.ErrLockAlreadyAcquired {
			return nil, fmt.Errorf("failed to acquire lock. object has been locked by another process")
		}
		return nil, err
	}

	defer lock.Release()

	err := s.db.Where("ledger = ? AND key = ?", ledger, key).Last(&tx).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get transaction. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return &tx, nil
}

// GetRange fetches transactions with keys included in a specified range.
// No lock is acquired in this operation.
func (s *PostgresStore) GetRange(ledger, startKey, endKey string, inclusive bool, limit, offset int) ([]*types.Transaction, error) {

	var err error
	var txs []*types.Transaction
	var q *gorm.DB

	if len(startKey) > 0 && len(endKey) > 0 {
		if !inclusive {
			q = s.db.Where("ledger = ? AND (key >= ? AND key < ?)", ledger, startKey, endKey)
		} else {
			q = s.db.Where("ledger = ? AND (key >= ? OR key <= ?)", ledger, startKey+"%", endKey+"%")
		}
	} else if len(startKey) > 0 && len(endKey) == 0 {
		q = s.db.Where("ledger = ? AND key like ?", ledger, startKey+"%")
	} else {
		// setting endKey only is a little tricky as the call code may construct
		// through a secondary process or rule, so add the '%' operator will most likely
		// result in wrong query. So we just let the external decide where to put it.
		q = s.db.Where("ledger = ? AND key like ?", ledger, endKey)
	}

	err = q.
		Limit(limit).
		Offset(offset).
		Select("DISTINCT ON (key) *").
		Order("key").
		Order("created_at desc").
		Find(&txs).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get transactions. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return txs, nil
}

// Close releases any resource held
func (s *PostgresStore) Close() error {
	if s.db != nil {
		return s.db.Close()
	}
	return nil
}
<commit_msg>completely remove block that updates the isolation type used as we do not depend on a specific isolation given that we use an external locking mechanism.<commit_after>package impl

import (
	"errors"
	"fmt"
	"time"

	"os"

	"github.com/ellcrys/util"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres" // gorm requires it
	"github.com/kr/pretty"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
)

var log = logging.MustGetLogger("postgres.store")

// ErrChainExist represents an error about an existing chain
var ErrChainExist = errors.New("chain already exists")

// LedgerTableName represents the name of the table where all
// known ledger info are stored.
const LedgerTableName = "ledgers"

// TransactionTableName represents the name of the table where all
// transactions are stored.
const TransactionTableName = "transactions"

// PostgresStore defines a store implementation
// on the postgres database. It implements the Store interface
type PostgresStore struct {
	db         *gorm.DB
	blockchain types.Blockchain
	locker     types.Lock
}

// SetBlockchainImplementation sets sets a reference of the blockchain implementation
func (s *PostgresStore) SetBlockchainImplementation(b types.Blockchain) {
	s.blockchain = b
}

// GetImplementationName returns the name of this store implementation
func (s *PostgresStore) GetImplementationName() string {
	return "postgres.store"
}

// Connect connects to a postgres server and returns a client
// or error if connection failed.
func (s *PostgresStore) Connect(dbAddr string) (interface{}, error) {

	var err error
	s.db, err = gorm.Open("postgres", dbAddr)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to store backend. %s", err)
	}

	s.db.LogMode(false)

	return s.db, nil
}

// MakeLegderHash takes a ledger and computes a hash
func (s *PostgresStore) MakeLegderHash(ledger *types.Ledger) string {
	return util.Sha256(fmt.Sprintf("%s;%t;%d", ledger.Name, ledger.Public, ledger.CreatedAt))
}

// Init initializes the types. Creates the necessary tables such as the
// the tables and public and private system ledgers
func (s *PostgresStore) Init(systemPublicLedgerName, systemPrivateLedgerName string) error {

	// create ledger table if not existing
	if !s.db.HasTable(LedgerTableName) {
		if err := s.db.CreateTable(&types.Ledger{}).Error; err != nil {
			return fmt.Errorf("failed to create `%s` table. %s", LedgerTableName, err)
		}
	}

	// create transaction table if not existing
	if !s.db.HasTable(TransactionTableName) {
		if err := s.db.CreateTable(&types.Transaction{}).
			AddIndex("idx_name_ledger_key_created_at", "ledger", "key", "created_at").Error; err != nil {
			return fmt.Errorf("failed to create `%s` table. %s", TransactionTableName, err)
		}
	}

	// create system ledgers
	var systemLedgers = [][]interface{}{
		[]interface{}{systemPublicLedgerName, true},   // public
		[]interface{}{systemPrivateLedgerName, false}, // private
	}
	for _, ledger := range systemLedgers {
		var c int
		if err := s.db.Model(&types.Ledger{}).Where("name = ?", ledger[0].(string)).Count(&c).Error; err != nil {
			return fmt.Errorf("failed to check existence of ledger named %s: %s", ledger[0].(string), err)
		}
		if c == 0 {
			_, err := s.CreateLedger(ledger[0].(string), true, ledger[1].(bool))
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// Destroy removes the database tables.
// Will only work in a test environment (Test Only!!!)
func Destroy(dbAddr string) error {

	if os.Getenv("APP_ENV") != "test" {
		return fmt.Errorf("Cowardly refusing to do it! Can only call Destroy() in test environment")
	}

	db, err := gorm.Open("postgres", dbAddr)
	if err != nil {
		return fmt.Errorf("failed to connect to store backend")
	}

	return db.DropTable(types.Ledger{}, types.Transaction{}).Error
}

// Clear the database tables.
// Will only work in a test environment (Test Only!!!)
func Clear(dbAddr string) error {

	if os.Getenv("APP_ENV") != "test" {
		return fmt.Errorf("Cowardly refusing to do it! Can only call Destroy() in test environment")
	}

	db, err := gorm.Open("postgres", dbAddr)
	if err != nil {
		return fmt.Errorf("failed to connect to store backend")
	}

	err = db.Delete(types.Transaction{}).Error
	if err != nil {
		return err
	}

	err = db.Delete(types.Ledger{}).Error
	if err != nil {
		return err
	}

	return nil
}

// CreateLedgerThen creates a new ledger and accepts an additional operation (via the thenFunc) to be
// executed before the ledger creation transaction is committed. If the thenFunc returns an error, the
// transaction is rolled back and error returned
func (s *PostgresStore) CreateLedgerThen(name string, chained, public bool, thenFunc func() error) (*types.Ledger, error) {

	tx := s.db.Begin()

	err := tx.Exec(`SET TRANSACTION isolation level repeatable read`).Error
	if err != nil {
		tx.Rollback()
		return nil, fmt.Errorf("failed to set transaction isolation level. %s", err)
	}

	newLedger := &types.Ledger{
		Name:      name,
		Public:    public,
		Chained:   chained,
		CreatedAt: time.Now().Unix(),
	}

	newLedger.Hash = s.MakeLegderHash(newLedger)

	if err := tx.Create(newLedger).Error; err != nil {
		tx.Rollback()
		if common.IsUniqueConstraintError(err, "name") {
			return nil, fmt.Errorf("ledger with matching name already exists")
		} else if common.IsUniqueConstraintError(err, "hash") {
			return nil, fmt.Errorf("hash is being used by another ledger")
		}
		return nil, err
	}

	// create chain
	if chained {
		_, err := s.blockchain.CreateChain(name, public)
		if err != nil {
			tx.Rollback()
			return nil, err
		}
	}

	// run the companion functions and Rollback
	// the transaction if error was returned
	if thenFunc != nil {
		if err = thenFunc(); err != nil {
			tx.Rollback()
			return nil, err
		}
	}

	tx.Commit()

	return newLedger, nil
}

// CreateLedger creates a new ledger.
func (s *PostgresStore) CreateLedger(name string, chained, public bool) (*types.Ledger, error) {
	return s.CreateLedgerThen(name, chained, public, nil)
}

// GetLedger fetches a ledger meta information
func (s *PostgresStore) GetLedger(name string) (*types.Ledger, error) {

	var l types.Ledger

	err := s.db.Where("name = ?", name).Last(&l).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get ledger. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return &l, nil
}

// makeTxLockKey constructs a lock key using a transaction key and ledger name
func makeTxLockKey(ledgerName, key string) string {
	return fmt.Sprintf("tx;key;%s;%s", ledgerName, key)
}

// PutThen adds transactions to the store and returns a list of transaction receipts.
// Any transaction that failed to be created will result in an error receipt being created
// and returned along with success receipts of they successfully added transactions.
// However, all transactions will be rolled back if the `thenFunc` returns error. Only the
// transaction that are successfully added will be passed to the thenFunc.
// Future work may allow the caller to determine the behaviour via an additional parameter.
func (s *PostgresStore) PutThen(ledgerName string, txs []*types.Transaction, thenFunc func(validTxss []*types.Transaction) error) ([]*types.TxReceipt, error) {

	var err error
	var validTxs []*types.Transaction
	txReceipts := []*types.TxReceipt{}

	dbTx := s.db.Begin()

	// create transactions and add transaction receipts for
	// successfully stored transactions
	for _, tx := range txs {
		// acquire lock on the transaction via its key
		lock := common.NewLock(makeTxLockKey(tx.LedgerInternal, tx.Key))
		if err := lock.Acquire(); err != nil {
			if err == types.ErrLockAlreadyAcquired {
				txReceipts = append(txReceipts, &types.TxReceipt{
					ID:  tx.ID,
					Err: "failed to acquire lock. object has been locked by another process",
				})
			} else {
				txReceipts = append(txReceipts, &types.TxReceipt{
					ID:  tx.ID,
					Err: err.Error(),
				})
			}
			continue
		}

		// ensure the current transaction is not a stale transaction
		// when checked with the valid transactions. If this is not done, before we call the tx.Create
		// the entire transaction will not be committed by the postgres driver
		isStale := false
		for _, vTx := range validTxs {
			if len(tx.RevisionTo) > 0 && tx.RevisionTo == vTx.RevisionTo && tx.KeyInternal == vTx.KeyInternal {
				isStale = true
				break
			}
		}
		if isStale {
			log.Info("Stale transaction (%s)", tx.ID)
			txReceipts = append(txReceipts, &types.TxReceipt{
				ID:  tx.ID,
				Err: "stale object",
			})
			lock.Release()
			continue
		}

		tx.Hash = tx.MakeHash()
		tx.Ledger = ledgerName
		txReceipt := &types.TxReceipt{ID: tx.ID}
		err = dbTx.Create(tx).Error
		if err != nil {
			log.Errorf("Failed to create transaction (%s): %s", tx.ID, err)
			txReceipt.Err = err.Error()
			if common.CompareErr(err, fmt.Errorf(`pq: duplicate key value violates unique constraint "idx_name_revision_to"`)) == 0 {
				txReceipt.Err = "stale object"
			}
		} else {
			fmt.Println("Added new valid transaction")
			validTxs = append(validTxs, tx)
		}

		if err = lock.Release(); err != nil {
			fmt.Println("Error releasing lock: ", err)
		}

		txReceipts = append(txReceipts, txReceipt)
	}

	// run the companion functions. Rollback
	// the transactions only if error was returned
	if thenFunc != nil {
		if err = thenFunc(validTxs); err != nil {
			return txReceipts, err
		}
	}

	if err := dbTx.Debug().Commit().Error; err != nil {
		pretty.Println(dbTx.GetErrors())
		return nil, err
	}

	return txReceipts, nil
}

// Put creates one or more transactions associated to a ledger.
// Returns a list of transaction receipts and a general error.
func (s *PostgresStore) Put(ledgerName string, txs []*types.Transaction) ([]*types.TxReceipt, error) {
	return s.PutThen(ledgerName, txs, nil)
}

// Get fetches a transaction by its ledger and key
func (s *PostgresStore) Get(ledger, key string) (*types.Transaction, error) {
	var tx types.Transaction

	// acquire lock on the transaction via its key
	lock := common.NewLock(makeTxLockKey(ledger, key))
	if err := lock.Acquire(); err != nil {
		if err == types.ErrLockAlreadyAcquired {
			return nil, fmt.Errorf("failed to acquire lock. object has been locked by another process")
		}
		return nil, err
	}

	defer lock.Release()

	err := s.db.Where("ledger = ? AND key = ?", ledger, key).Last(&tx).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get transaction. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return &tx, nil
}

// GetRange fetches transactions with keys included in a specified range.
// No lock is acquired in this operation.
func (s *PostgresStore) GetRange(ledger, startKey, endKey string, inclusive bool, limit, offset int) ([]*types.Transaction, error) {

	var err error
	var txs []*types.Transaction
	var q *gorm.DB

	if len(startKey) > 0 && len(endKey) > 0 {
		if !inclusive {
			q = s.db.Where("ledger = ? AND (key >= ? AND key < ?)", ledger, startKey, endKey)
		} else {
			q = s.db.Where("ledger = ? AND (key >= ? OR key <= ?)", ledger, startKey+"%", endKey+"%")
		}
	} else if len(startKey) > 0 && len(endKey) == 0 {
		q = s.db.Where("ledger = ? AND key like ?", ledger, startKey+"%")
	} else {
		// setting endKey only is a little tricky as the call code may construct
		// through a secondary process or rule, so add the '%' operator will most likely
		// result in wrong query. So we just let the external decide where to put it.
		q = s.db.Where("ledger = ? AND key like ?", ledger, endKey)
	}

	err = q.
		Limit(limit).
		Offset(offset).
		Select("DISTINCT ON (key) *").
		Order("key").
		Order("created_at desc").
		Find(&txs).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, fmt.Errorf("failed to get transactions. %s", err)
	} else if err == gorm.ErrRecordNotFound {
		return nil, nil
	}

	return txs, nil
}

// Close releases any resource held
func (s *PostgresStore) Close() error {
	if s.db != nil {
		return s.db.Close()
	}
	return nil
}
<|endoftext|>


<commit_before>package turnpike_test

import (
	"code.google.com/p/go.net/websocket"
	"github.com/jcelliott/turnpike"
	"net/http"
)

func ExampleClient_NewClient() {
	c := turnpike.NewClient()
	err := c.Connect("ws://127.0.0.1:8080/ws", "http://localhost/")
	if err != nil {
		fmt.Println("Error connecting:", err)
		return
	}

	c.Call("rpc:test")
}
<commit_msg>Simplify and fix client example<commit_after>package turnpike_test

import (
	"github.com/jcelliott/turnpike"
)

func ExampleClient_NewClient() {
	c := turnpike.NewClient()
	err := c.Connect("ws://127.0.0.1:8080/ws", "http://localhost/")
	if err != nil {
		panic("Error connecting:" + err.Error())
	}

	c.Call("rpc:test")
}
<|endoftext|>


<commit_before>package acme

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"
)

var (
	// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
	UserAgent string

	// HTTPClient is an HTTP client with a reasonable timeout value and
	// potentially a custom *x509.CertPool based on the caCertificatesEnvVar
	// environment variable (see the `initCertPool` function)
	HTTPClient = http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			TLSHandshakeTimeout:   15 * time.Second,
			ResponseHeaderTimeout: 15 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
			TLSClientConfig: &tls.Config{
				RootCAs: initCertPool(),
			},
		},
	}
)

const (
	// defaultGoUserAgent is the Go HTTP package user agent string. Too
	// bad it isn't exported. If it changes, we should update it here, too.
	defaultGoUserAgent = "Go-http-client/1.1"

	// ourUserAgent is the User-Agent of this underlying library package.
	ourUserAgent = "xenolf-acme"

	// caCertificatesEnvVar is the environment variable name that can be used to
	// specify the path to PEM encoded CA Certificates that can be used to
	// authenticate an ACME server with a HTTPS certificate not issued by a CA in
	// the system-wide trusted root list.
	caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
)

// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
func initCertPool() *x509.CertPool {
	if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
		customCAs, err := ioutil.ReadFile(customCACertsPath)
		if err != nil {
			panic(fmt.Sprintf("error reading %s=%q: %v",
				caCertificatesEnvVar, customCACertsPath, err))
		}
		certPool := x509.NewCertPool()
		if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
			panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
				caCertificatesEnvVar, customCACertsPath, err))
		}
		return certPool
	}
	return nil
}

// httpHead performs a HEAD request with a proper User-Agent string.
// The response body (resp.Body) is already closed when this function returns.
func httpHead(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodHead, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to head %q: %v", url, err)
	}

	req.Header.Set("User-Agent", userAgent())

	resp, err = HTTPClient.Do(req)
	if err != nil {
		return resp, fmt.Errorf("failed to do head %q: %v", url, err)
	}
	resp.Body.Close()
	return resp, err
}

// httpPost performs a POST request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodPost, url, body)
	if err != nil {
		return nil, fmt.Errorf("failed to post %q: %v", url, err)
	}
	req.Header.Set("Content-Type", bodyType)
	req.Header.Set("User-Agent", userAgent())

	return HTTPClient.Do(req)
}

// httpGet performs a GET request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpGet(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get %q: %v", url, err)
	}
	req.Header.Set("User-Agent", userAgent())

	return HTTPClient.Do(req)
}

// getJSON performs an HTTP GET request and parses the response body
// as JSON, into the provided respBody object.
func getJSON(uri string, respBody interface{}) (http.Header, error) {
	resp, err := httpGet(uri)
	if err != nil {
		return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= http.StatusBadRequest {
		return resp.Header, handleHTTPError(resp)
	}

	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
}

// postJSON performs an HTTP POST request and parses the response body
// as JSON, into the provided respBody object.
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
	jsonBytes, err := json.Marshal(reqBody)
	if err != nil {
		return nil, errors.New("Failed to marshal network message")
	}

	resp, err := j.post(uri, jsonBytes)
	if err != nil {
		return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode >= http.StatusBadRequest {

		err := handleHTTPError(resp)

		switch err.(type) {

		case NonceError:

			// Retry once if the nonce was invalidated

			retryResp, err := j.post(uri, jsonBytes)
			if err != nil {
				return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
			}

			defer retryResp.Body.Close()

			if retryResp.StatusCode >= http.StatusBadRequest {
				return retryResp.Header, handleHTTPError(retryResp)
			}

			if respBody == nil {
				return retryResp.Header, nil
			}

			return retryResp.Header, json.NewDecoder(retryResp.Body).Decode(respBody)

		default:
			return resp.Header, err

		}

	}

	if respBody == nil {
		return resp.Header, nil
	}

	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
}

// userAgent builds and returns the User-Agent string to use in requests.
func userAgent() string {
	ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent)
	return strings.TrimSpace(ua)
}
<commit_msg>feat: CA Server Name. (#590)<commit_after>package acme

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"
)

var (
	// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
	UserAgent string

	// HTTPClient is an HTTP client with a reasonable timeout value and
	// potentially a custom *x509.CertPool based on the caCertificatesEnvVar
	// environment variable (see the `initCertPool` function)
	HTTPClient = http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			TLSHandshakeTimeout:   15 * time.Second,
			ResponseHeaderTimeout: 15 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
			TLSClientConfig: &tls.Config{
				ServerName: os.Getenv(caServerNameEnvVar),
				RootCAs:    initCertPool(),
			},
		},
	}
)

const (
	// defaultGoUserAgent is the Go HTTP package user agent string. Too
	// bad it isn't exported. If it changes, we should update it here, too.
	defaultGoUserAgent = "Go-http-client/1.1"

	// ourUserAgent is the User-Agent of this underlying library package.
	ourUserAgent = "xenolf-acme"

	// caCertificatesEnvVar is the environment variable name that can be used to
	// specify the path to PEM encoded CA Certificates that can be used to
	// authenticate an ACME server with a HTTPS certificate not issued by a CA in
	// the system-wide trusted root list.
	caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"

	// caServerNameEnvVar is the environment variable name that can be used to
	// specify the CA server name that can be used to
	// authenticate an ACME server with a HTTPS certificate not issued by a CA in
	// the system-wide trusted root list.
	caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
)

// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
func initCertPool() *x509.CertPool {
	if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
		customCAs, err := ioutil.ReadFile(customCACertsPath)
		if err != nil {
			panic(fmt.Sprintf("error reading %s=%q: %v",
				caCertificatesEnvVar, customCACertsPath, err))
		}
		certPool := x509.NewCertPool()
		if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
			panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
				caCertificatesEnvVar, customCACertsPath, err))
		}
		return certPool
	}
	return nil
}

// httpHead performs a HEAD request with a proper User-Agent string.
// The response body (resp.Body) is already closed when this function returns.
func httpHead(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodHead, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to head %q: %v", url, err)
	}

	req.Header.Set("User-Agent", userAgent())

	resp, err = HTTPClient.Do(req)
	if err != nil {
		return resp, fmt.Errorf("failed to do head %q: %v", url, err)
	}
	resp.Body.Close()
	return resp, err
}

// httpPost performs a POST request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodPost, url, body)
	if err != nil {
		return nil, fmt.Errorf("failed to post %q: %v", url, err)
	}
	req.Header.Set("Content-Type", bodyType)
	req.Header.Set("User-Agent", userAgent())

	return HTTPClient.Do(req)
}

// httpGet performs a GET request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpGet(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get %q: %v", url, err)
	}
	req.Header.Set("User-Agent", userAgent())

	return HTTPClient.Do(req)
}

// getJSON performs an HTTP GET request and parses the response body
// as JSON, into the provided respBody object.
func getJSON(uri string, respBody interface{}) (http.Header, error) {
	resp, err := httpGet(uri)
	if err != nil {
		return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= http.StatusBadRequest {
		return resp.Header, handleHTTPError(resp)
	}

	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
}

// postJSON performs an HTTP POST request and parses the response body
// as JSON, into the provided respBody object.
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
	jsonBytes, err := json.Marshal(reqBody)
	if err != nil {
		return nil, errors.New("Failed to marshal network message")
	}

	resp, err := j.post(uri, jsonBytes)
	if err != nil {
		return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode >= http.StatusBadRequest {

		err := handleHTTPError(resp)

		switch err.(type) {

		case NonceError:

			// Retry once if the nonce was invalidated

			retryResp, err := j.post(uri, jsonBytes)
			if err != nil {
				return nil, fmt.Errorf("Failed to post JWS message. -> %v", err)
			}

			defer retryResp.Body.Close()

			if retryResp.StatusCode >= http.StatusBadRequest {
				return retryResp.Header, handleHTTPError(retryResp)
			}

			if respBody == nil {
				return retryResp.Header, nil
			}

			return retryResp.Header, json.NewDecoder(retryResp.Body).Decode(respBody)

		default:
			return resp.Header, err

		}

	}

	if respBody == nil {
		return resp.Header, nil
	}

	return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
}

// userAgent builds and returns the User-Agent string to use in requests.
func userAgent() string {
	ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent)
	return strings.TrimSpace(ua)
}
<|endoftext|>