<commit_before>package lfs

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

	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

func ClearTempObjects() {
	filepath.Walk(LocalObjectTempDir, func(path string, info os.FileInfo, err error) error {
		if shouldDeleteTempObject(path, info) {
			return os.RemoveAll(path)
		}

		return err
	})
}

func shouldDeleteTempObject(path string, info os.FileInfo) bool {
	if info.IsDir() {
		return false
	}

	base := filepath.Base(path)
	parts := strings.SplitN(base, "-", 2)
	if len(parts) < 2 {
		tracerx.Printf("Removing invalid tmp object file: %s", path)
		return true
	}

	if FileExists(localMediaPathNoCreate(parts[0])) {
		tracerx.Printf("Removing existing tmp object file: %s", path)
		return true
	}

	if time.Since(info.ModTime()) > (3 * time.Hour) {
		tracerx.Printf("Removing old tmp object file: %s", path)
		return true
	}

	return false
}
<commit_msg>ンンー ンンンン ンーンン<commit_after>package lfs

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

	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

func ClearTempObjects() {
	filepath.Walk(LocalObjectTempDir, func(path string, info os.FileInfo, err error) error {
		if shouldDeleteTempObject(path, info) {
			return os.RemoveAll(path)
		}

		return err
	})
}

func shouldDeleteTempObject(path string, info os.FileInfo) bool {
	if info.IsDir() {
		return false
	}

	base := filepath.Base(path)
	parts := strings.SplitN(base, "-", 2)
	oid := parts[0]
	if len(parts) < 2 || len(oid) != 64 {
		tracerx.Printf("Removing invalid tmp object file: %s", path)
		return true
	}

	if FileExists(localMediaPathNoCreate(oid)) {
		tracerx.Printf("Removing existing tmp object file: %s", path)
		return true
	}

	if time.Since(info.ModTime()) > (3 * time.Hour) {
		tracerx.Printf("Removing old tmp object file: %s", path)
		return true
	}

	return false
}
<|endoftext|>


<commit_before>//  Copyright (c) 2013 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 rest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/couchbaselabs/go-couchbase"

	"github.com/couchbaselabs/sync_gateway/base"
	"github.com/couchbaselabs/sync_gateway/db"
)

// The URL that stats will be reported to if deployment_id is set in the config
const kStatsReportURL = "http://localhost:9999/stats"
const kStatsReportInterval = time.Hour

// Shared context of HTTP handlers: primarily a registry of databases by name. It also stores
// the configuration settings so handlers can refer to them.
// This struct is accessed from HTTP handlers running on multiple goroutines, so it needs to
// be thread-safe.
type ServerContext struct {
	config      *ServerConfig
	databases_  map[string]*db.DatabaseContext
	lock        sync.RWMutex
	statsTicker *time.Ticker
	HTTPClient  *http.Client
}

func NewServerContext(config *ServerConfig) *ServerContext {
	sc := &ServerContext{
		config:     config,
		databases_: map[string]*db.DatabaseContext{},
		HTTPClient: http.DefaultClient,
	}

	// Initialize the go-couchbase library's global configuration variables:
	couchbase.PoolSize = DefaultMaxConnections
	couchbase.PoolOverflow = DefaultMaxOverflowConnections
	if config.MaxConnections != nil {
		couchbase.PoolSize = *config.MaxConnections
	}
	if config.MaxOverflowConnections != nil {
		couchbase.PoolOverflow = *config.MaxOverflowConnections
	}

	if config.DeploymentID != nil {
		sc.startStatsReporter()
	}
	return sc
}

func (sc *ServerContext) Close() {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	sc.stopStatsReporter()
	for _, ctx := range sc.databases_ {
		ctx.Close()
	}
	sc.databases_ = nil
}

// Returns the DatabaseContext with the given name
func (sc *ServerContext) GetDatabase(name string) (*db.DatabaseContext, error) {
	sc.lock.RLock()
	dbc := sc.databases_[name]
	sc.lock.RUnlock()
	if dbc != nil {
		return dbc, nil
	} else if db.ValidateDatabaseName(name) != nil {
		return nil, &base.HTTPError{http.StatusBadRequest, "invalid database name '" + name + "'"}
	} else if sc.config.ConfigServer == nil {
		return nil, &base.HTTPError{http.StatusNotFound, "no such database '" + name + "'"}
	} else {
		// Let's ask the config server if it knows this database:
		base.Log("Asking config server %q about db %q...", *sc.config.ConfigServer, name)
		config, err := sc.getDbConfigFromServer(name)
		if err != nil {
			return nil, err
		}
		if dbc, err = sc.AddDatabaseFromConfig(config); err != nil {
			return nil, err
		}
		return dbc, nil
	}
}

func (sc *ServerContext) AllDatabaseNames() []string {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	names := make([]string, 0, len(sc.databases_))
	for name, _ := range sc.databases_ {
		names = append(names, name)
	}
	return names
}

func (sc *ServerContext) registerDatabase(dbcontext *db.DatabaseContext) error {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	name := dbcontext.Name
	if sc.databases_[name] != nil {
		return &base.HTTPError{http.StatusPreconditionFailed, // what CouchDB returns
			fmt.Sprintf("Duplicate database name %q", name)}
	}
	sc.databases_[name] = dbcontext
	return nil
}

// Adds a database to the ServerContext given its configuration.
func (sc *ServerContext) AddDatabaseFromConfig(config *DbConfig) (*db.DatabaseContext, error) {
	server := "http://localhost:8091"
	pool := "default"
	bucketName := config.name

	if config.Server != nil {
		server = *config.Server
	}
	if config.Pool != nil {
		pool = *config.Pool
	}
	if config.Bucket != nil {
		bucketName = *config.Bucket
	}
	dbName := config.name
	if dbName == "" {
		dbName = bucketName
	}
	base.Log("Opening db /%s as bucket %q, pool %q, server <%s>",
		dbName, bucketName, pool, server)

	if err := db.ValidateDatabaseName(dbName); err != nil {
		return nil, err
	}

	var importDocs, autoImport bool
	switch config.ImportDocs {
	case nil, false:
	case true:
		importDocs = true
	case "continuous":
		importDocs = true
		autoImport = true
	default:
		return nil, fmt.Errorf("Unrecognized value for ImportDocs: %#v", config.ImportDocs)
	}

	// Connect to the bucket and add the database:
	spec := base.BucketSpec{
		Server:     server,
		PoolName:   pool,
		BucketName: bucketName,
	}
	if config.Username != "" {
		spec.Auth = config
	}
	bucket, err := db.ConnectToBucket(spec)
	if err != nil {
		return nil, err
	}
	dbcontext, err := db.NewDatabaseContext(dbName, bucket, autoImport)
	if err != nil {
		return nil, err
	}

	syncFn := ""
	if config.Sync != nil {
		syncFn = *config.Sync
	}
	if err := dbcontext.ApplySyncFun(syncFn, importDocs); err != nil {
		return nil, err
	}

	if config.RevsLimit != nil && *config.RevsLimit > 0 {
		dbcontext.RevsLimit = *config.RevsLimit
	}

	if dbcontext.ChannelMapper == nil {
		base.Warn("Database %q sync function undefined; using default", dbName)
	}

	// Create default users & roles:
	if err := sc.installPrincipals(dbcontext, config.Roles, "role"); err != nil {
		return nil, err
	} else if err := sc.installPrincipals(dbcontext, config.Users, "user"); err != nil {
		return nil, err
	}

	// Register it so HTTP handlers can find it:
	if err := sc.registerDatabase(dbcontext); err != nil {
		dbcontext.Close()
		return nil, err
	}
	return dbcontext, nil
}

func (sc *ServerContext) RemoveDatabase(dbName string) bool {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	context := sc.databases_[dbName]
	if context == nil {
		return false
	}
	base.Log("Closing db /%s (bucket %q)", context.Name, context.Bucket.GetName())
	context.Close()
	delete(sc.databases_, dbName)
	return true
}

func (sc *ServerContext) installPrincipals(context *db.DatabaseContext, spec map[string]*PrincipalConfig, what string) error {
	for name, princ := range spec {
		princ.Name = &name
		_, err := updatePrincipal(context, *princ, (what == "user"), (name == "GUEST"))
		if err != nil {
			// A conflict error just means updatePrincipal didn't overwrite an existing user.
			if status, _ := base.ErrorAsHTTPStatus(err); status != http.StatusConflict {
				return fmt.Errorf("Couldn't create %s %q: %v", what, name, err)
			}
		} else if name == "GUEST" {
			base.Log("    Reset guest user to config")
		} else {
			base.Log("    Created %s %q", what, name)
		}
	}
	return nil
}

// Fetch a configuration for a database from the ConfigServer
func (sc *ServerContext) getDbConfigFromServer(dbName string) (*DbConfig, error) {
	if sc.config.ConfigServer == nil {
		return nil, &base.HTTPError{http.StatusNotFound, "not_found"}
	}

	urlStr := *sc.config.ConfigServer
	if !strings.HasSuffix(urlStr, "/") {
		urlStr += "/"
	}
	urlStr += url.QueryEscape(dbName)
	res, err := sc.HTTPClient.Get(urlStr)
	if err != nil {
		return nil, &base.HTTPError{http.StatusBadGateway,
			"Error contacting config server: " + err.Error()}
	} else if res.StatusCode >= 300 {
		return nil, &base.HTTPError{res.StatusCode, res.Status}
	}

	var config DbConfig
	j := json.NewDecoder(res.Body)
	if err = j.Decode(&config); err != nil {
		return nil, &base.HTTPError{http.StatusBadGateway,
			"Bad response from config server: " + err.Error()}
	}

	if err = config.setup(dbName); err != nil {
		return nil, err
	}
	return &config, nil
}

