<commit_before>package mysql

import (
	"database/sql"
	"time"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	log "github.com/funkygao/log4go"
	_ "github.com/funkygao/mysql"
)

type mysqlStore struct {
	cf     *config
	zkzone *zk.ZkZone

	shutdownCh chan struct{}
	refreshCh  chan struct{}

	// mysql store, initialized on refresh
	// TODO https://github.com/hashicorp/go-memdb
	appClusterMap       map[string]string              // appid:cluster
	appSecretMap        map[string]string              // appid:secret
	appSubMap           map[string]map[string]struct{} // appid:topics
	appPubMap           map[string]map[string]struct{} // appid:subscribed topics
	appConsumerGroupMap map[string]string              // appid:group
	shadowQueueMap      map[string]string              // appid.topic.ver:group
}

func New(cf *config) *mysqlStore {
	if cf.Zone == "" {
		panic("empty zone")
	}
	zkAddrs := ctx.ZoneZkAddrs(cf.Zone)
	if len(zkAddrs) == 0 {
		panic("empty zookeeper addr")
	}

	return &mysqlStore{
		cf:         cf,
		zkzone:     zk.NewZkZone(zk.DefaultConfig(cf.Zone, zkAddrs)), // TODO session timeout
		shutdownCh: make(chan struct{}),
		refreshCh:  make(chan struct{}),
	}
}

func (this *mysqlStore) Name() string {
	return "mysql"
}

type applicationRecord struct {
	AppId, Cluster, AppSecret string
}

type appTopicRecord struct {
	AppId, TopicName string
}

type appSubscribeRecord struct {
	AppId, TopicName string
}

type appConsumerGroupRecord struct {
	AppId, GroupName string
}

type shadowQueueRecord struct {
	AppId, TopicName, Ver string
	Group                 string
}

func (this *mysqlStore) Start() error {
	if err := this.refreshFromMysql(); err != nil {
		// refuse to start if mysql conn fails
		return err
	}

	go func() {
		ticker := time.NewTicker(this.cf.Refresh)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				this.refreshFromMysql()
				log.Info("manager refreshed from mysql")

				select {
				case this.refreshCh <- struct{}{}:
				default:
				}

			case <-this.shutdownCh:
				log.Info("mysql manager stopped")
				return
			}
		}
	}()

	return nil
}

func (this *mysqlStore) Stop() {
	close(this.shutdownCh)
}

func (this *mysqlStore) refreshFromMysql() error {
	dsn, err := this.zkzone.KatewayMysqlDsn()
	if err != nil {
		log.Error("mysql manager store fetching mysql dsn: %v", err)
		return err
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}
	defer db.Close()

	// if mysql dies, keep old/stale manager records as it was
	if err = this.fetchApplicationRecords(db); err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}

	if err = this.fetchTopicRecords(db); err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}

	if err = this.fetchSubscribeRecords(db); err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}

	if err = this.fetchAppGroupRecords(db); err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}

	if err = this.fetchShadowQueueRecords(db); err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}

	return nil
}

func (this *mysqlStore) shadowKey(appid, topic, ver string) string {
	return appid + "." + topic + "." + ver
}

func (this *mysqlStore) fetchShadowQueueRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName,Version,GroupName FROM group_shadow WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var shadow shadowQueueRecord
	shadowQueueMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&shadow.AppId, &shadow.TopicName, &shadow.Ver, &shadow.Group)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		shadowQueueMap[this.shadowKey(shadow.AppId, shadow.TopicName, shadow.Ver)] = shadow.Group
	}

	this.shadowQueueMap = shadowQueueMap
	return nil
}

func (this *mysqlStore) fetchAppGroupRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,GroupName FROM application_group WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var group appConsumerGroupRecord
	appGroupMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&group.AppId, &group.GroupName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		appGroupMap[group.AppId] = group.GroupName
	}

	this.appConsumerGroupMap = appGroupMap
	return nil
}

func (this *mysqlStore) fetchApplicationRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,Cluster,AppSecret FROM application WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app applicationRecord
	appClusterMap := make(map[string]string)
	appSecretMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.Cluster, &app.AppSecret)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		appSecretMap[app.AppId] = app.AppSecret
		appClusterMap[app.AppId] = app.Cluster
	}

	this.appClusterMap = appClusterMap
	this.appSecretMap = appSecretMap
	return nil
}

func (this *mysqlStore) fetchSubscribeRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName FROM topics_subscriber WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app appSubscribeRecord
	m := make(map[string]map[string]struct{})
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.TopicName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		if _, present := m[app.AppId]; !present {
			m[app.AppId] = make(map[string]struct{})
		}

		m[app.AppId][app.TopicName] = struct{}{}
	}

	this.appSubMap = m

	return nil
}

func (this *mysqlStore) fetchTopicRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName FROM topics WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app appTopicRecord
	m := make(map[string]map[string]struct{})
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.TopicName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		if _, present := m[app.AppId]; !present {
			m[app.AppId] = make(map[string]struct{})
		}

		m[app.AppId][app.TopicName] = struct{}{}
	}

	this.appPubMap = m

	return nil
}
<commit_msg>tweak of the err msg<commit_after>package mysql

import (
	"database/sql"
	"time"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	log "github.com/funkygao/log4go"
	_ "github.com/funkygao/mysql"
)

type mysqlStore struct {
	cf     *config
	zkzone *zk.ZkZone

	shutdownCh chan struct{}
	refreshCh  chan struct{}

	// mysql store, initialized on refresh
	// TODO https://github.com/hashicorp/go-memdb
	appClusterMap       map[string]string              // appid:cluster
	appSecretMap        map[string]string              // appid:secret
	appSubMap           map[string]map[string]struct{} // appid:topics
	appPubMap           map[string]map[string]struct{} // appid:subscribed topics
	appConsumerGroupMap map[string]string              // appid:group
	shadowQueueMap      map[string]string              // appid.topic.ver:group
}

func New(cf *config) *mysqlStore {
	if cf.Zone == "" {
		panic("empty zone")
	}
	zkAddrs := ctx.ZoneZkAddrs(cf.Zone)
	if len(zkAddrs) == 0 {
		panic("empty zookeeper addr")
	}

	return &mysqlStore{
		cf:         cf,
		zkzone:     zk.NewZkZone(zk.DefaultConfig(cf.Zone, zkAddrs)), // TODO session timeout
		shutdownCh: make(chan struct{}),
		refreshCh:  make(chan struct{}),
	}
}

func (this *mysqlStore) Name() string {
	return "mysql"
}

type applicationRecord struct {
	AppId, Cluster, AppSecret string
}

type appTopicRecord struct {
	AppId, TopicName string
}

type appSubscribeRecord struct {
	AppId, TopicName string
}

type appConsumerGroupRecord struct {
	AppId, GroupName string
}

type shadowQueueRecord struct {
	AppId, TopicName, Ver string
	Group                 string
}

func (this *mysqlStore) Start() error {
	if err := this.refreshFromMysql(); err != nil {
		// refuse to start if mysql conn fails
		return err
	}

	go func() {
		ticker := time.NewTicker(this.cf.Refresh)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				this.refreshFromMysql()
				log.Info("manager refreshed from mysql")

				select {
				case this.refreshCh <- struct{}{}:
				default:
				}

			case <-this.shutdownCh:
				log.Info("mysql manager stopped")
				return
			}
		}
	}()

	return nil
}

func (this *mysqlStore) Stop() {
	close(this.shutdownCh)
}

func (this *mysqlStore) refreshFromMysql() error {
	dsn, err := this.zkzone.KatewayMysqlDsn()
	if err != nil {
		log.Error("mysql manager store fetching mysql dsn: %v", err)
		return err
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Error("mysql manager store: %v", err)
		return err
	}
	defer db.Close()

	// if mysql dies, keep old/stale manager records as it was
	if err = this.fetchApplicationRecords(db); err != nil {
		log.Error("apps: %v", err)
		return err
	}

	if err = this.fetchTopicRecords(db); err != nil {
		log.Error("topics: %v", err)
		return err
	}

	if err = this.fetchSubscribeRecords(db); err != nil {
		log.Error("subs: %v", err)
		return err
	}

	if err = this.fetchAppGroupRecords(db); err != nil {
		log.Error("app groups: %v", err)
		return err
	}

	if err = this.fetchShadowQueueRecords(db); err != nil {
		log.Error("shadow queues: %v", err)
		return err
	}

	return nil
}

func (this *mysqlStore) shadowKey(appid, topic, ver string) string {
	return appid + "." + topic + "." + ver
}

func (this *mysqlStore) fetchShadowQueueRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName,Version,GroupName FROM group_shadow WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var shadow shadowQueueRecord
	shadowQueueMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&shadow.AppId, &shadow.TopicName, &shadow.Ver, &shadow.Group)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		shadowQueueMap[this.shadowKey(shadow.AppId, shadow.TopicName, shadow.Ver)] = shadow.Group
	}

	this.shadowQueueMap = shadowQueueMap
	return nil
}

func (this *mysqlStore) fetchAppGroupRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,GroupName FROM application_group WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var group appConsumerGroupRecord
	appGroupMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&group.AppId, &group.GroupName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		appGroupMap[group.AppId] = group.GroupName
	}

	this.appConsumerGroupMap = appGroupMap
	return nil
}

func (this *mysqlStore) fetchApplicationRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,Cluster,AppSecret FROM application WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app applicationRecord
	appClusterMap := make(map[string]string)
	appSecretMap := make(map[string]string)
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.Cluster, &app.AppSecret)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		appSecretMap[app.AppId] = app.AppSecret
		appClusterMap[app.AppId] = app.Cluster
	}

	this.appClusterMap = appClusterMap
	this.appSecretMap = appSecretMap
	return nil
}

func (this *mysqlStore) fetchSubscribeRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName FROM topics_subscriber WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app appSubscribeRecord
	m := make(map[string]map[string]struct{})
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.TopicName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		if _, present := m[app.AppId]; !present {
			m[app.AppId] = make(map[string]struct{})
		}

		m[app.AppId][app.TopicName] = struct{}{}
	}

	this.appSubMap = m

	return nil
}

func (this *mysqlStore) fetchTopicRecords(db *sql.DB) error {
	rows, err := db.Query("SELECT AppId,TopicName FROM topics WHERE Status=1")
	if err != nil {
		return err
	}
	defer rows.Close()

	var app appTopicRecord
	m := make(map[string]map[string]struct{})
	for rows.Next() {
		err = rows.Scan(&app.AppId, &app.TopicName)
		if err != nil {
			log.Error("mysql manager store: %v", err)
			continue
		}

		if _, present := m[app.AppId]; !present {
			m[app.AppId] = make(map[string]struct{})
		}

		m[app.AppId][app.TopicName] = struct{}{}
	}

	this.appPubMap = m

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package util

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/pkg/errors"

	netutil "k8s.io/apimachinery/pkg/util/net"
	versionutil "k8s.io/apimachinery/pkg/util/version"
	"k8s.io/klog"
	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
	pkgversion "k8s.io/kubernetes/pkg/version"
)

