<commit_before>package main

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

type Paste struct {
	Expiration string
	Content    []byte
	UUID       string
}

var uuidValidator = regexp.MustCompile("^[a-zA-Z0-9-_]+$")

// CONFIG
type Config struct {
	Version               string
	CompressedStaticFiles bool
	Host                  string
	Port                  int
	MaxSize               int
	FileUploadEnabled     bool
}

var (
	config     *Config
	configLock = new(sync.RWMutex)
)

func init() {
	loadConfig(true)
	s := make(chan os.Signal, 1)
	signal.Notify(s, syscall.SIGHUP)
	go func() {
		for {
			<-s
			loadConfig(false)
			log.Print("Reloaded configuration")
		}
	}()
}

func GetConfig() *Config {
	configLock.RLock()
	defer configLock.RUnlock()
	return config
}

func loadConfig(fail bool) {
	file, err := ioutil.ReadFile("config.json")
	if err != nil {
		log.Print("Open config error: ", err)
		if fail {
			os.Exit(1)
		}
	}

	temp := new(Config)
	if err = json.Unmarshal(file, temp); err != nil {
		log.Print("Parse config error: ", err)
		if fail {
			os.Exit(1)
		}
	}
	configLock.Lock()
	config = temp
	configLock.Unlock()
}

func newHandler(w http.ResponseWriter, r *http.Request) {
	data := struct {
		Settings *Config
	}{
		config,
	}
	t := template.Must(template.ParseFiles("templates/base.html", "templates/new.html"))
	t.Execute(w, data)
}

func abs(x int64) int64 {
	switch {
	case x < 0:
		return -x
	case x == 0:
		return 0 // return correctly abs(-0)
	}
	return x
}

func humanizedExpiration(since int64, t time.Time) string {
	abs_since := abs(since)
	if abs_since < 60 {
		return fmt.Sprintf("in %d s", abs_since)
	}

	if abs_since < 60*60 {
		mins_since := abs_since / 60
		return fmt.Sprintf("in %d m", mins_since)
	}

	if abs_since < 60*60*24 {
		hours_since := abs_since / (60 * 60)
		return fmt.Sprintf("in %d h", hours_since)
	}

	if abs_since < 60*60*24*10 {
		days_since := abs_since / (60 * 60 * 24)
		return fmt.Sprintf("in %d day(s)", days_since)
	}

	return fmt.Sprintf("on %s %d, %d", t.Month(), t.Day(), t.Year())
}

func pasteHandler(w http.ResponseWriter, r *http.Request) {
	paste_id := strings.TrimPrefix(r.URL.Path, "/paste/")
	keep_alive := false
	burn_after_reading := false
	humanized_expiration := ""

	if !uuidValidator.MatchString(paste_id) {
		log.Print("Invalue paste ID error")
		w.WriteHeader(http.StatusNotFound)
		data := struct {
			Settings *Config
		}{
			config,
		}
		t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
		t.Execute(w, data)
		return
	}

	paste := &Paste{UUID: paste_id}
	err := paste.load()
	if err != nil {
		log.Print("Load file error")
		w.WriteHeader(http.StatusNotFound)
		data := struct {
			Settings *Config
		}{
			config,
		}
		t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
		t.Execute(w, data)
		return
	}

	if strings.HasPrefix(paste.Expiration, "burn_after_reading") {
		// burn_after_reading contains the paste creation date
		// if this read appends 10 seconds after the creation date
		// we don't delete the paste because it means it's the redirection
		// to the paste that happens during the paste creation
		burn_after_reading = true
		keep_alive = true
		keep_alive_timestamp := strings.TrimPrefix(paste.Expiration, "burn_after_reading#")
		t, _ := time.Parse(time.RFC3339Nano, keep_alive_timestamp)
		since := int64(time.Since(t) / time.Second)
		if since > 10 {
			keep_alive = false
			paste.delete()
		}
	} else {
		keep_alive = true
		t, _ := time.Parse(time.RFC3339Nano, paste.Expiration)
		since := int64(time.Since(t) / time.Second)
		if since > 0 {
			keep_alive = false
			paste.delete()
			log.Print("Expiration error")
			w.WriteHeader(http.StatusNotFound)
			data := struct {
				Settings *Config
			}{
				config,
			}
			t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
			t.Execute(w, data)
			return
		} else {
			humanized_expiration = humanizedExpiration(since, t)
		}
	}

	data := struct {
		Settings            *Config
		Paste               *Paste
		KeepAlive           bool
		BurnAfterReading    bool
		HumanizedExpiration string
	}{
		config,
		paste,
		keep_alive,
		burn_after_reading,
		humanized_expiration,
	}
	t := template.Must(template.ParseFiles("templates/base.html", "templates/paste.html"))
	t.Execute(w, data)

}

func createHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		log.Print("Not a post. Redirecting")
		http.Redirect(w, r, "/new/", 302)
		return
	}
	expiration := r.FormValue("expiration")
	content := r.FormValue("content")

	// Create UUID from content
	h := sha1.New()
	h.Write([]byte(content))
	uuid := base64.URLEncoding.EncodeToString(h.Sum(nil))
	uuid = strings.TrimSuffix(uuid, "=")
	uuid = strings.Replace(uuid, "/", "-", -1)

	// Check paste content size
	if len(content)/2 > config.MaxSize {
		w.Header().Set("Content-Type", "application/json")
		mapD := map[string]string{"status": "error", "message": "Content too big"}
		mapB, _ := json.Marshal(mapD)
		fmt.Fprintf(w, string(mapB))
	} else {
		// Save paste
		p := &Paste{Expiration: expiration, Content: []byte(content), UUID: uuid}
		err := p.save()
		var mapD map[string]string
		w.Header().Set("Content-Type", "application/json")
		if err != nil {
			log.Printf("could not save paste: %v", err)
			mapD = map[string]string{"status": "error", "message": "Could not save"}
		} else {
			// Response
			mapD = map[string]string{"status": "ok", "paste": uuid}
		}
		mapB, _ := json.Marshal(mapD)
		fmt.Fprintf(w, string(mapB))
	}
}

func (p *Paste) save() error {
	filename := p.UUID + ".txt"
	expiration := ""
	t := time.Now()
	if p.Expiration == "burn_after_reading" {
		expiration = fmt.Sprintf("burn_after_reading#%s", t.Format(time.RFC3339Nano))
	} else {
		if p.Expiration == "1_day" {
			expiration = time.Now().Add(24 * time.Hour).Format(time.RFC3339Nano)
		} else if p.Expiration == "1_month" {
			expiration = time.Now().Add(30 * 24 * time.Hour).Format(time.RFC3339Nano)
		} else if p.Expiration == "never" {
			expiration = time.Now().Add(100 * 365 * 24 * time.Hour).Format(time.RFC3339Nano)
		}
	}
	filecontent := fmt.Sprintf("%s\n%s\n", expiration, p.Content)
	return ioutil.WriteFile("./pastes/"+filename, []byte(filecontent), 0600)
}

func (p *Paste) load() error {
	// TODO
	// Handle malformed file
	// Handle cannot open file
	filename := p.UUID + ".txt"
	content, err := ioutil.ReadFile("./pastes/" + filename)
	if err != nil {
		return fmt.Errorf("Load file error")
	}
	lines := strings.Split(string(content), "\n")
	for i, line := range lines {
		if i == 0 {
			p.Expiration = line
		} else if i == 1 {
			p.Content = []byte(line)
		}
	}
	return nil
}

func (p *Paste) delete() error {
	filename := p.UUID + ".txt"
	return os.Remove("./pastes/" + filename)
}

func indexRedirect(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/new/", 302)
}

func Log(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL)
		handler.ServeHTTP(w, r)
	})
}