//////// STATISTICS REPORT:

func (sc *ServerContext) startStatsReporter() {
	interval := kStatsReportInterval
	if sc.config.StatsReportInterval != nil {
		if *sc.config.StatsReportInterval <= 0 {
			return
		}
		interval = time.Duration(*sc.config.StatsReportInterval) * time.Second
	}
	sc.statsTicker = time.NewTicker(interval)
	go func() {
		for _ = range sc.statsTicker.C {
			sc.reportStats()
		}
	}()
	base.Log("Will report server stats for %q every %v",
		*sc.config.DeploymentID, interval)
}

func (sc *ServerContext) stopStatsReporter() {
	if sc.statsTicker != nil {
		sc.statsTicker.Stop()
		sc.reportStats() // Report stuff since the last tick
	}
}

// POST a report of database statistics
func (sc *ServerContext) reportStats() {
	if sc.config.DeploymentID == nil {
		panic("Can't reportStats without DeploymentID")
	}
	stats := sc.Stats()
	if stats == nil {
		return // No activity
	}
	base.Log("Reporting server stats to %s ...", kStatsReportURL)
	body, _ := json.Marshal(stats)
	bodyReader := bytes.NewReader(body)
	_, err := sc.HTTPClient.Post(kStatsReportURL, "application/json", bodyReader)
	if err != nil {
		base.Warn("Error posting stats: %v", err)
	}
}

func (sc *ServerContext) Stats() map[string]interface{} {
	sc.lock.RLock()
	defer sc.lock.RUnlock()
	var stats []map[string]interface{}
	any := false
	for _, dbc := range sc.databases_ {
		max := dbc.ChangesClientStats.MaxCount()
		total := dbc.ChangesClientStats.TotalCount()
		dbc.ChangesClientStats.Reset()
		stats = append(stats, map[string]interface{}{
			"max_connections":   max,
			"total_connections": total,
		})
		any = any || total > 0
	}
	if !any {
		return nil
	}
	return map[string]interface{}{
		"deploymentID": *sc.config.DeploymentID,
		"databases":    stats,
	}
}
<commit_msg>Fixed interpretation of PoolOverflow in previous commit<commit_after>//  Copyright (c) 2013 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 rest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/couchbaselabs/go-couchbase"

	"github.com/couchbaselabs/sync_gateway/base"
	"github.com/couchbaselabs/sync_gateway/db"
)

// The URL that stats will be reported to if deployment_id is set in the config
const kStatsReportURL = "http://localhost:9999/stats"
const kStatsReportInterval = time.Hour

// Shared context of HTTP handlers: primarily a registry of databases by name. It also stores
// the configuration settings so handlers can refer to them.
// This struct is accessed from HTTP handlers running on multiple goroutines, so it needs to
// be thread-safe.
type ServerContext struct {
	config      *ServerConfig
	databases_  map[string]*db.DatabaseContext
	lock        sync.RWMutex
	statsTicker *time.Ticker
	HTTPClient  *http.Client
}

func NewServerContext(config *ServerConfig) *ServerContext {
	sc := &ServerContext{
		config:     config,
		databases_: map[string]*db.DatabaseContext{},
		HTTPClient: http.DefaultClient,
	}

	// Initialize the go-couchbase library's global configuration variables:
	couchbase.PoolSize = DefaultMaxConnections
	couchbase.PoolOverflow = couchbase.PoolSize + DefaultMaxOverflowConnections
	if config.MaxConnections != nil {
		couchbase.PoolSize = *config.MaxConnections
	}
	if config.MaxOverflowConnections != nil {
		couchbase.PoolOverflow = couchbase.PoolSize + *config.MaxOverflowConnections
	}

	if config.DeploymentID != nil {
		sc.startStatsReporter()
	}
	return sc
}

func (sc *ServerContext) Close() {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	sc.stopStatsReporter()
	for _, ctx := range sc.databases_ {
		ctx.Close()
	}
	sc.databases_ = nil
}

// Returns the DatabaseContext with the given name
func (sc *ServerContext) GetDatabase(name string) (*db.DatabaseContext, error) {
	sc.lock.RLock()
	dbc := sc.databases_[name]
	sc.lock.RUnlock()
	if dbc != nil {
		return dbc, nil
	} else if db.ValidateDatabaseName(name) != nil {
		return nil, &base.HTTPError{http.StatusBadRequest, "invalid database name '" + name + "'"}
	} else if sc.config.ConfigServer == nil {
		return nil, &base.HTTPError{http.StatusNotFound, "no such database '" + name + "'"}
	} else {
		// Let's ask the config server if it knows this database:
		base.Log("Asking config server %q about db %q...", *sc.config.ConfigServer, name)
		config, err := sc.getDbConfigFromServer(name)
		if err != nil {
			return nil, err
		}
		if dbc, err = sc.AddDatabaseFromConfig(config); err != nil {
			return nil, err
		}
		return dbc, nil
	}
}

func (sc *ServerContext) AllDatabaseNames() []string {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	names := make([]string, 0, len(sc.databases_))
	for name, _ := range sc.databases_ {
		names = append(names, name)
	}
	return names
}

func (sc *ServerContext) registerDatabase(dbcontext *db.DatabaseContext) error {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	name := dbcontext.Name
	if sc.databases_[name] != nil {
		return &base.HTTPError{http.StatusPreconditionFailed, // what CouchDB returns
			fmt.Sprintf("Duplicate database name %q", name)}
	}
	sc.databases_[name] = dbcontext
	return nil
}

// Adds a database to the ServerContext given its configuration.
func (sc *ServerContext) AddDatabaseFromConfig(config *DbConfig) (*db.DatabaseContext, error) {
	server := "http://localhost:8091"
	pool := "default"
	bucketName := config.name

	if config.Server != nil {
		server = *config.Server
	}
	if config.Pool != nil {
		pool = *config.Pool
	}
	if config.Bucket != nil {
		bucketName = *config.Bucket
	}
	dbName := config.name
	if dbName == "" {
		dbName = bucketName
	}
	base.Log("Opening db /%s as bucket %q, pool %q, server <%s>",
		dbName, bucketName, pool, server)

	if err := db.ValidateDatabaseName(dbName); err != nil {
		return nil, err
	}

	var importDocs, autoImport bool
	switch config.ImportDocs {
	case nil, false:
	case true:
		importDocs = true
	case "continuous":
		importDocs = true
		autoImport = true
	default:
		return nil, fmt.Errorf("Unrecognized value for ImportDocs: %#v", config.ImportDocs)
	}

	// Connect to the bucket and add the database:
	spec := base.BucketSpec{
		Server:     server,
		PoolName:   pool,
		BucketName: bucketName,
	}
	if config.Username != "" {
		spec.Auth = config
	}
	bucket, err := db.ConnectToBucket(spec)
	if err != nil {
		return nil, err
	}
	dbcontext, err := db.NewDatabaseContext(dbName, bucket, autoImport)
	if err != nil {
		return nil, err
	}

	syncFn := ""
	if config.Sync != nil {
		syncFn = *config.Sync
	}
	if err := dbcontext.ApplySyncFun(syncFn, importDocs); err != nil {
		return nil, err
	}

	if config.RevsLimit != nil && *config.RevsLimit > 0 {
		dbcontext.RevsLimit = *config.RevsLimit
	}

	if dbcontext.ChannelMapper == nil {
		base.Warn("Database %q sync function undefined; using default", dbName)
	}

	// Create default users & roles:
	if err := sc.installPrincipals(dbcontext, config.Roles, "role"); err != nil {
		return nil, err
	} else if err := sc.installPrincipals(dbcontext, config.Users, "user"); err != nil {
		return nil, err
	}

	// Register it so HTTP handlers can find it:
	if err := sc.registerDatabase(dbcontext); err != nil {
		dbcontext.Close()
		return nil, err
	}
	return dbcontext, nil
}

func (sc *ServerContext) RemoveDatabase(dbName string) bool {
	sc.lock.Lock()
	defer sc.lock.Unlock()

	context := sc.databases_[dbName]
	if context == nil {
		return false
	}
	base.Log("Closing db /%s (bucket %q)", context.Name, context.Bucket.GetName())
	context.Close()
	delete(sc.databases_, dbName)
	return true
}

func (sc *ServerContext) installPrincipals(context *db.DatabaseContext, spec map[string]*PrincipalConfig, what string) error {
	for name, princ := range spec {
		princ.Name = &name
		_, err := updatePrincipal(context, *princ, (what == "user"), (name == "GUEST"))
		if err != nil {
			// A conflict error just means updatePrincipal didn't overwrite an existing user.
			if status, _ := base.ErrorAsHTTPStatus(err); status != http.StatusConflict {
				return fmt.Errorf("Couldn't create %s %q: %v", what, name, err)
			}
		} else if name == "GUEST" {
			base.Log("    Reset guest user to config")
		} else {
			base.Log("    Created %s %q", what, name)
		}
	}
	return nil
}

// Fetch a configuration for a database from the ConfigServer
func (sc *ServerContext) getDbConfigFromServer(dbName string) (*DbConfig, error) {
	if sc.config.ConfigServer == nil {
		return nil, &base.HTTPError{http.StatusNotFound, "not_found"}
	}

	urlStr := *sc.config.ConfigServer
	if !strings.HasSuffix(urlStr, "/") {
		urlStr += "/"
	}
	urlStr += url.QueryEscape(dbName)
	res, err := sc.HTTPClient.Get(urlStr)
	if err != nil {
		return nil, &base.HTTPError{http.StatusBadGateway,
			"Error contacting config server: " + err.Error()}
	} else if res.StatusCode >= 300 {
		return nil, &base.HTTPError{res.StatusCode, res.Status}
	}

	var config DbConfig
	j := json.NewDecoder(res.Body)
	if err = j.Decode(&config); err != nil {
		return nil, &base.HTTPError{http.StatusBadGateway,
			"Bad response from config server: " + err.Error()}
	}

	if err = config.setup(dbName); err != nil {
		return nil, err
	}
	return &config, nil
}