const (
	getReleaseVersionTimeout = time.Duration(10 * time.Second)
)

var (
	kubeReleaseBucketURL  = "https://dl.k8s.io"
	kubeReleaseRegex      = regexp.MustCompile(`^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?$`)
	kubeReleaseLabelRegex = regexp.MustCompile(`^[[:lower:]]+(-[-\w_\.]+)?$`)
	kubeBucketPrefixes    = regexp.MustCompile(`^((release|ci|ci-cross)/)?([-\w_\.+]+)$`)
)

// KubernetesReleaseVersion is helper function that can fetch
// available version information from release servers based on
// label names, like "stable" or "latest".
//
// If argument is already semantic version string, it
// will return same string.
//
// In case of labels, it tries to fetch from release
// servers and then return actual semantic version.
//
// Available names on release servers:
//  stable      (latest stable release)
//  stable-1    (latest stable release in 1.x)
//  stable-1.0  (and similarly 1.1, 1.2, 1.3, ...)
//  latest      (latest release, including alpha/beta)
//  latest-1    (latest release in 1.x, including alpha/beta)
//  latest-1.0  (and similarly 1.1, 1.2, 1.3, ...)
func KubernetesReleaseVersion(version string) (string, error) {
	return kubernetesReleaseVersion(version, fetchFromURL)
}

func kubernetesReleaseVersion(version string, fetcher func(string, time.Duration) (string, error)) (string, error) {
	ver := normalizedBuildVersion(version)
	if len(ver) != 0 {
		return ver, nil
	}

	bucketURL, versionLabel, err := splitVersion(version)
	if err != nil {
		return "", err
	}

	// revalidate, if exact build from e.g. CI bucket requested.
	ver = normalizedBuildVersion(versionLabel)
	if len(ver) != 0 {
		return ver, nil
	}

	// kubeReleaseLabelRegex matches labels such as: latest, latest-1, latest-1.10
	if kubeReleaseLabelRegex.MatchString(versionLabel) {
		// Try to obtain a client version.
		// pkgversion.Get().String() should always return a correct version added by the golang
		// linker and the build system. The version can still be missing when doing unit tests
		// on individual packages.
		clientVersion, clientVersionErr := kubeadmVersion(pkgversion.Get().String())
		// Fetch version from the internet.
		url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
		body, err := fetcher(url, getReleaseVersionTimeout)
		if err != nil {
			// If the network operaton was successful but the server did not reply with StatusOK
			if body != "" {
				return "", err
			}
			if clientVersionErr == nil {
				// Handle air-gapped environments by falling back to the client version.
				klog.Warningf("could not fetch a Kubernetes version from the internet: %v", err)
				klog.Warningf("falling back to the local client version: %s", clientVersion)
				return kubernetesReleaseVersion(clientVersion, fetcher)
			}
		}

		if clientVersionErr != nil {
			if err != nil {
				klog.Warningf("could not obtain neither client nor remote version; fall back to: %s", constants.CurrentKubernetesVersion)
				return kubernetesReleaseVersion(constants.CurrentKubernetesVersion.String(), fetcher)
			}

			klog.Warningf("could not obtain client version; using remote version: %s", body)
			return kubernetesReleaseVersion(body, fetcher)
		}

		// both the client and the remote version are obtained; validate them and pick a stable version
		body, err = validateStableVersion(body, clientVersion)
		if err != nil {
			return "", err
		}
		// Re-validate received version and return.
		return kubernetesReleaseVersion(body, fetcher)
	}
	return "", errors.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}

// KubernetesVersionToImageTag is helper function that replaces all
// non-allowed symbols in tag strings with underscores.
// Image tag can only contain lowercase and uppercase letters, digits,
// underscores, periods and dashes.
// Current usage is for CI images where all of symbols except '+' are valid,
// but function is for generic usage where input can't be always pre-validated.
func KubernetesVersionToImageTag(version string) string {
	allowed := regexp.MustCompile(`[^-a-zA-Z0-9_\.]`)
	return allowed.ReplaceAllString(version, "_")
}

// KubernetesIsCIVersion checks if user requested CI version
func KubernetesIsCIVersion(version string) bool {
	subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
	if len(subs) == 1 && len(subs[0]) == 4 && strings.HasPrefix(subs[0][2], "ci") {
		return true
	}
	return false
}

// Internal helper: returns normalized build version (with "v" prefix if needed)
// If input doesn't match known version pattern, returns empty string.
func normalizedBuildVersion(version string) string {
	if kubeReleaseRegex.MatchString(version) {
		if strings.HasPrefix(version, "v") {
			return version
		}
		return "v" + version
	}
	return ""
}

// Internal helper: split version parts,
// Return base URL and cleaned-up version
func splitVersion(version string) (string, string, error) {
	var urlSuffix string
	subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
	if len(subs) != 1 || len(subs[0]) != 4 {
		return "", "", errors.Errorf("invalid version %q", version)
	}

	switch {
	case strings.HasPrefix(subs[0][2], "ci"):
		// Just use whichever the user specified
		urlSuffix = subs[0][2]
	default:
		urlSuffix = "release"
	}
	url := fmt.Sprintf("%s/%s", kubeReleaseBucketURL, urlSuffix)
	return url, subs[0][3], nil
}

// Internal helper: return content of URL
func fetchFromURL(url string, timeout time.Duration) (string, error) {
	klog.V(2).Infof("fetching Kubernetes version from URL: %s", url)
	client := &http.Client{Timeout: timeout, Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
	resp, err := client.Get(url)
	if err != nil {
		return "", errors.Errorf("unable to get URL %q: %s", url, err.Error())
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", errors.Errorf("unable to read content of URL %q: %s", url, err.Error())
	}
	bodyString := strings.TrimSpace(string(body))

	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("unable to fetch file. URL: %q, status: %v", url, resp.Status)
		return bodyString, errors.New(msg)
	}
	return bodyString, nil
}

// kubeadmVersion returns the version of the client without metadata.
func kubeadmVersion(info string) (string, error) {
	v, err := versionutil.ParseSemantic(info)
	if err != nil {
		return "", errors.Wrap(err, "kubeadm version error")
	}
	// There is no utility in versionutil to get the version without the metadata,
	// so this needs some manual formatting.
	// Discard offsets after a release label and keep the labels down to e.g. `alpha.0` instead of
	// including the offset e.g. `alpha.0.206`. This is done to comply with GCR image tags.
	pre := v.PreRelease()
	patch := v.Patch()
	if len(pre) > 0 {
		if patch > 0 {
			// If the patch version is more than zero, decrement it and remove the label.
			// this is done to comply with the latest stable patch release.
			patch = patch - 1
			pre = ""
		} else {
			split := strings.Split(pre, ".")
			if len(split) > 2 {
				pre = split[0] + "." + split[1] // Exclude the third element
			} else if len(split) < 2 {
				pre = split[0] + ".0" // Append .0 to a partial label
			}
			pre = "-" + pre
		}
	}
	vStr := fmt.Sprintf("v%d.%d.%d%s", v.Major(), v.Minor(), patch, pre)
	return vStr, nil
}

// Validate if the remote version is one Minor release newer than the client version.
// This is done to conform with "stable-X" and only allow remote versions from
// the same Patch level release.
func validateStableVersion(remoteVersion, clientVersion string) (string, error) {
	verRemote, err := versionutil.ParseGeneric(remoteVersion)
	if err != nil {
		return "", errors.Wrap(err, "remote version error")
	}
	verClient, err := versionutil.ParseGeneric(clientVersion)
	if err != nil {
		return "", errors.Wrap(err, "client version error")
	}
	// If the remote Major version is bigger or if the Major versions are the same,
	// but the remote Minor is bigger use the client version release. This handles Major bumps too.
	if verClient.Major() < verRemote.Major() ||
		(verClient.Major() == verRemote.Major()) && verClient.Minor() < verRemote.Minor() {
		estimatedRelease := fmt.Sprintf("stable-%d.%d", verClient.Major(), verClient.Minor())
		klog.Infof("remote version is much newer: %s; falling back to: %s", remoteVersion, estimatedRelease)
		return estimatedRelease, nil
	}
	return remoteVersion, nil
}
<commit_msg>added comment<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package util

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/pkg/errors"

	netutil "k8s.io/apimachinery/pkg/util/net"
	versionutil "k8s.io/apimachinery/pkg/util/version"
	"k8s.io/klog"
	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
	pkgversion "k8s.io/kubernetes/pkg/version"
)

const (
	getReleaseVersionTimeout = time.Duration(10 * time.Second)
)

var (
	kubeReleaseBucketURL  = "https://dl.k8s.io"
	kubeReleaseRegex      = regexp.MustCompile(`^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?$`)
	kubeReleaseLabelRegex = regexp.MustCompile(`^[[:lower:]]+(-[-\w_\.]+)?$`)
	kubeBucketPrefixes    = regexp.MustCompile(`^((release|ci|ci-cross)/)?([-\w_\.+]+)$`)
)

// KubernetesReleaseVersion is helper function that can fetch
// available version information from release servers based on
// label names, like "stable" or "latest".
//
// If argument is already semantic version string, it
// will return same string.
//
// In case of labels, it tries to fetch from release
// servers and then return actual semantic version.
//
// Available names on release servers:
//  stable      (latest stable release)
//  stable-1    (latest stable release in 1.x)
//  stable-1.0  (and similarly 1.1, 1.2, 1.3, ...)
//  latest      (latest release, including alpha/beta)
//  latest-1    (latest release in 1.x, including alpha/beta)
//  latest-1.0  (and similarly 1.1, 1.2, 1.3, ...)
func KubernetesReleaseVersion(version string) (string, error) {
	return kubernetesReleaseVersion(version, fetchFromURL)
}

// kubernetesReleaseVersion is a helper function to fetch
// available version information. Used for testing to eliminate
// the need for internet calls.
func kubernetesReleaseVersion(version string, fetcher func(string, time.Duration) (string, error)) (string, error) {
	ver := normalizedBuildVersion(version)
	if len(ver) != 0 {
		return ver, nil
	}

	bucketURL, versionLabel, err := splitVersion(version)
	if err != nil {
		return "", err
	}

	// revalidate, if exact build from e.g. CI bucket requested.
	ver = normalizedBuildVersion(versionLabel)
	if len(ver) != 0 {
		return ver, nil
	}

	// kubeReleaseLabelRegex matches labels such as: latest, latest-1, latest-1.10
	if kubeReleaseLabelRegex.MatchString(versionLabel) {
		// Try to obtain a client version.
		// pkgversion.Get().String() should always return a correct version added by the golang
		// linker and the build system. The version can still be missing when doing unit tests
		// on individual packages.
		clientVersion, clientVersionErr := kubeadmVersion(pkgversion.Get().String())
		// Fetch version from the internet.
		url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
		body, err := fetcher(url, getReleaseVersionTimeout)
		if err != nil {
			// If the network operaton was successful but the server did not reply with StatusOK
			if body != "" {
				return "", err
			}
			if clientVersionErr == nil {
				// Handle air-gapped environments by falling back to the client version.
				klog.Warningf("could not fetch a Kubernetes version from the internet: %v", err)
				klog.Warningf("falling back to the local client version: %s", clientVersion)
				return kubernetesReleaseVersion(clientVersion, fetcher)
			}
		}

		if clientVersionErr != nil {
			if err != nil {
				klog.Warningf("could not obtain neither client nor remote version; fall back to: %s", constants.CurrentKubernetesVersion)
				return kubernetesReleaseVersion(constants.CurrentKubernetesVersion.String(), fetcher)
			}

			klog.Warningf("could not obtain client version; using remote version: %s", body)
			return kubernetesReleaseVersion(body, fetcher)
		}

		// both the client and the remote version are obtained; validate them and pick a stable version
		body, err = validateStableVersion(body, clientVersion)
		if err != nil {
			return "", err
		}
		// Re-validate received version and return.
		return kubernetesReleaseVersion(body, fetcher)
	}
	return "", errors.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}