func main() {
	http.HandleFunc("/", indexRedirect)
	http.HandleFunc("/new/", newHandler)
	http.HandleFunc("/paste/create", createHandler)
	http.HandleFunc("/paste/", pasteHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	log.Printf("Serving from http://%s:%d\n", config.Host, config.Port)
	err := http.ListenAndServe(config.Host+":"+strconv.Itoa(config.Port), Log(http.DefaultServeMux))
	if err != nil {
		log.Printf("ListenAndServe: %v", err)
	}

}
<commit_msg>Remove unused function GetConfig<commit_after>package main

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

type Paste struct {
	Expiration string
	Content    []byte
	UUID       string
}

var uuidValidator = regexp.MustCompile("^[a-zA-Z0-9-_]+$")

// CONFIG
type Config struct {
	Version               string
	CompressedStaticFiles bool
	Host                  string
	Port                  int
	MaxSize               int
	FileUploadEnabled     bool
}

var (
	config     *Config
	configLock = new(sync.RWMutex)
)

func init() {
	loadConfig(true)
	s := make(chan os.Signal, 1)
	signal.Notify(s, syscall.SIGHUP)
	go func() {
		for {
			<-s
			loadConfig(false)
			log.Print("Reloaded configuration")
		}
	}()
}

func loadConfig(fail bool) {
	file, err := ioutil.ReadFile("config.json")
	if err != nil {
		log.Print("Open config error: ", err)
		if fail {
			os.Exit(1)
		}
	}

	temp := new(Config)
	if err = json.Unmarshal(file, temp); err != nil {
		log.Print("Parse config error: ", err)
		if fail {
			os.Exit(1)
		}
	}
	configLock.Lock()
	config = temp
	configLock.Unlock()
}

func newHandler(w http.ResponseWriter, r *http.Request) {
	data := struct {
		Settings *Config
	}{
		config,
	}
	t := template.Must(template.ParseFiles("templates/base.html", "templates/new.html"))
	t.Execute(w, data)
}

func abs(x int64) int64 {
	switch {
	case x < 0:
		return -x
	case x == 0:
		return 0 // return correctly abs(-0)
	}
	return x
}

func humanizedExpiration(since int64, t time.Time) string {
	abs_since := abs(since)
	if abs_since < 60 {
		return fmt.Sprintf("in %d s", abs_since)
	}

	if abs_since < 60*60 {
		mins_since := abs_since / 60
		return fmt.Sprintf("in %d m", mins_since)
	}

	if abs_since < 60*60*24 {
		hours_since := abs_since / (60 * 60)
		return fmt.Sprintf("in %d h", hours_since)
	}

	if abs_since < 60*60*24*10 {
		days_since := abs_since / (60 * 60 * 24)
		return fmt.Sprintf("in %d day(s)", days_since)
	}

	return fmt.Sprintf("on %s %d, %d", t.Month(), t.Day(), t.Year())
}

func pasteHandler(w http.ResponseWriter, r *http.Request) {
	paste_id := strings.TrimPrefix(r.URL.Path, "/paste/")
	keep_alive := false
	burn_after_reading := false
	humanized_expiration := ""

	if !uuidValidator.MatchString(paste_id) {
		log.Print("Invalue paste ID error")
		w.WriteHeader(http.StatusNotFound)
		data := struct {
			Settings *Config
		}{
			config,
		}
		t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
		t.Execute(w, data)
		return
	}

	paste := &Paste{UUID: paste_id}
	err := paste.load()
	if err != nil {
		log.Print("Load file error")
		w.WriteHeader(http.StatusNotFound)
		data := struct {
			Settings *Config
		}{
			config,
		}
		t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
		t.Execute(w, data)
		return
	}

	if strings.HasPrefix(paste.Expiration, "burn_after_reading") {
		// burn_after_reading contains the paste creation date
		// if this read appends 10 seconds after the creation date
		// we don't delete the paste because it means it's the redirection
		// to the paste that happens during the paste creation
		burn_after_reading = true
		keep_alive = true
		keep_alive_timestamp := strings.TrimPrefix(paste.Expiration, "burn_after_reading#")
		t, _ := time.Parse(time.RFC3339Nano, keep_alive_timestamp)
		since := int64(time.Since(t) / time.Second)
		if since > 10 {
			keep_alive = false
			paste.delete()
		}
	} else {
		keep_alive = true
		t, _ := time.Parse(time.RFC3339Nano, paste.Expiration)
		since := int64(time.Since(t) / time.Second)
		if since > 0 {
			keep_alive = false
			paste.delete()
			log.Print("Expiration error")
			w.WriteHeader(http.StatusNotFound)
			data := struct {
				Settings *Config
			}{
				config,
			}
			t := template.Must(template.ParseFiles("templates/base.html", "templates/404.html"))
			t.Execute(w, data)
			return
		} else {
			humanized_expiration = humanizedExpiration(since, t)
		}
	}

	data := struct {
		Settings            *Config
		Paste               *Paste
		KeepAlive           bool
		BurnAfterReading    bool
		HumanizedExpiration string
	}{
		config,
		paste,
		keep_alive,
		burn_after_reading,
		humanized_expiration,
	}
	t := template.Must(template.ParseFiles("templates/base.html", "templates/paste.html"))
	t.Execute(w, data)

}

func createHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		log.Print("Not a post. Redirecting")
		http.Redirect(w, r, "/new/", 302)
		return
	}
	expiration := r.FormValue("expiration")
	content := r.FormValue("content")

	// Create UUID from content
	h := sha1.New()
	h.Write([]byte(content))
	uuid := base64.URLEncoding.EncodeToString(h.Sum(nil))
	uuid = strings.TrimSuffix(uuid, "=")
	uuid = strings.Replace(uuid, "/", "-", -1)

	// Check paste content size
	if len(content)/2 > config.MaxSize {
		w.Header().Set("Content-Type", "application/json")
		mapD := map[string]string{"status": "error", "message": "Content too big"}
		mapB, _ := json.Marshal(mapD)
		fmt.Fprintf(w, string(mapB))
	} else {
		// Save paste
		p := &Paste{Expiration: expiration, Content: []byte(content), UUID: uuid}
		err := p.save()
		var mapD map[string]string
		w.Header().Set("Content-Type", "application/json")
		if err != nil {
			log.Printf("could not save paste: %v", err)
			mapD = map[string]string{"status": "error", "message": "Could not save"}
		} else {
			// Response
			mapD = map[string]string{"status": "ok", "paste": uuid}
		}
		mapB, _ := json.Marshal(mapD)
		fmt.Fprintf(w, string(mapB))
	}
}

func (p *Paste) save() error {
	filename := p.UUID + ".txt"
	expiration := ""
	t := time.Now()
	if p.Expiration == "burn_after_reading" {
		expiration = fmt.Sprintf("burn_after_reading#%s", t.Format(time.RFC3339Nano))
	} else {
		if p.Expiration == "1_day" {
			expiration = time.Now().Add(24 * time.Hour).Format(time.RFC3339Nano)
		} else if p.Expiration == "1_month" {
			expiration = time.Now().Add(30 * 24 * time.Hour).Format(time.RFC3339Nano)
		} else if p.Expiration == "never" {
			expiration = time.Now().Add(100 * 365 * 24 * time.Hour).Format(time.RFC3339Nano)
		}
	}
	filecontent := fmt.Sprintf("%s\n%s\n", expiration, p.Content)
	return ioutil.WriteFile("./pastes/"+filename, []byte(filecontent), 0600)
}

func (p *Paste) load() error {
	// TODO
	// Handle malformed file
	// Handle cannot open file
	filename := p.UUID + ".txt"
	content, err := ioutil.ReadFile("./pastes/" + filename)
	if err != nil {
		return fmt.Errorf("Load file error")
	}
	lines := strings.Split(string(content), "\n")
	for i, line := range lines {
		if i == 0 {
			p.Expiration = line
		} else if i == 1 {
			p.Content = []byte(line)
		}
	}
	return nil
}

func (p *Paste) delete() error {
	filename := p.UUID + ".txt"
	return os.Remove("./pastes/" + filename)
}

func indexRedirect(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/new/", 302)
}

func Log(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL)
		handler.ServeHTTP(w, r)
	})
}