//////// STATISTICS REPORT:

func (sc *ServerContext) startStatsReporter() {
	interval := kStatsReportInterval
	if sc.config.StatsReportInterval != nil {
		if *sc.config.StatsReportInterval <= 0 {
			return
		}
		interval = time.Duration(*sc.config.StatsReportInterval) * time.Second
	}
	sc.statsTicker = time.NewTicker(interval)
	go func() {
		for _ = range sc.statsTicker.C {
			sc.reportStats()
		}
	}()
	base.Log("Will report server stats for %q every %v",
		*sc.config.DeploymentID, interval)
}

func (sc *ServerContext) stopStatsReporter() {
	if sc.statsTicker != nil {
		sc.statsTicker.Stop()
		sc.reportStats() // Report stuff since the last tick
	}
}

// POST a report of database statistics
func (sc *ServerContext) reportStats() {
	if sc.config.DeploymentID == nil {
		panic("Can't reportStats without DeploymentID")
	}
	stats := sc.Stats()
	if stats == nil {
		return // No activity
	}
	base.Log("Reporting server stats to %s ...", kStatsReportURL)
	body, _ := json.Marshal(stats)
	bodyReader := bytes.NewReader(body)
	_, err := sc.HTTPClient.Post(kStatsReportURL, "application/json", bodyReader)
	if err != nil {
		base.Warn("Error posting stats: %v", err)
	}
}

func (sc *ServerContext) Stats() map[string]interface{} {
	sc.lock.RLock()
	defer sc.lock.RUnlock()
	var stats []map[string]interface{}
	any := false
	for _, dbc := range sc.databases_ {
		max := dbc.ChangesClientStats.MaxCount()
		total := dbc.ChangesClientStats.TotalCount()
		dbc.ChangesClientStats.Reset()
		stats = append(stats, map[string]interface{}{
			"max_connections":   max,
			"total_connections": total,
		})
		any = any || total > 0
	}
	if !any {
		return nil
	}
	return map[string]interface{}{
		"deploymentID": *sc.config.DeploymentID,
		"databases":    stats,
	}
}
<|endoftext|>


<commit_before>package command

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

// OperatorKeygenCommand is a Command implementation that generates an encryption
// key for use in `nomad agent`.
type OperatorKeygenCommand struct {
	Meta
}

func (c *OperatorKeygenCommand) Synopsis() string {
	return "Generates a new encryption key"
}

func (c *OperatorKeygenCommand) Help() string {
	helpText := `
Usage: nomad operator keygen

  Generates a new encryption key that can be used to configure the
  agent to encrypt traffic. The output of this command is already
  in the proper format that the agent expects.
`
	return strings.TrimSpace(helpText)
}

func (c *OperatorKeygenCommand) Name() string { return "operator keygen" }

func (c *OperatorKeygenCommand) Run(_ []string) int {
	key := make([]byte, 16)
	n, err := rand.Reader.Read(key)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error reading random data: %s", err))
		return 1
	}
	if n != 16 {
		c.Ui.Error(fmt.Sprintf("Couldn't read enough entropy. Generate more entropy!"))
		return 1
	}

	c.Ui.Output(base64.StdEncoding.EncodeToString(key))
	return 0
}
<commit_msg>Generate 32-byte gossip key for nomad operator keygen command<commit_after>package command

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

// OperatorKeygenCommand is a Command implementation that generates an encryption
// key for use in `nomad agent`.
type OperatorKeygenCommand struct {
	Meta
}

func (c *OperatorKeygenCommand) Synopsis() string {
	return "Generates a new encryption key"
}

func (c *OperatorKeygenCommand) Help() string {
	helpText := `
Usage: nomad operator keygen

  Generates a new 32-byte encryption key that can be used to configure the
  agent to encrypt traffic. The output of this command is already
  in the proper format that the agent expects.
`
	return strings.TrimSpace(helpText)
}

func (c *OperatorKeygenCommand) Name() string { return "operator keygen" }

func (c *OperatorKeygenCommand) Run(_ []string) int {
	key := make([]byte, 32)
	n, err := rand.Reader.Read(key)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error reading random data: %s", err))
		return 1
	}
	if n != 32 {
		c.Ui.Error(fmt.Sprintf("Couldn't read enough entropy. Generate more entropy!"))
		return 1
	}

	c.Ui.Output(base64.StdEncoding.EncodeToString(key))
	return 0
}
<|endoftext|>


<commit_before>package master

import (
	"net"
	"reflect"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/diff"
	apiserveroptions "k8s.io/apiserver/pkg/server/options"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	utilconfig "k8s.io/apiserver/pkg/util/flag"
	kubeapiserveroptions "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
	cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
	kapi "k8s.io/kubernetes/pkg/api"
	apiv1 "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/apis/componentconfig"
	extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
	kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
	kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
	scheduleroptions "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
)