// KubernetesVersionToImageTag is helper function that replaces all
// non-allowed symbols in tag strings with underscores.
// Image tag can only contain lowercase and uppercase letters, digits,
// underscores, periods and dashes.
// Current usage is for CI images where all of symbols except '+' are valid,
// but function is for generic usage where input can't be always pre-validated.
func KubernetesVersionToImageTag(version string) string {
	allowed := regexp.MustCompile(`[^-a-zA-Z0-9_\.]`)
	return allowed.ReplaceAllString(version, "_")
}

// KubernetesIsCIVersion checks if user requested CI version
func KubernetesIsCIVersion(version string) bool {
	subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
	if len(subs) == 1 && len(subs[0]) == 4 && strings.HasPrefix(subs[0][2], "ci") {
		return true
	}
	return false
}

// Internal helper: returns normalized build version (with "v" prefix if needed)
// If input doesn't match known version pattern, returns empty string.
func normalizedBuildVersion(version string) string {
	if kubeReleaseRegex.MatchString(version) {
		if strings.HasPrefix(version, "v") {
			return version
		}
		return "v" + version
	}
	return ""
}

// Internal helper: split version parts,
// Return base URL and cleaned-up version
func splitVersion(version string) (string, string, error) {
	var urlSuffix string
	subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
	if len(subs) != 1 || len(subs[0]) != 4 {
		return "", "", errors.Errorf("invalid version %q", version)
	}

	switch {
	case strings.HasPrefix(subs[0][2], "ci"):
		// Just use whichever the user specified
		urlSuffix = subs[0][2]
	default:
		urlSuffix = "release"
	}
	url := fmt.Sprintf("%s/%s", kubeReleaseBucketURL, urlSuffix)
	return url, subs[0][3], nil
}

// Internal helper: return content of URL
func fetchFromURL(url string, timeout time.Duration) (string, error) {
	klog.V(2).Infof("fetching Kubernetes version from URL: %s", url)
	client := &http.Client{Timeout: timeout, Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
	resp, err := client.Get(url)
	if err != nil {
		return "", errors.Errorf("unable to get URL %q: %s", url, err.Error())
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", errors.Errorf("unable to read content of URL %q: %s", url, err.Error())
	}
	bodyString := strings.TrimSpace(string(body))

	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("unable to fetch file. URL: %q, status: %v", url, resp.Status)
		return bodyString, errors.New(msg)
	}
	return bodyString, nil
}

// kubeadmVersion returns the version of the client without metadata.
func kubeadmVersion(info string) (string, error) {
	v, err := versionutil.ParseSemantic(info)
	if err != nil {
		return "", errors.Wrap(err, "kubeadm version error")
	}
	// There is no utility in versionutil to get the version without the metadata,
	// so this needs some manual formatting.
	// Discard offsets after a release label and keep the labels down to e.g. `alpha.0` instead of
	// including the offset e.g. `alpha.0.206`. This is done to comply with GCR image tags.
	pre := v.PreRelease()
	patch := v.Patch()
	if len(pre) > 0 {
		if patch > 0 {
			// If the patch version is more than zero, decrement it and remove the label.
			// this is done to comply with the latest stable patch release.
			patch = patch - 1
			pre = ""
		} else {
			split := strings.Split(pre, ".")
			if len(split) > 2 {
				pre = split[0] + "." + split[1] // Exclude the third element
			} else if len(split) < 2 {
				pre = split[0] + ".0" // Append .0 to a partial label
			}
			pre = "-" + pre
		}
	}
	vStr := fmt.Sprintf("v%d.%d.%d%s", v.Major(), v.Minor(), patch, pre)
	return vStr, nil
}

// Validate if the remote version is one Minor release newer than the client version.
// This is done to conform with "stable-X" and only allow remote versions from
// the same Patch level release.
func validateStableVersion(remoteVersion, clientVersion string) (string, error) {
	verRemote, err := versionutil.ParseGeneric(remoteVersion)
	if err != nil {
		return "", errors.Wrap(err, "remote version error")
	}
	verClient, err := versionutil.ParseGeneric(clientVersion)
	if err != nil {
		return "", errors.Wrap(err, "client version error")
	}
	// If the remote Major version is bigger or if the Major versions are the same,
	// but the remote Minor is bigger use the client version release. This handles Major bumps too.
	if verClient.Major() < verRemote.Major() ||
		(verClient.Major() == verRemote.Major()) && verClient.Minor() < verRemote.Minor() {
		estimatedRelease := fmt.Sprintf("stable-%d.%d", verClient.Major(), verClient.Minor())
		klog.Infof("remote version is much newer: %s; falling back to: %s", remoteVersion, estimatedRelease)
		return estimatedRelease, nil
	}
	return remoteVersion, nil
}
<|endoftext|>


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

package app

import (
	"fmt"
	"io"
	"net"
	"time"

	"github.com/spf13/cobra"
	corev1 "k8s.io/api/core/v1"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/server/healthz"
	genericoptions "k8s.io/apiserver/pkg/server/options"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/kubernetes-incubator/metrics-server/pkg/apiserver"
	genericmetrics "github.com/kubernetes-incubator/metrics-server/pkg/apiserver/generic"
	"github.com/kubernetes-incubator/metrics-server/pkg/manager"
	"github.com/kubernetes-incubator/metrics-server/pkg/provider/sink"
	"github.com/kubernetes-incubator/metrics-server/pkg/sources"
	"github.com/kubernetes-incubator/metrics-server/pkg/sources/summary"
)

// NewCommandStartMetricsServer provides a CLI handler for the metrics server entrypoint
func NewCommandStartMetricsServer(out, errOut io.Writer, stopCh <-chan struct{}) *cobra.Command {
	o := NewMetricsServerOptions()

	cmd := &cobra.Command{
		Short: "Launch metrics-server",
		Long:  "Launch metrics-server",
		RunE: func(c *cobra.Command, args []string) error {
			if err := o.Run(stopCh); err != nil {
				return err
			}
			return nil
		},
	}

	flags := cmd.Flags()
	flags.DurationVar(&o.MetricResolution, "metric-resolution", o.MetricResolution, "The resolution at which metrics-server will retain metrics.")

	flags.BoolVar(&o.InsecureKubeletTLS, "kubelet-insecure-tls", o.InsecureKubeletTLS, "Do not verify CA of serving certificates presented by Kubelets.  For testing purposes only.")
	flags.BoolVar(&o.DeprecatedCompletelyInsecureKubelet, "deprecated-kubelet-completely-insecure", o.DeprecatedCompletelyInsecureKubelet, "Do not use any encryption, authorization, or authentication when communicating with the Kubelet.")
	flags.IntVar(&o.KubeletPort, "kubelet-port", o.KubeletPort, "The port to use to connect to Kubelets.")
	flags.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "The path to the kubeconfig used to connect to the Kubernetes API server and the Kubelets (defaults to in-cluster config)")
	flags.StringSliceVar(&o.KubeletPreferredAddressTypes, "kubelet-preferred-address-types", o.KubeletPreferredAddressTypes, "The priority of node address types to use when determining which address to use to connect to a particular node")
	flags.StringVar(&o.KubeletCAFile, "kubelet-certificate-authority", "", "Path to the CA to use to validate the Kubelet's serving certificates.")

	flags.MarkDeprecated("deprecated-kubelet-completely-insecure", "This is rarely the right option, since it leaves kubelet communication completely insecure.  If you encounter auth errors, make sure you've enabled token webhook auth on the Kubelet, and if you're in a test cluster with self-signed Kubelet certificates, consider using kubelet-insecure-tls instead.")

	o.SecureServing.AddFlags(flags)
	o.Authentication.AddFlags(flags)
	o.Authorization.AddFlags(flags)
	o.Features.AddFlags(flags)

	return cmd
}

type MetricsServerOptions struct {
	// genericoptions.ReccomendedOptions - EtcdOptions
	SecureServing  *genericoptions.SecureServingOptionsWithLoopback
	Authentication *genericoptions.DelegatingAuthenticationOptions
	Authorization  *genericoptions.DelegatingAuthorizationOptions
	Features       *genericoptions.FeatureOptions

	Kubeconfig string

	// Only to be used to for testing
	DisableAuthForTesting bool

	MetricResolution time.Duration

	KubeletPort                  int
	InsecureKubeletTLS           bool
	KubeletPreferredAddressTypes []string
	KubeletCAFile                string

	DeprecatedCompletelyInsecureKubelet bool
}

// NewMetricsServerOptions constructs a new set of default options for metrics-server.
func NewMetricsServerOptions() *MetricsServerOptions {
	o := &MetricsServerOptions{
		SecureServing:  genericoptions.WithLoopback(genericoptions.NewSecureServingOptions()),
		Authentication: genericoptions.NewDelegatingAuthenticationOptions(),
		Authorization:  genericoptions.NewDelegatingAuthorizationOptions(),
		Features:       genericoptions.NewFeatureOptions(),

		MetricResolution:             60 * time.Second,
		KubeletPort:                  10250,
		KubeletPreferredAddressTypes: make([]string, len(summary.DefaultAddressTypePriority)),
	}

	for i, addrType := range summary.DefaultAddressTypePriority {
		o.KubeletPreferredAddressTypes[i] = string(addrType)
	}

	return o
}

func (o MetricsServerOptions) Config() (*apiserver.Config, error) {
	if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
		return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
	}

	serverConfig := genericapiserver.NewConfig(genericmetrics.Codecs)
	if err := o.SecureServing.ApplyTo(serverConfig); err != nil {
		return nil, err
	}

	if !o.DisableAuthForTesting {
		if err := o.Authentication.ApplyTo(&serverConfig.Authentication, serverConfig.SecureServing, nil); err != nil {
			return nil, err
		}
		if err := o.Authorization.ApplyTo(&serverConfig.Authorization); err != nil {
			return nil, err
		}
	}

	return &apiserver.Config{
		GenericConfig:  serverConfig,
		ProviderConfig: genericmetrics.ProviderConfig{},
	}, nil
}