func main() {
	http.HandleFunc("/", indexRedirect)
	http.HandleFunc("/new/", newHandler)
	http.HandleFunc("/paste/create", createHandler)
	http.HandleFunc("/paste/", pasteHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	log.Printf("Serving from http://%s:%d\n", config.Host, config.Port)
	err := http.ListenAndServe(config.Host+":"+strconv.Itoa(config.Port), Log(http.DefaultServeMux))
	if err != nil {
		log.Printf("ListenAndServe: %v", err)
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package kubeadm

import (
	"fmt"

	authv1 "k8s.io/api/authorization/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/util/version"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
)

const (
	kubeletConfigConfigMapKey = "kubelet"
)

var (
	kubeletConfigConfigMapName   string
	kubeletConfigRoleName        string
	kubeletConfigRoleBindingName string

	kubeletConfigConfigMapResource = &authv1.ResourceAttributes{
		Namespace: kubeSystemNamespace,
		Name:      "",
		Resource:  "configmaps",
		Verb:      "get",
	}
)

// Define container for all the test specification aimed at verifying
// that kubeadm creates the kubelet-config ConfigMap, that it is properly configured
// and that all the related RBAC rules are in place
var _ = Describe("kubelet-config ConfigMap", func() {

	// Get an instance of the k8s test framework
	f := framework.NewDefaultFramework("kubelet-config")

	// Tests in this container are not expected to create new objects in the cluster
	// so we are disabling the creation of a namespace in order to get a faster execution
	f.SkipNamespaceCreation = true

	// kubelet-config map is named using the kubernetesVersion as a suffix, and so
	// it is necessary to get it from the kubeadm-config ConfigMap before testing
	ginkgo.BeforeEach(func() {
		// if the kubelet-config map name is already known exit
		if kubeletConfigConfigMapName != "" {
			return
		}

		// gets the ClusterConfiguration from the kubeadm kubeadm-config ConfigMap as a untyped map
		m := getClusterConfiguration(f.ClientSet)

		// Extract the kubernetesVersion
		// TODO: remove this after the UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		// At that point parsing the k8s version will no longer be needed in this test.
		gomega.Expect(m).To(gomega.HaveKey("kubernetesVersion"))
		k8sVersionString := m["kubernetesVersion"].(string)
		k8sVersion, err := version.ParseSemantic(k8sVersionString)
		if err != nil {
			framework.Failf("error reading kubernetesVersion from %s ConfigMap: %v", kubeadmConfigName, err)
		}

		// Extract the value of the UnversionedKubeletConfigMap feature gate if its present.
		// TODO: remove this after the UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		UnversionedKubeletConfigMap := true
		if _, ok := m["featureGates"]; ok {
			if featureGates, ok := m["featureGates"].(map[interface{}]interface{}); ok {
				if val, ok := featureGates["UnversionedKubeletConfigMap"]; ok {
					if valBool, ok := val.(bool); ok {
						UnversionedKubeletConfigMap = valBool
					} else {
						framework.Failf("unable to cast the value of feature gate UnversionedKubeletConfigMap to bool")
					}
				}
			} else {
				framework.Failf("unable to cast the featureGates field in the %s ConfigMap", kubeadmConfigName)
			}
		}

		// Computes all the names derived from the kubernetesVersion
		kubeletConfigConfigMapName = "kubelet-config"
		kubeletConfigRoleName = "kubeadm:kubelet-config"
		// TODO: remove this after the UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		if !UnversionedKubeletConfigMap {
			kubeletConfigConfigMapName = fmt.Sprintf("kubelet-config-%d.%d", k8sVersion.Major(), k8sVersion.Minor())
			kubeletConfigRoleName = fmt.Sprintf("kubeadm:kubelet-config-%d.%d", k8sVersion.Major(), k8sVersion.Minor())
		}
		kubeletConfigRoleBindingName = kubeletConfigRoleName
		kubeletConfigConfigMapResource.Name = kubeletConfigConfigMapName
	})

	ginkgo.It("should exist and be properly configured", func() {
		cm := GetConfigMap(f.ClientSet, kubeSystemNamespace, kubeletConfigConfigMapName)

		gomega.Expect(cm.Data).To(gomega.HaveKey(kubeletConfigConfigMapKey))
	})

	ginkgo.It("should have related Role and RoleBinding", func() {
		ExpectRole(f.ClientSet, kubeSystemNamespace, kubeletConfigRoleName)
		ExpectRoleBinding(f.ClientSet, kubeSystemNamespace, kubeletConfigRoleBindingName)
	})

	ginkgo.It("should be accessible for bootstrap tokens", func() {
		ExpectSubjectHasAccessToResource(f.ClientSet,
			rbacv1.GroupKind, bootstrapTokensGroup,
			kubeadmConfigConfigMapResource,
		)
	})

	ginkgo.It("should be accessible for nodes", func() {
		ExpectSubjectHasAccessToResource(f.ClientSet,
			rbacv1.GroupKind, nodesGroup,
			kubeadmConfigConfigMapResource,
		)
	})
})
<commit_msg>test/e2e_kubeadm: fix matching UnversionedKubeletConfigMap defaults<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package kubeadm

import (
	"context"
	"fmt"

	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/version"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
)

const (
	kubeletConfigConfigMapKey = "kubelet"
)

var (
	kubeletConfigConfigMapName   string
	kubeletConfigRoleName        string
	kubeletConfigRoleBindingName string

	// TODO: remove these versioned strings and related logic once the UnversionedKubeletConfigMap
	// feature gate goes GA:
	// https://github.com/kubernetes/kubeadm/issues/1582
	kubeletConfigConfigMapNameVersioned   string
	kubeletConfigRoleNameVersioned        string
	kubeletConfigRoleBindingNameVersioned string
)

// Define container for all the test specification aimed at verifying
// that kubeadm creates the kubelet-config ConfigMap, that it is properly configured
// and that all the related RBAC rules are in place
var _ = Describe("kubelet-config ConfigMap", func() {

	// Get an instance of the k8s test framework
	f := framework.NewDefaultFramework("kubelet-config")

	// Tests in this container are not expected to create new objects in the cluster
	// so we are disabling the creation of a namespace in order to get a faster execution
	f.SkipNamespaceCreation = true

	// kubelet-config map is named using the kubernetesVersion as a suffix, and so
	// it is necessary to get it from the kubeadm-config ConfigMap before testing
	ginkgo.BeforeEach(func() {
		// if the kubelet-config map name is already known exit
		if kubeletConfigConfigMapName != "" {
			return
		}

		// gets the ClusterConfiguration from the kubeadm kubeadm-config ConfigMap as a untyped map
		m := getClusterConfiguration(f.ClientSet)

		// Extract the kubernetesVersion
		// TODO: remove this after the UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		// At that point parsing the k8s version will no longer be needed in this test.
		gomega.Expect(m).To(gomega.HaveKey("kubernetesVersion"))
		k8sVersionString := m["kubernetesVersion"].(string)
		k8sVersion, err := version.ParseSemantic(k8sVersionString)
		if err != nil {
			framework.Failf("error reading kubernetesVersion from %s ConfigMap: %v", kubeadmConfigName, err)
		}

		kubeletConfigConfigMapName = "kubelet-config"
		kubeletConfigRoleName = "kubeadm:kubelet-config"
		kubeletConfigRoleBindingName = kubeletConfigRoleName

		kubeletConfigConfigMapNameVersioned = fmt.Sprintf("kubelet-config-%d.%d", k8sVersion.Major(), k8sVersion.Minor())
		kubeletConfigRoleNameVersioned = fmt.Sprintf("kubeadm:kubelet-config-%d.%d", k8sVersion.Major(), k8sVersion.Minor())
		kubeletConfigRoleBindingNameVersioned = kubeletConfigRoleNameVersioned
	})

	ginkgo.It("should exist and be properly configured", func() {
		// TODO: switch to GetConfigMap once UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		cm, err := f.ClientSet.CoreV1().
			ConfigMaps(kubeSystemNamespace).
			Get(context.TODO(), kubeletConfigConfigMapName, metav1.GetOptions{})
		if err != nil {
			cm, err = f.ClientSet.CoreV1().
				ConfigMaps(kubeSystemNamespace).
				Get(context.TODO(), kubeletConfigConfigMapNameVersioned, metav1.GetOptions{})
			framework.ExpectNoError(err, "error getting ConfigMap %q or %q from namespace %q",
				kubeletConfigConfigMapName, kubeletConfigConfigMapNameVersioned, kubeSystemNamespace)
		}
		gomega.Expect(cm.Data).To(gomega.HaveKey(kubeletConfigConfigMapKey))
	})

	ginkgo.It("should have related Role and RoleBinding", func() {
		// TODO: switch to ExpectRole(Binding) once UnversionedKubeletConfigMap feature gate goes GA:
		// https://github.com/kubernetes/kubeadm/issues/1582
		_, err := f.ClientSet.RbacV1().
			Roles(kubeSystemNamespace).
			Get(context.TODO(), kubeletConfigRoleName, metav1.GetOptions{})
		if err != nil {
			_, err = f.ClientSet.RbacV1().
				Roles(kubeSystemNamespace).
				Get(context.TODO(), kubeletConfigRoleNameVersioned, metav1.GetOptions{})
			framework.ExpectNoError(err, "error getting Role %q or %q from namespace %q",
				kubeletConfigRoleName, kubeletConfigRoleNameVersioned, kubeSystemNamespace)
		}
		_, err = f.ClientSet.RbacV1().
			Roles(kubeSystemNamespace).
			Get(context.TODO(), kubeletConfigRoleBindingName, metav1.GetOptions{})
		if err != nil {
			_, err = f.ClientSet.RbacV1().
				Roles(kubeSystemNamespace).
				Get(context.TODO(), kubeletConfigRoleBindingNameVersioned, metav1.GetOptions{})
			framework.ExpectNoError(err, "error getting RoleBinding %q or %q from namespace %q",
				kubeletConfigRoleBindingName, kubeletConfigRoleBindingNameVersioned, kubeSystemNamespace)
		}
	})

	ginkgo.It("should be accessible for bootstrap tokens", func() {
		ExpectSubjectHasAccessToResource(f.ClientSet,
			rbacv1.GroupKind, bootstrapTokensGroup,
			kubeadmConfigConfigMapResource,
		)
	})

	ginkgo.It("should be accessible for nodes", func() {
		ExpectSubjectHasAccessToResource(f.ClientSet,
			rbacv1.GroupKind, nodesGroup,
			kubeadmConfigConfigMapResource,
		)
	})
})
<|endoftext|>


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

package main

import (
	"fmt"
	"sync"
)

// A local-only, memory-only implementation of Cfg interface, useful
// for development and testing.

type CfgMem struct {
	m             sync.Mutex
	CASNext       uint64
	Entries       map[string]*CfgMemEntry
	subscriptions map[string][]chan<- CfgEvent
}

type CfgMemEntry struct {
	CAS uint64
	Val []byte
}

func NewCfgMem() *CfgMem {
	return &CfgMem{
		CASNext:       1,
		Entries:       make(map[string]*CfgMemEntry),
		subscriptions: make(map[string][]chan<- CfgEvent),
	}
}

func (c *CfgMem) Get(key string, cas uint64) (
	[]byte, uint64, error) {
	c.m.Lock()
	defer c.m.Unlock()

	entry, exists := c.Entries[key]
	if !exists {
		return nil, 0, nil
	}
	if cas != 0 && cas != entry.CAS {
		return nil, 0, &CfgCASError{}
	}
	val := make([]byte, len(entry.Val))
	copy(val, entry.Val)
	return val, entry.CAS, nil
}

func (c *CfgMem) Set(key string, val []byte, cas uint64) (
	uint64, error) {
	c.m.Lock()
	defer c.m.Unlock()

	prevEntry, exists := c.Entries[key]
	if cas == 0 {
		if exists {
			return 0, fmt.Errorf("error: entry already exists, key: %s", key)
		}
	} else { // cas != 0
		if !exists || cas != prevEntry.CAS {
			return 0, &CfgCASError{}
		}
	}
	nextEntry := &CfgMemEntry{
		CAS: c.CASNext,
		Val: make([]byte, len(val)),
	}
	copy(nextEntry.Val, val)
	c.Entries[key] = nextEntry
	c.CASNext += 1
	c.fireEvent(key, nextEntry.CAS)
	return nextEntry.CAS, nil
}

func (c *CfgMem) Del(key string, cas uint64) error {
	c.m.Lock()
	defer c.m.Unlock()

	if cas != 0 {
		entry, exists := c.Entries[key]
		if !exists || cas != entry.CAS {
			return &CfgCASError{}
		}
	}
	delete(c.Entries, key)
	c.fireEvent(key, 0)
	return nil
}

func (c *CfgMem) Subscribe(key string, ch chan CfgEvent) error {
	c.m.Lock()
	defer c.m.Unlock()

	a, exists := c.subscriptions[key]
	if !exists || a == nil {
		a = make([]chan<- CfgEvent, 0)
	}
	c.subscriptions[key] = append(a, ch)
	return nil
}

func (c *CfgMem) fireEvent(key string, cas uint64) {
	for _, c := range c.subscriptions[key] {
		go func() { c <- CfgEvent{Key: key, CAS: cas} }()
	}
}
<commit_msg>capture range iterator value in closure<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package main

import (
	"fmt"
	"sync"
)

// A local-only, memory-only implementation of Cfg interface, useful
// for development and testing.

type CfgMem struct {
	m             sync.Mutex
	CASNext       uint64
	Entries       map[string]*CfgMemEntry
	subscriptions map[string][]chan<- CfgEvent
}

type CfgMemEntry struct {
	CAS uint64
	Val []byte
}

func NewCfgMem() *CfgMem {
	return &CfgMem{
		CASNext:       1,
		Entries:       make(map[string]*CfgMemEntry),
		subscriptions: make(map[string][]chan<- CfgEvent),
	}
}

func (c *CfgMem) Get(key string, cas uint64) (
	[]byte, uint64, error) {
	c.m.Lock()
	defer c.m.Unlock()

	entry, exists := c.Entries[key]
	if !exists {
		return nil, 0, nil
	}
	if cas != 0 && cas != entry.CAS {
		return nil, 0, &CfgCASError{}
	}
	val := make([]byte, len(entry.Val))
	copy(val, entry.Val)
	return val, entry.CAS, nil
}

func (c *CfgMem) Set(key string, val []byte, cas uint64) (
	uint64, error) {
	c.m.Lock()
	defer c.m.Unlock()

	prevEntry, exists := c.Entries[key]
	if cas == 0 {
		if exists {
			return 0, fmt.Errorf("error: entry already exists, key: %s", key)
		}
	} else { // cas != 0
		if !exists || cas != prevEntry.CAS {
			return 0, &CfgCASError{}
		}
	}
	nextEntry := &CfgMemEntry{
		CAS: c.CASNext,
		Val: make([]byte, len(val)),
	}
	copy(nextEntry.Val, val)
	c.Entries[key] = nextEntry
	c.CASNext += 1
	c.fireEvent(key, nextEntry.CAS)
	return nextEntry.CAS, nil
}

func (c *CfgMem) Del(key string, cas uint64) error {
	c.m.Lock()
	defer c.m.Unlock()

	if cas != 0 {
		entry, exists := c.Entries[key]
		if !exists || cas != entry.CAS {
			return &CfgCASError{}
		}
	}
	delete(c.Entries, key)
	c.fireEvent(key, 0)
	return nil
}

func (c *CfgMem) Subscribe(key string, ch chan CfgEvent) error {
	c.m.Lock()
	defer c.m.Unlock()

	a, exists := c.subscriptions[key]
	if !exists || a == nil {
		a = make([]chan<- CfgEvent, 0)
	}
	c.subscriptions[key] = append(a, ch)
	return nil
}

func (c *CfgMem) fireEvent(key string, cas uint64) {
	for _, c := range c.subscriptions[key] {
		go func(c chan<- CfgEvent) { c <- CfgEvent{Key: key, CAS: cas} }(c)
	}
}
<|endoftext|>


<commit_before>// Common contains structs, data, and interfaces that
// need to be referenced by other packages
package common

import (
	"time"
)

const (
	// How many bytes of entropy must be produced each entropy cycle
	EntropyVolume int = 32

	// How big a single segment of data is for a host, in bytes
	MinSegmentSize int = 512
	MaxSegmentSize int = 1048576 // 1 MB

	// How many hosts participate in each quorum
	// This number is chosen to minimize the probability of a single quorum
	// 	becoming more than 80% compromised by an attacker controlling 1/2 of
	// 	the network.
	QuorumSize int = 128

	// How long a single step in the consensus algorithm takes
	StepDuration time.Duration = 15 * time.Second
)

type Entropy [EntropyVolume]byte

// An Identifier uniquely identifies a host.
// A byte is used for now, but a more sophisticated data structure will be
// used eventually -- probably a path through a tree.
type Identifier byte

// An Address couples an Identifier with its network address.
type Address struct {
	Id   Identifier
	Host string
	Port int
}

// Messages are for sending things over the network.
// Each message has a single destination, and it is
// the job of the network package to interpret the
// destinations.
type Message struct {
	Destination Address
	Payload     []byte
}

type MessageHandler interface {
	Identifier() Identifier
	HandleMessage([]byte)
}

type MessageSender interface {
	Address() Address
	SendMessage(m *Message) error
}
<commit_msg>rename MessageSender and add a member function<commit_after>// Common contains structs, data, and interfaces that
// need to be referenced by other packages
package common

import (
	"time"
)

const (
	// How many bytes of entropy must be produced each entropy cycle
	EntropyVolume int = 32

	// How big a single segment of data is for a host, in bytes
	MinSegmentSize int = 512
	MaxSegmentSize int = 1048576 // 1 MB

	// How many participants are in each quorum
	// This number is chosen to minimize the probability of a single quorum
	// 	becoming more than 80% compromised by an attacker controlling 1/2 of
	// 	the network.
	QuorumSize int = 128

	// How long a single step in the consensus algorithm takes
	StepDuration time.Duration = 15 * time.Second
)

type Entropy [EntropyVolume]byte

// An Identifier uniquely identifies a participant on a host.
type Identifier byte

// An Address couples an Identifier with its network address.
type Address struct {
	Id   Identifier
	Host string
	Port int
}

// Messages are for sending things over the network.
// Each message has a single destination, and it is
// the job of the network package to route a message
// to its intended destination.
type Message struct {
	Destination Address
	Payload     []byte
}

// A MessageHandler is a function that processes a message payload.
// MessageHandlers are associated with various Identifiers by a MessageRouter.
type MessageHandler interface {
	HandleMessage([]byte)
}

// A MessageRouter both transmits outgoing messages and processes incoming messages.
// It allows MessageHandlers to be associated with a given Identifier.
type MessageRouter interface {
	Address() Address
	AddMessageHandler(MessageHandler) Address
	SendMessage(*Message) error
}
<|endoftext|>


<commit_before>// Package curl implements the Curl hashing function.
package curl

import (
	. "github.com/iotaledger/iota.go/consts"
	. "github.com/iotaledger/iota.go/trinary"
)

const (
	// StateSize is the size of the Curl hash function.
	StateSize = HashTrinarySize * 3
	// NumberOfRounds is the default number of rounds in transform.
	NumberOfRounds = 81
)

var (
	// optional transform function in C.
	transformC func(Trits)
	// TruthTable of the Curl hash function.
	TruthTable = [11]int8{1, 0, -1, 2, 1, -1, 0, 2, -1, 1, 0}
	// Indices of the Curl hash function.
	Indices [StateSize + 1]int
)

func init() {
	for i := 0; i < StateSize; i++ {
		p := -365

		if Indices[i] < 365 {
			p = 364
		}

		Indices[i+1] = Indices[i] + p
	}
}

// Curl is a sponge function with an internal State of size StateSize.
// b = r + c, b = StateSize, r = HashSize, c = StateSize - HashSize
type Curl struct {
	State Trits
}

// NewCurl initializes a new instance with an empty State.
func NewCurl() *Curl {
	c := &Curl{
		State: make(Trits, StateSize),
	}
	return c
}

//Squeeze do Squeeze in sponge func.
func (c *Curl) Squeeze() Trytes {
	ret := MustTritsToTrytes(c.State[:HashTrinarySize])
	c.Transform()

	return ret
}

// Absorb fills the internal State of the sponge with the given trits.
// It panics if the given trytes are not valid.
func (c *Curl) Absorb(inn Trytes) {
	var in Trits
	if len(inn) == 0 {
		in = Trits{0}
	} else {
		in = MustTrytesToTrits(inn)
	}
	var lenn int
	for i := 0; i < len(in); i += lenn {
		lenn = HashTrinarySize

		if len(in)-i < HashTrinarySize {
			lenn = len(in) - i
		}

		copy(c.State, in[i:i+lenn])
		c.Transform()
	}
}

// Transform does Transform in sponge func.
func (c *Curl) Transform() {
	if transformC != nil {
		transformC(c.State)
		return
	}

	var cpy [StateSize]int8

	for r := NumberOfRounds; r > 0; r-- {
		copy(cpy[:], c.State)
		c.State = c.State[:StateSize]
		for i := 0; i < StateSize; i++ {
			t1 := Indices[i]
			t2 := Indices[i+1]
			c.State[i] = TruthTable[cpy[t1]+(cpy[t2]<<2)+5]
		}
	}
}

// Reset the internal State of the Curl sponge by filling it with all 0's.
func (c *Curl) Reset() {
	for i := range c.State {
		c.State[i] = 0
	}
}

// HashTrits returns hash of the given trits.
func HashTrits(trits Trits) Trits {
	c := NewCurl()
	c.Absorb(MustTritsToTrytes(trits))
	return MustTrytesToTrits(c.Squeeze())
}

// HashTrytes returns hash of t.
func HashTrytes(t Trytes) Trytes {
	c := NewCurl()
	c.Absorb(t)
	return c.Squeeze()
}
<commit_msg>removes unneeded reassignment<commit_after>// Package curl implements the Curl hashing function.
package curl

import (
	. "github.com/iotaledger/iota.go/consts"
	. "github.com/iotaledger/iota.go/trinary"
)

const (
	// StateSize is the size of the Curl hash function.
	StateSize = HashTrinarySize * 3
	// NumberOfRounds is the default number of rounds in transform.
	NumberOfRounds = 81
)

var (
	// optional transform function in C.
	transformC func(Trits)
	// TruthTable of the Curl hash function.
	TruthTable = [11]int8{1, 0, -1, 2, 1, -1, 0, 2, -1, 1, 0}
	// Indices of the Curl hash function.
	Indices [StateSize + 1]int
)

func init() {
	for i := 0; i < StateSize; i++ {
		p := -365

		if Indices[i] < 365 {
			p = 364
		}

		Indices[i+1] = Indices[i] + p
	}
}

// Curl is a sponge function with an internal State of size StateSize.
// b = r + c, b = StateSize, r = HashSize, c = StateSize - HashSize
type Curl struct {
	State Trits
}

// NewCurl initializes a new instance with an empty State.
func NewCurl() *Curl {
	c := &Curl{
		State: make(Trits, StateSize),
	}
	return c
}

//Squeeze do Squeeze in sponge func.
func (c *Curl) Squeeze() Trytes {
	ret := MustTritsToTrytes(c.State[:HashTrinarySize])
	c.Transform()

	return ret
}

// Absorb fills the internal State of the sponge with the given trits.
// It panics if the given trytes are not valid.
func (c *Curl) Absorb(inn Trytes) {
	var in Trits
	if len(inn) == 0 {
		in = Trits{0}
	} else {
		in = MustTrytesToTrits(inn)
	}
	var lenn int
	for i := 0; i < len(in); i += lenn {
		lenn = HashTrinarySize

		if len(in)-i < HashTrinarySize {
			lenn = len(in) - i
		}

		copy(c.State, in[i:i+lenn])
		c.Transform()
	}
}

// Transform does Transform in sponge func.
func (c *Curl) Transform() {
	if transformC != nil {
		transformC(c.State)
		return
	}

	var cpy [StateSize]int8

	for r := NumberOfRounds; r > 0; r-- {
		copy(cpy[:], c.State)
		for i := 0; i < StateSize; i++ {
			t1 := Indices[i]
			t2 := Indices[i+1]
			c.State[i] = TruthTable[cpy[t1]+(cpy[t2]<<2)+5]
		}
	}
}

// Reset the internal State of the Curl sponge by filling it with all 0's.
func (c *Curl) Reset() {
	for i := range c.State {
		c.State[i] = 0
	}
}

// HashTrits returns hash of the given trits.
func HashTrits(trits Trits) Trits {
	c := NewCurl()
	c.Absorb(MustTritsToTrytes(trits))
	return MustTrytesToTrits(c.Squeeze())
}

// HashTrytes returns hash of t.
func HashTrytes(t Trytes) Trytes {
	c := NewCurl()
	c.Absorb(t)
	return c.Squeeze()
}
<|endoftext|>


<commit_before>package discordgo

/*
func (c *Channel) Messages() (messages []Message) {
}

func (c *Channel) SendMessage() (content string) {
}
*/
<commit_msg>No longer used.<commit_after><|endoftext|>


<commit_before>package stripe

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strconv"
)

type Currency string

const (
	AED Currency = "aed" // United Arab Emirates Dirham
	AFN Currency = "afn" // Afghan Afghani
	ALL Currency = "all" // Albanian Lek
	AMD Currency = "amd" // Armenian Dram
	ANG Currency = "ang" // Netherlands Antillean Gulden
	AOA Currency = "aoa" // Angolan Kwanza
	ARS Currency = "ars" // Argentine Peso
	AUD Currency = "aud" // Australian Dollar
	AWG Currency = "awg" // Aruban Florin
	AZN Currency = "azn" // Azerbaijani Manat
	BAM Currency = "bam" // Bosnia & Herzegovina Convertible Mark
	BBD Currency = "bbd" // Barbadian Dollar
	BDT Currency = "bdt" // Bangladeshi Taka
	BGN Currency = "bgn" // Bulgarian Lev
	BIF Currency = "bif" // Burundian Franc
	BMD Currency = "bmd" // Bermudian Dollar
	BND Currency = "bnd" // Brunei Dollar
	BOB Currency = "bob" // Bolivian Boliviano
	BRL Currency = "brl" // Brazilian Real
	BSD Currency = "bsd" // Bahamian Dollar
	BWP Currency = "bwp" // Botswana Pula
	BZD Currency = "bzd" // Belize Dollar
	CAD Currency = "cad" // Canadian Dollar
	CDF Currency = "cdf" // Congolese Franc
	CHF Currency = "chf" // Swiss Franc
	CLP Currency = "clp" // Chilean Peso
	CNY Currency = "cny" // Chinese Renminbi Yuan
	COP Currency = "cop" // Colombian Peso
	CRC Currency = "crc" // Costa Rican Colón
	CVE Currency = "cve" // Cape Verdean Escudo
	CZK Currency = "czk" // Czech Koruna
	DJF Currency = "djf" // Djiboutian Franc
	DKK Currency = "dkk" // Danish Krone
	DOP Currency = "dop" // Dominican Peso
	DZD Currency = "dzd" // Algerian Dinar
	EEK Currency = "eek" // Estonian Kroon
	EGP Currency = "egp" // Egyptian Pound
	ETB Currency = "etb" // Ethiopian Birr
	EUR Currency = "eur" // Euro
	FJD Currency = "fjd" // Fijian Dollar
	FKP Currency = "fkp" // Falkland Islands Pound
	GBP Currency = "gbp" // British Pound
	GEL Currency = "gel" // Georgian Lari
	GIP Currency = "gip" // Gibraltar Pound
	GMD Currency = "gmd" // Gambian Dalasi
	GNF Currency = "gnf" // Guinean Franc
	GTQ Currency = "gtq" // Guatemalan Quetzal
	GYD Currency = "gyd" // Guyanese Dollar
	HKD Currency = "hkd" // Hong Kong Dollar
	HNL Currency = "hnl" // Honduran Lempira
	HRK Currency = "hrk" // Croatian Kuna
	HTG Currency = "htg" // Haitian Gourde
	HUF Currency = "huf" // Hungarian Forint
	IDR Currency = "idr" // Indonesian Rupiah
	ILS Currency = "ils" // Israeli New Sheqel
	INR Currency = "inr" // Indian Rupee
	ISK Currency = "isk" // Icelandic Króna
	JMD Currency = "jmd" // Jamaican Dollar
	JPY Currency = "jpy" // Japanese Yen
	KES Currency = "kes" // Kenyan Shilling
	KGS Currency = "kgs" // Kyrgyzstani Som
	KHR Currency = "khr" // Cambodian Riel
	KMF Currency = "kmf" // Comorian Franc
	KRW Currency = "krw" // South Korean Won
	KYD Currency = "kyd" // Cayman Islands Dollar
	KZT Currency = "kzt" // Kazakhstani Tenge
	LAK Currency = "lak" // Lao Kip
	LBP Currency = "lbp" // Lebanese Pound
	LKR Currency = "lkr" // Sri Lankan Rupee
	LRD Currency = "lrd" // Liberian Dollar
	LSL Currency = "lsl" // Lesotho Loti
	LTL Currency = "ltl" // Lithuanian Litas
	LVL Currency = "lvl" // Latvian Lats
	MAD Currency = "mad" // Moroccan Dirham
	MDL Currency = "mdl" // Moldovan Leu
	MGA Currency = "mga" // Malagasy Ariary
	MKD Currency = "mkd" // Macedonian Denar
	MNT Currency = "mnt" // Mongolian Tögrög
	MOP Currency = "mop" // Macanese Pataca
	MRO Currency = "mro" // Mauritanian Ouguiya
	MUR Currency = "mur" // Mauritian Rupee
	MVR Currency = "mvr" // Maldivian Rufiyaa
	MWK Currency = "mwk" // Malawian Kwacha
	MXN Currency = "mxn" // Mexican Peso
	MYR Currency = "myr" // Malaysian Ringgit
	MZN Currency = "mzn" // Mozambican Metical
	NAD Currency = "nad" // Namibian Dollar
	NGN Currency = "ngn" // Nigerian Naira
	NIO Currency = "nio" // Nicaraguan Córdoba
	NOK Currency = "nok" // Norwegian Krone
	NPR Currency = "npr" // Nepalese Rupee
	NZD Currency = "nzd" // New Zealand Dollar
	PAB Currency = "pab" // Panamanian Balboa
	PEN Currency = "pen" // Peruvian Nuevo Sol
	PGK Currency = "pgk" // Papua New Guinean Kina
	PHP Currency = "php" // Philippine Peso
	PKR Currency = "pkr" // Pakistani Rupee
	PLN Currency = "pln" // Polish Złoty
	PYG Currency = "pyg" // Paraguayan Guaraní
	QAR Currency = "qar" // Qatari Riyal
	RON Currency = "ron" // Romanian Leu
	RSD Currency = "rsd" // Serbian Dinar
	RUB Currency = "rub" // Russian Ruble
	RWF Currency = "rwf" // Rwandan Franc
	SAR Currency = "sar" // Saudi Riyal
	SBD Currency = "sbd" // Solomon Islands Dollar
	SCR Currency = "scr" // Seychellois Rupee
	SEK Currency = "sek" // Swedish Krona
	SGD Currency = "sgd" // Singapore Dollar
	SHP Currency = "shp" // Saint Helenian Pound
	SLL Currency = "sll" // Sierra Leonean Leone
	SOS Currency = "sos" // Somali Shilling
	SRD Currency = "srd" // Surinamese Dollar
	STD Currency = "std" // São Tomé and Príncipe Dobra
	SVC Currency = "svc" // Salvadoran Colón
	SZL Currency = "szl" // Swazi Lilangeni
	THB Currency = "thb" // Thai Baht
	TJS Currency = "tjs" // Tajikistani Somoni
	TOP Currency = "top" // Tongan Paʻanga
	TRY Currency = "try" // Turkish Lira
	TTD Currency = "ttd" // Trinidad and Tobago Dollar
	TWD Currency = "twd" // New Taiwan Dollar
	TZS Currency = "tzs" // Tanzanian Shilling
	UAH Currency = "uah" // Ukrainian Hryvnia
	UGX Currency = "ugx" // Ugandan Shilling
	USD Currency = "usd" // United States Dollar
	UYU Currency = "uyu" // Uruguayan Peso
	UZS Currency = "uzs" // Uzbekistani Som
	VEF Currency = "vef" // Venezuelan Bolívar
	VND Currency = "vnd" // Vietnamese Đồng
	VUV Currency = "vuv" // Vanuatu Vatu
	WST Currency = "wst" // Samoan Tala
	XAF Currency = "xaf" // Central African Cfa Franc
	XCD Currency = "xcd" // East Caribbean Dollar
	XOF Currency = "xof" // West African Cfa Franc
	XPF Currency = "xpf" // Cfp Franc
	YER Currency = "yer" // Yemeni Rial
	ZAR Currency = "zar" // South African Rand
	ZMW Currency = "zmw" // Zambian Kwacha
)

type ChargeParams struct {
	Amount    uint64
	Currency  Currency
	Customer  string
	Token     string
	Card      *CardParams
	Desc      string
	Meta      map[string]string
	NoCapture bool
	Statement string
	//Fee       uint
}

type RefundParams struct {
	Amount uint64
	Fee    bool
}

type CaptureParams struct {
	Amount uint64
	Fee    bool
}

type Charge struct {
	Id             string            `json:"id"`
	Created        int64             `json:"created"`
	Live           bool              `json:"livemode"`
	Paid           bool              `json:"paid"`
	Amount         uint64            `json:"amount"`
	Currency       Currency          `json:"currency"`
	Refunded       bool              `json:"refunded"`
	Card           *Card             `json: "card"`
	Captured       bool              `json:"captured"`
	Customer       string            `json:"customer"`
	Refunds        []*Refund         `json:"refunds",omitempty`
	Balance        string            `json:"balance_transaction"`
	FailMsg        string            `json:"failure_message,omitempty"`
	FailCode       string            `json:"failure_code,omitempty"`
	AmountRefunded uint64            `json:"amount_refunded,omitempty"`
	Invoice        string            `json:"invoice,omitempty"`
	Desc           string            `json:"description,omitempty"`
	Dispute        string            `json:"dispute,omitempty"`
	Metadata       map[string]string `json:"metadata"`
}

type Refund struct {
	Amount   uint64   `json:"amount"`
	Created  int64    `json:"created"`
	Currency Currency `json:"currency"`
}

type ChargeClient struct {
	api   Api
	token string
}

func (c *ChargeClient) Create(params *ChargeParams) (*Charge, error) {
	body := &url.Values{
		"amount":   {strconv.FormatUint(params.Amount, 10)},
		"currency": {string(params.Currency)},
	}

	if len(params.Customer) > 0 {
		body.Add("customer", params.Customer)
	} else if len(params.Token) > 0 {
		body.Add("card", params.Token)
	} else if params.Card != nil {
		params.Card.appendTo(body)
	} else {
		err := errors.New("Invalid charge params: Either customer, card token or card need to be set")
		return nil, err
	}

	if len(params.Desc) > 0 {
		body.Add("description", params.Desc)
	}

	for k, v := range params.Meta {
		body.Add(fmt.Sprintf("metadata[%v]", k), v)
	}

	body.Add("capture", strconv.FormatBool(!params.NoCapture))

	if len(params.Statement) > 0 {
		body.Add("statement_description", params.Desc)
	}

	res, err := c.api.Call("POST", "/charges", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Get(id string) (*Charge, error) {
	res, err := c.api.Call("GET", "/charges/"+id, c.token, nil)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Update(id string, params *ChargeParams) (*Charge, error) {
	body := &url.Values{
		"description": {params.Desc},
	}

	for k, v := range params.Meta {
		body.Add(fmt.Sprintf("metadata[%v]", k), v)
	}

	res, err := c.api.Call("POST", "/charges/"+id, c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Refund(id string, params *RefundParams) (*Charge, error) {
	body := &url.Values{}

	if params != nil {
		if params.Amount > 0 {
			body.Add("amount", strconv.FormatUint(params.Amount, 10))
		}

		if params.Fee {
			body.Add("refund_application_fee", strconv.FormatBool(params.Fee))
		}
	}

	res, err := c.api.Call("POST", "/charges/"+id+"/refund", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Capture(id string, params *CaptureParams) (*Charge, error) {
	body := &url.Values{}

	if params != nil {
		if params.Amount > 0 {
			body.Add("amount", strconv.FormatUint(params.Amount, 10))
		}

		if params.Fee {
			body.Add("refund_application_fee", strconv.FormatBool(params.Fee))
		}
	}

	res, err := c.api.Call("POST", "/charges/"+id+"/capture", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}
<commit_msg>mark metadata as optional<commit_after>package stripe

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strconv"
)

type Currency string

const (
	AED Currency = "aed" // United Arab Emirates Dirham
	AFN Currency = "afn" // Afghan Afghani
	ALL Currency = "all" // Albanian Lek
	AMD Currency = "amd" // Armenian Dram
	ANG Currency = "ang" // Netherlands Antillean Gulden
	AOA Currency = "aoa" // Angolan Kwanza
	ARS Currency = "ars" // Argentine Peso
	AUD Currency = "aud" // Australian Dollar
	AWG Currency = "awg" // Aruban Florin
	AZN Currency = "azn" // Azerbaijani Manat
	BAM Currency = "bam" // Bosnia & Herzegovina Convertible Mark
	BBD Currency = "bbd" // Barbadian Dollar
	BDT Currency = "bdt" // Bangladeshi Taka
	BGN Currency = "bgn" // Bulgarian Lev
	BIF Currency = "bif" // Burundian Franc
	BMD Currency = "bmd" // Bermudian Dollar
	BND Currency = "bnd" // Brunei Dollar
	BOB Currency = "bob" // Bolivian Boliviano
	BRL Currency = "brl" // Brazilian Real
	BSD Currency = "bsd" // Bahamian Dollar
	BWP Currency = "bwp" // Botswana Pula
	BZD Currency = "bzd" // Belize Dollar
	CAD Currency = "cad" // Canadian Dollar
	CDF Currency = "cdf" // Congolese Franc
	CHF Currency = "chf" // Swiss Franc
	CLP Currency = "clp" // Chilean Peso
	CNY Currency = "cny" // Chinese Renminbi Yuan
	COP Currency = "cop" // Colombian Peso
	CRC Currency = "crc" // Costa Rican Colón
	CVE Currency = "cve" // Cape Verdean Escudo
	CZK Currency = "czk" // Czech Koruna
	DJF Currency = "djf" // Djiboutian Franc
	DKK Currency = "dkk" // Danish Krone
	DOP Currency = "dop" // Dominican Peso
	DZD Currency = "dzd" // Algerian Dinar
	EEK Currency = "eek" // Estonian Kroon
	EGP Currency = "egp" // Egyptian Pound
	ETB Currency = "etb" // Ethiopian Birr
	EUR Currency = "eur" // Euro
	FJD Currency = "fjd" // Fijian Dollar
	FKP Currency = "fkp" // Falkland Islands Pound
	GBP Currency = "gbp" // British Pound
	GEL Currency = "gel" // Georgian Lari
	GIP Currency = "gip" // Gibraltar Pound
	GMD Currency = "gmd" // Gambian Dalasi
	GNF Currency = "gnf" // Guinean Franc
	GTQ Currency = "gtq" // Guatemalan Quetzal
	GYD Currency = "gyd" // Guyanese Dollar
	HKD Currency = "hkd" // Hong Kong Dollar
	HNL Currency = "hnl" // Honduran Lempira
	HRK Currency = "hrk" // Croatian Kuna
	HTG Currency = "htg" // Haitian Gourde
	HUF Currency = "huf" // Hungarian Forint
	IDR Currency = "idr" // Indonesian Rupiah
	ILS Currency = "ils" // Israeli New Sheqel
	INR Currency = "inr" // Indian Rupee
	ISK Currency = "isk" // Icelandic Króna
	JMD Currency = "jmd" // Jamaican Dollar
	JPY Currency = "jpy" // Japanese Yen
	KES Currency = "kes" // Kenyan Shilling
	KGS Currency = "kgs" // Kyrgyzstani Som
	KHR Currency = "khr" // Cambodian Riel
	KMF Currency = "kmf" // Comorian Franc
	KRW Currency = "krw" // South Korean Won
	KYD Currency = "kyd" // Cayman Islands Dollar
	KZT Currency = "kzt" // Kazakhstani Tenge
	LAK Currency = "lak" // Lao Kip
	LBP Currency = "lbp" // Lebanese Pound
	LKR Currency = "lkr" // Sri Lankan Rupee
	LRD Currency = "lrd" // Liberian Dollar
	LSL Currency = "lsl" // Lesotho Loti
	LTL Currency = "ltl" // Lithuanian Litas
	LVL Currency = "lvl" // Latvian Lats
	MAD Currency = "mad" // Moroccan Dirham
	MDL Currency = "mdl" // Moldovan Leu
	MGA Currency = "mga" // Malagasy Ariary
	MKD Currency = "mkd" // Macedonian Denar
	MNT Currency = "mnt" // Mongolian Tögrög
	MOP Currency = "mop" // Macanese Pataca
	MRO Currency = "mro" // Mauritanian Ouguiya
	MUR Currency = "mur" // Mauritian Rupee
	MVR Currency = "mvr" // Maldivian Rufiyaa
	MWK Currency = "mwk" // Malawian Kwacha
	MXN Currency = "mxn" // Mexican Peso
	MYR Currency = "myr" // Malaysian Ringgit
	MZN Currency = "mzn" // Mozambican Metical
	NAD Currency = "nad" // Namibian Dollar
	NGN Currency = "ngn" // Nigerian Naira
	NIO Currency = "nio" // Nicaraguan Córdoba
	NOK Currency = "nok" // Norwegian Krone
	NPR Currency = "npr" // Nepalese Rupee
	NZD Currency = "nzd" // New Zealand Dollar
	PAB Currency = "pab" // Panamanian Balboa
	PEN Currency = "pen" // Peruvian Nuevo Sol
	PGK Currency = "pgk" // Papua New Guinean Kina
	PHP Currency = "php" // Philippine Peso
	PKR Currency = "pkr" // Pakistani Rupee
	PLN Currency = "pln" // Polish Złoty
	PYG Currency = "pyg" // Paraguayan Guaraní
	QAR Currency = "qar" // Qatari Riyal
	RON Currency = "ron" // Romanian Leu
	RSD Currency = "rsd" // Serbian Dinar
	RUB Currency = "rub" // Russian Ruble
	RWF Currency = "rwf" // Rwandan Franc
	SAR Currency = "sar" // Saudi Riyal
	SBD Currency = "sbd" // Solomon Islands Dollar
	SCR Currency = "scr" // Seychellois Rupee
	SEK Currency = "sek" // Swedish Krona
	SGD Currency = "sgd" // Singapore Dollar
	SHP Currency = "shp" // Saint Helenian Pound
	SLL Currency = "sll" // Sierra Leonean Leone
	SOS Currency = "sos" // Somali Shilling
	SRD Currency = "srd" // Surinamese Dollar
	STD Currency = "std" // São Tomé and Príncipe Dobra
	SVC Currency = "svc" // Salvadoran Colón
	SZL Currency = "szl" // Swazi Lilangeni
	THB Currency = "thb" // Thai Baht
	TJS Currency = "tjs" // Tajikistani Somoni
	TOP Currency = "top" // Tongan Paʻanga
	TRY Currency = "try" // Turkish Lira
	TTD Currency = "ttd" // Trinidad and Tobago Dollar
	TWD Currency = "twd" // New Taiwan Dollar
	TZS Currency = "tzs" // Tanzanian Shilling
	UAH Currency = "uah" // Ukrainian Hryvnia
	UGX Currency = "ugx" // Ugandan Shilling
	USD Currency = "usd" // United States Dollar
	UYU Currency = "uyu" // Uruguayan Peso
	UZS Currency = "uzs" // Uzbekistani Som
	VEF Currency = "vef" // Venezuelan Bolívar
	VND Currency = "vnd" // Vietnamese Đồng
	VUV Currency = "vuv" // Vanuatu Vatu
	WST Currency = "wst" // Samoan Tala
	XAF Currency = "xaf" // Central African Cfa Franc
	XCD Currency = "xcd" // East Caribbean Dollar
	XOF Currency = "xof" // West African Cfa Franc
	XPF Currency = "xpf" // Cfp Franc
	YER Currency = "yer" // Yemeni Rial
	ZAR Currency = "zar" // South African Rand
	ZMW Currency = "zmw" // Zambian Kwacha
)

type ChargeParams struct {
	Amount    uint64
	Currency  Currency
	Customer  string
	Token     string
	Card      *CardParams
	Desc      string
	Meta      map[string]string
	NoCapture bool
	Statement string
	//Fee       uint
}

type RefundParams struct {
	Amount uint64
	Fee    bool
}

type CaptureParams struct {
	Amount uint64
	Fee    bool
}

type Charge struct {
	Id             string            `json:"id"`
	Created        int64             `json:"created"`
	Live           bool              `json:"livemode"`
	Paid           bool              `json:"paid"`
	Amount         uint64            `json:"amount"`
	Currency       Currency          `json:"currency"`
	Refunded       bool              `json:"refunded"`
	Card           *Card             `json: "card"`
	Captured       bool              `json:"captured"`
	Customer       string            `json:"customer"`
	Refunds        []*Refund         `json:"refunds",omitempty`
	Balance        string            `json:"balance_transaction"`
	FailMsg        string            `json:"failure_message,omitempty"`
	FailCode       string            `json:"failure_code,omitempty"`
	AmountRefunded uint64            `json:"amount_refunded,omitempty"`
	Invoice        string            `json:"invoice,omitempty"`
	Desc           string            `json:"description,omitempty"`
	Dispute        string            `json:"dispute,omitempty"`
	Metadata       map[string]string `json:"metadata,omitempty"`
}

type Refund struct {
	Amount   uint64   `json:"amount"`
	Created  int64    `json:"created"`
	Currency Currency `json:"currency"`
}

type ChargeClient struct {
	api   Api
	token string
}

func (c *ChargeClient) Create(params *ChargeParams) (*Charge, error) {
	body := &url.Values{
		"amount":   {strconv.FormatUint(params.Amount, 10)},
		"currency": {string(params.Currency)},
	}

	if len(params.Customer) > 0 {
		body.Add("customer", params.Customer)
	} else if len(params.Token) > 0 {
		body.Add("card", params.Token)
	} else if params.Card != nil {
		params.Card.appendTo(body)
	} else {
		err := errors.New("Invalid charge params: Either customer, card token or card need to be set")
		return nil, err
	}

	if len(params.Desc) > 0 {
		body.Add("description", params.Desc)
	}

	for k, v := range params.Meta {
		body.Add(fmt.Sprintf("metadata[%v]", k), v)
	}

	body.Add("capture", strconv.FormatBool(!params.NoCapture))

	if len(params.Statement) > 0 {
		body.Add("statement_description", params.Desc)
	}

	res, err := c.api.Call("POST", "/charges", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Get(id string) (*Charge, error) {
	res, err := c.api.Call("GET", "/charges/"+id, c.token, nil)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Update(id string, params *ChargeParams) (*Charge, error) {
	body := &url.Values{
		"description": {params.Desc},
	}

	for k, v := range params.Meta {
		body.Add(fmt.Sprintf("metadata[%v]", k), v)
	}

	res, err := c.api.Call("POST", "/charges/"+id, c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Refund(id string, params *RefundParams) (*Charge, error) {
	body := &url.Values{}

	if params != nil {
		if params.Amount > 0 {
			body.Add("amount", strconv.FormatUint(params.Amount, 10))
		}

		if params.Fee {
			body.Add("refund_application_fee", strconv.FormatBool(params.Fee))
		}
	}

	res, err := c.api.Call("POST", "/charges/"+id+"/refund", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}

func (c *ChargeClient) Capture(id string, params *CaptureParams) (*Charge, error) {
	body := &url.Values{}

	if params != nil {
		if params.Amount > 0 {
			body.Add("amount", strconv.FormatUint(params.Amount, 10))
		}

		if params.Fee {
			body.Add("refund_application_fee", strconv.FormatBool(params.Fee))
		}
	}

	res, err := c.api.Call("POST", "/charges/"+id+"/capture", c.token, body)
	if err != nil {
		return nil, err
	}

	var charge Charge
	err = json.Unmarshal(res, &charge)
	if err != nil {
		return nil, err
	}

	return &charge, nil
}
<|endoftext|>


<commit_before>// Package chronos is a scheduling tool for Go based on:
//  https://github.com/carlescere/scheduler

package chronos

import (
	"errors"
	"time"
)

const (
	Day  = 24 * time.Hour
	Week =  7 * Day
)

type ChronosJob struct {
	schedule scheduler
}

type scheduler interface {
	next() (time.Duration, bool, error)
}

// Accepts periods in every time unit from ns to weeks,
// months and years need to be considered separately as
// their length is not constant
type periodic struct {
	start   time.Time
	end     time.Time
	started bool
	ammount int
	unit    time.Duration
}

func (schedule *periodic) next() (time.Duration, bool, error) {
	if schedule.unit == 0 || schedule.ammount == 0 {
		return 0, false, errors.New("0 is not a valid period")
	}
	now := time.Now()
	if !schedule.end.IsZero && now.After(schedule.end) {
		return 0, false, nil
	}
	if !schedule.started {
		schedule.started = true
		if now.After(schedule.start) {
			return 0, true, nil
		}
		return schedule.start.Sub(now), true, nil
	}
	return schedule.ammount * schedule.unit, true, nil
}

// Monthly periods need to be considered separately as
// their length is not constant (28-31 days)
type monthly struct {
	start   time.Time
	end     time.Time
	started bool
	n       int
	ammount int
}

func (schedule *monthly) next() (time.Duration, bool, error) {
	if schedule.ammount == 0 {
		return 0, false, errors.New("0 months is not a valid period")
	}
	now := time.Now()
	if !schedule.end.IsZero && now.After(schedule.end) {
		return 0, false, nil
	}
	if !schedule.started {
		schedule.started = true
		if now.After(schedule.start) {
			return 0, true, nil
		}
		return schedule.start.Sub(now), true, nil
	}
	if now.Before(schedule.start) {
		next := schedule.start
	} else {
		next := schedule.previous.AddDate(0, schedule.n, 0)
		// If the day does not exist for that month time.Time.AddDate
		// normalizes it, so we need to substract the extra days
		if next.Day() != schedule.start.Day() {
			next = next.AddDate(0, 0, -next.Day())
		}
	}
	schedule.n += schedule.ammount
	return next.Sub(now), true, nil
}

// Yearly periods need to be considered separately as
// their length is not constant (365-366 days)
type yearly struct {
	start   time.Time
	end     time.Time
	started bool
	n       int
	ammount int
}

func (schedule *yearly) next() (time.Duration, bool, error) {
	if schedule.ammount == 0 {
		return 0, false, errors.New("0 years is not a valid period")
	}
	now := time.Now()
	if !schedule.end.IsZero && now.After(schedule.end) {
		return 0, false, nil
	}
	if !schedule.started {
		schedule.started = true
		if now.After(schedule.start) {
			return 0, true, nil
		}
		return schedule.start.Sub(now), true, nil
	}
	if now.Before(schedule.start) {
		next := schedule.start
	} else {
		next := schedule.start.AddDate(schedule.n, 0, 0)
		// If the day does not exist for that month time.Time.AddDate
		// normalizes it, so we need to substract the extra days
		if next.Day() != schedule.start.Day() {
			next = next.AddDate(0, 0, -next.Day())
		}
	}
	schedule.n += schedule.ammount
	return next.Sub(now), true, nil
}<commit_msg>Re-implementation of periodic, monthly and yearly schedulers<commit_after>// Package chronos is a scheduling tool for Go based on:
//  https://github.com/carlescere/scheduler

package chronos

import (
	"errors"
	"time"
)

const (
	Day  = 24 * time.Hour
	Week =  7 * Day
)

type scheduler interface {
	// Returns wether there is another event scheduled and the remaining time
	next() (bool, time.Duration)
}

// Accepts periods in every time unit from ns to weeks, months and years need to
// be considered separately as their length is not constant
type periodic struct {
	start,                 // Start time
	end      time.Time     // End time, zero value means no end
	started  bool          // Internal flag to handle first executions
	ammount  time.Duration // Period
	n        int           // Number of already executed events
}

// Constructor
func newPeriodic(start, end time.Time, ammount int, unit time.Duration, notInmediately bool) (*periodic, error) {
	// Check the input is valid
	if ammount == 0 || unit == 0 {
		return nil, errors.New("0 is not a valid period")
	}
	// If no start time was assigned, use current time
	if start.IsZero() {
		start = time.Now()
	}
	// If notInmediately was called, the starting date should not be returned
	// by periodic.next() call, so we add 1 to the event count to avoid it
	var n int
	if notInmediately {
		n = 1
	}

	return &periodic{start:start, end:end, started:notInmediately,
	                 ammount:time.Duration(ammount*int(unit)), n:n},
	       nil
}

// Auxiliar function that returns the execution time candidate
func (s *periodic) getCandidate() time.Time {
	return s.start.Add(time.Duration(s.n*int(s.ammount)))
}

// Implements scheduler.next()
func (s *periodic) next() (bool, time.Duration) {
	// Calculate the next iteration
	next := s.getCandidate()
	for next.Before(time.Now()) {
		if !s.started {
			break
		}
		s.n++
		next = s.getCandidate()
	}
	if !s.started {
		s.started = true
	}

	// Check if the end date has arrived
	return s.end.IsZero() || next.Before(s.end), next.Sub(time.Now())
}

// Monthly periods need to be considered separately as their length is not
// constant (28-31 days)
type monthly struct {
	start,             // Start time
	end      time.Time // End time, zero value means no end
	started  bool      // Internal flag to handle first executions
	ammount,           // Ammount of months that made up a period
	n        int       // Number of already executed events
}

// Constructor
func newMonthly(start, end time.Time, ammount int, notInmediately bool) (*monthly, error) {
	// Check the input is valid
	if ammount == 0 {
		return nil, errors.New("0 months is not a valid period")
	}
	// If no start time was assigned, use current time
	if start.IsZero() {
		start = time.Now()
	}
	// If notInmediately was called, the starting date should not be returned
	// by periodic.next() call, so we add 1 to the event count to avoid it
	var n int
	if notInmediately {
		n = 1
	}

	return &monthly{start:start, end:end, started:notInmediately,
	                ammount:ammount, n:n},
	       nil
}

func (s *monthly) getCandidate() time.Time {
	res := s.start.AddDate(0, s.n*s.ammount, 0)
	if res.Day() != s.start.Day() {
		res = res.AddDate(0, 0, -res.Day())
	}
	return res
}

// Implements scheduler.next()
func (s *monthly) next() (bool, time.Duration) {
	// Calculate the next iteration
	next := s.getCandidate()
	for next.Before(time.Now()) {
		if !s.started {
			break
		}
		s.n++
		next = s.getCandidate()
	}
	if !s.started {
		s.started = true
	}

	// Check if the end date has arrived
	return s.end.IsZero() || next.Before(s.end), next.Sub(time.Now())
}

// Yearly periods need to be considered separately as
// their length is not constant (365-366 days)
type yearly struct {
	start,             // Start time
	end      time.Time // End time, zero value means no end
	started  bool      // Internal flag to handle first executions
	ammount,           // Ammount of years that made up a period
	n        int       // Number of already executed events
}

// Constructor
func newYearly(start, end time.Time, ammount int, notInmediately bool) (*yearly, error) {
	// Check the input is valid
	if ammount == 0 {
		return nil, errors.New("0 years is not a valid period")
	}
	// If no start time was assigned, use current time
	if start.IsZero() {
		start = time.Now()
	}
	// If notInmediately was called, the starting date should not be returned
	// by periodic.next() call, so we add 1 to the event count to avoid it
	var n int
	if notInmediately {
		n = 1
	}

	return &yearly{start:start, end:end, started:notInmediately,
	               ammount:ammount, n:n},
	       nil
}

func (s *yearly) getCandidate() time.Time {
	res := s.start.AddDate(s.n*s.ammount, 0, 0)
	if res.Day() != s.start.Day() {
		res = res.AddDate(0, 0, -res.Day())
	}
	return res
}

// implements scheduler.next()
func (s *yearly) next() (bool, time.Duration) {
	// Calculate the next iteration
	next := s.getCandidate()
	for next.Before(time.Now()) {
		if !s.started {
			break
		}
		s.n++
		next = s.getCandidate()
	}
	if !s.started {
		s.started = true
	}

	// Check if the end date has arrived
	return s.end.IsZero() || next.Before(s.end), next.Sub(time.Now())
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"github.com/franela/goreq"
	"github.com/gin-gonic/gin"
	"log"
	"strconv"
	"sync/atomic"
	"time"
)

var errTaskNotFresh = errors.New("This task has been running too long to request a token.")
var errAlreadyGivenKey = errors.New("This task has already been given a token.")
var usedTaskIds = NewTtlSet()

func createToken(token string, opts interface{}) (string, error) {
	r, err := VaultRequest{goreq.Request{
		Uri:             vaultPath("/v1/auth/token/create", ""),
		Method:          "POST",
		Body:            opts,
		MaxRedirects:    10,
		RedirectHeaders: true,
	}.WithHeader("X-Vault-Token", token)}.Do()
	if err == nil {
		defer r.Body.Close()
		switch r.StatusCode {
		case 200:
			var t vaultTokenResp
			if err := r.Body.FromJsonTo(&t); err == nil {
				return t.Auth.ClientToken, nil
			} else {
				return "", err
			}
		default:
			var e vaultError
			e.Code = r.StatusCode
			if err := r.Body.FromJsonTo(&e); err == nil {
				return "", e
			} else {
				e.Errors = []string{"communication error."}
				return "", e
			}
		}
	} else {
		return "", err
	}
}

func createWrappedToken(token string, opts interface{}, wrapTTL time.Duration) (string, error) {
	wrapTTLSeconds := strconv.Itoa(int(wrapTTL.Seconds()))

	r, err := VaultRequest{
		goreq.Request{
			Uri:             vaultPath("/v1/auth/token/create", ""),
			Method:          "POST",
			Body:            opts,
			MaxRedirects:    10,
			RedirectHeaders: true,
		}.WithHeader("X-Vault-Token", token).WithHeader("X-Vault-Wrap-TTL", wrapTTLSeconds),
	}.Do()
	defer r.Body.Close()

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

	if r.StatusCode != 200 {
		var e vaultError
		e.Code = r.StatusCode
		if err := r.Body.FromJsonTo(&e); err == nil {
			return "", e
		} else {
			e.Errors = []string{"communication error."}
			return "", e
		}
	}

	t := &vaultTokenResp{}
	if err := r.Body.FromJsonTo(t); err != nil {
		return "", err
	}

	if t.WrapInfo.Token == "" {
		return "",  errors.New("Request for wrapped token did not return wrapped response")
	}

	return t.WrapInfo.Token, nil
}

func createTokenPair(token string, p *policy) (string, error) {
	pol := p.Policies
	if len(pol) == 0 { // explicitly set the policy, else the token will inherit ours
		pol = []string{"default"}
	}

	permTokenOpts := struct {
		Ttl      string            `json:"ttl,omitempty"`
		Policies []string          `json:"policies"`
		Meta     map[string]string `json:"meta,omitempty"`
		NumUses  int               `json:"num_uses"`
		NoParent bool              `json:"no_parent"`
	}{time.Duration(time.Duration(p.Ttl) * time.Second).String(), pol, p.Meta, p.NumUses, true}

	return createWrappedToken(token, permTokenOpts, 10 * time.Minute)
}

func Provide(c *gin.Context) {
	requestStartTime := time.Now()
	state.RLock()
	status := state.Status
	token := state.Token
	state.RUnlock()

	remoteIp := c.Request.RemoteAddr

	atomic.AddInt32(&state.Stats.Requests, 1)

	if status == StatusSealed {
		log.Printf("Rejected token request from %s. Reason: sealed.", remoteIp)
		atomic.AddInt32(&state.Stats.Denied, 1)
		c.JSON(503, struct {
			Status string `json:"status"`
			Ok     bool   `json:"ok"`
			Error  string `json:"error"`
		}{string(state.Status), false, "Gatekeeper is sealed."})
		return
	}

	var reqParams struct {
		TaskId    string `json:"task_id"`
	}
	decoder := json.NewDecoder(c.Request.Body)
	if err := decoder.Decode(&reqParams); err == nil {
		if usedTaskIds.Has(reqParams.TaskId) {
			log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errAlreadyGivenKey)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(403, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, errAlreadyGivenKey.Error()})
			return
		}
		/*
			The task can start, but the task's framework may have not reported
			that it is RUNNING back to mesos. In this case, the task will still
			be STAGING and have a statuses length of 0.

			This is a network race, so we just sleep and try again.
		*/
		gMT := func(taskId string) (mesosTask, error) {
			task, err := getMesosTask(taskId)
			for i := time.Duration(0); i < 3 && err == nil && len(task.Statuses) == 0; i++ {
				time.Sleep((500 + 250*i) * time.Millisecond)
				task, err = getMesosTask(taskId)
			}
			return task, err
		}
		if task, err := gMT(reqParams.TaskId); err == nil {
			if len(task.Statuses) == 0 {
				log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v (no status)", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(403, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, errTaskNotFresh.Error()})
				return
			}
			// https://github.com/apache/mesos/blob/a61074586d778d432ba991701c9c4de9459db897/src/webui/master/static/js/controllers.js#L148
			startTime := time.Unix(0, int64(task.Statuses[0].Timestamp*1000000000))
			if time.Now().Sub(startTime) > config.MaxTaskLife {
				log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v (no status)", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(403, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, errTaskNotFresh.Error()})
				return
			}
			state.RLock()
			policy := activePolicies.Get(task.Name)
			state.RUnlock()
			if tempToken, err := createTokenPair(token, policy); err == nil {
				log.Printf("Provided token pair for %s in %v. (Task Id: %s) (Task Name: %s). Policies: %v", remoteIp, time.Now().Sub(requestStartTime), reqParams.TaskId, task.Name, policy.Policies)
				atomic.AddInt32(&state.Stats.Successful, 1)
				usedTaskIds.Put(reqParams.TaskId, config.MaxTaskLife+1*time.Minute)
				c.JSON(200, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Token  string `json:"token"`
				}{string(state.Status), true, tempToken})
			} else {
				log.Printf("Failed to create token pair for %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(500, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, err.Error()})
			}
		} else if err == errNoSuchTask {
			log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errNoSuchTask)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(403, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, err.Error()})
		} else {
			log.Printf("Failed to retrieve task information for %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, err)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(500, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, err.Error()})
		}
	} else {
		log.Printf("Rejected token request from %s. Reason: %v", remoteIp, err)
		atomic.AddInt32(&state.Stats.Denied, 1)
		c.JSON(400, struct {
			Status string `json:"status"`
			Ok     bool   `json:"ok"`
			Error  string `json:"error"`
		}{string(state.Status), false, err.Error()})
	}
}
<commit_msg>provider: make the perm token renewable<commit_after>package main

import (
	"encoding/json"
	"errors"
	"github.com/franela/goreq"
	"github.com/gin-gonic/gin"
	"log"
	"strconv"
	"sync/atomic"
	"time"
)

var errTaskNotFresh = errors.New("This task has been running too long to request a token.")
var errAlreadyGivenKey = errors.New("This task has already been given a token.")
var usedTaskIds = NewTtlSet()

func createToken(token string, opts interface{}) (string, error) {
	r, err := VaultRequest{goreq.Request{
		Uri:             vaultPath("/v1/auth/token/create", ""),
		Method:          "POST",
		Body:            opts,
		MaxRedirects:    10,
		RedirectHeaders: true,
	}.WithHeader("X-Vault-Token", token)}.Do()
	if err == nil {
		defer r.Body.Close()
		switch r.StatusCode {
		case 200:
			var t vaultTokenResp
			if err := r.Body.FromJsonTo(&t); err == nil {
				return t.Auth.ClientToken, nil
			} else {
				return "", err
			}
		default:
			var e vaultError
			e.Code = r.StatusCode
			if err := r.Body.FromJsonTo(&e); err == nil {
				return "", e
			} else {
				e.Errors = []string{"communication error."}
				return "", e
			}
		}
	} else {
		return "", err
	}
}

func createWrappedToken(token string, opts interface{}, wrapTTL time.Duration) (string, error) {
	wrapTTLSeconds := strconv.Itoa(int(wrapTTL.Seconds()))

	r, err := VaultRequest{
		goreq.Request{
			Uri:             vaultPath("/v1/auth/token/create", ""),
			Method:          "POST",
			Body:            opts,
			MaxRedirects:    10,
			RedirectHeaders: true,
		}.WithHeader("X-Vault-Token", token).WithHeader("X-Vault-Wrap-TTL", wrapTTLSeconds),
	}.Do()
	defer r.Body.Close()

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

	if r.StatusCode != 200 {
		var e vaultError
		e.Code = r.StatusCode
		if err := r.Body.FromJsonTo(&e); err == nil {
			return "", e
		} else {
			e.Errors = []string{"communication error."}
			return "", e
		}
	}

	t := &vaultTokenResp{}
	if err := r.Body.FromJsonTo(t); err != nil {
		return "", err
	}

	if t.WrapInfo.Token == "" {
		return "",  errors.New("Request for wrapped token did not return wrapped response")
	}

	return t.WrapInfo.Token, nil
}

func createTokenPair(token string, p *policy) (string, error) {
	pol := p.Policies
	if len(pol) == 0 { // explicitly set the policy, else the token will inherit ours
		pol = []string{"default"}
	}

	permTokenOpts := struct {
		Ttl       string            `json:"ttl,omitempty"`
		Policies  []string          `json:"policies"`
		Meta      map[string]string `json:"meta,omitempty"`
		NumUses   int               `json:"num_uses"`
		NoParent  bool              `json:"no_parent"`
		Renewable bool              `json:"renewable"`
	}{time.Duration(time.Duration(p.Ttl) * time.Second).String(), pol, p.Meta, p.NumUses, true, true}

	return createWrappedToken(token, permTokenOpts, 10*time.Minute)
}

func Provide(c *gin.Context) {
	requestStartTime := time.Now()
	state.RLock()
	status := state.Status
	token := state.Token
	state.RUnlock()

	remoteIp := c.Request.RemoteAddr

	atomic.AddInt32(&state.Stats.Requests, 1)

	if status == StatusSealed {
		log.Printf("Rejected token request from %s. Reason: sealed.", remoteIp)
		atomic.AddInt32(&state.Stats.Denied, 1)
		c.JSON(503, struct {
			Status string `json:"status"`
			Ok     bool   `json:"ok"`
			Error  string `json:"error"`
		}{string(state.Status), false, "Gatekeeper is sealed."})
		return
	}

	var reqParams struct {
		TaskId    string `json:"task_id"`
	}
	decoder := json.NewDecoder(c.Request.Body)
	if err := decoder.Decode(&reqParams); err == nil {
		if usedTaskIds.Has(reqParams.TaskId) {
			log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errAlreadyGivenKey)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(403, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, errAlreadyGivenKey.Error()})
			return
		}
		/*
			The task can start, but the task's framework may have not reported
			that it is RUNNING back to mesos. In this case, the task will still
			be STAGING and have a statuses length of 0.

			This is a network race, so we just sleep and try again.
		*/
		gMT := func(taskId string) (mesosTask, error) {
			task, err := getMesosTask(taskId)
			for i := time.Duration(0); i < 3 && err == nil && len(task.Statuses) == 0; i++ {
				time.Sleep((500 + 250*i) * time.Millisecond)
				task, err = getMesosTask(taskId)
			}
			return task, err
		}
		if task, err := gMT(reqParams.TaskId); err == nil {
			if len(task.Statuses) == 0 {
				log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v (no status)", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(403, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, errTaskNotFresh.Error()})
				return
			}
			// https://github.com/apache/mesos/blob/a61074586d778d432ba991701c9c4de9459db897/src/webui/master/static/js/controllers.js#L148
			startTime := time.Unix(0, int64(task.Statuses[0].Timestamp*1000000000))
			if time.Now().Sub(startTime) > config.MaxTaskLife {
				log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v (no status)", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(403, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, errTaskNotFresh.Error()})
				return
			}
			state.RLock()
			policy := activePolicies.Get(task.Name)
			state.RUnlock()
			if tempToken, err := createTokenPair(token, policy); err == nil {
				log.Printf("Provided token pair for %s in %v. (Task Id: %s) (Task Name: %s). Policies: %v", remoteIp, time.Now().Sub(requestStartTime), reqParams.TaskId, task.Name, policy.Policies)
				atomic.AddInt32(&state.Stats.Successful, 1)
				usedTaskIds.Put(reqParams.TaskId, config.MaxTaskLife+1*time.Minute)
				c.JSON(200, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Token  string `json:"token"`
				}{string(state.Status), true, tempToken})
			} else {
				log.Printf("Failed to create token pair for %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errTaskNotFresh)
				atomic.AddInt32(&state.Stats.Denied, 1)
				c.JSON(500, struct {
					Status string `json:"status"`
					Ok     bool   `json:"ok"`
					Error  string `json:"error"`
				}{string(state.Status), false, err.Error()})
			}
		} else if err == errNoSuchTask {
			log.Printf("Rejected token request from %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, errNoSuchTask)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(403, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, err.Error()})
		} else {
			log.Printf("Failed to retrieve task information for %s (Task Id: %s). Reason: %v", remoteIp, reqParams.TaskId, err)
			atomic.AddInt32(&state.Stats.Denied, 1)
			c.JSON(500, struct {
				Status string `json:"status"`
				Ok     bool   `json:"ok"`
				Error  string `json:"error"`
			}{string(state.Status), false, err.Error()})
		}
	} else {
		log.Printf("Rejected token request from %s. Reason: %v", remoteIp, err)
		atomic.AddInt32(&state.Stats.Denied, 1)
		c.JSON(400, struct {
			Status string `json:"status"`
			Ok     bool   `json:"ok"`
			Error  string `json:"error"`
		}{string(state.Status), false, err.Error()})
	}
}
<|endoftext|>