func TestAPIServerDefaults(t *testing.T) {
	defaults := kubeapiserveroptions.NewServerRunOptions()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &kubeapiserveroptions.ServerRunOptions{
		ServiceNodePortRange: kubeapiserveroptions.DefaultServiceNodePortRange,
		MasterCount:          1,
		GenericServerRunOptions: &apiserveroptions.ServerRunOptions{
			MaxRequestsInFlight:         400,
			MaxMutatingRequestsInFlight: 200,
			MinRequestTimeout:           1800,
			AdmissionControl:            "AlwaysAdmit",
		},
		Etcd: &apiserveroptions.EtcdOptions{
			StorageConfig: storagebackend.Config{
				ServerList: nil,
				Prefix:     "/registry",
				DeserializationCacheSize: 0,
				Copier: kapi.Scheme,
			},
			DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
			DeleteCollectionWorkers: 1,
			EnableGarbageCollection: true,
			EnableWatchCache:        true,
		},
		SecureServing: &apiserveroptions.SecureServingOptions{
			ServingOptions: apiserveroptions.ServingOptions{
				BindAddress: net.ParseIP("0.0.0.0"),
				BindPort:    6443,
			},
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "/var/run/kubernetes",
				PairName:      "apiserver",
			},
		},
		InsecureServing: &apiserveroptions.ServingOptions{
			BindAddress: net.ParseIP("127.0.0.1"),
			BindPort:    8080,
		},
		EventTTL: 1 * time.Hour,
		KubeletConfig: kubeletclient.KubeletClientConfig{
			Port:         10250,
			ReadOnlyPort: 10255,
			PreferredAddressTypes: []string{
				string(apiv1.NodeHostName),
				string(apiv1.NodeInternalDNS),
				string(apiv1.NodeInternalIP),
				string(apiv1.NodeExternalDNS),
				string(apiv1.NodeExternalIP),
				string(apiv1.NodeLegacyHostIP),
			},
			EnableHttps: true,
			HTTPTimeout: time.Duration(5) * time.Second,
		},
		Audit: &apiserveroptions.AuditLogOptions{},
		Features: &apiserveroptions.FeatureOptions{
			EnableProfiling: true,
		},
		Authentication: &kubeoptions.BuiltInAuthenticationOptions{
			Anonymous:       &kubeoptions.AnonymousAuthenticationOptions{Allow: true},
			AnyToken:        &kubeoptions.AnyTokenAuthenticationOptions{},
			BootstrapToken:  &kubeoptions.BootstrapTokenAuthenticationOptions{},
			ClientCert:      &apiserveroptions.ClientCertAuthenticationOptions{},
			Keystone:        &kubeoptions.KeystoneAuthenticationOptions{},
			OIDC:            &kubeoptions.OIDCAuthenticationOptions{},
			PasswordFile:    &kubeoptions.PasswordFileAuthenticationOptions{},
			RequestHeader:   &apiserveroptions.RequestHeaderAuthenticationOptions{},
			ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{},
			TokenFile:       &kubeoptions.TokenFileAuthenticationOptions{},
			WebHook:         &kubeoptions.WebHookAuthenticationOptions{CacheTTL: 2 * time.Minute},
		},
		Authorization: &kubeoptions.BuiltInAuthorizationOptions{
			Mode: "AlwaysAllow",
			WebhookCacheAuthorizedTTL:   5 * time.Minute,
			WebhookCacheUnauthorizedTTL: 30 * time.Second,
		},
		CloudProvider: &kubeoptions.CloudProviderOptions{},
		StorageSerialization: &kubeoptions.StorageSerializationOptions{
			StorageVersions:        kapi.Registry.AllPreferredGroupVersions(),
			DefaultStorageVersions: kapi.Registry.AllPreferredGroupVersions(),
		},
		APIEnablement: &kubeoptions.APIEnablementOptions{
			RuntimeConfig: utilconfig.ConfigurationMap{},
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestCMServerDefaults(t *testing.T) {
	defaults := cmapp.NewCMServer()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &cmapp.CMServer{
		KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{
			Port:                              10252, // disabled
			Address:                           "0.0.0.0",
			ConcurrentEndpointSyncs:           5,
			ConcurrentRCSyncs:                 5,
			ConcurrentRSSyncs:                 5,
			ConcurrentDaemonSetSyncs:          2,
			ConcurrentJobSyncs:                5,
			ConcurrentResourceQuotaSyncs:      5,
			ConcurrentDeploymentSyncs:         5,
			ConcurrentNamespaceSyncs:          2,
			ConcurrentSATokenSyncs:            5,
			ConcurrentServiceSyncs:            1,
			ConcurrentGCSyncs:                 20,
			LookupCacheSizeForRC:              4096,
			LookupCacheSizeForRS:              4096,
			LookupCacheSizeForDaemonSet:       1024,
			ConfigureCloudRoutes:              true,
			NodeCIDRMaskSize:                  24,
			ServiceSyncPeriod:                 metav1.Duration{Duration: 5 * time.Minute},
			ResourceQuotaSyncPeriod:           metav1.Duration{Duration: 5 * time.Minute},
			NamespaceSyncPeriod:               metav1.Duration{Duration: 5 * time.Minute},
			PVClaimBinderSyncPeriod:           metav1.Duration{Duration: 15 * time.Second},
			HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 30 * time.Second},
			DeploymentControllerSyncPeriod:    metav1.Duration{Duration: 30 * time.Second},
			MinResyncPeriod:                   metav1.Duration{Duration: 12 * time.Hour},
			RegisterRetryCount:                10,
			RouteReconciliationPeriod:         metav1.Duration{Duration: 10 * time.Second},
			PodEvictionTimeout:                metav1.Duration{Duration: 5 * time.Minute},
			NodeMonitorGracePeriod:            metav1.Duration{Duration: 40 * time.Second},
			NodeStartupGracePeriod:            metav1.Duration{Duration: 60 * time.Second},
			NodeMonitorPeriod:                 metav1.Duration{Duration: 5 * time.Second},
			ClusterName:                       "kubernetes",
			TerminatedPodGCThreshold:          12500,
			VolumeConfiguration: componentconfig.VolumeConfiguration{
				EnableDynamicProvisioning:  true,
				EnableHostPathProvisioning: false,
				FlexVolumePluginDir:        "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/",
				PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{
					MaximumRetry:             3,
					MinimumTimeoutNFS:        300,
					IncrementTimeoutNFS:      30,
					MinimumTimeoutHostPath:   60,
					IncrementTimeoutHostPath: 30,
				},
			},
			ContentType:  "application/vnd.kubernetes.protobuf",
			KubeAPIQPS:   20.0,
			KubeAPIBurst: 30,
			LeaderElection: componentconfig.LeaderElectionConfiguration{
				LeaderElect:   true,
				LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
				RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
				RetryPeriod:   metav1.Duration{Duration: 2 * time.Second},
			},
			ClusterSigningCertFile:            "/etc/kubernetes/ca/ca.pem",
			ClusterSigningKeyFile:             "/etc/kubernetes/ca/ca.key",
			EnableGarbageCollector:            true,
			DisableAttachDetachReconcilerSync: false,
			ReconcilerSyncLoopPeriod:          metav1.Duration{Duration: 60 * time.Second},
			Controllers:                       []string{"*"},
			EnableTaintManager:                true,
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestSchedulerServerDefaults(t *testing.T) {
	defaults := scheduleroptions.NewSchedulerServer()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &scheduleroptions.SchedulerServer{
		KubeSchedulerConfiguration: componentconfig.KubeSchedulerConfiguration{
			Port:                           10251, // disabled
			Address:                        "0.0.0.0",
			AlgorithmProvider:              "DefaultProvider",
			ContentType:                    "application/vnd.kubernetes.protobuf",
			KubeAPIQPS:                     50,
			KubeAPIBurst:                   100,
			SchedulerName:                  "default-scheduler",
			HardPodAffinitySymmetricWeight: 1,
			FailureDomains:                 "kubernetes.io/hostname,failure-domain.beta.kubernetes.io/zone,failure-domain.beta.kubernetes.io/region",
			LeaderElection: componentconfig.LeaderElectionConfiguration{
				LeaderElect: true,
				LeaseDuration: metav1.Duration{
					Duration: 15 * time.Second,
				},
				RenewDeadline: metav1.Duration{
					Duration: 10 * time.Second,
				},
				RetryPeriod: metav1.Duration{
					Duration: 2 * time.Second,
				},
			},
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestGetAPIGroupVersionOverrides(t *testing.T) {
	testcases := map[string]struct {
		DisabledVersions         map[string][]string
		ExpectedDisabledVersions []schema.GroupVersion
		ExpectedEnabledVersions  []schema.GroupVersion
	}{
		"empty": {
			DisabledVersions:         nil,
			ExpectedDisabledVersions: []schema.GroupVersion{},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion},
		},
		"* -> v1": {
			DisabledVersions:         map[string][]string{"": {"*"}},
			ExpectedDisabledVersions: []schema.GroupVersion{apiv1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
		},
		"v1": {
			DisabledVersions:         map[string][]string{"": {"v1"}},
			ExpectedDisabledVersions: []schema.GroupVersion{apiv1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
		},
		"* -> v1beta1": {
			DisabledVersions:         map[string][]string{"extensions": {"*"}},
			ExpectedDisabledVersions: []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion},
		},
		"extensions/v1beta1": {
			DisabledVersions:         map[string][]string{"extensions": {"v1beta1"}},
			ExpectedDisabledVersions: []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion},
		},
	}

	for k, tc := range testcases {
		config := configapi.MasterConfig{KubernetesMasterConfig: &configapi.KubernetesMasterConfig{DisabledAPIGroupVersions: tc.DisabledVersions}}
		overrides := getAPIResourceConfig(config)

		for _, expected := range tc.ExpectedDisabledVersions {
			if overrides.AnyResourcesForVersionEnabled(expected) {
				t.Errorf("%s: Expected %v", k, expected)
			}
		}

		for _, expected := range tc.ExpectedEnabledVersions {
			if !overrides.AllResourcesForVersionEnabled(expected) {
				t.Errorf("%s: Expected %v", k, expected)
			}
		}
	}
}
<commit_msg>Add test to notice upstream changes in group perferred versions<commit_after>package master

import (
	"net"
	"reflect"
	"strings"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/diff"
	apiserveroptions "k8s.io/apiserver/pkg/server/options"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	utilconfig "k8s.io/apiserver/pkg/util/flag"
	kubeapiserveroptions "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
	cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
	kapi "k8s.io/kubernetes/pkg/api"
	apiv1 "k8s.io/kubernetes/pkg/api/v1"
	"k8s.io/kubernetes/pkg/apis/componentconfig"
	extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
	kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
	kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
	scheduleroptions "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
)

var expectedGroupPreferredVersions []string = []string{
	// keep this sorted:
	"apps/v1beta1,authentication.k8s.io/v1",
	"authorization.k8s.io/v1",
	"autoscaling/v1",
	"batch/v1",
	"certificates.k8s.io/v1beta1",
	"componentconfig/v1alpha1",
	"extensions/v1beta1",
	"federation/v1beta1",
	"imagepolicy.k8s.io/v1alpha1",
	"policy/v1beta1",
	"rbac.authorization.k8s.io/v1beta1",
	"settings.k8s.io/v1alpha1",
	"storage.k8s.io/v1beta1",
	"v1",
}

func TestPreferredGroupVersions(t *testing.T) {
	s := kapi.Registry.AllPreferredGroupVersions()
	expected := strings.Join(expectedGroupPreferredVersions, ",")
	if s != expected {
		t.Errorf("unexpected preferred group versions: %v", diff.StringDiff(expected, s))
	}
}

func TestAPIServerDefaults(t *testing.T) {
	defaults := kubeapiserveroptions.NewServerRunOptions()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &kubeapiserveroptions.ServerRunOptions{
		ServiceNodePortRange: kubeapiserveroptions.DefaultServiceNodePortRange,
		MasterCount:          1,
		GenericServerRunOptions: &apiserveroptions.ServerRunOptions{
			MaxRequestsInFlight:         400,
			MaxMutatingRequestsInFlight: 200,
			MinRequestTimeout:           1800,
			AdmissionControl:            "AlwaysAdmit",
		},
		Etcd: &apiserveroptions.EtcdOptions{
			StorageConfig: storagebackend.Config{
				ServerList: nil,
				Prefix:     "/registry",
				DeserializationCacheSize: 0,
				Copier: kapi.Scheme,
			},
			DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
			DeleteCollectionWorkers: 1,
			EnableGarbageCollection: true,
			EnableWatchCache:        true,
		},
		SecureServing: &apiserveroptions.SecureServingOptions{
			ServingOptions: apiserveroptions.ServingOptions{
				BindAddress: net.ParseIP("0.0.0.0"),
				BindPort:    6443,
			},
			ServerCert: apiserveroptions.GeneratableKeyCert{
				CertDirectory: "/var/run/kubernetes",
				PairName:      "apiserver",
			},
		},
		InsecureServing: &apiserveroptions.ServingOptions{
			BindAddress: net.ParseIP("127.0.0.1"),
			BindPort:    8080,
		},
		EventTTL: 1 * time.Hour,
		KubeletConfig: kubeletclient.KubeletClientConfig{
			Port:         10250,
			ReadOnlyPort: 10255,
			PreferredAddressTypes: []string{
				string(apiv1.NodeHostName),
				string(apiv1.NodeInternalDNS),
				string(apiv1.NodeInternalIP),
				string(apiv1.NodeExternalDNS),
				string(apiv1.NodeExternalIP),
				string(apiv1.NodeLegacyHostIP),
			},
			EnableHttps: true,
			HTTPTimeout: time.Duration(5) * time.Second,
		},
		Audit: &apiserveroptions.AuditLogOptions{},
		Features: &apiserveroptions.FeatureOptions{
			EnableProfiling: true,
		},
		Authentication: &kubeoptions.BuiltInAuthenticationOptions{
			Anonymous:       &kubeoptions.AnonymousAuthenticationOptions{Allow: true},
			AnyToken:        &kubeoptions.AnyTokenAuthenticationOptions{},
			BootstrapToken:  &kubeoptions.BootstrapTokenAuthenticationOptions{},
			ClientCert:      &apiserveroptions.ClientCertAuthenticationOptions{},
			Keystone:        &kubeoptions.KeystoneAuthenticationOptions{},
			OIDC:            &kubeoptions.OIDCAuthenticationOptions{},
			PasswordFile:    &kubeoptions.PasswordFileAuthenticationOptions{},
			RequestHeader:   &apiserveroptions.RequestHeaderAuthenticationOptions{},
			ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{},
			TokenFile:       &kubeoptions.TokenFileAuthenticationOptions{},
			WebHook:         &kubeoptions.WebHookAuthenticationOptions{CacheTTL: 2 * time.Minute},
		},
		Authorization: &kubeoptions.BuiltInAuthorizationOptions{
			Mode: "AlwaysAllow",
			WebhookCacheAuthorizedTTL:   5 * time.Minute,
			WebhookCacheUnauthorizedTTL: 30 * time.Second,
		},
		CloudProvider: &kubeoptions.CloudProviderOptions{},
		StorageSerialization: &kubeoptions.StorageSerializationOptions{
			StorageVersions:        kapi.Registry.AllPreferredGroupVersions(),
			DefaultStorageVersions: kapi.Registry.AllPreferredGroupVersions(),
		},
		APIEnablement: &kubeoptions.APIEnablementOptions{
			RuntimeConfig: utilconfig.ConfigurationMap{},
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestCMServerDefaults(t *testing.T) {
	defaults := cmapp.NewCMServer()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &cmapp.CMServer{
		KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{
			Port:                              10252, // disabled
			Address:                           "0.0.0.0",
			ConcurrentEndpointSyncs:           5,
			ConcurrentRCSyncs:                 5,
			ConcurrentRSSyncs:                 5,
			ConcurrentDaemonSetSyncs:          2,
			ConcurrentJobSyncs:                5,
			ConcurrentResourceQuotaSyncs:      5,
			ConcurrentDeploymentSyncs:         5,
			ConcurrentNamespaceSyncs:          2,
			ConcurrentSATokenSyncs:            5,
			ConcurrentServiceSyncs:            1,
			ConcurrentGCSyncs:                 20,
			LookupCacheSizeForRC:              4096,
			LookupCacheSizeForRS:              4096,
			LookupCacheSizeForDaemonSet:       1024,
			ConfigureCloudRoutes:              true,
			NodeCIDRMaskSize:                  24,
			ServiceSyncPeriod:                 metav1.Duration{Duration: 5 * time.Minute},
			ResourceQuotaSyncPeriod:           metav1.Duration{Duration: 5 * time.Minute},
			NamespaceSyncPeriod:               metav1.Duration{Duration: 5 * time.Minute},
			PVClaimBinderSyncPeriod:           metav1.Duration{Duration: 15 * time.Second},
			HorizontalPodAutoscalerSyncPeriod: metav1.Duration{Duration: 30 * time.Second},
			DeploymentControllerSyncPeriod:    metav1.Duration{Duration: 30 * time.Second},
			MinResyncPeriod:                   metav1.Duration{Duration: 12 * time.Hour},
			RegisterRetryCount:                10,
			RouteReconciliationPeriod:         metav1.Duration{Duration: 10 * time.Second},
			PodEvictionTimeout:                metav1.Duration{Duration: 5 * time.Minute},
			NodeMonitorGracePeriod:            metav1.Duration{Duration: 40 * time.Second},
			NodeStartupGracePeriod:            metav1.Duration{Duration: 60 * time.Second},
			NodeMonitorPeriod:                 metav1.Duration{Duration: 5 * time.Second},
			ClusterName:                       "kubernetes",
			TerminatedPodGCThreshold:          12500,
			VolumeConfiguration: componentconfig.VolumeConfiguration{
				EnableDynamicProvisioning:  true,
				EnableHostPathProvisioning: false,
				FlexVolumePluginDir:        "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/",
				PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{
					MaximumRetry:             3,
					MinimumTimeoutNFS:        300,
					IncrementTimeoutNFS:      30,
					MinimumTimeoutHostPath:   60,
					IncrementTimeoutHostPath: 30,
				},
			},
			ContentType:  "application/vnd.kubernetes.protobuf",
			KubeAPIQPS:   20.0,
			KubeAPIBurst: 30,
			LeaderElection: componentconfig.LeaderElectionConfiguration{
				LeaderElect:   true,
				LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
				RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
				RetryPeriod:   metav1.Duration{Duration: 2 * time.Second},
			},
			ClusterSigningCertFile:            "/etc/kubernetes/ca/ca.pem",
			ClusterSigningKeyFile:             "/etc/kubernetes/ca/ca.key",
			EnableGarbageCollector:            true,
			DisableAttachDetachReconcilerSync: false,
			ReconcilerSyncLoopPeriod:          metav1.Duration{Duration: 60 * time.Second},
			Controllers:                       []string{"*"},
			EnableTaintManager:                true,
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestSchedulerServerDefaults(t *testing.T) {
	defaults := scheduleroptions.NewSchedulerServer()

	// This is a snapshot of the default config
	// If the default changes (new fields are added, or default values change), we want to know
	// Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults
	expectedDefaults := &scheduleroptions.SchedulerServer{
		KubeSchedulerConfiguration: componentconfig.KubeSchedulerConfiguration{
			Port:                           10251, // disabled
			Address:                        "0.0.0.0",
			AlgorithmProvider:              "DefaultProvider",
			ContentType:                    "application/vnd.kubernetes.protobuf",
			KubeAPIQPS:                     50,
			KubeAPIBurst:                   100,
			SchedulerName:                  "default-scheduler",
			HardPodAffinitySymmetricWeight: 1,
			FailureDomains:                 "kubernetes.io/hostname,failure-domain.beta.kubernetes.io/zone,failure-domain.beta.kubernetes.io/region",
			LeaderElection: componentconfig.LeaderElectionConfiguration{
				LeaderElect: true,
				LeaseDuration: metav1.Duration{
					Duration: 15 * time.Second,
				},
				RenewDeadline: metav1.Duration{
					Duration: 10 * time.Second,
				},
				RetryPeriod: metav1.Duration{
					Duration: 2 * time.Second,
				},
			},
		},
	}

	if !reflect.DeepEqual(defaults, expectedDefaults) {
		t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults))
		t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults")
	}
}

func TestGetAPIGroupVersionOverrides(t *testing.T) {
	testcases := map[string]struct {
		DisabledVersions         map[string][]string
		ExpectedDisabledVersions []schema.GroupVersion
		ExpectedEnabledVersions  []schema.GroupVersion
	}{
		"empty": {
			DisabledVersions:         nil,
			ExpectedDisabledVersions: []schema.GroupVersion{},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion},
		},
		"* -> v1": {
			DisabledVersions:         map[string][]string{"": {"*"}},
			ExpectedDisabledVersions: []schema.GroupVersion{apiv1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
		},
		"v1": {
			DisabledVersions:         map[string][]string{"": {"v1"}},
			ExpectedDisabledVersions: []schema.GroupVersion{apiv1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
		},
		"* -> v1beta1": {
			DisabledVersions:         map[string][]string{"extensions": {"*"}},
			ExpectedDisabledVersions: []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion},
		},
		"extensions/v1beta1": {
			DisabledVersions:         map[string][]string{"extensions": {"v1beta1"}},
			ExpectedDisabledVersions: []schema.GroupVersion{extensionsapiv1beta1.SchemeGroupVersion},
			ExpectedEnabledVersions:  []schema.GroupVersion{apiv1.SchemeGroupVersion},
		},
	}

	for k, tc := range testcases {
		config := configapi.MasterConfig{KubernetesMasterConfig: &configapi.KubernetesMasterConfig{DisabledAPIGroupVersions: tc.DisabledVersions}}
		overrides := getAPIResourceConfig(config)

		for _, expected := range tc.ExpectedDisabledVersions {
			if overrides.AnyResourcesForVersionEnabled(expected) {
				t.Errorf("%s: Expected %v", k, expected)
			}
		}

		for _, expected := range tc.ExpectedEnabledVersions {
			if !overrides.AllResourcesForVersionEnabled(expected) {
				t.Errorf("%s: Expected %v", k, expected)
			}
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package api_test

import (
	"math/rand"
	"reflect"
	"testing"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/testapi"
	apitesting "k8s.io/kubernetes/pkg/api/testing"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/apimachinery/registered"
	"k8s.io/kubernetes/pkg/util/diff"

	"github.com/google/gofuzz"
)

func TestDeepCopyApiObjects(t *testing.T) {
	for i := 0; i < *fuzzIters; i++ {
		for _, version := range []unversioned.GroupVersion{testapi.Default.InternalGroupVersion(), registered.GroupOrDie(api.GroupName).GroupVersion} {
			f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63()))
			for kind := range api.Scheme.KnownTypes(version) {
				doDeepCopyTest(t, version.WithKind(kind), f)
			}
		}
	}
}

func doDeepCopyTest(t *testing.T, kind unversioned.GroupVersionKind, f *fuzz.Fuzzer) {
	item, err := api.Scheme.New(kind)
	if err != nil {
		t.Fatalf("Could not create a %v: %s", kind, err)
	}
	f.Fuzz(item)
	itemCopy, err := api.Scheme.DeepCopy(item)
	if err != nil {
		t.Errorf("Could not deep copy a %v: %s", kind, err)
		return
	}

	if !reflect.DeepEqual(item, itemCopy) {
		t.Errorf("\nexpected: %#v\n\ngot:      %#v\n\ndiff:      %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy))
	}
}

func TestDeepCopySingleType(t *testing.T) {
	for i := 0; i < *fuzzIters; i++ {
		for _, version := range []unversioned.GroupVersion{testapi.Default.InternalGroupVersion(), registered.GroupOrDie(api.GroupName).GroupVersion} {
			f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63()))
			doDeepCopyTest(t, version.WithKind("Pod"), f)
		}
	}
}
<commit_msg>Add tests for deepcopy of structs<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package api_test

import (
	"bytes"
	"math/rand"
	"reflect"
	"testing"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/testapi"
	apitesting "k8s.io/kubernetes/pkg/api/testing"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/apimachinery/registered"
	"k8s.io/kubernetes/pkg/util/diff"

	"github.com/google/gofuzz"
)

func TestDeepCopyApiObjects(t *testing.T) {
	for i := 0; i < *fuzzIters; i++ {
		for _, version := range []unversioned.GroupVersion{testapi.Default.InternalGroupVersion(), registered.GroupOrDie(api.GroupName).GroupVersion} {
			f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63()))
			for kind := range api.Scheme.KnownTypes(version) {
				doDeepCopyTest(t, version.WithKind(kind), f)
			}
		}
	}
}

func doDeepCopyTest(t *testing.T, kind unversioned.GroupVersionKind, f *fuzz.Fuzzer) {
	item, err := api.Scheme.New(kind)
	if err != nil {
		t.Fatalf("Could not create a %v: %s", kind, err)
	}
	f.Fuzz(item)
	itemCopy, err := api.Scheme.DeepCopy(item)
	if err != nil {
		t.Errorf("Could not deep copy a %v: %s", kind, err)
		return
	}

	if !reflect.DeepEqual(item, itemCopy) {
		t.Errorf("\nexpected: %#v\n\ngot:      %#v\n\ndiff:      %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy))
	}

	prefuzzData := &bytes.Buffer{}
	if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil {
		t.Errorf("Could not encode a %v: %s", kind, err)
		return
	}

	// Refuzz the copy, which should have no effect on the original
	f.Fuzz(itemCopy)

	postfuzzData := &bytes.Buffer{}
	if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil {
		t.Errorf("Could not encode a %v: %s", kind, err)
		return
	}

	if bytes.Compare(prefuzzData.Bytes(), postfuzzData.Bytes()) != 0 {
		t.Log(diff.StringDiff(prefuzzData.String(), postfuzzData.String()))
		t.Errorf("Fuzzing copy modified original of %#v", kind)
		return
	}
}

func TestDeepCopySingleType(t *testing.T) {
	for i := 0; i < *fuzzIters; i++ {
		for _, version := range []unversioned.GroupVersion{testapi.Default.InternalGroupVersion(), registered.GroupOrDie(api.GroupName).GroupVersion} {
			f := apitesting.FuzzerFor(t, version, rand.NewSource(rand.Int63()))
			doDeepCopyTest(t, version.WithKind("Pod"), f)
		}
	}
}
<|endoftext|>


<commit_before>package app

import (
	"context"
	"io"
	"net/url"
	"reflect"
	"strings"

	"github.com/maxence-charriere/go-app/v8/pkg/errors"
)

// Composer is the interface that describes a customized, independent and
// reusable UI element.
//
// Satisfying this interface is done by embedding app.Compo into a struct and
// implementing the Render function.
//
// Example:
//  type Hello struct {
//      app.Compo
//  }
//
//  func (c *Hello) Render() app.UI {
//      return app.Text("hello")
//  }
type Composer interface {
	UI

	// Render returns the node tree that define how the component is desplayed.
	Render() UI

	// Update update the component appearance. It should be called when a field
	// used to render the component has been modified.
	Update()

	// Dispatch executes the given function on the goroutine dedicated to
	// updating the UI.
	Dispatch(func())

	// Context returns the component's context.
	Context() Context
}

// PreRenderer is the interface that describes a component that performs
// instruction when it is server-side pre-rendered.
//
// A pre-rendered component helps in achieving SEO friendly content.
type PreRenderer interface {
	// The function called when the component is server-side pre-rendered.
	//
	// If pre-rendering requires blocking operations such as performing an HTTP
	// request, ensure that they are done synchronously. A good practice is to
	// avoid using goroutines during pre-rendering.
	OnPreRender(Context)
}

// Mounter is the interface that describes a component that can perform
// additional actions when mounted.
type Mounter interface {
	Composer

	// The function called when the component is mounted. It is always called on
	// the UI goroutine.
	OnMount(Context)
}

// Dismounter is the interface that describes a component that can perform
// additional actions when dismounted.
type Dismounter interface {
	Composer

	// The function called when the component is dismounted. It is always called
	// on the UI goroutine.
	OnDismount()
}

// Navigator is the interface that describes a component that can perform
// additional actions when navigated on.
type Navigator interface {
	Composer

	// The function that called when the component is navigated on. It is always
	// called on the UI goroutine.
	OnNav(Context)
}

type deprecatedNavigator interface {
	OnNav(Context, *url.URL)
}

// Updater is the interface that describes a component that is notified when the
// application is updated.
type Updater interface {
	// The function called when the application is updated. It is always called
	// on the UI goroutine.
	OnAppUpdate(Context)
}

// Resizer is the interface that describes a component that is notified when the
// application size changes.
type Resizer interface {
	// The function called when the application is resized. It is always called
	// on the UI goroutine.
	OnAppResize(Context)
}

// Compo represents the base struct to use in order to build a component.
type Compo struct {
	disp       Dispatcher
	ctx        context.Context
	ctxCancel  func()
	parentElem UI
	root       UI
	this       Composer
}

// Kind returns the ui element kind.
func (c *Compo) Kind() Kind {
	return Component
}

// JSValue returns the javascript value of the component root.
func (c *Compo) JSValue() Value {
	return c.root.JSValue()
}

// Mounted reports whether the component is mounted.
func (c *Compo) Mounted() bool {
	return c.Dispatcher() != nil &&
		c.ctx != nil &&
		c.ctx.Err() == nil &&
		c.root != nil && c.root.Mounted() &&
		c.self() != nil
}

// Dispatcher returns the dispatcher that manages the component.
func (c *Compo) Dispatcher() Dispatcher {
	return c.disp
}

// Context returns the component's context.
func (c *Compo) Context() Context {
	return makeContext(c.self())
}

// Render describes the component content. This is a default implementation to
// satisfy the app.Composer interface. It should be redefined when app.Compo is
// embedded.
func (c *Compo) Render() UI {
	return Div().
		DataSet("compo-type", c.name()).
		Style("border", "1px solid currentColor").
		Style("padding", "12px 0").
		Body(
			H1().Text("Component "+strings.TrimPrefix(c.name(), "*")),
			P().Body(
				Text("Change appearance by implementing: "),
				Code().
					Style("color", "deepskyblue").
					Style("margin", "0 6px").
					Text("func (c "+c.name()+") Render() app.UI"),
			),
		)
}

// Dispatch executes the given function on the goroutine dedicated to updating
// the UI.
func (c *Compo) Dispatch(fn func()) {
	c.Dispatcher().Dispatch(fn)
}

// Update triggers a component appearance update. It should be called when a
// field used to render the component has been modified. Updates are always
// performed on the UI goroutine.
func (c *Compo) Update() {
	if !c.Mounted() {
		return
	}

	c.Dispatcher().Dispatch(func() {
		if !c.Mounted() {
			return
		}

		if err := c.updateRoot(); err != nil {
			panic(err)
		}
	})
}

func (c *Compo) name() string {
	name := reflect.TypeOf(c.self()).String()
	name = strings.ReplaceAll(name, "main.", "")
	return name
}

func (c *Compo) self() UI {
	return c.this
}

func (c *Compo) setSelf(n UI) {
	if n != nil {
		c.this = n.(Composer)
		return
	}

	c.this = nil
}

func (c *Compo) context() context.Context {
	return c.ctx
}

func (c *Compo) attributes() map[string]string {
	return nil
}

func (c *Compo) eventHandlers() map[string]eventHandler {
	return nil
}

func (c *Compo) parent() UI {
	return c.parentElem
}

func (c *Compo) setParent(p UI) {
	c.parentElem = p
}

func (c *Compo) children() []UI {
	return []UI{c.root}
}

func (c *Compo) mount(d Dispatcher) error {
	if c.Mounted() {
		return errors.New("mounting component failed").
			Tag("reason", "already mounted").
			Tag("name", c.name()).
			Tag("kind", c.Kind())
	}

	c.disp = d
	c.ctx, c.ctxCancel = context.WithCancel(context.Background())

	root := c.render()
	if err := mount(d, root); err != nil {
		return errors.New("mounting component failed").
			Tag("name", c.name()).
			Tag("kind", c.Kind()).
			Wrap(err)
	}
	root.setParent(c.this)
	c.root = root

	if mounter, ok := c.self().(Mounter); ok && !c.Dispatcher().isServerSideMode() {
		mounter.OnMount(makeContext(c.self()))
	}

	return nil
}

func (c *Compo) dismount() {
	dismount(c.root)
	c.ctxCancel()
	c.disp = nil

	if dismounter, ok := c.this.(Dismounter); ok {
		dismounter.OnDismount()
	}
}

func (c *Compo) update(n UI) error {
	if c.self() == n || !c.Mounted() {
		return nil
	}

	if n.Kind() != c.Kind() || n.name() != c.name() {
		return errors.New("updating ui element failed").
			Tag("replace", true).
			Tag("reason", "different element types").
			Tag("current-kind", c.Kind()).
			Tag("current-name", c.name()).
			Tag("updated-kind", n.Kind()).
			Tag("updated-name", n.name())
	}

	aval := reflect.Indirect(reflect.ValueOf(c.self()))
	bval := reflect.Indirect(reflect.ValueOf(n))
	compotype := reflect.ValueOf(c).Elem().Type()

	for i := 0; i < aval.NumField(); i++ {
		a := aval.Field(i)
		b := bval.Field(i)

		if a.Type() == compotype {
			continue
		}

		if !a.CanSet() {
			continue
		}

		if !reflect.DeepEqual(a.Interface(), b.Interface()) {
			a.Set(b)
		}
	}

	return c.updateRoot()
}

func (c *Compo) updateRoot() error {
	a := c.root
	b := c.render()

	err := update(a, b)
	if isErrReplace(err) {
		err = c.replaceRoot(b)
	}

	if err != nil {
		return errors.New("updating component failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Wrap(err)
	}

	return nil
}

func (c *Compo) replaceRoot(n UI) error {
	old := c.root
	new := n

	if err := mount(c.Dispatcher(), new); err != nil {
		return errors.New("replacing component root failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Tag("root-kind", old.Kind()).
			Tag("root-name", old.name()).
			Tag("new-root-kind", new.Kind()).
			Tag("new-root-name", new.name()).
			Wrap(err)
	}

	var parent UI
	for {
		parent = c.parent()
		if parent == nil || parent.Kind() == HTML {
			break
		}
	}

	if parent == nil {
		return errors.New("replacing component root failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Tag("reason", "coponent does not have html element parents")
	}

	c.root = new
	new.setParent(c.self())

	oldjs := old.JSValue()
	newjs := n.JSValue()
	parent.JSValue().replaceChild(newjs, oldjs)

	dismount(old)
	return nil
}

func (c *Compo) render() UI {
	elems := FilterUIElems(c.this.Render())
	return elems[0]
}

func (c *Compo) onNav(u *url.URL) {
	c.root.onNav(u)

	if nav, ok := c.self().(Navigator); ok {
		ctx := makeContext(c.self())
		nav.OnNav(ctx)
		return
	}

	if nav, ok := c.self().(deprecatedNavigator); ok {
		ctx := makeContext(c.self())
		Log("%s", errors.New("a deprecated component interface is in use").
			Tag("component", reflect.TypeOf(c.self())).
			Tag("interface", "app.Navigator").
			Tag("deprecated-signature", "OnNav(app.Context, *url.URL)").
			Tag("valid-signature", "OnNav(app.Context)").
			Tag("how-to-fix", "refactor component to use the valid signature"))
		nav.OnNav(ctx, u)
	}
}

func (c *Compo) onAppUpdate() {
	c.root.onAppUpdate()

	if updater, ok := c.self().(Updater); ok {
		updater.OnAppUpdate(makeContext(c.self()))
	}
}

func (c *Compo) onAppResize() {
	c.root.onAppResize()

	if resizer, ok := c.self().(Resizer); ok {
		resizer.OnAppResize(makeContext(c.self()))
	}
}

func (c *Compo) preRender(p Page) {
	c.root.preRender(p)

	if preRenderer, ok := c.self().(PreRenderer); ok {
		preRenderer.OnPreRender(makeContext(c.self()))
	}
}

func (c *Compo) html(w io.Writer) {
	c.htmlWithIndent(w, 0)
}

func (c *Compo) htmlWithIndent(w io.Writer, indent int) {
	if c.root == nil {
		c.root = c.render()
		c.root.setSelf(c.root)
	}

	c.root.htmlWithIndent(w, indent)
}
<commit_msg>remove composer context<commit_after>package app

import (
	"context"
	"io"
	"net/url"
	"reflect"
	"strings"

	"github.com/maxence-charriere/go-app/v8/pkg/errors"
)

// Composer is the interface that describes a customized, independent and
// reusable UI element.
//
// Satisfying this interface is done by embedding app.Compo into a struct and
// implementing the Render function.
//
// Example:
//  type Hello struct {
//      app.Compo
//  }
//
//  func (c *Hello) Render() app.UI {
//      return app.Text("hello")
//  }
type Composer interface {
	UI

	// Render returns the node tree that define how the component is desplayed.
	Render() UI

	// Update update the component appearance. It should be called when a field
	// used to render the component has been modified.
	Update()

	// Dispatch executes the given function on the goroutine dedicated to
	// updating the UI.
	Dispatch(func())
}

// PreRenderer is the interface that describes a component that performs
// instruction when it is server-side pre-rendered.
//
// A pre-rendered component helps in achieving SEO friendly content.
type PreRenderer interface {
	// The function called when the component is server-side pre-rendered.
	//
	// If pre-rendering requires blocking operations such as performing an HTTP
	// request, ensure that they are done synchronously. A good practice is to
	// avoid using goroutines during pre-rendering.
	OnPreRender(Context)
}

// Mounter is the interface that describes a component that can perform
// additional actions when mounted.
type Mounter interface {
	Composer

	// The function called when the component is mounted. It is always called on
	// the UI goroutine.
	OnMount(Context)
}

// Dismounter is the interface that describes a component that can perform
// additional actions when dismounted.
type Dismounter interface {
	Composer

	// The function called when the component is dismounted. It is always called
	// on the UI goroutine.
	OnDismount()
}

// Navigator is the interface that describes a component that can perform
// additional actions when navigated on.
type Navigator interface {
	Composer

	// The function that called when the component is navigated on. It is always
	// called on the UI goroutine.
	OnNav(Context)
}

type deprecatedNavigator interface {
	OnNav(Context, *url.URL)
}

// Updater is the interface that describes a component that is notified when the
// application is updated.
type Updater interface {
	// The function called when the application is updated. It is always called
	// on the UI goroutine.
	OnAppUpdate(Context)
}

// Resizer is the interface that describes a component that is notified when the
// application size changes.
type Resizer interface {
	// The function called when the application is resized. It is always called
	// on the UI goroutine.
	OnAppResize(Context)
}

// Compo represents the base struct to use in order to build a component.
type Compo struct {
	disp       Dispatcher
	ctx        context.Context
	ctxCancel  func()
	parentElem UI
	root       UI
	this       Composer
}

// Kind returns the ui element kind.
func (c *Compo) Kind() Kind {
	return Component
}

// JSValue returns the javascript value of the component root.
func (c *Compo) JSValue() Value {
	return c.root.JSValue()
}

// Mounted reports whether the component is mounted.
func (c *Compo) Mounted() bool {
	return c.Dispatcher() != nil &&
		c.ctx != nil &&
		c.ctx.Err() == nil &&
		c.root != nil && c.root.Mounted() &&
		c.self() != nil
}

// Dispatcher returns the dispatcher that manages the component.
func (c *Compo) Dispatcher() Dispatcher {
	return c.disp
}

// Context returns the component's context.
func (c *Compo) Context() Context {
	return makeContext(c.self())
}

// Render describes the component content. This is a default implementation to
// satisfy the app.Composer interface. It should be redefined when app.Compo is
// embedded.
func (c *Compo) Render() UI {
	return Div().
		DataSet("compo-type", c.name()).
		Style("border", "1px solid currentColor").
		Style("padding", "12px 0").
		Body(
			H1().Text("Component "+strings.TrimPrefix(c.name(), "*")),
			P().Body(
				Text("Change appearance by implementing: "),
				Code().
					Style("color", "deepskyblue").
					Style("margin", "0 6px").
					Text("func (c "+c.name()+") Render() app.UI"),
			),
		)
}

// Dispatch executes the given function on the goroutine dedicated to updating
// the UI.
func (c *Compo) Dispatch(fn func()) {
	c.Dispatcher().Dispatch(fn)
}

// Update triggers a component appearance update. It should be called when a
// field used to render the component has been modified. Updates are always
// performed on the UI goroutine.
func (c *Compo) Update() {
	if !c.Mounted() {
		return
	}

	c.Dispatcher().Dispatch(func() {
		if !c.Mounted() {
			return
		}

		if err := c.updateRoot(); err != nil {
			panic(err)
		}
	})
}

func (c *Compo) name() string {
	name := reflect.TypeOf(c.self()).String()
	name = strings.ReplaceAll(name, "main.", "")
	return name
}

func (c *Compo) self() UI {
	return c.this
}

func (c *Compo) setSelf(n UI) {
	if n != nil {
		c.this = n.(Composer)
		return
	}

	c.this = nil
}

func (c *Compo) context() context.Context {
	return c.ctx
}

func (c *Compo) attributes() map[string]string {
	return nil
}

func (c *Compo) eventHandlers() map[string]eventHandler {
	return nil
}

func (c *Compo) parent() UI {
	return c.parentElem
}

func (c *Compo) setParent(p UI) {
	c.parentElem = p
}

func (c *Compo) children() []UI {
	return []UI{c.root}
}

func (c *Compo) mount(d Dispatcher) error {
	if c.Mounted() {
		return errors.New("mounting component failed").
			Tag("reason", "already mounted").
			Tag("name", c.name()).
			Tag("kind", c.Kind())
	}

	c.disp = d
	c.ctx, c.ctxCancel = context.WithCancel(context.Background())

	root := c.render()
	if err := mount(d, root); err != nil {
		return errors.New("mounting component failed").
			Tag("name", c.name()).
			Tag("kind", c.Kind()).
			Wrap(err)
	}
	root.setParent(c.this)
	c.root = root

	if mounter, ok := c.self().(Mounter); ok && !c.Dispatcher().isServerSideMode() {
		mounter.OnMount(makeContext(c.self()))
	}

	return nil
}

func (c *Compo) dismount() {
	dismount(c.root)
	c.ctxCancel()
	c.disp = nil

	if dismounter, ok := c.this.(Dismounter); ok {
		dismounter.OnDismount()
	}
}

func (c *Compo) update(n UI) error {
	if c.self() == n || !c.Mounted() {
		return nil
	}

	if n.Kind() != c.Kind() || n.name() != c.name() {
		return errors.New("updating ui element failed").
			Tag("replace", true).
			Tag("reason", "different element types").
			Tag("current-kind", c.Kind()).
			Tag("current-name", c.name()).
			Tag("updated-kind", n.Kind()).
			Tag("updated-name", n.name())
	}

	aval := reflect.Indirect(reflect.ValueOf(c.self()))
	bval := reflect.Indirect(reflect.ValueOf(n))
	compotype := reflect.ValueOf(c).Elem().Type()

	for i := 0; i < aval.NumField(); i++ {
		a := aval.Field(i)
		b := bval.Field(i)

		if a.Type() == compotype {
			continue
		}

		if !a.CanSet() {
			continue
		}

		if !reflect.DeepEqual(a.Interface(), b.Interface()) {
			a.Set(b)
		}
	}

	return c.updateRoot()
}

func (c *Compo) updateRoot() error {
	a := c.root
	b := c.render()

	err := update(a, b)
	if isErrReplace(err) {
		err = c.replaceRoot(b)
	}

	if err != nil {
		return errors.New("updating component failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Wrap(err)
	}

	return nil
}

func (c *Compo) replaceRoot(n UI) error {
	old := c.root
	new := n

	if err := mount(c.Dispatcher(), new); err != nil {
		return errors.New("replacing component root failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Tag("root-kind", old.Kind()).
			Tag("root-name", old.name()).
			Tag("new-root-kind", new.Kind()).
			Tag("new-root-name", new.name()).
			Wrap(err)
	}

	var parent UI
	for {
		parent = c.parent()
		if parent == nil || parent.Kind() == HTML {
			break
		}
	}

	if parent == nil {
		return errors.New("replacing component root failed").
			Tag("kind", c.Kind()).
			Tag("name", c.name()).
			Tag("reason", "coponent does not have html element parents")
	}

	c.root = new
	new.setParent(c.self())

	oldjs := old.JSValue()
	newjs := n.JSValue()
	parent.JSValue().replaceChild(newjs, oldjs)

	dismount(old)
	return nil
}

func (c *Compo) render() UI {
	elems := FilterUIElems(c.this.Render())
	return elems[0]
}

func (c *Compo) onNav(u *url.URL) {
	c.root.onNav(u)

	if nav, ok := c.self().(Navigator); ok {
		ctx := makeContext(c.self())
		nav.OnNav(ctx)
		return
	}

	if nav, ok := c.self().(deprecatedNavigator); ok {
		ctx := makeContext(c.self())
		Log("%s", errors.New("a deprecated component interface is in use").
			Tag("component", reflect.TypeOf(c.self())).
			Tag("interface", "app.Navigator").
			Tag("deprecated-signature", "OnNav(app.Context, *url.URL)").
			Tag("valid-signature", "OnNav(app.Context)").
			Tag("how-to-fix", "refactor component to use the valid signature"))
		nav.OnNav(ctx, u)
	}
}

func (c *Compo) onAppUpdate() {
	c.root.onAppUpdate()

	if updater, ok := c.self().(Updater); ok {
		updater.OnAppUpdate(makeContext(c.self()))
	}
}

func (c *Compo) onAppResize() {
	c.root.onAppResize()

	if resizer, ok := c.self().(Resizer); ok {
		resizer.OnAppResize(makeContext(c.self()))
	}
}

func (c *Compo) preRender(p Page) {
	c.root.preRender(p)

	if preRenderer, ok := c.self().(PreRenderer); ok {
		preRenderer.OnPreRender(makeContext(c.self()))
	}
}

func (c *Compo) html(w io.Writer) {
	c.htmlWithIndent(w, 0)
}

func (c *Compo) htmlWithIndent(w io.Writer, indent int) {
	if c.root == nil {
		c.root = c.render()
		c.root.setSelf(c.root)
	}

	c.root.htmlWithIndent(w, indent)
}
<|endoftext|>


<commit_before><commit_msg>initial commit. adding readDirectory() function<commit_after><|endoftext|>


<commit_before>package budget

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// Transaction is a single transaction with a cost and name
type Transaction struct {
	Cost float64
	Name string
}

// Budget is a monthly budget
type Budget struct {
	Total        float64
	Remaining    float64
	TagMap       map[string]string
	Transactions []Transaction
}

// Parse parses a single month's budget
func Parse(r, tm *bufio.Reader) (Budget, error) {
	b := Budget{}
	// first line is the total for the month
	line, _, err := r.ReadLine()
	if err != nil {
		return Budget{}, err
	}

	total, err := strconv.ParseFloat(string(line), 64)
	if err != nil {
		return Budget{}, err
	}

	b.Total = total
	tagMap := map[string]string{}

	for {
		line, _, err := r.ReadLine()
		if err == io.EOF {
			break
		}

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

		if strings.HasPrefix(string(line), "#") {
			sp := strings.Split(string(line), ":")
			if len(sp) != 2 {
				return Budget{}, fmt.Errorf("invalid tag line %q", string(line))
			}

			tn := strings.TrimPrefix(sp[0], "# ")
			items := strings.Split(sp[1], ",")

			for _, item := range items {
				tagMap[strings.TrimSpace(item)] = tn
			}

			continue
		}

		t := strings.SplitN(string(line), " ", 2)
		if strings.TrimSpace(string(line)) == "" {
			continue
		}

		if len(t) != 2 {
			return Budget{}, fmt.Errorf("invalid line %q", line)
		}

		cost, err := strconv.ParseFloat(t[0], 64)
		if err != nil {
			return Budget{}, err
		}

		trans := Transaction{Cost: cost, Name: strings.TrimSpace(t[1])}

		b.Transactions = append(b.Transactions, trans)
	}

	fileTagMap, err := parseTagMapFile(tm)
	if err != nil {
		return b, err
	}

	b.TagMap = mergeTagMaps(tagMap, fileTagMap)
	b.Remaining = b.Total
	for _, trans := range b.Transactions {
		b.Remaining -= trans.Cost
	}

	return b, nil
}

func parseTagMapFile(r *bufio.Reader) (map[string]string, error) {
	tagMap := map[string]string{}

	for {
		line, _, err := r.ReadLine()
		if err == io.EOF {
			break
		}

		if err != nil {
			return map[string]string{}, err
		}

		if strings.TrimSpace(string(line)) == "" {
			continue
		}

		sp := strings.Split(string(line), ":")
		if len(sp) != 2 {
			return map[string]string{}, fmt.Errorf("invalid tag line %q", string(line))
		}

		tn := sp[0]
		items := strings.Split(sp[1], ",")

		for _, item := range items {
			tagMap[strings.TrimSpace(item)] = tn
		}
	}

	return tagMap, nil
}

func mergeTagMaps(tm, ftm map[string]string) map[string]string {
	for k, v := range ftm {
		if tm[k] == "" {
			tm[k] = v
		}
	}

	return tm
}
<commit_msg>ignore commas in costs<commit_after>package budget

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// Transaction is a single transaction with a cost and name
type Transaction struct {
	Cost float64
	Name string
}

// Budget is a monthly budget
type Budget struct {
	Total        float64
	Remaining    float64
	TagMap       map[string]string
	Transactions []Transaction
}

// Parse parses a single month's budget
func Parse(r, tm *bufio.Reader) (Budget, error) {
	b := Budget{}
	// first line is the total for the month
	line, _, err := r.ReadLine()
	if err != nil {
		return Budget{}, err
	}

	total, err := strconv.ParseFloat(string(line), 64)
	if err != nil {
		return Budget{}, err
	}

	b.Total = total
	tagMap := map[string]string{}

	for {
		line, _, err := r.ReadLine()
		if err == io.EOF {
			break
		}

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

		if strings.HasPrefix(string(line), "#") {
			sp := strings.Split(string(line), ":")
			if len(sp) != 2 {
				return Budget{}, fmt.Errorf("invalid tag line %q", string(line))
			}

			tn := strings.TrimPrefix(sp[0], "# ")
			items := strings.Split(sp[1], ",")

			for _, item := range items {
				tagMap[strings.TrimSpace(item)] = tn
			}

			continue
		}

		t := strings.SplitN(string(line), " ", 2)
		if strings.TrimSpace(string(line)) == "" {
			continue
		}

		if len(t) != 2 {
			return Budget{}, fmt.Errorf("invalid line %q", line)
		}

		costStr := strings.ReplaceAll(t[0], ",", "")
		cost, err := strconv.ParseFloat(costStr, 64)
		if err != nil {
			return Budget{}, err
		}

		trans := Transaction{Cost: cost, Name: strings.TrimSpace(t[1])}

		b.Transactions = append(b.Transactions, trans)
	}

	fileTagMap, err := parseTagMapFile(tm)
	if err != nil {
		return b, err
	}

	b.TagMap = mergeTagMaps(tagMap, fileTagMap)
	b.Remaining = b.Total
	for _, trans := range b.Transactions {
		b.Remaining -= trans.Cost
	}

	return b, nil
}

func parseTagMapFile(r *bufio.Reader) (map[string]string, error) {
	tagMap := map[string]string{}

	for {
		line, _, err := r.ReadLine()
		if err == io.EOF {
			break
		}

		if err != nil {
			return map[string]string{}, err
		}

		if strings.TrimSpace(string(line)) == "" {
			continue
		}

		sp := strings.Split(string(line), ":")
		if len(sp) != 2 {
			return map[string]string{}, fmt.Errorf("invalid tag line %q", string(line))
		}

		tn := sp[0]
		items := strings.Split(sp[1], ",")

		for _, item := range items {
			tagMap[strings.TrimSpace(item)] = tn
		}
	}

	return tagMap, nil
}

func mergeTagMaps(tm, ftm map[string]string) map[string]string {
	for k, v := range ftm {
		if tm[k] == "" {
			tm[k] = v
		}
	}

	return tm
}
<|endoftext|>