func (o MetricsServerOptions) Run(stopCh <-chan struct{}) error {
	// grab the config for the API server
	config, err := o.Config()
	if err != nil {
		return err
	}
	config.GenericConfig.EnableMetrics = true

	// set up the client config
	var clientConfig *rest.Config
	if len(o.Kubeconfig) > 0 {
		loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig}
		loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})

		clientConfig, err = loader.ClientConfig()
	} else {
		clientConfig, err = rest.InClusterConfig()
	}
	if err != nil {
		return fmt.Errorf("unable to construct lister client config: %v", err)
	}

	// set up the informers
	kubeClient, err := kubernetes.NewForConfig(clientConfig)
	if err != nil {
		return fmt.Errorf("unable to construct lister client: %v", err)
	}
	// we should never need to resync, since we're not worried about missing events,
	// and resync is actually for regular interval-based reconciliation these days,
	// so set the default resync interval to 0
	informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)

	// set up the source manager
	kubeletRestCfg := rest.CopyConfig(clientConfig)
	if len(o.KubeletCAFile) > 0 {
		kubeletRestCfg.TLSClientConfig.CAFile = o.KubeletCAFile
		kubeletRestCfg.TLSClientConfig.CAData = nil
	}
	kubeletConfig := summary.GetKubeletConfig(kubeletRestCfg, o.KubeletPort, o.InsecureKubeletTLS, o.DeprecatedCompletelyInsecureKubelet)
	kubeletClient, err := summary.KubeletClientFor(kubeletConfig)
	if err != nil {
		return fmt.Errorf("unable to construct a client to connect to the kubelets: %v", err)
	}

	// set up an address resolver according to the user's priorities
	addrPriority := make([]corev1.NodeAddressType, len(o.KubeletPreferredAddressTypes))
	for i, addrType := range o.KubeletPreferredAddressTypes {
		addrPriority[i] = corev1.NodeAddressType(addrType)
	}
	addrResolver := summary.NewPriorityNodeAddressResolver(addrPriority)

	sourceProvider := summary.NewSummaryProvider(informerFactory.Core().V1().Nodes().Lister(), kubeletClient, addrResolver)
	scrapeTimeout := time.Duration(float64(o.MetricResolution) * 0.90) // scrape timeout is 90% of the scrape interval
	sources.RegisterDurationMetrics(scrapeTimeout)
	sourceManager := sources.NewSourceManager(sourceProvider, scrapeTimeout)

	// set up the in-memory sink and provider
	metricSink, metricsProvider := sink.NewSinkProvider()

	// set up the general manager
	manager.RegisterDurationMetrics(o.MetricResolution)
	mgr := manager.NewManager(sourceManager, metricSink, o.MetricResolution)

	// inject the providers into the config
	config.ProviderConfig.Node = metricsProvider
	config.ProviderConfig.Pod = metricsProvider

	// complete the config to get an API server
	server, err := config.Complete(informerFactory).New()
	if err != nil {
		return err
	}

	// add health checks
	server.AddHealthzChecks(healthz.NamedCheck("healthz", mgr.CheckHealth))

	// run everything (the apiserver runs the shared informer factory for us)
	mgr.RunUntil(stopCh)
	return server.GenericAPIServer.PrepareRun().Run(stopCh)
}
<commit_msg>cmd/metrics-server/app: refactor secure serving config<commit_after>// Copyright 2018 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package app

import (
	"fmt"
	"io"
	"net"
	"time"

	"github.com/spf13/cobra"
	corev1 "k8s.io/api/core/v1"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/apiserver/pkg/server/healthz"
	genericoptions "k8s.io/apiserver/pkg/server/options"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/kubernetes-incubator/metrics-server/pkg/apiserver"
	genericmetrics "github.com/kubernetes-incubator/metrics-server/pkg/apiserver/generic"
	"github.com/kubernetes-incubator/metrics-server/pkg/manager"
	"github.com/kubernetes-incubator/metrics-server/pkg/provider/sink"
	"github.com/kubernetes-incubator/metrics-server/pkg/sources"
	"github.com/kubernetes-incubator/metrics-server/pkg/sources/summary"
)

// NewCommandStartMetricsServer provides a CLI handler for the metrics server entrypoint
func NewCommandStartMetricsServer(out, errOut io.Writer, stopCh <-chan struct{}) *cobra.Command {
	o := NewMetricsServerOptions()

	cmd := &cobra.Command{
		Short: "Launch metrics-server",
		Long:  "Launch metrics-server",
		RunE: func(c *cobra.Command, args []string) error {
			if err := o.Run(stopCh); err != nil {
				return err
			}
			return nil
		},
	}

	flags := cmd.Flags()
	flags.DurationVar(&o.MetricResolution, "metric-resolution", o.MetricResolution, "The resolution at which metrics-server will retain metrics.")

	flags.BoolVar(&o.InsecureKubeletTLS, "kubelet-insecure-tls", o.InsecureKubeletTLS, "Do not verify CA of serving certificates presented by Kubelets.  For testing purposes only.")
	flags.BoolVar(&o.DeprecatedCompletelyInsecureKubelet, "deprecated-kubelet-completely-insecure", o.DeprecatedCompletelyInsecureKubelet, "Do not use any encryption, authorization, or authentication when communicating with the Kubelet.")
	flags.IntVar(&o.KubeletPort, "kubelet-port", o.KubeletPort, "The port to use to connect to Kubelets.")
	flags.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "The path to the kubeconfig used to connect to the Kubernetes API server and the Kubelets (defaults to in-cluster config)")
	flags.StringSliceVar(&o.KubeletPreferredAddressTypes, "kubelet-preferred-address-types", o.KubeletPreferredAddressTypes, "The priority of node address types to use when determining which address to use to connect to a particular node")
	flags.StringVar(&o.KubeletCAFile, "kubelet-certificate-authority", "", "Path to the CA to use to validate the Kubelet's serving certificates.")

	flags.MarkDeprecated("deprecated-kubelet-completely-insecure", "This is rarely the right option, since it leaves kubelet communication completely insecure.  If you encounter auth errors, make sure you've enabled token webhook auth on the Kubelet, and if you're in a test cluster with self-signed Kubelet certificates, consider using kubelet-insecure-tls instead.")

	o.SecureServing.AddFlags(flags)
	o.Authentication.AddFlags(flags)
	o.Authorization.AddFlags(flags)
	o.Features.AddFlags(flags)

	return cmd
}

type MetricsServerOptions struct {
	// genericoptions.ReccomendedOptions - EtcdOptions
	SecureServing  *genericoptions.SecureServingOptionsWithLoopback
	Authentication *genericoptions.DelegatingAuthenticationOptions
	Authorization  *genericoptions.DelegatingAuthorizationOptions
	Features       *genericoptions.FeatureOptions

	Kubeconfig string

	// Only to be used to for testing
	DisableAuthForTesting bool

	MetricResolution time.Duration

	KubeletPort                  int
	InsecureKubeletTLS           bool
	KubeletPreferredAddressTypes []string
	KubeletCAFile                string

	DeprecatedCompletelyInsecureKubelet bool
}

// NewMetricsServerOptions constructs a new set of default options for metrics-server.
func NewMetricsServerOptions() *MetricsServerOptions {
	o := &MetricsServerOptions{
		SecureServing:  genericoptions.NewSecureServingOptions().WithLoopback(),
		Authentication: genericoptions.NewDelegatingAuthenticationOptions(),
		Authorization:  genericoptions.NewDelegatingAuthorizationOptions(),
		Features:       genericoptions.NewFeatureOptions(),

		MetricResolution:             60 * time.Second,
		KubeletPort:                  10250,
		KubeletPreferredAddressTypes: make([]string, len(summary.DefaultAddressTypePriority)),
	}

	for i, addrType := range summary.DefaultAddressTypePriority {
		o.KubeletPreferredAddressTypes[i] = string(addrType)
	}

	return o
}

func (o MetricsServerOptions) Config() (*apiserver.Config, error) {
	if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
		return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
	}

	serverConfig := genericapiserver.NewConfig(genericmetrics.Codecs)
	if err := o.SecureServing.ApplyTo(&serverConfig.SecureServing, &serverConfig.LoopbackClientConfig); err != nil {
		return nil, err
	}

	if !o.DisableAuthForTesting {
		if err := o.Authentication.ApplyTo(&serverConfig.Authentication, serverConfig.SecureServing, nil); err != nil {
			return nil, err
		}
		if err := o.Authorization.ApplyTo(&serverConfig.Authorization); err != nil {
			return nil, err
		}
	}

	return &apiserver.Config{
		GenericConfig:  serverConfig,
		ProviderConfig: genericmetrics.ProviderConfig{},
	}, nil
}

func (o MetricsServerOptions) Run(stopCh <-chan struct{}) error {
	// grab the config for the API server
	config, err := o.Config()
	if err != nil {
		return err
	}
	config.GenericConfig.EnableMetrics = true

	// set up the client config
	var clientConfig *rest.Config
	if len(o.Kubeconfig) > 0 {
		loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig}
		loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})

		clientConfig, err = loader.ClientConfig()
	} else {
		clientConfig, err = rest.InClusterConfig()
	}
	if err != nil {
		return fmt.Errorf("unable to construct lister client config: %v", err)
	}

	// set up the informers
	kubeClient, err := kubernetes.NewForConfig(clientConfig)
	if err != nil {
		return fmt.Errorf("unable to construct lister client: %v", err)
	}
	// we should never need to resync, since we're not worried about missing events,
	// and resync is actually for regular interval-based reconciliation these days,
	// so set the default resync interval to 0
	informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)

	// set up the source manager
	kubeletRestCfg := rest.CopyConfig(clientConfig)
	if len(o.KubeletCAFile) > 0 {
		kubeletRestCfg.TLSClientConfig.CAFile = o.KubeletCAFile
		kubeletRestCfg.TLSClientConfig.CAData = nil
	}
	kubeletConfig := summary.GetKubeletConfig(kubeletRestCfg, o.KubeletPort, o.InsecureKubeletTLS, o.DeprecatedCompletelyInsecureKubelet)
	kubeletClient, err := summary.KubeletClientFor(kubeletConfig)
	if err != nil {
		return fmt.Errorf("unable to construct a client to connect to the kubelets: %v", err)
	}

	// set up an address resolver according to the user's priorities
	addrPriority := make([]corev1.NodeAddressType, len(o.KubeletPreferredAddressTypes))
	for i, addrType := range o.KubeletPreferredAddressTypes {
		addrPriority[i] = corev1.NodeAddressType(addrType)
	}
	addrResolver := summary.NewPriorityNodeAddressResolver(addrPriority)

	sourceProvider := summary.NewSummaryProvider(informerFactory.Core().V1().Nodes().Lister(), kubeletClient, addrResolver)
	scrapeTimeout := time.Duration(float64(o.MetricResolution) * 0.90) // scrape timeout is 90% of the scrape interval
	sources.RegisterDurationMetrics(scrapeTimeout)
	sourceManager := sources.NewSourceManager(sourceProvider, scrapeTimeout)

	// set up the in-memory sink and provider
	metricSink, metricsProvider := sink.NewSinkProvider()

	// set up the general manager
	manager.RegisterDurationMetrics(o.MetricResolution)
	mgr := manager.NewManager(sourceManager, metricSink, o.MetricResolution)

	// inject the providers into the config
	config.ProviderConfig.Node = metricsProvider
	config.ProviderConfig.Pod = metricsProvider

	// complete the config to get an API server
	server, err := config.Complete(informerFactory).New()
	if err != nil {
		return err
	}

	// add health checks
	server.AddHealthzChecks(healthz.NamedCheck("healthz", mgr.CheckHealth))

	// run everything (the apiserver runs the shared informer factory for us)
	mgr.RunUntil(stopCh)
	return server.GenericAPIServer.PrepareRun().Run(stopCh)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/1and1/soma/internal/adm"
	"github.com/1and1/soma/internal/cmpl"
	"github.com/1and1/soma/lib/proto"
	"github.com/codegangsta/cli"
)

func registerValidity(app cli.App) *cli.App {
	app.Commands = append(app.Commands,
		[]cli.Command{
			{
				Name:  "validity",
				Usage: "SUBCOMMANDS for system property validity",
				Subcommands: []cli.Command{
					{
						Name:         "create",
						Usage:        "Create a new system property validity",
						Action:       runtime(cmdValidityCreate),
						BashComplete: cmpl.ValidityCreate,
					},
					{
						Name:   "delete",
						Usage:  "Delete a system property validity",
						Action: runtime(cmdValidityDelete),
					},
					{
						Name:   "list",
						Usage:  "List system property validity records",
						Action: runtime(cmdValidityList),
					},
					{
						Name:   "show",
						Usage:  "Show details about a system property validity",
						Action: runtime(cmdValidityShow),
					},
				},
			},
		}...,
	)
	return &app
}

func cmdValidityCreate(c *cli.Context) error {
	multiple := []string{}
	unique := []string{"on", "direct", "inherited"}
	required := []string{"on", "direct", "inherited"}

	opts := map[string][]string{}
	if err := adm.ParseVariadicArguments(
		opts,
		multiple,
		unique,
		required,
		c.Args().Tail()); err != nil {
		return err
	}

	req := proto.Request{}
	req.Validity = &proto.Validity{
		SystemProperty: c.Args().First(),
		ObjectType:     opts["on"][0],
		Direct:         utl.GetValidatedBool(opts["direct"][0]),
		Inherited:      utl.GetValidatedBool(opts["inherited"][0]),
	}

	resp := utl.PostRequestWithBody(Client, req, "/validity/")
	fmt.Println(resp)
	return nil
}

func cmdValidityDelete(c *cli.Context) error {
	if err := adm.VerifySingleArgument(c); err != nil {
		return err
	}

	path := fmt.Sprintf("/validity/%s", c.Args().First())

	resp := utl.DeleteRequest(Client, path)
	fmt.Println(resp)
	return nil
}

func cmdValidityList(c *cli.Context) error {
	resp := utl.GetRequest(Client, "/validity/")
	fmt.Println(resp)
	return nil
}

func cmdValidityShow(c *cli.Context) error {
	if err := adm.VerifySingleArgument(c); err != nil {
		return err
	}

	path := fmt.Sprintf("/validity/%s", c.Args().First())

	resp := utl.GetRequest(Client, path)
	fmt.Println(resp)
	return nil
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>somaadm: convert cmdValidity* from util to adm<commit_after>package main

import (
	"fmt"
	"net/url"
	"strconv"

	"github.com/1and1/soma/internal/adm"
	"github.com/1and1/soma/internal/cmpl"
	"github.com/1and1/soma/lib/proto"
	"github.com/codegangsta/cli"
)

func registerValidity(app cli.App) *cli.App {
	app.Commands = append(app.Commands,
		[]cli.Command{
			{
				Name:  "validity",
				Usage: "SUBCOMMANDS for system property validity",
				Subcommands: []cli.Command{
					{
						Name:         "create",
						Usage:        "Create a new system property validity",
						Action:       runtime(cmdValidityCreate),
						BashComplete: cmpl.ValidityCreate,
					},
					{
						Name:   "delete",
						Usage:  "Delete a system property validity",
						Action: runtime(cmdValidityDelete),
					},
					{
						Name:   "list",
						Usage:  "List system property validity records",
						Action: runtime(cmdValidityList),
					},
					{
						Name:   "show",
						Usage:  "Show details about a system property validity",
						Action: runtime(cmdValidityShow),
					},
				},
			},
		}...,
	)
	return &app
}

func cmdValidityCreate(c *cli.Context) error {
	unique := []string{"on", "direct", "inherited"}
	required := []string{"on", "direct", "inherited"}
	opts := map[string][]string{}
	if err := adm.ParseVariadicArguments(
		opts,
		[]string{},
		unique,
		required,
		c.Args().Tail(),
	); err != nil {
		return err
	}

	req := proto.Request{}
	req.Validity = &proto.Validity{
		SystemProperty: c.Args().First(),
		ObjectType:     opts["on"][0],
	}

	{
		var err error
		if req.Validity.Direct, err = strconv.ParseBool(
			opts[`direct`][0]); err != nil {
			return err
		}
		if req.Validity.Inherited, err = strconv.ParseBool(
			opts[`inherited`][0]); err != nil {
			return err
		}
	}

	if resp, err := adm.PostReqBody(req, `/validity/`); err != nil {
		return err
	} else {
		return adm.FormatOut(c, resp, `command`)
	}
}

func cmdValidityDelete(c *cli.Context) error {
	if err := adm.VerifySingleArgument(c); err != nil {
		return err
	}

	esc := url.QueryEscape(c.Args().First())
	path := fmt.Sprintf("/validity/%s", esc)
	if resp, err := adm.DeleteReq(path); err != nil {
		return err
	} else {
		return adm.FormatOut(c, resp, `command`)
	}
}

func cmdValidityList(c *cli.Context) error {
	if err := adm.VerifyNoArgument(c); err != nil {
		return err
	}

	if resp, err := adm.GetReq(`/validity/`); err != nil {
		return err
	} else {
		return adm.FormatOut(c, resp, `list`)
	}
}

func cmdValidityShow(c *cli.Context) error {
	if err := adm.VerifySingleArgument(c); err != nil {
		return err
	}

	esc := url.QueryEscape(c.Args().First())
	path := fmt.Sprintf("/validity/%s", esc)
	if resp, err := adm.GetReq(path); err != nil {
		return err
	} else {
		return adm.FormatOut(c, resp, `show`)
	}
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"golang.org/x/sys/unix"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

var (
	help    = flag.Bool("h", false, "Help")
	version = flag.Bool("V", false, "Version")
)

func usage() string {
	return "switch_root [-h] [-V]\nswitch_root newroot init"
}

// get device for file descriptor
func getDev(fd int) (dev uint64, err error) {
	var stat syscall.Stat_t

	if err := syscall.Fstat(fd, &stat); err != nil {
		return 0, err
	}

	return stat.Dev, nil
}

// check for the symlink bit
func isSymlink(mode uint32) bool {
	// numbers come from `man fstatat`
	return (mode & 0170000) == 0120000
}

// recursively deletes a directory and everything in it
// works with a file descriptor, can delete files not referenceable
// any more (e.g. after chroot)
// does not descent into mounts
func recursiveDelete(fd int) error {
	var rb syscall.Stat_t

	if err := syscall.Fstat(fd, &rb); err != nil {
		return err
	}

	// may need to call syscall.ReadDirent multiple times
	for {

		// get the subfiles in buff
		buff := make([]byte, 4096)
		var nbuff int
		nbuff, err := syscall.ReadDirent(fd, buff)
		if err != nil {
			return err
		}
		if nbuff <= 0 {
			break
		}

		_, _, names := syscall.ParseDirent(buff, nbuff, []string{})

		for _, name := range names {
			// try to open it with O_DIRECTORY to know if it's a directory
			// NOTE O_DIRECTORY is linux specific
			namefd, err := syscall.Openat(fd, name, syscall.O_DIRECTORY, syscall.O_RDWR)
			if err != nil {
				// just delete files
				if err := unix.Unlinkat(fd, name, 0); err != nil {
					return err
				}
			} else {
				defer syscall.Close(namefd)

				var nameStatT unix.Stat_t
				if err := unix.Fstatat(fd, name, &nameStatT, unix.AT_SYMLINK_NOFOLLOW); err != nil {
					return err
				}

				if fdDev, err := getDev(fd); err != nil {
					return err
				} else if fdDev != nameStatT.Dev {
					// on different filesystem, don't go there
					continue
				}

				// not actually a dir, but a symlink to a dir, treat like file and remove
				if isSymlink(nameStatT.Mode) {
					if err := unix.Unlinkat(fd, name, 0); err != nil {
						return err
					}
					continue
				}

				// actual recursion
				if err := recursiveDelete(namefd); err != nil {
					return err
				}
				// back from recursion, dir is not empty, delete
				if err := unix.Unlinkat(fd, name, unix.AT_REMOVEDIR); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// do a proper exec to retain PID 1.
// It returns an error if the command exits incorrectly.
func execCommand(path string) error {
	return syscall.Exec(path, []string{path}, []string{})
}

// check if dir is empty
func isEmpty(name string) (bool, error) {
	f, err := os.Open(name)
	if err != nil {
		return false, err
	}
	defer f.Close()

	_, err = f.Readdirnames(1)
	if err == io.EOF { // this means emtpy
		return true, nil
	}
	return false, err
}

// moves a mountpoint
func moveMount(oldPath string, newPath string) error {
	return syscall.Mount(oldPath, newPath, "", syscall.MS_MOVE, "")
}

// moves common 'special' mounts if the exist
func specialFS(newRoot string) error {
	var mounts = []string{"/dev", "/proc", "/sys", "/run"}

	for _, mount := range mounts {
		path := filepath.Join(newRoot, mount)
		// skip all mounting if the dir does not exists
		if _, err := os.Stat(mount); os.IsNotExist(err) {
			fmt.Println("switch_root: Skipping", mount)
			continue
		} else if err != nil {
			return err
		}
		// make sure the target dir exist and is empty
		if _, err := os.Stat(path); os.IsNotExist(err) {
			if err := syscall.Mkdir(path, 0); err != nil {
				return err
			}
		} else if err != nil {
			return err
		}
		if empty, err := isEmpty(path); err != nil {
			return err
		} else if !empty {
			return fmt.Errorf("%v must be empty", path)
		}
		if err := moveMount(mount, path); err != nil {
			return err
		}
	}
	return nil
}

// switchroot moves special mounts (dev, proc, sys, run) to the new dir,
// then does a chroot, moves the root mount to the new dir and finally
// DELETES EVERYTHING in the old dir and execs init
func switchRoot(newRoot string, init string) error {
	log.Printf("switch_root: moving mounts")
	if err := specialFS(newRoot); err != nil {
		return fmt.Errorf("switch_root: moving mounts failed %v", err)
	}

	log.Printf("switch_root: Changing directory")
	if err := syscall.Chdir(newRoot); err != nil {
		return fmt.Errorf("switch_root: failed change directory to new_root %v", err)
	}

	// Open "/" now, we need the file descriptor later
	oldRoot, err := os.Open("/")
	if err != nil {
		return err
	}
	defer oldRoot.Close()

	log.Printf("switch_root: Moving /")
	if err := moveMount(newRoot, "/"); err != nil {
		return err
	}

	log.Printf("switch_root: Changing root!")
	if err := syscall.Chroot("."); err != nil {
		return fmt.Errorf("switch_root: fatal chroot error %v", err)
	}

	log.Printf("switch_root: Deleting old /")
	if err := recursiveDelete(int(oldRoot.Fd())); err != nil {
		panic(err)
	}

	log.Printf("switch_root: executing init")
	if err := execCommand(init); err != nil {
		return fmt.Errorf("switch_root: exec failed %v", err)
	}

	return nil
}

func main() {
	flag.Parse()

	if len(flag.Args()) == 0 {
		fmt.Println(usage())
		os.Exit(0)
	}

	if *help {
		fmt.Println(usage())
		os.Exit(0)
	}

	if *version {
		fmt.Println("Version XX")
		os.Exit(0)
	}

	newRoot := flag.Args()[0]
	init := flag.Args()[1]

	if err := switchRoot(newRoot, init); err != nil {
		log.Fatalf("switch_root failed %v\n", err)
	}
}
<commit_msg>Rewrite recursive delete func<commit_after>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"golang.org/x/sys/unix"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

var (
	help    = flag.Bool("h", false, "Help")
	version = flag.Bool("V", false, "Version")
)

func usage() string {
	return "switch_root [-h] [-V]\nswitch_root newroot init"
}

// get device for file descriptor
func getDev(fd int) (dev uint64, err error) {
	var stat syscall.Stat_t

	if err := syscall.Fstat(fd, &stat); err != nil {
		return 0, err
	}

	return stat.Dev, nil
}

// recursively deletes a directory and everything in it
// works with a file descriptor, can delete files not referenceable
// any more (e.g. after chroot)
// does not descent into mounts
func recursiveDelete(fd int) error {
	parentDev, err := getDev(fd)
	if err != nil {
		return err
	}

  // the file descriptor is already open, but allocating a os.File
	// here for it makes reading the files in the dir so much nicer
	dir := os.NewFile(uintptr(fd), "__ignored__") // filename is completely irrelevant
	defer dir.Close()
	names, err := dir.Readdirnames(-1)
	if err != nil {
		return err
	}

	for _, name := range names {
		// loop here, but handle loop in separate function to make defer work as expected
		if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
			return err
		}
	}
	return nil
}

// don't call this directly, this function is the loop content of recursiveDelete
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
	// O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all symlinks (even when pointing to a dir)
	// we need to filter out symlinks because getDev later follows them
	childFd, err := syscall.Openat(parentFd, childName, syscall.O_DIRECTORY | syscall.O_NOFOLLOW, syscall.O_RDWR)
	if err != nil {
		// either file or symlink, delete in any case
		if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
			return err
		}
	} else {
		// open succeeded, which means it is a real directory
		defer unix.Close(childFd)

		// don't descent into other file systems
		if childFdDev, err := getDev(childFd); err != nil {
			return err
		} else if childFdDev != parentDev {
			// this means continue in recursiveDelete
			return nil
		}

		if err:= recursiveDelete(childFd); err != nil {
			return err
		}
		// back from recursion, dir is now empty, delete
		if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
			return err
		}
	}
	return nil
}

// do a proper exec to retain PID 1.
// It returns an error if the command exits incorrectly.
func execCommand(path string) error {
	return syscall.Exec(path, []string{path}, []string{})
}

// check if dir is empty
func isEmpty(name string) (bool, error) {
	f, err := os.Open(name)
	if err != nil {
		return false, err
	}
	defer f.Close()

	_, err = f.Readdirnames(1)
	if err == io.EOF { // this means emtpy
		return true, nil
	}
	return false, err
}

// moves a mountpoint
func moveMount(oldPath string, newPath string) error {
	return syscall.Mount(oldPath, newPath, "", syscall.MS_MOVE, "")
}

// moves common 'special' mounts if the exist
func specialFS(newRoot string) error {
	var mounts = []string{"/dev", "/proc", "/sys", "/run"}

	for _, mount := range mounts {
		path := filepath.Join(newRoot, mount)
		// skip all mounting if the dir does not exists
		if _, err := os.Stat(mount); os.IsNotExist(err) {
			fmt.Println("switch_root: Skipping", mount)
			continue
		} else if err != nil {
			return err
		}
		// make sure the target dir exist and is empty
		if _, err := os.Stat(path); os.IsNotExist(err) {
			if err := syscall.Mkdir(path, 0); err != nil {
				return err
			}
		} else if err != nil {
			return err
		}
		if empty, err := isEmpty(path); err != nil {
			return err
		} else if !empty {
			return fmt.Errorf("%v must be empty", path)
		}
		if err := moveMount(mount, path); err != nil {
			return err
		}
	}
	return nil
}

// switchroot moves special mounts (dev, proc, sys, run) to the new dir,
// then does a chroot, moves the root mount to the new dir and finally
// DELETES EVERYTHING in the old dir and execs init
func switchRoot(newRoot string, init string) error {
	log.Printf("switch_root: moving mounts")
	if err := specialFS(newRoot); err != nil {
		return fmt.Errorf("switch_root: moving mounts failed %v", err)
	}

	log.Printf("switch_root: Changing directory")
	if err := syscall.Chdir(newRoot); err != nil {
		return fmt.Errorf("switch_root: failed change directory to new_root %v", err)
	}

	// Open "/" now, we need the file descriptor later
	oldRoot, err := os.Open("/")
	if err != nil {
		return err
	}
	defer oldRoot.Close()

	log.Printf("switch_root: Moving /")
	if err := moveMount(newRoot, "/"); err != nil {
		return err
	}

	log.Printf("switch_root: Changing root!")
	if err := syscall.Chroot("."); err != nil {
		return fmt.Errorf("switch_root: fatal chroot error %v", err)
	}

	log.Printf("switch_root: Deleting old /")
	if err := recursiveDelete(int(oldRoot.Fd())); err != nil {
		panic(err)
	}

	log.Printf("switch_root: executing init")
	if err := execCommand(init); err != nil {
		return fmt.Errorf("switch_root: exec failed %v", err)
	}

	return nil
}

func main() {
	flag.Parse()

	if len(flag.Args()) == 0 {
		fmt.Println(usage())
		os.Exit(0)
	}

	if *help {
		fmt.Println(usage())
		os.Exit(0)
	}

	if *version {
		fmt.Println("Version XX")
		os.Exit(0)
	}

	newRoot := flag.Args()[0]
	init := flag.Args()[1]

	if err := switchRoot(newRoot, init); err != nil {
		log.Fatalf("switch_root failed %v\n", err)
	}
}
<|endoftext|>


<commit_before>// Package sendgrid provides a simple interface to interact with the SendGrid API
package sendgrid

import (
	"github.com/sendgrid/rest" // depends on version 2.2.0
	"github.com/sendgrid/sendgrid-go/helpers/mail"
)

// Version is this client library's current version
const Version = "3.1.0"

// Client ...
type Client struct {
	// rest.Request
	rest.Request
}

// GetRequest returns a default request object.
func GetRequest(key string, endpoint string, host string) rest.Request {
	if host == "" {
		host = "https://api.sendgrid.com"
	}
	baseURL := host + endpoint
	requestHeaders := map[string]string{
		requestHeaders["Authorization"] = "Bearer " + key,
		requestHeaders["User-Agent"] = "sendgrid/" + Version + ";go",
		requestHeaders["Accept"] = "application/json",
	}
	request := rest.Request{
		BaseURL: baseURL,
		Headers: requestHeaders,
	}
	return request
}

//Send ...
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
	cl.Body = mail.GetRequestBody(email)
	return API(cl.Request)
}

// NewSendClient ...
func NewSendClient(key string) *Client {
	request := GetRequest(key, "/v3/mail/send", "")
	request.Method = "POST"
	return &Client{request}
}

// DefaultClient is used if no custom HTTP client is defined
var DefaultClient = rest.DefaultClient

// API sets up the request to the SendGrid API, this is main interface.
func API(request rest.Request) (*rest.Response, error) {
	return DefaultClient.API(request)
}
<commit_msg>not =, :<commit_after>// Package sendgrid provides a simple interface to interact with the SendGrid API
package sendgrid

import (
	"github.com/sendgrid/rest" // depends on version 2.2.0
	"github.com/sendgrid/sendgrid-go/helpers/mail"
)

// Version is this client library's current version
const Version = "3.1.0"

// Client ...
type Client struct {
	// rest.Request
	rest.Request
}

// GetRequest returns a default request object.
func GetRequest(key string, endpoint string, host string) rest.Request {
	if host == "" {
		host = "https://api.sendgrid.com"
	}
	baseURL := host + endpoint
	requestHeaders := map[string]string{
		requestHeaders["Authorization"]: "Bearer " + key,
		requestHeaders["User-Agent"]: "sendgrid/" + Version + ";go",
		requestHeaders["Accept"]: "application/json",
	}
	request := rest.Request{
		BaseURL: baseURL,
		Headers: requestHeaders,
	}
	return request
}

//Send ...
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
	cl.Body = mail.GetRequestBody(email)
	return API(cl.Request)
}

// NewSendClient ...
func NewSendClient(key string) *Client {
	request := GetRequest(key, "/v3/mail/send", "")
	request.Method = "POST"
	return &Client{request}
}

// DefaultClient is used if no custom HTTP client is defined
var DefaultClient = rest.DefaultClient

// API sets up the request to the SendGrid API, this is main interface.
func API(request rest.Request) (*rest.Response, error) {
	return DefaultClient.API(request)
}
<|endoftext|>


<commit_before>package mend

import (
	"errors"
	"reflect"
)

func Mend(dst, src interface{}) error {
	vdst := reflect.ValueOf(dst)
	vsrc := reflect.ValueOf(src)
	if src == nil || dst == nil {
		return errors.New("Nil value passed to mend")
	}
	if vsrc.Kind() == reflect.Ptr {
		vsrc = vsrc.Elem()
	}
	return mend(vdst, vsrc)
}

func mend(dst, src reflect.Value) error {
	switch dst.Kind() {
	case reflect.Struct:
		for i := 0; i < dst.NumField(); i++ {
			mend(dst.Field(i), src.Field(i))
		}
	case reflect.Slice:
		if !dst.CanAddr() {
			return nil
		}
		dst.Set(reflect.AppendSlice(dst, src))

	case reflect.Map:
		for _, key := range src.MapKeys() {
			srcIndex := src.MapIndex(key)
			dstIndex := dst.MapIndex(key)

			if !isZero(srcIndex) && isZero(dstIndex) {
				dst.SetMapIndex(key, srcIndex)
			} else {
				switch reflect.TypeOf(srcIndex.Interface()).Kind() {
				case reflect.Map:
					mend(dstIndex, srcIndex)
				}
			}
		}
	case reflect.Interface, reflect.Ptr:
		mend(dst.Elem(), src)

	default:
		if !isZero(src.Interface()) && isZero(dst.Interface()) {
			dst.Set(src)
		}

	}
	return nil
}

func isZero(x interface{}) bool {
	return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}
<commit_msg>pass on unexported field<commit_after>package mend

import (
	"errors"
	"reflect"
)

func Mend(dst, src interface{}) error {
	vdst := reflect.ValueOf(dst)
	vsrc := reflect.ValueOf(src)
	if src == nil || dst == nil {
		return errors.New("Nil value passed to mend")
	}
	if vsrc.Kind() == reflect.Ptr {
		vsrc = vsrc.Elem()
	}
	return mend(vdst, vsrc)
}

func mend(dst, src reflect.Value) error {
	switch dst.Kind() {
	case reflect.Struct:
		for i := 0; i < dst.NumField(); i++ {
			mend(dst.Field(i), src.Field(i))
		}
	case reflect.Slice:
		if !dst.CanAddr() {
			return nil
		}
		dst.Set(reflect.AppendSlice(dst, src))

	case reflect.Map:
		for _, key := range src.MapKeys() {
			srcIndex := src.MapIndex(key)
			dstIndex := dst.MapIndex(key)

			if !isZero(srcIndex) && isZero(dstIndex) {

				dst.SetMapIndex(key, srcIndex)
			} else {
				switch reflect.TypeOf(srcIndex.Interface()).Kind() {
				case reflect.Map:
					mend(dstIndex, srcIndex)
				}
			}
		}
	case reflect.Interface, reflect.Ptr:
		mend(dst.Elem(), src)

	default:
		if dst.CanSet() {
			// dest is empty and src has a value
			if !isZero(src.Interface()) && isZero(dst.Interface()) {
				dst.Set(src)
			}
		}
	}
	return nil
}

func isZero(x interface{}) bool {
	return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}
<|endoftext|>


<commit_before>// Package sessions provides sessions support for net/http and valyala/fasthttp
// unique with auto-GC, register unlimited number of databases to Load and Update/Save the sessions in external server or to an external (no/or/and sql) database
// Usage net/http:
// // init a new sessions manager( if you use only one web framework inside your app then you can use the package-level functions like: sessions.Start/sessions.Destroy)
// manager := sessions.New(sessions.Config{})
// // start a session for a particular client
// manager.Start(http.ResponseWriter, *http.Request)
//
// // destroy a session from the server and client,
//  // don't call it on each handler, only on the handler you want the client to 'logout' or something like this:
// manager.Destroy(http.ResponseWriter, *http.Request)
//
//
// Usage valyala/fasthttp:
// // init a new sessions manager( if you use only one web framework inside your app then you can use the package-level functions like: sessions.Start/sessions.Destroy)
// manager := sessions.New(sessions.Config{})
// // start a session for a particular client
// manager.StartFasthttp(*fasthttp.RequestCtx)
//
// // destroy a session from the server and client,
//  // don't call it on each handler, only on the handler you want the client to 'logout' or something like this:
// manager.DestroyFasthttp(*fasthttp.Request)
//
// Note that, now, you can use both fasthttp and net/http within the same sessions manager(.New) instance!
// So now, you can share sessions between a net/http app and valyala/fasthttp app
package sessions

import (
	"encoding/base64"
	"net/http"
	"strings"
	"time"

	"github.com/valyala/fasthttp"
)

const (
	// Version current version number
	Version = "1.0.0"
)

type (
	// Sessions is the start point of this package
	// contains all the registered sessions and manages them
	Sessions interface {
		// Set options/configuration fields in runtime
		Set(...OptionSetter)

		// UseDatabase ,optionally, adds a session database to the manager's provider,
		// a session db doesn't have write access
		// see https://github.com/kataras/go-sessions/tree/master/sessiondb
		UseDatabase(Database)

		// Start starts the session for the particular net/http request
		Start(http.ResponseWriter, *http.Request) Session

		// Destroy kills the net/http session and remove the associated cookie
		Destroy(http.ResponseWriter, *http.Request)

		// StartFasthttp starts the session for the particular valyala/fasthttp request
		StartFasthttp(*fasthttp.RequestCtx) Session

		// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
		DestroyFasthttp(*fasthttp.RequestCtx)

		// DestroyByID removes the session entry
		// from the server-side memory (and database if registered).
		// Client's session cookie will still exist but it will be reseted on the next request.
		//
		// It's safe to use it even if you are not sure if a session with that id exists.
		// Works for both net/http & fasthttp
		DestroyByID(string)
		// DestroyAll removes all sessions
		// from the server-side memory (and database if registered).
		// Client's session cookie will still exist but it will be reseted on the next request.
		// Works for both net/http & fasthttp
		DestroyAll()
	}

	// sessions contains the cookie's name, the provider and a duration for GC and cookie life expire
	sessions struct {
		config   Config
		provider *provider
	}
)

// New creates & returns a new Sessions(manager) and start its GC (calls the .Init)
func New(setters ...OptionSetter) Sessions {
	c := Config{}.Validate()
	sess := &sessions{config: c, provider: newProvider()}
	sess.Set(setters...)

	return sess
}

var defaultSessions = New(Config{}.Validate())

// Set options/configuration fields in runtime
func Set(setters ...OptionSetter) {
	defaultSessions.Set(setters...)
}

func (s *sessions) Set(setters ...OptionSetter) {
	for _, setter := range setters {
		setter.Set(&s.config)
	}
}

// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func UseDatabase(db Database) {
	defaultSessions.UseDatabase(db)
}

// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func (s *sessions) UseDatabase(db Database) {
	s.provider.RegisterDatabase(db)
}

// Start starts the session for the particular net/http request
func Start(res http.ResponseWriter, req *http.Request) Session {
	return defaultSessions.Start(res, req)
}

// Start starts the session for the particular net/http request
func (s *sessions) Start(res http.ResponseWriter, req *http.Request) Session {
	var sess Session

	cookieValue := GetCookie(s.config.Cookie, req)
	if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
		sid := SessionIDGenerator(s.config.CookieLength)
		sess = s.provider.Init(sid, s.config.Expires)
		cookie := &http.Cookie{}

		// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
		cookie.Name = s.config.Cookie
		cookie.Value = sid
		cookie.Path = "/"
		if !s.config.DisableSubdomainPersistence {

			requestDomain := req.URL.Host
			if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
				requestDomain = requestDomain[0:portIdx]
			}
			if IsValidCookieDomain(requestDomain) {

				// RFC2109, we allow level 1 subdomains, but no further
				// if we have localhost.com , we want the localhost.cos.
				// so if we have something like: mysubdomain.localhost.com we want the localhost here
				// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
				// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
				if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
					// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
					s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
					if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
						//is mysubdomain.localhost ||  mysubsubdomain.mysubdomain.localhost
						s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
					}
					// replace the s with the requestDomain before the domain's siffux
					subdomainSuff := strings.LastIndexByte(requestDomain, '.')
					if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
						requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
					}
				}
				// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
				cookie.Domain = "." + requestDomain // . to allow persistance
			}

		}
		cookie.HttpOnly = true

		// MaxAge=0 means no 'Max-Age' attribute specified.
		// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
		// MaxAge>0 means Max-Age attribute present and given in seconds
		if s.config.Expires >= 0 {
			if s.config.Expires == 0 { // unlimited life
				cookie.Expires = CookieExpireUnlimited
			} else { // > 0
				cookie.Expires = time.Now().Add(s.config.Expires)
			}
			cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
		} else {
			// if it's -1 then the cookie is deleted when the browser closes
			// so MaxAge = -1
			cookie.MaxAge = -1
		}

		AddCookie(cookie, res)
	} else {
		sess = s.provider.Read(cookieValue, s.config.Expires)
	}
	return sess
}

// Destroy kills the net/http session and remove the associated cookie
func Destroy(res http.ResponseWriter, req *http.Request) {
	defaultSessions.Destroy(res, req)
}

// Destroy kills the net/http session and remove the associated cookie
func (s *sessions) Destroy(res http.ResponseWriter, req *http.Request) {
	cookieValue := GetCookie(s.config.Cookie, req)
	if cookieValue == "" { // nothing to destroy
		return
	}
	RemoveCookie(s.config.Cookie, res, req)
	s.provider.Destroy(cookieValue)
}

// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
// Works for both net/http & fasthttp
func DestroyByID(sid string) {
	defaultSessions.DestroyByID(sid)
}

// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
// Works for both net/http & fasthttp
func (s *sessions) DestroyByID(sid string) {
	s.provider.Destroy(sid)
}

// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
// Works for both net/http & fasthttp
func DestroyAll() {
	defaultSessions.DestroyAll()
}

// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
// Works for both net/http & fasthttp
func (s *sessions) DestroyAll() {
	s.provider.DestroyAll()
}

// StartFasthttp starts the session for the particular valyala/fasthttp request
func StartFasthttp(reqCtx *fasthttp.RequestCtx) Session {
	return defaultSessions.StartFasthttp(reqCtx)
}

// Start starts the session for the particular valyala/fasthttp request
func (s *sessions) StartFasthttp(reqCtx *fasthttp.RequestCtx) Session {
	var sess Session

	cookieValue := GetFasthttpCookie(s.config.Cookie, reqCtx)

	if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
		sid := SessionIDGenerator(s.config.CookieLength)
		sess = s.provider.Init(sid, s.config.Expires)
		cookie := fasthttp.AcquireCookie()
		// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
		cookie.SetKey(s.config.Cookie)
		cookie.SetValue(sid)
		cookie.SetPath("/")
		if !s.config.DisableSubdomainPersistence {
			requestDomain := string(reqCtx.Host())
			if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
				requestDomain = requestDomain[0:portIdx]
			}
			if IsValidCookieDomain(requestDomain) {

				// RFC2109, we allow level 1 subdomains, but no further
				// if we have localhost.com , we want the localhost.cos.
				// so if we have something like: mysubdomain.localhost.com we want the localhost here
				// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
				// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
				if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
					// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
					s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
					if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
						//is mysubdomain.localhost ||  mysubsubdomain.mysubdomain.localhost
						s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
					}
					// replace the s with the requestDomain before the domain's siffux
					subdomainSuff := strings.LastIndexByte(requestDomain, '.')
					if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
						requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
					}
				}
				// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
				cookie.SetDomain("." + requestDomain) // . to allow persistance
			}

		}
		cookie.SetHTTPOnly(true)
		if s.config.Expires == 0 {
			// unlimited life
			cookie.SetExpire(CookieExpireUnlimited)
		} else if s.config.Expires > 0 {
			cookie.SetExpire(time.Now().Add(s.config.Expires))
		} // if it's -1 then the cookie is deleted when the browser closes

		AddFasthttpCookie(cookie, reqCtx)
		fasthttp.ReleaseCookie(cookie)
	} else {
		sess = s.provider.Read(cookieValue, s.config.Expires)
	}
	return sess
}

// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
func DestroyFasthttp(reqCtx *fasthttp.RequestCtx) {
	defaultSessions.DestroyFasthttp(reqCtx)
}

// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
func (s *sessions) DestroyFasthttp(reqCtx *fasthttp.RequestCtx) {
	cookieValue := GetFasthttpCookie(s.config.Cookie, reqCtx)
	if cookieValue == "" { // nothing to destroy
		return
	}
	RemoveFasthttpCookie(s.config.Cookie, reqCtx)
	s.provider.Destroy(cookieValue)
}

// Global generator, no logic for per-manager for now.

// SessionIDGenerator returns a random string, used to set the session id
// you are able to override this to use your own method for generate session ids
var SessionIDGenerator = func(strLength int) string {
	return base64.URLEncoding.EncodeToString(random(strLength))
}
<commit_msg>https://github.com/kataras/iris/issues/610<commit_after>// Package sessions provides sessions support for net/http and valyala/fasthttp
// unique with auto-GC, register unlimited number of databases to Load and Update/Save the sessions in external server or to an external (no/or/and sql) database
// Usage net/http:
// // init a new sessions manager( if you use only one web framework inside your app then you can use the package-level functions like: sessions.Start/sessions.Destroy)
// manager := sessions.New(sessions.Config{})
// // start a session for a particular client
// manager.Start(http.ResponseWriter, *http.Request)
//
// // destroy a session from the server and client,
//  // don't call it on each handler, only on the handler you want the client to 'logout' or something like this:
// manager.Destroy(http.ResponseWriter, *http.Request)
//
//
// Usage valyala/fasthttp:
// // init a new sessions manager( if you use only one web framework inside your app then you can use the package-level functions like: sessions.Start/sessions.Destroy)
// manager := sessions.New(sessions.Config{})
// // start a session for a particular client
// manager.StartFasthttp(*fasthttp.RequestCtx)
//
// // destroy a session from the server and client,
//  // don't call it on each handler, only on the handler you want the client to 'logout' or something like this:
// manager.DestroyFasthttp(*fasthttp.Request)
//
// Note that, now, you can use both fasthttp and net/http within the same sessions manager(.New) instance!
// So now, you can share sessions between a net/http app and valyala/fasthttp app
package sessions

import (
	"encoding/base64"
	"net/http"
	"strings"
	"time"

	"github.com/valyala/fasthttp"
)

const (
	// Version current version number
	Version = "1.0.0"
)

type (
	// Sessions is the start point of this package
	// contains all the registered sessions and manages them
	Sessions interface {
		// Set options/configuration fields in runtime
		Set(...OptionSetter)

		// UseDatabase ,optionally, adds a session database to the manager's provider,
		// a session db doesn't have write access
		// see https://github.com/kataras/go-sessions/tree/master/sessiondb
		UseDatabase(Database)

		// Start starts the session for the particular net/http request
		Start(http.ResponseWriter, *http.Request) Session

		// Destroy kills the net/http session and remove the associated cookie
		Destroy(http.ResponseWriter, *http.Request)

		// StartFasthttp starts the session for the particular valyala/fasthttp request
		StartFasthttp(*fasthttp.RequestCtx) Session

		// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
		DestroyFasthttp(*fasthttp.RequestCtx)

		// DestroyByID removes the session entry
		// from the server-side memory (and database if registered).
		// Client's session cookie will still exist but it will be reseted on the next request.
		//
		// It's safe to use it even if you are not sure if a session with that id exists.
		// Works for both net/http & fasthttp
		DestroyByID(string)
		// DestroyAll removes all sessions
		// from the server-side memory (and database if registered).
		// Client's session cookie will still exist but it will be reseted on the next request.
		// Works for both net/http & fasthttp
		DestroyAll()
	}

	// sessions contains the cookie's name, the provider and a duration for GC and cookie life expire
	sessions struct {
		config   Config
		provider *provider
	}
)

// New creates & returns a new Sessions(manager) and start its GC (calls the .Init)
func New(setters ...OptionSetter) Sessions {
	c := Config{}.Validate()
	sess := &sessions{config: c, provider: newProvider()}
	sess.Set(setters...)

	return sess
}

var defaultSessions = New(Config{}.Validate())

// Set options/configuration fields in runtime
func Set(setters ...OptionSetter) {
	defaultSessions.Set(setters...)
}

func (s *sessions) Set(setters ...OptionSetter) {
	for _, setter := range setters {
		setter.Set(&s.config)
	}
}

// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func UseDatabase(db Database) {
	defaultSessions.UseDatabase(db)
}

// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func (s *sessions) UseDatabase(db Database) {
	s.provider.RegisterDatabase(db)
}

// Start starts the session for the particular net/http request
func Start(res http.ResponseWriter, req *http.Request) Session {
	return defaultSessions.Start(res, req)
}

// Start starts the session for the particular net/http request
func (s *sessions) Start(res http.ResponseWriter, req *http.Request) Session {
	var sess Session

	cookieValue := GetCookie(s.config.Cookie, req)
	if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
		sid := SessionIDGenerator(s.config.CookieLength)
		sess = s.provider.Init(sid, s.config.Expires)
		cookie := &http.Cookie{}

		// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
		cookie.Name = s.config.Cookie
		cookie.Value = sid
		cookie.Path = "/"
		if !s.config.DisableSubdomainPersistence {

			requestDomain := req.URL.Host
			if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
				requestDomain = requestDomain[0:portIdx]
			}
			if IsValidCookieDomain(requestDomain) {

				// RFC2109, we allow level 1 subdomains, but no further
				// if we have localhost.com , we want the localhost.cos.
				// so if we have something like: mysubdomain.localhost.com we want the localhost here
				// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
				// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
				if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
					// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
					s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
					if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
						//is mysubdomain.localhost ||  mysubsubdomain.mysubdomain.localhost
						s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
					}
					// replace the s with the requestDomain before the domain's siffux
					subdomainSuff := strings.LastIndexByte(requestDomain, '.')
					if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
						requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
					}
				}
				// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
				cookie.Domain = "." + requestDomain // . to allow persistance
			}

		}
		cookie.HttpOnly = true

		// MaxAge=0 means no 'Max-Age' attribute specified.
		// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
		// MaxAge>0 means Max-Age attribute present and given in seconds
		if s.config.Expires >= 0 {
			if s.config.Expires == 0 { // unlimited life
				cookie.Expires = CookieExpireUnlimited
			} else { // > 0
				cookie.Expires = time.Now().Add(s.config.Expires)
			}
			cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
		}

		AddCookie(cookie, res)
	} else {
		sess = s.provider.Read(cookieValue, s.config.Expires)
	}
	return sess
}

// Destroy kills the net/http session and remove the associated cookie
func Destroy(res http.ResponseWriter, req *http.Request) {
	defaultSessions.Destroy(res, req)
}

// Destroy kills the net/http session and remove the associated cookie
func (s *sessions) Destroy(res http.ResponseWriter, req *http.Request) {
	cookieValue := GetCookie(s.config.Cookie, req)
	if cookieValue == "" { // nothing to destroy
		return
	}
	RemoveCookie(s.config.Cookie, res, req)
	s.provider.Destroy(cookieValue)
}

// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
// Works for both net/http & fasthttp
func DestroyByID(sid string) {
	defaultSessions.DestroyByID(sid)
}

// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
// Works for both net/http & fasthttp
func (s *sessions) DestroyByID(sid string) {
	s.provider.Destroy(sid)
}

// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
// Works for both net/http & fasthttp
func DestroyAll() {
	defaultSessions.DestroyAll()
}

// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
// Works for both net/http & fasthttp
func (s *sessions) DestroyAll() {
	s.provider.DestroyAll()
}

// StartFasthttp starts the session for the particular valyala/fasthttp request
func StartFasthttp(reqCtx *fasthttp.RequestCtx) Session {
	return defaultSessions.StartFasthttp(reqCtx)
}

// Start starts the session for the particular valyala/fasthttp request
func (s *sessions) StartFasthttp(reqCtx *fasthttp.RequestCtx) Session {
	var sess Session

	cookieValue := GetFasthttpCookie(s.config.Cookie, reqCtx)

	if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
		sid := SessionIDGenerator(s.config.CookieLength)
		sess = s.provider.Init(sid, s.config.Expires)
		cookie := fasthttp.AcquireCookie()
		// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
		cookie.SetKey(s.config.Cookie)
		cookie.SetValue(sid)
		cookie.SetPath("/")
		if !s.config.DisableSubdomainPersistence {
			requestDomain := string(reqCtx.Host())
			if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
				requestDomain = requestDomain[0:portIdx]
			}
			if IsValidCookieDomain(requestDomain) {

				// RFC2109, we allow level 1 subdomains, but no further
				// if we have localhost.com , we want the localhost.cos.
				// so if we have something like: mysubdomain.localhost.com we want the localhost here
				// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
				// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
				if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
					// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
					s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
					if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
						//is mysubdomain.localhost ||  mysubsubdomain.mysubdomain.localhost
						s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
					}
					// replace the s with the requestDomain before the domain's siffux
					subdomainSuff := strings.LastIndexByte(requestDomain, '.')
					if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
						requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
					}
				}
				// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
				cookie.SetDomain("." + requestDomain) // . to allow persistance
			}

		}
		cookie.SetHTTPOnly(true)
		if s.config.Expires == 0 {
			// unlimited life
			cookie.SetExpire(CookieExpireUnlimited)
		} else if s.config.Expires > 0 {
			cookie.SetExpire(time.Now().Add(s.config.Expires))
		} // if it's -1 then the cookie is deleted when the browser closes

		AddFasthttpCookie(cookie, reqCtx)
		fasthttp.ReleaseCookie(cookie)
	} else {
		sess = s.provider.Read(cookieValue, s.config.Expires)
	}
	return sess
}

// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
func DestroyFasthttp(reqCtx *fasthttp.RequestCtx) {
	defaultSessions.DestroyFasthttp(reqCtx)
}

// DestroyFasthttp kills the valyala/fasthttp session and remove the associated cookie
func (s *sessions) DestroyFasthttp(reqCtx *fasthttp.RequestCtx) {
	cookieValue := GetFasthttpCookie(s.config.Cookie, reqCtx)
	if cookieValue == "" { // nothing to destroy
		return
	}
	RemoveFasthttpCookie(s.config.Cookie, reqCtx)
	s.provider.Destroy(cookieValue)
}

// Global generator, no logic for per-manager for now.

// SessionIDGenerator returns a random string, used to set the session id
// you are able to override this to use your own method for generate session ids
var SessionIDGenerator = func(strLength int) string {
	return base64.URLEncoding.EncodeToString(random(strLength))
}
<|endoftext|>