<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"github.com/go-clog/clog"
	"os"
	"strings"
	"github.com/rhaamo/lutrainit/shared/ipc"
	"time"
	"github.com/go-ini/ini"
)

var (
	// MainConfig of the daemon
	MainConfig struct {
		Persist		bool
		Autologins	[]string

		Log struct {
			Filename 	string
			Rotate   	bool
			Daily   	bool
			MaxSize	 	int
			MaxLines 	int64
			MaxDays  	int64
			BufferLen	int64
		}

		StartedReexec	bool
	}
)

func parseLine(line string, s *Service) error {
	if strings.HasPrefix(line, "Needs:") {
		specified := strings.Split(strings.TrimPrefix(line, "Needs:"), ",")
		for _, nd := range specified {
			s.Needs = append(s.Needs, ServiceType(strings.TrimSpace(nd)))
		}
	} else if strings.HasPrefix(line, "Provides:") {
		specified := strings.Split(strings.TrimPrefix(line, "Provides:"), ",")
		for _, nd := range specified {
			s.Provides = append(s.Provides, ServiceType(strings.TrimSpace(nd)))
		}
	} else if strings.HasPrefix(line, "Startup:") {
		if s.Startup != "" {
			return fmt.Errorf("Startup already set")
		}
		s.Startup = Command(strings.TrimSpace(strings.TrimPrefix(line, "Startup:")))
	} else if strings.HasPrefix(line, "Shutdown:") {
		if s.Shutdown != "" {
			return fmt.Errorf("Shutdown already set")
		}
		s.Shutdown = Command(strings.TrimSpace(strings.TrimPrefix(line, "Shutdown:")))
	} else if strings.HasPrefix(line, "Name:") {
		if s.Name == "" {
			s.Name = ServiceName(strings.TrimSpace(strings.TrimPrefix(line, "Name:")))
		}
	} else if strings.HasPrefix(line, "Description:") {
		if s.Description == "" {
			s.Description = strings.TrimSpace(strings.TrimPrefix(line, "Description:"))
		}
	} else if strings.HasPrefix(line, "PIDFile:") {
		if s.PIDFile == "" {
			s.PIDFile = strings.TrimSpace(strings.TrimPrefix(line, "PIDFile:"))
		}
	} else if strings.HasPrefix(line, "Type:") {
		if s.Type == "" {
			serviceType := strings.TrimSpace(strings.TrimPrefix(line, "Type:"))
			switch serviceType {
			case "simple":
				s.Type = "simple"
			case "forking":
				s.Type = "forking"
			case "oneshot":
				s.Type = "oneshot"
			default:
				clog.Warn("[lutra] Invalid service type: %s, forcing Type=simple", serviceType)
				s.Type = "simple"
			}
		}
	} else if strings.HasPrefix(line, "Autostart:") {
		s.AutoStart = strings.TrimSpace(strings.TrimPrefix(line, "Autostart:")) == "true"
	} else if strings.HasPrefix(line, "ExecPreStart:") {
		if s.ExecPreStart == "" {
			s.ExecPreStart = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPreStart:")))
		}
	} else if strings.HasPrefix(line, "ExecPreStop:") {
		if s.ExecPreStop == "" {
			s.ExecPreStop = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPreStop:")))
		}
	} else if strings.HasPrefix(line, "ExecPostStart:") {
		if s.ExecPostStart == "" {
			s.ExecPostStart = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPostStart:")))
		}
	} else if strings.HasPrefix(line, "ExecPostStop:") {
		if s.ExecPostStop == "" {
			s.ExecPostStop = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPostStop:")))
		}
	}
	return nil
}

// ParseConfig a single config file into the services it provides
func ParseConfig(r io.Reader, filename string) (Service, error) {
	s := Service{
		Deleted: false,
		AutoStart: true,
	}
	var line string
	var err error
	scanner := bufio.NewReader(r)

	for {
		line, err = scanner.ReadString('\n')
		switch err {
		case io.EOF:
			if err := parseLine(line, &s); err != nil {
				clog.Error(2, err.Error())
			}

			// Check for configuration sanity before returning
			if err := checkSanity(&s, filename); err != nil {
				return Service{}, err
			}

			return s, nil
		case nil:
			if err := parseLine(line, &s); err != nil {
				clog.Error(2, err.Error())
			}
		default:
			return Service{}, err
		}
	}
}

// ParseServiceConfigs parse all the config in directory dir return a map of
// providers of ServiceTypes from that directory.
func ParseServiceConfigs(dir string, reloading bool) error {
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, fstat := range files {
		if fstat.IsDir() {
			// Mostly to skip "." and ".."
			continue
		}

		// We only want to parse files ending with .service
		if !strings.HasSuffix(fstat.Name(), ".service") {
			continue
		}

		f, err := os.Open(dir + "/" + fstat.Name())
		if err != nil {
			clog.Error(2, err.Error())
			continue
		}
		s, err := ParseConfig(f, fstat.Name())
		f.Close()
		if err != nil {
			clog.Error(2, err.Error())
			continue
		}

		for _, t := range s.Provides {
			if s.AutoStart {
				StartupServices[t] = append(StartupServices[t], &StartupService{
					Name: s.Name,
					AutoStart: s.AutoStart,

					Needs: s.Needs,
					Provides: s.Provides,
				})
			}
		}

		// If we are not reloading, set initial state and actions
		if !reloading {
			s.State = NotStarted
			s.LastAction = Unknown
			s.LastActionAt = time.Now().UTC().Unix()

			LoadedServices[s.Name] = &s
		} else {
			// We are reloading, AND, the init service is still present, mark it as not-deleted
			// And also not overwrite the whole service, just update what could have changed

			LoadedServices[s.Name].Deleted = false
			LoadedServices[s.Name].Description = s.Description
			LoadedServices[s.Name].Needs = s.Needs
			LoadedServices[s.Name].Provides = s.Provides
			LoadedServices[s.Name].AutoStart = s.AutoStart
			LoadedServices[s.Name].PIDFile = s.PIDFile
			LoadedServices[s.Name].Startup = s.Startup
			LoadedServices[s.Name].Shutdown = s.Shutdown
			LoadedServices[s.Name].Type = s.Type
		}

	}
	return nil
}

func checkSanity(service *Service, filename string) error {

	if ! ipc.IsCustASCII(string(service.Name)) {
		return fmt.Errorf("%s has invalid service name '%s', only a-Z0-9_-. allowed", filename, service.Name)
	}

	for _, provide := range service.Provides {
		if !ipc.IsCustASCIISpace(string(provide)) {
			return fmt.Errorf("%s has invalid provides '%s', only a-Z0-9_-. allowed", filename, provide)
		}
	}

	for _, need := range service.Needs {
		if !ipc.IsCustASCIISpace(string(need)) {
			return fmt.Errorf("%s has invalid needs '%s', only a-Z0-9_-. allowed", filename, need)
		}
	}

	return nil
}

// ParseSetupConfig parse the main configuration
func ParseSetupConfig(fname string) (err error) {
	Cfg, err := ini.InsensitiveLoad(fname)
	if err != nil {
		clog.Error(2, "Failed to parse '%s': %v", fname, err)
		return err
	}
	Cfg.NameMapper = ini.TitleUnderscore

	sec := Cfg.Section("global")
	MainConfig.Persist = sec.Key("Persist").MustBool(true)
	MainConfig.Autologins = sec.Key("Autologin").Strings(",")

	sec = Cfg.Section("logging")
	MainConfig.Log.Filename = sec.Key("filename").MustString("/var/log/lutrainit.log")
	MainConfig.Log.Daily = sec.Key("rotate_daily").MustBool(true)
	MainConfig.Log.MaxDays = sec.Key("max_days").MustInt64(7)
	MainConfig.Log.Rotate = sec.Key("rotate").MustBool(true)
	MainConfig.Log.MaxSize = sec.Key("max_size_shift").MustInt(28)
	MainConfig.Log.MaxLines = sec.Key("max_lines").MustInt64(1000000)
	MainConfig.Log.BufferLen = sec.Key("buffer_len").MustInt64(100)

	return err
}

// ReloadConfig both Main and Services ones
func ReloadConfig(reloading bool, withFile bool) (err error) {
	if reloading {
		clog.Info("Parsing configurations with reloading...")
	} else {
		clog.Info("Parsing configurations...")
	}

	if err := ParseSetupConfig("/etc/lutrainit/lutra.conf"); err != nil {
		clog.Error(2,"[lutra] Failed to parse Main Configuration: %s", err.Error())
		return err
	}
	clog.Info("Main config parsed.")

	if err = setupLogging(withFile); err != nil {
		clog.Error(2, "[lutra] Failed to re-setup logging: %s", err.Error())
		return err
	}
	clog.Info("Logging updated.")

	// Mark all as deleted
	for k := range LoadedServices {
		LoadedServices[k].Deleted = true
	}

	// Then re-parse
	err = ParseServiceConfigs("/etc/lutrainit/lutra.d", reloading)
	if err != nil {
		clog.Error(2, "[lutra] Cannot re-parse service configs: %s", err.Error())
		return err
	}

	dissappeared := 0
	for k := range LoadedServices {
		if LoadedServices[k].Deleted {
			dissappeared++
		}
	}
	if dissappeared > 0 {
		clog.Info("[lutra] It looks like %d Services files dissappeared :|", dissappeared)
	}
	clog.Info("Services re-parsed.")

	return err
}
<commit_msg>Reload config should also take care of theses elements<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"github.com/go-clog/clog"
	"os"
	"strings"
	"github.com/rhaamo/lutrainit/shared/ipc"
	"time"
	"github.com/go-ini/ini"
)

var (
	// MainConfig of the daemon
	MainConfig struct {
		Persist		bool
		Autologins	[]string

		Log struct {
			Filename 	string
			Rotate   	bool
			Daily   	bool
			MaxSize	 	int
			MaxLines 	int64
			MaxDays  	int64
			BufferLen	int64
		}

		StartedReexec	bool
	}
)

func parseLine(line string, s *Service) error {
	if strings.HasPrefix(line, "Needs:") {
		specified := strings.Split(strings.TrimPrefix(line, "Needs:"), ",")
		for _, nd := range specified {
			s.Needs = append(s.Needs, ServiceType(strings.TrimSpace(nd)))
		}
	} else if strings.HasPrefix(line, "Provides:") {
		specified := strings.Split(strings.TrimPrefix(line, "Provides:"), ",")
		for _, nd := range specified {
			s.Provides = append(s.Provides, ServiceType(strings.TrimSpace(nd)))
		}
	} else if strings.HasPrefix(line, "Startup:") {
		if s.Startup != "" {
			return fmt.Errorf("Startup already set")
		}
		s.Startup = Command(strings.TrimSpace(strings.TrimPrefix(line, "Startup:")))
	} else if strings.HasPrefix(line, "Shutdown:") {
		if s.Shutdown != "" {
			return fmt.Errorf("Shutdown already set")
		}
		s.Shutdown = Command(strings.TrimSpace(strings.TrimPrefix(line, "Shutdown:")))
	} else if strings.HasPrefix(line, "Name:") {
		if s.Name == "" {
			s.Name = ServiceName(strings.TrimSpace(strings.TrimPrefix(line, "Name:")))
		}
	} else if strings.HasPrefix(line, "Description:") {
		if s.Description == "" {
			s.Description = strings.TrimSpace(strings.TrimPrefix(line, "Description:"))
		}
	} else if strings.HasPrefix(line, "PIDFile:") {
		if s.PIDFile == "" {
			s.PIDFile = strings.TrimSpace(strings.TrimPrefix(line, "PIDFile:"))
		}
	} else if strings.HasPrefix(line, "Type:") {
		if s.Type == "" {
			serviceType := strings.TrimSpace(strings.TrimPrefix(line, "Type:"))
			switch serviceType {
			case "simple":
				s.Type = "simple"
			case "forking":
				s.Type = "forking"
			case "oneshot":
				s.Type = "oneshot"
			default:
				clog.Warn("[lutra] Invalid service type: %s, forcing Type=simple", serviceType)
				s.Type = "simple"
			}
		}
	} else if strings.HasPrefix(line, "Autostart:") {
		s.AutoStart = strings.TrimSpace(strings.TrimPrefix(line, "Autostart:")) == "true"
	} else if strings.HasPrefix(line, "ExecPreStart:") {
		if s.ExecPreStart == "" {
			s.ExecPreStart = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPreStart:")))
		}
	} else if strings.HasPrefix(line, "ExecPreStop:") {
		if s.ExecPreStop == "" {
			s.ExecPreStop = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPreStop:")))
		}
	} else if strings.HasPrefix(line, "ExecPostStart:") {
		if s.ExecPostStart == "" {
			s.ExecPostStart = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPostStart:")))
		}
	} else if strings.HasPrefix(line, "ExecPostStop:") {
		if s.ExecPostStop == "" {
			s.ExecPostStop = Command(strings.TrimSpace(strings.TrimPrefix(line, "ExecPostStop:")))
		}
	}
	return nil
}

// ParseConfig a single config file into the services it provides
func ParseConfig(r io.Reader, filename string) (Service, error) {
	s := Service{
		Deleted: false,
		AutoStart: true,
	}
	var line string
	var err error
	scanner := bufio.NewReader(r)

	for {
		line, err = scanner.ReadString('\n')
		switch err {
		case io.EOF:
			if err := parseLine(line, &s); err != nil {
				clog.Error(2, err.Error())
			}

			// Check for configuration sanity before returning
			if err := checkSanity(&s, filename); err != nil {
				return Service{}, err
			}

			return s, nil
		case nil:
			if err := parseLine(line, &s); err != nil {
				clog.Error(2, err.Error())
			}
		default:
			return Service{}, err
		}
	}
}

// ParseServiceConfigs parse all the config in directory dir return a map of
// providers of ServiceTypes from that directory.
func ParseServiceConfigs(dir string, reloading bool) error {
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, fstat := range files {
		if fstat.IsDir() {
			// Mostly to skip "." and ".."
			continue
		}

		// We only want to parse files ending with .service
		if !strings.HasSuffix(fstat.Name(), ".service") {
			continue
		}

		f, err := os.Open(dir + "/" + fstat.Name())
		if err != nil {
			clog.Error(2, err.Error())
			continue
		}
		s, err := ParseConfig(f, fstat.Name())
		f.Close()
		if err != nil {
			clog.Error(2, err.Error())
			continue
		}

		for _, t := range s.Provides {
			if s.AutoStart {
				StartupServices[t] = append(StartupServices[t], &StartupService{
					Name: s.Name,
					AutoStart: s.AutoStart,

					Needs: s.Needs,
					Provides: s.Provides,
				})
			}
		}

		// If we are not reloading, set initial state and actions
		if !reloading {
			s.State = NotStarted
			s.LastAction = Unknown
			s.LastActionAt = time.Now().UTC().Unix()

			LoadedServices[s.Name] = &s
		} else {
			// We are reloading, AND, the init service is still present, mark it as not-deleted
			// And also not overwrite the whole service, just update what could have changed

			LoadedServices[s.Name].Deleted = false
			LoadedServices[s.Name].Description = s.Description
			LoadedServices[s.Name].Needs = s.Needs
			LoadedServices[s.Name].Provides = s.Provides
			LoadedServices[s.Name].AutoStart = s.AutoStart
			LoadedServices[s.Name].PIDFile = s.PIDFile
			LoadedServices[s.Name].ExecPreStart = s.ExecPreStart
			LoadedServices[s.Name].Startup = s.Startup
			LoadedServices[s.Name].ExecPostStart = s.ExecPostStart
			LoadedServices[s.Name].ExecPreStop = s.ExecPreStop
			LoadedServices[s.Name].Shutdown = s.Shutdown
			LoadedServices[s.Name].ExecPostStop = s.ExecPostStop
			LoadedServices[s.Name].Type = s.Type
		}

	}
	return nil
}

func checkSanity(service *Service, filename string) error {

	if ! ipc.IsCustASCII(string(service.Name)) {
		return fmt.Errorf("%s has invalid service name '%s', only a-Z0-9_-. allowed", filename, service.Name)
	}

	for _, provide := range service.Provides {
		if !ipc.IsCustASCIISpace(string(provide)) {
			return fmt.Errorf("%s has invalid provides '%s', only a-Z0-9_-. allowed", filename, provide)
		}
	}

	for _, need := range service.Needs {
		if !ipc.IsCustASCIISpace(string(need)) {
			return fmt.Errorf("%s has invalid needs '%s', only a-Z0-9_-. allowed", filename, need)
		}
	}

	return nil
}

// ParseSetupConfig parse the main configuration
func ParseSetupConfig(fname string) (err error) {
	Cfg, err := ini.InsensitiveLoad(fname)
	if err != nil {
		clog.Error(2, "Failed to parse '%s': %v", fname, err)
		return err
	}
	Cfg.NameMapper = ini.TitleUnderscore

	sec := Cfg.Section("global")
	MainConfig.Persist = sec.Key("Persist").MustBool(true)
	MainConfig.Autologins = sec.Key("Autologin").Strings(",")

	sec = Cfg.Section("logging")
	MainConfig.Log.Filename = sec.Key("filename").MustString("/var/log/lutrainit.log")
	MainConfig.Log.Daily = sec.Key("rotate_daily").MustBool(true)
	MainConfig.Log.MaxDays = sec.Key("max_days").MustInt64(7)
	MainConfig.Log.Rotate = sec.Key("rotate").MustBool(true)
	MainConfig.Log.MaxSize = sec.Key("max_size_shift").MustInt(28)
	MainConfig.Log.MaxLines = sec.Key("max_lines").MustInt64(1000000)
	MainConfig.Log.BufferLen = sec.Key("buffer_len").MustInt64(100)

	return err
}

// ReloadConfig both Main and Services ones
func ReloadConfig(reloading bool, withFile bool) (err error) {
	if reloading {
		clog.Info("Parsing configurations with reloading...")
	} else {
		clog.Info("Parsing configurations...")
	}

	if err := ParseSetupConfig("/etc/lutrainit/lutra.conf"); err != nil {
		clog.Error(2,"[lutra] Failed to parse Main Configuration: %s", err.Error())
		return err
	}
	clog.Info("Main config parsed.")

	if err = setupLogging(withFile); err != nil {
		clog.Error(2, "[lutra] Failed to re-setup logging: %s", err.Error())
		return err
	}
	clog.Info("Logging updated.")

	// Mark all as deleted
	for k := range LoadedServices {
		LoadedServices[k].Deleted = true
	}

	// Then re-parse
	err = ParseServiceConfigs("/etc/lutrainit/lutra.d", reloading)
	if err != nil {
		clog.Error(2, "[lutra] Cannot re-parse service configs: %s", err.Error())
		return err
	}

	dissappeared := 0
	for k := range LoadedServices {
		if LoadedServices[k].Deleted {
			dissappeared++
		}
	}
	if dissappeared > 0 {
		clog.Info("[lutra] It looks like %d Services files dissappeared :|", dissappeared)
	}
	clog.Info("Services re-parsed.")

	return err
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/version"
)

func certificatesGet(d *Daemon, r *http.Request) Response {
	recursion := util.IsRecursionRequest(r)

	if recursion {
		certResponses := []api.Certificate{}

		baseCerts, err := d.cluster.CertificatesGet()
		if err != nil {
			return SmartError(err)
		}
		for _, baseCert := range baseCerts {
			resp := api.Certificate{}
			resp.Fingerprint = baseCert.Fingerprint
			resp.Certificate = baseCert.Certificate
			resp.Name = baseCert.Name
			if baseCert.Type == 1 {
				resp.Type = "client"
			} else {
				resp.Type = "unknown"
			}
			certResponses = append(certResponses, resp)
		}
		return SyncResponse(true, certResponses)
	}

	body := []string{}
	for _, cert := range d.clientCerts {
		fingerprint := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, shared.CertFingerprint(&cert))
		body = append(body, fingerprint)
	}

	return SyncResponse(true, body)
}

func readSavedClientCAList(d *Daemon) {
	d.clientCerts = []x509.Certificate{}

	dbCerts, err := d.cluster.CertificatesGet()
	if err != nil {
		logger.Infof("Error reading certificates from database: %s", err)
		return
	}

	for _, dbCert := range dbCerts {
		certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
		if certBlock == nil {
			logger.Infof("Error decoding certificate for %s: %s", dbCert.Name, err)
			continue
		}

		cert, err := x509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			logger.Infof("Error reading certificate for %s: %s", dbCert.Name, err)
			continue
		}
		d.clientCerts = append(d.clientCerts, *cert)
	}
}

func saveCert(dbObj *db.Cluster, host string, cert *x509.Certificate) error {
	baseCert := new(db.CertInfo)
	baseCert.Fingerprint = shared.CertFingerprint(cert)
	baseCert.Type = 1
	baseCert.Name = host
	baseCert.Certificate = string(
		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
	)

	return dbObj.CertSave(baseCert)
}

func certificatesPost(d *Daemon, r *http.Request) Response {
	// Parse the request
	req := api.CertificatesPost{}
	if err := shared.ReadToJSON(r.Body, &req); err != nil {
		return BadRequest(err)
	}

	// Access check
	secret, err := cluster.ConfigGetString(d.cluster, "core.trust_password")
	if err != nil {
		return SmartError(err)
	}
	if d.checkTrustedClient(r) != nil && util.PasswordCheck(secret, req.Password) != nil {
		return Forbidden(nil)
	}

	if req.Type != "client" {
		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
	}

	// Extract the certificate
	var cert *x509.Certificate
	var name string
	if req.Certificate != "" {
		data, err := base64.StdEncoding.DecodeString(req.Certificate)
		if err != nil {
			return BadRequest(err)
		}

		cert, err = x509.ParseCertificate(data)
		if err != nil {
			return BadRequest(errors.Wrap(err, "invalid certificate material"))
		}
		name = req.Name
	} else if r.TLS != nil {
		if len(r.TLS.PeerCertificates) < 1 {
			return BadRequest(fmt.Errorf("No client certificate provided"))
		}
		cert = r.TLS.PeerCertificates[len(r.TLS.PeerCertificates)-1]

		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			return InternalError(err)
		}

		name = remoteHost
	} else {
		return BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
	}

	fingerprint := shared.CertFingerprint(cert)

	if !isClusterNotification(r) {
		// Store the certificate in the cluster database.
		existingCerts, err := d.cluster.CertificatesGet()
		if err != nil {
			return SmartError(err)
		}
		for _, existingCert := range existingCerts {
			if fingerprint == existingCert.Fingerprint {
				return BadRequest(fmt.Errorf("Certificate already in trust store"))
			}
		}

		err = saveCert(d.cluster, name, cert)
		if err != nil {
			return SmartError(err)
		}

		// Notify other nodes about the new certificate.
		notifier, err := cluster.NewNotifier(
			d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
		if err != nil {
			return SmartError(err)
		}
		req := api.CertificatesPost{
			Certificate: base64.StdEncoding.EncodeToString(cert.Raw),
		}
		req.Name = name
		req.Type = "client"

		err = notifier(func(client lxd.ContainerServer) error {
			return client.CreateCertificate(req)
		})
		if err != nil {
			return SmartError(err)
		}
	}

	d.clientCerts = append(d.clientCerts, *cert)

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
}

var certificatesCmd = Command{name: "certificates", untrustedPost: true, get: certificatesGet, post: certificatesPost}

func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	cert, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}

	return SyncResponseETag(true, cert, cert)
}

func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, error) {
	resp := api.Certificate{}

	dbCertInfo, err := db.CertificateGet(fingerprint)
	if err != nil {
		return resp, err
	}

	resp.Fingerprint = dbCertInfo.Fingerprint
	resp.Certificate = dbCertInfo.Certificate
	resp.Name = dbCertInfo.Name
	if dbCertInfo.Type == 1 {
		resp.Type = "client"
	} else {
		resp.Type = "unknown"
	}

	return resp, nil
}

func certificateFingerprintPut(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}
	fingerprint = oldEntry.Fingerprint

	err = util.EtagCheck(r, oldEntry)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := api.CertificatePut{}
	if err := shared.ReadToJSON(r.Body, &req); err != nil {
		return BadRequest(err)
	}

	return doCertificateUpdate(d, fingerprint, req)
}

func certificateFingerprintPatch(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}
	fingerprint = oldEntry.Fingerprint

	err = util.EtagCheck(r, oldEntry)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := oldEntry
	reqRaw := shared.Jmap{}
	if err := json.NewDecoder(r.Body).Decode(&reqRaw); err != nil {
		return BadRequest(err)
	}

	// Get name
	value, err := reqRaw.GetString("name")
	if err == nil {
		req.Name = value
	}

	// Get type
	value, err = reqRaw.GetString("type")
	if err == nil {
		req.Type = value
	}

	return doCertificateUpdate(d, fingerprint, req.Writable())
}

func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response {
	if req.Type != "client" {
		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
	}

	err := d.cluster.CertUpdate(fingerprint, req.Name, 1)
	if err != nil {
		return SmartError(err)
	}

	return EmptySyncResponse
}

func certificateFingerprintDelete(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	certInfo, err := d.cluster.CertificateGet(fingerprint)
	if err != nil {
		return NotFound(err)
	}

	err = d.cluster.CertDelete(certInfo.Fingerprint)
	if err != nil {
		return SmartError(err)
	}
	readSavedClientCAList(d)

	return EmptySyncResponse
}

var certificateFingerprintCmd = Command{name: "certificates/{fingerprint}", get: certificateFingerprintGet, delete: certificateFingerprintDelete, put: certificateFingerprintPut, patch: certificateFingerprintPatch}
<commit_msg>lxd/certificates: Log password failures<commit_after>package main

import (
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/version"

	log "github.com/lxc/lxd/shared/log15"
)

func certificatesGet(d *Daemon, r *http.Request) Response {
	recursion := util.IsRecursionRequest(r)

	if recursion {
		certResponses := []api.Certificate{}

		baseCerts, err := d.cluster.CertificatesGet()
		if err != nil {
			return SmartError(err)
		}
		for _, baseCert := range baseCerts {
			resp := api.Certificate{}
			resp.Fingerprint = baseCert.Fingerprint
			resp.Certificate = baseCert.Certificate
			resp.Name = baseCert.Name
			if baseCert.Type == 1 {
				resp.Type = "client"
			} else {
				resp.Type = "unknown"
			}
			certResponses = append(certResponses, resp)
		}
		return SyncResponse(true, certResponses)
	}

	body := []string{}
	for _, cert := range d.clientCerts {
		fingerprint := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, shared.CertFingerprint(&cert))
		body = append(body, fingerprint)
	}

	return SyncResponse(true, body)
}

func readSavedClientCAList(d *Daemon) {
	d.clientCerts = []x509.Certificate{}

	dbCerts, err := d.cluster.CertificatesGet()
	if err != nil {
		logger.Infof("Error reading certificates from database: %s", err)
		return
	}

	for _, dbCert := range dbCerts {
		certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
		if certBlock == nil {
			logger.Infof("Error decoding certificate for %s: %s", dbCert.Name, err)
			continue
		}

		cert, err := x509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			logger.Infof("Error reading certificate for %s: %s", dbCert.Name, err)
			continue
		}
		d.clientCerts = append(d.clientCerts, *cert)
	}
}

func saveCert(dbObj *db.Cluster, host string, cert *x509.Certificate) error {
	baseCert := new(db.CertInfo)
	baseCert.Fingerprint = shared.CertFingerprint(cert)
	baseCert.Type = 1
	baseCert.Name = host
	baseCert.Certificate = string(
		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
	)

	return dbObj.CertSave(baseCert)
}

func certificatesPost(d *Daemon, r *http.Request) Response {
	// Parse the request
	req := api.CertificatesPost{}
	if err := shared.ReadToJSON(r.Body, &req); err != nil {
		return BadRequest(err)
	}

	// Access check
	secret, err := cluster.ConfigGetString(d.cluster, "core.trust_password")
	if err != nil {
		return SmartError(err)
	}

	if d.checkTrustedClient(r) != nil && util.PasswordCheck(secret, req.Password) != nil {
		logger.Warn("Bad trust password", log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
		return Forbidden(nil)
	}

	if req.Type != "client" {
		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
	}

	// Extract the certificate
	var cert *x509.Certificate
	var name string
	if req.Certificate != "" {
		data, err := base64.StdEncoding.DecodeString(req.Certificate)
		if err != nil {
			return BadRequest(err)
		}

		cert, err = x509.ParseCertificate(data)
		if err != nil {
			return BadRequest(errors.Wrap(err, "invalid certificate material"))
		}
		name = req.Name
	} else if r.TLS != nil {
		if len(r.TLS.PeerCertificates) < 1 {
			return BadRequest(fmt.Errorf("No client certificate provided"))
		}
		cert = r.TLS.PeerCertificates[len(r.TLS.PeerCertificates)-1]

		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			return InternalError(err)
		}

		name = remoteHost
	} else {
		return BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
	}

	fingerprint := shared.CertFingerprint(cert)

	if !isClusterNotification(r) {
		// Store the certificate in the cluster database.
		existingCerts, err := d.cluster.CertificatesGet()
		if err != nil {
			return SmartError(err)
		}
		for _, existingCert := range existingCerts {
			if fingerprint == existingCert.Fingerprint {
				return BadRequest(fmt.Errorf("Certificate already in trust store"))
			}
		}

		err = saveCert(d.cluster, name, cert)
		if err != nil {
			return SmartError(err)
		}

		// Notify other nodes about the new certificate.
		notifier, err := cluster.NewNotifier(
			d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
		if err != nil {
			return SmartError(err)
		}
		req := api.CertificatesPost{
			Certificate: base64.StdEncoding.EncodeToString(cert.Raw),
		}
		req.Name = name
		req.Type = "client"

		err = notifier(func(client lxd.ContainerServer) error {
			return client.CreateCertificate(req)
		})
		if err != nil {
			return SmartError(err)
		}
	}

	d.clientCerts = append(d.clientCerts, *cert)

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
}

var certificatesCmd = Command{name: "certificates", untrustedPost: true, get: certificatesGet, post: certificatesPost}

func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	cert, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}

	return SyncResponseETag(true, cert, cert)
}

func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, error) {
	resp := api.Certificate{}

	dbCertInfo, err := db.CertificateGet(fingerprint)
	if err != nil {
		return resp, err
	}

	resp.Fingerprint = dbCertInfo.Fingerprint
	resp.Certificate = dbCertInfo.Certificate
	resp.Name = dbCertInfo.Name
	if dbCertInfo.Type == 1 {
		resp.Type = "client"
	} else {
		resp.Type = "unknown"
	}

	return resp, nil
}

func certificateFingerprintPut(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}
	fingerprint = oldEntry.Fingerprint

	err = util.EtagCheck(r, oldEntry)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := api.CertificatePut{}
	if err := shared.ReadToJSON(r.Body, &req); err != nil {
		return BadRequest(err)
	}

	return doCertificateUpdate(d, fingerprint, req)
}

func certificateFingerprintPatch(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
	if err != nil {
		return SmartError(err)
	}
	fingerprint = oldEntry.Fingerprint

	err = util.EtagCheck(r, oldEntry)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := oldEntry
	reqRaw := shared.Jmap{}
	if err := json.NewDecoder(r.Body).Decode(&reqRaw); err != nil {
		return BadRequest(err)
	}

	// Get name
	value, err := reqRaw.GetString("name")
	if err == nil {
		req.Name = value
	}

	// Get type
	value, err = reqRaw.GetString("type")
	if err == nil {
		req.Type = value
	}

	return doCertificateUpdate(d, fingerprint, req.Writable())
}

func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response {
	if req.Type != "client" {
		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
	}

	err := d.cluster.CertUpdate(fingerprint, req.Name, 1)
	if err != nil {
		return SmartError(err)
	}

	return EmptySyncResponse
}

func certificateFingerprintDelete(d *Daemon, r *http.Request) Response {
	fingerprint := mux.Vars(r)["fingerprint"]

	certInfo, err := d.cluster.CertificateGet(fingerprint)
	if err != nil {
		return NotFound(err)
	}

	err = d.cluster.CertDelete(certInfo.Fingerprint)
	if err != nil {
		return SmartError(err)
	}
	readSavedClientCAList(d)

	return EmptySyncResponse
}

var certificateFingerprintCmd = Command{name: "certificates/{fingerprint}", get: certificateFingerprintGet, delete: certificateFingerprintDelete, put: certificateFingerprintPut, patch: certificateFingerprintPatch}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/ericchiang/k8s"
	apiv1 "github.com/ericchiang/k8s/api/v1"
	batchv1 "github.com/ericchiang/k8s/apis/batch/v1"
	metav1 "github.com/ericchiang/k8s/apis/meta/v1"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog/log"
)

// CiBuilderClient is the interface for running kubernetes commands specific to this application
type CiBuilderClient interface {
	CreateCiBuilderJob(CiBuilderParams) (*batchv1.Job, error)
}

// CiBuilderParams contains the parameters required to create a ci builder job
type CiBuilderParams struct {
	RepoFullName         string
	RepoURL              string
	RepoBranch           string
	RepoRevision         string
	EnvironmentVariables map[string]string
}

type ciBuilderClientImpl struct {
	KubeClient *k8s.Client
}

// newCiBuilderClient return a estafette ci builder client
func newCiBuilderClient() (ciBuilderClient CiBuilderClient, err error) {

	kubeClient, err := k8s.NewInClusterClient()
	if err != nil {
		log.Error().Err(err).Msg("Creating k8s client failed")
		return
	}

	ciBuilderClient = &ciBuilderClientImpl{
		KubeClient: kubeClient,
	}

	return
}

// CreateCiBuilderJob creates an estafette-ci-builder job in Kubernetes to run the estafette build
func (cbc *ciBuilderClientImpl) CreateCiBuilderJob(ciBuilderParams CiBuilderParams) (job *batchv1.Job, err error) {

	// create job name of max 63 chars
	re := regexp.MustCompile("[^a-zA-Z0-9]+")
	repoName := re.ReplaceAllString(ciBuilderParams.RepoFullName, "-")
	if len(repoName) > 50 {
		repoName = repoName[:50]
	}
	jobName := strings.ToLower(fmt.Sprintf("build-%v-%v", repoName, ciBuilderParams.RepoRevision[:6]))

	// create envvars for job
	estafetteGitNameName := "ESTAFETTE_GIT_NAME"
	estafetteGitNameValue := ciBuilderParams.RepoFullName
	estafetteGitURLName := "ESTAFETTE_GIT_URL"
	estafetteGitURLValue := ciBuilderParams.RepoURL
	estafetteGitBranchName := "ESTAFETTE_GIT_BRANCH"
	estafetteGitBranchValue := ciBuilderParams.RepoBranch
	estafetteGitRevisionName := "ESTAFETTE_GIT_REVISION"
	estafetteGitRevisionValue := ciBuilderParams.RepoRevision
	estafetteBuildJobNameName := "ESTAFETTE_BUILD_JOB_NAME"
	estafetteBuildJobNameValue := jobName
	estafetteCiServerBaseURLName := "ESTAFETTE_CI_SERVER_BASE_URL"
	estafetteCiServerBaseURLValue := *estafetteCiBaseURL

	// temporarily pass build version equal to revision from the outside until estafette supports versioning
	estafetteBuildVersionName := "ESTAFETTE_BUILD_VERSION"
	estafetteBuildVersionValue := ciBuilderParams.RepoRevision
	estafetteBuildVersionPatchName := "ESTAFETTE_BUILD_VERSION_PATCH"
	estafetteBuildVersionPatchValue := "1"

	environmentVariables := []*apiv1.EnvVar{
		&apiv1.EnvVar{
			Name:  &estafetteGitNameName,
			Value: &estafetteGitNameValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitURLName,
			Value: &estafetteGitURLValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitBranchName,
			Value: &estafetteGitBranchValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitRevisionName,
			Value: &estafetteGitRevisionValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildVersionName,
			Value: &estafetteBuildVersionValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildVersionPatchName,
			Value: &estafetteBuildVersionPatchValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildJobNameName,
			Value: &estafetteBuildJobNameValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteCiServerBaseURLName,
			Value: &estafetteCiServerBaseURLValue,
		},
	}

	for key, value := range ciBuilderParams.EnvironmentVariables {
		environmentVariables = append(environmentVariables, &apiv1.EnvVar{
			Name:  &key,
			Value: &value,
		})
	}

	// other job config
	containerName := "estafette-ci-builder"
	image := fmt.Sprintf("estafette/estafette-ci-builder:%v", *estafetteCiBuilderVersion)
	restartPolicy := "Never"
	privileged := true

	job = &batchv1.Job{
		Metadata: &metav1.ObjectMeta{
			Name:      &jobName,
			Namespace: &cbc.KubeClient.Namespace,
			Labels: map[string]string{
				"createdBy": "estafette",
			},
		},
		Spec: &batchv1.JobSpec{
			Template: &apiv1.PodTemplateSpec{
				Metadata: &metav1.ObjectMeta{
					Labels: map[string]string{
						"createdBy": "estafette",
					},
				},
				Spec: &apiv1.PodSpec{
					Containers: []*apiv1.Container{
						&apiv1.Container{
							Name:  &containerName,
							Image: &image,
							Env:   environmentVariables,
							SecurityContext: &apiv1.SecurityContext{
								Privileged: &privileged,
							},
						},
					},
					RestartPolicy: &restartPolicy,
				},
			},
		},
	}

	// track call via prometheus
	outgoingAPIRequestTotal.With(prometheus.Labels{"target": "kubernetes"}).Inc()

	job, err = cbc.KubeClient.BatchV1().CreateJob(context.Background(), job)

	return
}
<commit_msg>set cpu and memory request and limit values<commit_after>package main

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/ericchiang/k8s"
	"github.com/ericchiang/k8s/api/resource"
	apiv1 "github.com/ericchiang/k8s/api/v1"
	batchv1 "github.com/ericchiang/k8s/apis/batch/v1"
	metav1 "github.com/ericchiang/k8s/apis/meta/v1"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/rs/zerolog/log"
)

// CiBuilderClient is the interface for running kubernetes commands specific to this application
type CiBuilderClient interface {
	CreateCiBuilderJob(CiBuilderParams) (*batchv1.Job, error)
}

// CiBuilderParams contains the parameters required to create a ci builder job
type CiBuilderParams struct {
	RepoFullName         string
	RepoURL              string
	RepoBranch           string
	RepoRevision         string
	EnvironmentVariables map[string]string
}

type ciBuilderClientImpl struct {
	KubeClient *k8s.Client
}

// newCiBuilderClient return a estafette ci builder client
func newCiBuilderClient() (ciBuilderClient CiBuilderClient, err error) {

	kubeClient, err := k8s.NewInClusterClient()
	if err != nil {
		log.Error().Err(err).Msg("Creating k8s client failed")
		return
	}

	ciBuilderClient = &ciBuilderClientImpl{
		KubeClient: kubeClient,
	}

	return
}

// CreateCiBuilderJob creates an estafette-ci-builder job in Kubernetes to run the estafette build
func (cbc *ciBuilderClientImpl) CreateCiBuilderJob(ciBuilderParams CiBuilderParams) (job *batchv1.Job, err error) {

	// create job name of max 63 chars
	re := regexp.MustCompile("[^a-zA-Z0-9]+")
	repoName := re.ReplaceAllString(ciBuilderParams.RepoFullName, "-")
	if len(repoName) > 50 {
		repoName = repoName[:50]
	}
	jobName := strings.ToLower(fmt.Sprintf("build-%v-%v", repoName, ciBuilderParams.RepoRevision[:6]))

	// create envvars for job
	estafetteGitNameName := "ESTAFETTE_GIT_NAME"
	estafetteGitNameValue := ciBuilderParams.RepoFullName
	estafetteGitURLName := "ESTAFETTE_GIT_URL"
	estafetteGitURLValue := ciBuilderParams.RepoURL
	estafetteGitBranchName := "ESTAFETTE_GIT_BRANCH"
	estafetteGitBranchValue := ciBuilderParams.RepoBranch
	estafetteGitRevisionName := "ESTAFETTE_GIT_REVISION"
	estafetteGitRevisionValue := ciBuilderParams.RepoRevision
	estafetteBuildJobNameName := "ESTAFETTE_BUILD_JOB_NAME"
	estafetteBuildJobNameValue := jobName
	estafetteCiServerBaseURLName := "ESTAFETTE_CI_SERVER_BASE_URL"
	estafetteCiServerBaseURLValue := *estafetteCiBaseURL

	// temporarily pass build version equal to revision from the outside until estafette supports versioning
	estafetteBuildVersionName := "ESTAFETTE_BUILD_VERSION"
	estafetteBuildVersionValue := ciBuilderParams.RepoRevision
	estafetteBuildVersionPatchName := "ESTAFETTE_BUILD_VERSION_PATCH"
	estafetteBuildVersionPatchValue := "1"

	environmentVariables := []*apiv1.EnvVar{
		&apiv1.EnvVar{
			Name:  &estafetteGitNameName,
			Value: &estafetteGitNameValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitURLName,
			Value: &estafetteGitURLValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitBranchName,
			Value: &estafetteGitBranchValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteGitRevisionName,
			Value: &estafetteGitRevisionValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildVersionName,
			Value: &estafetteBuildVersionValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildVersionPatchName,
			Value: &estafetteBuildVersionPatchValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteBuildJobNameName,
			Value: &estafetteBuildJobNameValue,
		},
		&apiv1.EnvVar{
			Name:  &estafetteCiServerBaseURLName,
			Value: &estafetteCiServerBaseURLValue,
		},
	}

	for key, value := range ciBuilderParams.EnvironmentVariables {
		environmentVariables = append(environmentVariables, &apiv1.EnvVar{
			Name:  &key,
			Value: &value,
		})
	}

	// define resource request and limit values to fit reasonably well inside a n1-highmem-4 machine
	cpuRequest := "1.0"
	cpuLimit := "3.0"
	memoryRequest := "2.0"
	memoryLimit := "20.0"

	// other job config
	containerName := "estafette-ci-builder"
	image := fmt.Sprintf("estafette/estafette-ci-builder:%v", *estafetteCiBuilderVersion)
	restartPolicy := "Never"
	privileged := true

	job = &batchv1.Job{
		Metadata: &metav1.ObjectMeta{
			Name:      &jobName,
			Namespace: &cbc.KubeClient.Namespace,
			Labels: map[string]string{
				"createdBy": "estafette",
			},
		},
		Spec: &batchv1.JobSpec{
			Template: &apiv1.PodTemplateSpec{
				Metadata: &metav1.ObjectMeta{
					Labels: map[string]string{
						"createdBy": "estafette",
					},
				},
				Spec: &apiv1.PodSpec{
					Containers: []*apiv1.Container{
						&apiv1.Container{
							Name:  &containerName,
							Image: &image,
							Env:   environmentVariables,
							SecurityContext: &apiv1.SecurityContext{
								Privileged: &privileged,
							},
							Resources: &apiv1.ResourceRequirements{
								Requests: map[string]*resource.Quantity{
									"cpu":    &resource.Quantity{String_: &cpuRequest},
									"memory": &resource.Quantity{String_: &memoryRequest},
								},
								Limits: map[string]*resource.Quantity{
									"cpu":    &resource.Quantity{String_: &cpuLimit},
									"memory": &resource.Quantity{String_: &memoryLimit},
								},
							},
						},
					},
					RestartPolicy: &restartPolicy,
				},
			},
		},
	}

	// track call via prometheus
	outgoingAPIRequestTotal.With(prometheus.Labels{"target": "kubernetes"}).Inc()

	job, err = cbc.KubeClient.BatchV1().CreateJob(context.Background(), job)

	return
}
<|endoftext|>


<commit_before><commit_msg>uni storage<commit_after><|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package common

import (
	"context"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/kubelet/events"
	runtimeclasstest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing"
	"k8s.io/kubernetes/test/e2e/framework"
	e2eevents "k8s.io/kubernetes/test/e2e/framework/events"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"

	"github.com/onsi/ginkgo"
)

var _ = ginkgo.Describe("[sig-node] RuntimeClass", func() {
	f := framework.NewDefaultFramework("runtimeclass")

	ginkgo.It("should reject a Pod requesting a non-existent RuntimeClass", func() {
		rcName := f.Namespace.Name + "-nonexistent"
		expectPodRejection(f, e2enode.NewRuntimeClassPod(rcName))
	})

	ginkgo.It("should reject a Pod requesting a RuntimeClass with an unconfigured handler", func() {
		handler := f.Namespace.Name + "-handler"
		rcName := createRuntimeClass(f, "unconfigured-handler", handler)
		pod := f.PodClient().Create(e2enode.NewRuntimeClassPod(rcName))
		expectSandboxFailureEvent(f, pod, handler)
	})

	// This test requires that the PreconfiguredRuntimeHandler has already been set up on nodes.
	ginkgo.It("should run a Pod requesting a RuntimeClass with a configured handler [NodeFeature:RuntimeHandler]", func() {
		// The built-in docker runtime does not support configuring runtime handlers.
		handler := e2enode.PreconfiguredRuntimeClassHandler(framework.TestContext.ContainerRuntime)

		rcName := createRuntimeClass(f, "preconfigured-handler", handler)
		pod := f.PodClient().Create(e2enode.NewRuntimeClassPod(rcName))
		expectPodSuccess(f, pod)
	})

	ginkgo.It("should reject a Pod requesting a deleted RuntimeClass", func() {
		rcName := createRuntimeClass(f, "delete-me", "runc")
		rcClient := f.ClientSet.NodeV1beta1().RuntimeClasses()

		ginkgo.By("Deleting RuntimeClass "+rcName, func() {
			err := rcClient.Delete(context.TODO(), rcName, metav1.DeleteOptions{})
			framework.ExpectNoError(err, "failed to delete RuntimeClass %s", rcName)

			ginkgo.By("Waiting for the RuntimeClass to disappear")
			framework.ExpectNoError(wait.PollImmediate(framework.Poll, time.Minute, func() (bool, error) {
				_, err := rcClient.Get(context.TODO(), rcName, metav1.GetOptions{})
				if apierrors.IsNotFound(err) {
					return true, nil // done
				}
				if err != nil {
					return true, err // stop wait with error
				}
				return false, nil
			}))
		})

		expectPodRejection(f, e2enode.NewRuntimeClassPod(rcName))
	})
})

// createRuntimeClass generates a RuntimeClass with the desired handler and a "namespaced" name,
// synchronously creates it, and returns the generated name.
func createRuntimeClass(f *framework.Framework, name, handler string) string {
	uniqueName := fmt.Sprintf("%s-%s", f.Namespace.Name, name)
	rc := runtimeclasstest.NewRuntimeClass(uniqueName, handler)
	rc, err := f.ClientSet.NodeV1beta1().RuntimeClasses().Create(context.TODO(), rc, metav1.CreateOptions{})
	framework.ExpectNoError(err, "failed to create RuntimeClass resource")
	return rc.GetName()
}

func expectPodRejection(f *framework.Framework, pod *v1.Pod) {
	_, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
	framework.ExpectError(err, "should be forbidden")
	framework.ExpectEqual(apierrors.IsForbidden(err), true, "should be forbidden error")
}

// expectPodSuccess waits for the given pod to terminate successfully.
func expectPodSuccess(f *framework.Framework, pod *v1.Pod) {
	framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespace(
		f.ClientSet, pod.Name, f.Namespace.Name))
}

// expectSandboxFailureEvent polls for an event with reason "FailedCreatePodSandBox" containing the
// expected message string.
func expectSandboxFailureEvent(f *framework.Framework, pod *v1.Pod, msg string) {
	eventSelector := fields.Set{
		"involvedObject.kind":      "Pod",
		"involvedObject.name":      pod.Name,
		"involvedObject.namespace": f.Namespace.Name,
		"reason":                   events.FailedCreatePodSandBox,
	}.AsSelector().String()
	framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(
		f.ClientSet, f.Namespace.Name, eventSelector, msg, framework.PodEventTimeout))
}
<commit_msg>Move the RuntimeClass tests out of node-kubelet-orphans<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package common

import (
	"context"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/kubelet/events"
	runtimeclasstest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing"
	"k8s.io/kubernetes/test/e2e/framework"
	e2eevents "k8s.io/kubernetes/test/e2e/framework/events"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"

	"github.com/onsi/ginkgo"
)

var _ = ginkgo.Describe("[sig-node] RuntimeClass", func() {
	f := framework.NewDefaultFramework("runtimeclass")

	ginkgo.It("should reject a Pod requesting a non-existent RuntimeClass [NodeFeature:RuntimeHandler]", func() {
		rcName := f.Namespace.Name + "-nonexistent"
		expectPodRejection(f, e2enode.NewRuntimeClassPod(rcName))
	})

	ginkgo.It("should reject a Pod requesting a RuntimeClass with an unconfigured handler [NodeFeature:RuntimeHandler]", func() {
		handler := f.Namespace.Name + "-handler"
		rcName := createRuntimeClass(f, "unconfigured-handler", handler)
		pod := f.PodClient().Create(e2enode.NewRuntimeClassPod(rcName))
		expectSandboxFailureEvent(f, pod, handler)
	})

	// This test requires that the PreconfiguredRuntimeHandler has already been set up on nodes.
	ginkgo.It("should run a Pod requesting a RuntimeClass with a configured handler [NodeFeature:RuntimeHandler]", func() {
		// The built-in docker runtime does not support configuring runtime handlers.
		handler := e2enode.PreconfiguredRuntimeClassHandler(framework.TestContext.ContainerRuntime)

		rcName := createRuntimeClass(f, "preconfigured-handler", handler)
		pod := f.PodClient().Create(e2enode.NewRuntimeClassPod(rcName))
		expectPodSuccess(f, pod)
	})

	ginkgo.It("should reject a Pod requesting a deleted RuntimeClass [NodeFeature:RuntimeHandler]", func() {
		rcName := createRuntimeClass(f, "delete-me", "runc")
		rcClient := f.ClientSet.NodeV1beta1().RuntimeClasses()

		ginkgo.By("Deleting RuntimeClass "+rcName, func() {
			err := rcClient.Delete(context.TODO(), rcName, metav1.DeleteOptions{})
			framework.ExpectNoError(err, "failed to delete RuntimeClass %s", rcName)

			ginkgo.By("Waiting for the RuntimeClass to disappear")
			framework.ExpectNoError(wait.PollImmediate(framework.Poll, time.Minute, func() (bool, error) {
				_, err := rcClient.Get(context.TODO(), rcName, metav1.GetOptions{})
				if apierrors.IsNotFound(err) {
					return true, nil // done
				}
				if err != nil {
					return true, err // stop wait with error
				}
				return false, nil
			}))
		})

		expectPodRejection(f, e2enode.NewRuntimeClassPod(rcName))
	})
})

// createRuntimeClass generates a RuntimeClass with the desired handler and a "namespaced" name,
// synchronously creates it, and returns the generated name.
func createRuntimeClass(f *framework.Framework, name, handler string) string {
	uniqueName := fmt.Sprintf("%s-%s", f.Namespace.Name, name)
	rc := runtimeclasstest.NewRuntimeClass(uniqueName, handler)
	rc, err := f.ClientSet.NodeV1beta1().RuntimeClasses().Create(context.TODO(), rc, metav1.CreateOptions{})
	framework.ExpectNoError(err, "failed to create RuntimeClass resource")
	return rc.GetName()
}

func expectPodRejection(f *framework.Framework, pod *v1.Pod) {
	_, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
	framework.ExpectError(err, "should be forbidden")
	framework.ExpectEqual(apierrors.IsForbidden(err), true, "should be forbidden error")
}

// expectPodSuccess waits for the given pod to terminate successfully.
func expectPodSuccess(f *framework.Framework, pod *v1.Pod) {
	framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespace(
		f.ClientSet, pod.Name, f.Namespace.Name))
}

// expectSandboxFailureEvent polls for an event with reason "FailedCreatePodSandBox" containing the
// expected message string.
func expectSandboxFailureEvent(f *framework.Framework, pod *v1.Pod, msg string) {
	eventSelector := fields.Set{
		"involvedObject.kind":      "Pod",
		"involvedObject.name":      pod.Name,
		"involvedObject.namespace": f.Namespace.Name,
		"reason":                   events.FailedCreatePodSandBox,
	}.AsSelector().String()
	framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(
		f.ClientSet, f.Namespace.Name, eventSelector, msg, framework.PodEventTimeout))
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 framework

import (
	"time"

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

	"k8s.io/api/core/v1"
	api "k8s.io/api/core/v1"
	apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes"
	kscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	crclient "sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	certmgrscheme "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/scheme"
	"github.com/jetstack/cert-manager/pkg/util/pki"
	"github.com/jetstack/cert-manager/test/e2e/framework/addon"
	"github.com/jetstack/cert-manager/test/e2e/framework/config"
	"github.com/jetstack/cert-manager/test/e2e/framework/helper"
	"github.com/jetstack/cert-manager/test/e2e/framework/log"
	"github.com/jetstack/cert-manager/test/e2e/framework/util"
	"github.com/jetstack/cert-manager/test/e2e/framework/util/errors"
)

// TODO: this really should be done somewhere in cert-manager proper
var Scheme = runtime.NewScheme()

func init() {
	kscheme.AddToScheme(Scheme)
	certmgrscheme.AddToScheme(Scheme)
}

// DefaultConfig contains the default shared config the is likely parsed from
// command line arguments.
var DefaultConfig = &config.Config{}

// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
type Framework struct {
	BaseName string

	Config *config.Config

	// KubeClientConfig which was used to create the connection.
	KubeClientConfig *rest.Config

	// Kubernetes API clientsets
	KubeClientSet          kubernetes.Interface
	CertManagerClientSet   clientset.Interface
	APIExtensionsClientSet apiextcs.Interface

	// controller-runtime client for newer controllers
	CRClient crclient.Client

	// Namespace in which all test resources should reside
	Namespace *v1.Namespace

	// To make sure that this framework cleans up after itself, no matter what,
	// we install a Cleanup action before each test and clear it after.  If we
	// should abort, the AfterSuite hook should run all Cleanup actions.
	cleanupHandle CleanupActionHandle

	requiredAddons []addon.Addon
	helper         *helper.Helper
}

// NewDefaultFramework makes a new framework for you, similar to NewFramework.
// It uses the suite-wide 'DefaultConfig' which should be populated by the
// testing harness in test/e2e/e2e_test.go
func NewDefaultFramework(baseName string) *Framework {
	return NewFramework(baseName, DefaultConfig)
}

// NewFramework makes a new framework and sets up a BeforeEach/AfterEach for
// you (you can write additional before/after each functions).
// It uses the config provided to it for the duration of the tests.
func NewFramework(baseName string, cfg *config.Config) *Framework {
	f := &Framework{
		Config:   cfg,
		BaseName: baseName,
	}

	f.helper = helper.NewHelper(cfg)
	BeforeEach(f.BeforeEach)
	AfterEach(f.AfterEach)

	return f
}

// BeforeEach gets a client and makes a namespace.
func (f *Framework) BeforeEach() {
	f.cleanupHandle = AddCleanupAction(f.AfterEach)

	By("Creating a kubernetes client")
	kubeConfig, err := util.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext)
	Expect(err).NotTo(HaveOccurred())
	f.KubeClientConfig = kubeConfig

	f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating an API extensions client")
	f.APIExtensionsClientSet, err = apiextcs.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating a cert manager client")
	f.CertManagerClientSet, err = clientset.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating a controller-runtime client")
	f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: Scheme})
	Expect(err).NotTo(HaveOccurred())

	By("Building a namespace api object")
	f.Namespace, err = f.CreateKubeNamespace(f.BaseName)
	Expect(err).NotTo(HaveOccurred())

	By("Using the namespace " + f.Namespace.Name)

	By("Building a ResourceQuota api object")
	_, err = f.CreateKubeResourceQuota()
	Expect(err).NotTo(HaveOccurred())

	f.helper.CMClient = f.CertManagerClientSet
	f.helper.KubeClient = f.KubeClientSet
}

// AfterEach deletes the namespace, after reading its events.
func (f *Framework) AfterEach() {
	RemoveCleanupAction(f.cleanupHandle)

	f.printAddonLogs()

	if !f.Config.Cleanup {
		return
	}

	for i := len(f.requiredAddons) - 1; i >= 0; i-- {
		a := f.requiredAddons[i]
		By("De-provisioning test-scoped addon")
		err := a.Deprovision()
		Expect(err).NotTo(HaveOccurred())
	}

	By("Deleting test namespace")
	err := f.DeleteKubeNamespace(f.Namespace.Name)
	Expect(err).NotTo(HaveOccurred())

	By("Waiting for test namespace to no longer exist")
	err = f.WaitForKubeNamespaceNotExist(f.Namespace.Name)
	Expect(err).NotTo(HaveOccurred())
}

func (f *Framework) printAddonLogs() {
	if CurrentGinkgoTestDescription().Failed {
		for _, a := range f.requiredAddons {
			if a, ok := a.(loggableAddon); ok {
				l, err := a.Logs()
				Expect(err).NotTo(HaveOccurred())

				// TODO: replace with writing logs to a file
				log.Logf("Got pod logs for addon: \n%s", l)
			}
		}
	}
}

// RequireGlobalAddon calls Setup on the given addon.
// This should be called in specs or describe blocks that require access to any
// of the global/shared addons in order to ensure their details are available.
// This method should only ever be called with addons defined in the 'addons' that
// are present in the 'globals' variable, as they will not be Provisioned properly
// otherwise.
func (f *Framework) RequireGlobalAddon(a addon.Addon) {
	BeforeEach(func() {
		By("Setting up access for global shared addon")
		err := a.Setup(f.Config)
		Expect(err).NotTo(HaveOccurred())
	})
}

type loggableAddon interface {
	Logs() (string, error)
}

// RequireAddon calls the Setup and Provision method on the given addon, failing
// the spec if provisioning fails.
func (f *Framework) RequireAddon(a addon.Addon) {
	f.requiredAddons = append(f.requiredAddons, a)

	BeforeEach(func() {
		By("Provisioning test-scoped addon")
		err := a.Setup(f.Config)
		if errors.IsSkip(err) {
			Skipf("Skipping test as addon could not be setup: %v", err)
		}
		Expect(err).NotTo(HaveOccurred())

		err = a.Provision()
		Expect(err).NotTo(HaveOccurred())
	})
}

func (f *Framework) Helper() *helper.Helper {
	return f.helper
}

func (f *Framework) CertificateDurationValid(c *v1alpha1.Certificate, duration time.Duration) {
	By("Verifying TLS certificate exists")
	secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(c.Spec.SecretName, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	certBytes, ok := secret.Data[api.TLSCertKey]
	if !ok {
		Failf("No certificate data found for Certificate %q", c.Name)
	}
	cert, err := pki.DecodeX509CertificateBytes(certBytes)
	Expect(err).NotTo(HaveOccurred())
	By("Verifying that the duration is valid")
	if cert.NotAfter.Sub(cert.NotBefore) != duration {
		Failf("Expected duration of %s, got %s [NotBefore: %s, NotAfter: %s]", duration, cert.NotAfter.Sub(cert.NotBefore), cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
	}
}

// CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing.
func CertManagerDescribe(text string, body func()) bool {
	return Describe("[cert-manager] "+text, body)
}

func ConformanceDescribe(text string, body func()) bool {
	return Describe("[Conformance] "+text, body)
}
<commit_msg>Fix loggableAddon interface in test framework<commit_after>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 framework

import (
	"time"

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

	"k8s.io/api/core/v1"
	api "k8s.io/api/core/v1"
	apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes"
	kscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	crclient "sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	certmgrscheme "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/scheme"
	"github.com/jetstack/cert-manager/pkg/util/pki"
	"github.com/jetstack/cert-manager/test/e2e/framework/addon"
	"github.com/jetstack/cert-manager/test/e2e/framework/config"
	"github.com/jetstack/cert-manager/test/e2e/framework/helper"
	"github.com/jetstack/cert-manager/test/e2e/framework/log"
	"github.com/jetstack/cert-manager/test/e2e/framework/util"
	"github.com/jetstack/cert-manager/test/e2e/framework/util/errors"
)

// TODO: this really should be done somewhere in cert-manager proper
var Scheme = runtime.NewScheme()

func init() {
	kscheme.AddToScheme(Scheme)
	certmgrscheme.AddToScheme(Scheme)
}

// DefaultConfig contains the default shared config the is likely parsed from
// command line arguments.
var DefaultConfig = &config.Config{}

// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
type Framework struct {
	BaseName string

	Config *config.Config

	// KubeClientConfig which was used to create the connection.
	KubeClientConfig *rest.Config

	// Kubernetes API clientsets
	KubeClientSet          kubernetes.Interface
	CertManagerClientSet   clientset.Interface
	APIExtensionsClientSet apiextcs.Interface

	// controller-runtime client for newer controllers
	CRClient crclient.Client

	// Namespace in which all test resources should reside
	Namespace *v1.Namespace

	// To make sure that this framework cleans up after itself, no matter what,
	// we install a Cleanup action before each test and clear it after.  If we
	// should abort, the AfterSuite hook should run all Cleanup actions.
	cleanupHandle CleanupActionHandle

	requiredAddons []addon.Addon
	helper         *helper.Helper
}

// NewDefaultFramework makes a new framework for you, similar to NewFramework.
// It uses the suite-wide 'DefaultConfig' which should be populated by the
// testing harness in test/e2e/e2e_test.go
func NewDefaultFramework(baseName string) *Framework {
	return NewFramework(baseName, DefaultConfig)
}

// NewFramework makes a new framework and sets up a BeforeEach/AfterEach for
// you (you can write additional before/after each functions).
// It uses the config provided to it for the duration of the tests.
func NewFramework(baseName string, cfg *config.Config) *Framework {
	f := &Framework{
		Config:   cfg,
		BaseName: baseName,
	}

	f.helper = helper.NewHelper(cfg)
	BeforeEach(f.BeforeEach)
	AfterEach(f.AfterEach)

	return f
}

// BeforeEach gets a client and makes a namespace.
func (f *Framework) BeforeEach() {
	f.cleanupHandle = AddCleanupAction(f.AfterEach)

	By("Creating a kubernetes client")
	kubeConfig, err := util.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext)
	Expect(err).NotTo(HaveOccurred())
	f.KubeClientConfig = kubeConfig

	f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating an API extensions client")
	f.APIExtensionsClientSet, err = apiextcs.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating a cert manager client")
	f.CertManagerClientSet, err = clientset.NewForConfig(kubeConfig)
	Expect(err).NotTo(HaveOccurred())

	By("Creating a controller-runtime client")
	f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: Scheme})
	Expect(err).NotTo(HaveOccurred())

	By("Building a namespace api object")
	f.Namespace, err = f.CreateKubeNamespace(f.BaseName)
	Expect(err).NotTo(HaveOccurred())

	By("Using the namespace " + f.Namespace.Name)

	By("Building a ResourceQuota api object")
	_, err = f.CreateKubeResourceQuota()
	Expect(err).NotTo(HaveOccurred())

	f.helper.CMClient = f.CertManagerClientSet
	f.helper.KubeClient = f.KubeClientSet
}

// AfterEach deletes the namespace, after reading its events.
func (f *Framework) AfterEach() {
	RemoveCleanupAction(f.cleanupHandle)

	f.printAddonLogs()

	if !f.Config.Cleanup {
		return
	}

	for i := len(f.requiredAddons) - 1; i >= 0; i-- {
		a := f.requiredAddons[i]
		By("De-provisioning test-scoped addon")
		err := a.Deprovision()
		Expect(err).NotTo(HaveOccurred())
	}

	By("Deleting test namespace")
	err := f.DeleteKubeNamespace(f.Namespace.Name)
	Expect(err).NotTo(HaveOccurred())

	By("Waiting for test namespace to no longer exist")
	err = f.WaitForKubeNamespaceNotExist(f.Namespace.Name)
	Expect(err).NotTo(HaveOccurred())
}

func (f *Framework) printAddonLogs() {
	if CurrentGinkgoTestDescription().Failed {
		for _, a := range f.requiredAddons {
			if a, ok := a.(loggableAddon); ok {
				l, err := a.Logs()
				Expect(err).NotTo(HaveOccurred())

				for ident, l := range l {
					// TODO: replace with writing logs to a file
					log.Logf("Got pod logs %q for addon: \n%s", ident, l)
				}
			}
		}
	}
}

// RequireGlobalAddon calls Setup on the given addon.
// This should be called in specs or describe blocks that require access to any
// of the global/shared addons in order to ensure their details are available.
// This method should only ever be called with addons defined in the 'addons' that
// are present in the 'globals' variable, as they will not be Provisioned properly
// otherwise.
func (f *Framework) RequireGlobalAddon(a addon.Addon) {
	BeforeEach(func() {
		By("Setting up access for global shared addon")
		err := a.Setup(f.Config)
		Expect(err).NotTo(HaveOccurred())
	})
}

type loggableAddon interface {
	Logs() (map[string]string, error)
}

// RequireAddon calls the Setup and Provision method on the given addon, failing
// the spec if provisioning fails.
func (f *Framework) RequireAddon(a addon.Addon) {
	f.requiredAddons = append(f.requiredAddons, a)

	BeforeEach(func() {
		By("Provisioning test-scoped addon")
		err := a.Setup(f.Config)
		if errors.IsSkip(err) {
			Skipf("Skipping test as addon could not be setup: %v", err)
		}
		Expect(err).NotTo(HaveOccurred())

		err = a.Provision()
		Expect(err).NotTo(HaveOccurred())
	})
}

func (f *Framework) Helper() *helper.Helper {
	return f.helper
}

func (f *Framework) CertificateDurationValid(c *v1alpha1.Certificate, duration time.Duration) {
	By("Verifying TLS certificate exists")
	secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(c.Spec.SecretName, metav1.GetOptions{})
	Expect(err).NotTo(HaveOccurred())
	certBytes, ok := secret.Data[api.TLSCertKey]
	if !ok {
		Failf("No certificate data found for Certificate %q", c.Name)
	}
	cert, err := pki.DecodeX509CertificateBytes(certBytes)
	Expect(err).NotTo(HaveOccurred())
	By("Verifying that the duration is valid")
	if cert.NotAfter.Sub(cert.NotBefore) != duration {
		Failf("Expected duration of %s, got %s [NotBefore: %s, NotAfter: %s]", duration, cert.NotAfter.Sub(cert.NotBefore), cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
	}
}

// CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing.
func CertManagerDescribe(text string, body func()) bool {
	return Describe("[cert-manager] "+text, body)
}

func ConformanceDescribe(text string, body func()) bool {
	return Describe("[Conformance] "+text, body)
}
<|endoftext|>


<commit_before>// +build linux darwin freebsd netbsd openbsd
// +build !nofuse

package commands

import (
	"fmt"
	"io"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	e "github.com/ipfs/go-ipfs/core/commands/e"
	nodeMount "github.com/ipfs/go-ipfs/fuse/node"
	config "github.com/ipfs/go-ipfs/repo/config"

	"gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
)

var MountCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only).",
		ShortDescription: `
Mount IPFS at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All IPFS objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

You may have to create /ipfs and /ipns before using 'ipfs mount':

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs daemon &
> ipfs mount
`,
		LongDescription: `
Mount IPFS at a read-only mountpoint on the OS. The default, /ipfs and /ipns,
are set in the configutation file, but can be overriden by the options.
All IPFS objects will be accessible under this directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

You may have to create /ipfs and /ipns before using 'ipfs mount':

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs daemon &
> ipfs mount

Example:

# setup
> mkdir foo
> echo "baz" > foo/bar
> ipfs add -r foo
added QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR foo/bar
added QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC foo
> ipfs ls QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR 12 bar
> ipfs cat QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz

# mount
> ipfs daemon &
> ipfs mount
IPFS mounted at: /ipfs
IPNS mounted at: /ipns
> cd /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
> ls
bar
> cat bar
baz
> cat /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC/bar
baz
> cat /ipfs/QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz
`,
	},
	Options: []cmdkit.Option{
		cmdkit.StringOption("ipfs-path", "f", "The path where IPFS should be mounted."),
		cmdkit.StringOption("ipns-path", "n", "The path where IPNS should be mounted."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		cfg, err := req.InvocContext().GetConfig()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		node, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		// error if we aren't running node in online mode
		if node.LocalMode() {
			res.SetError(errNotOnline, cmdkit.ErrClient)
			return
		}

		fsdir, found, err := req.Option("f").String()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		if !found {
			fsdir = cfg.Mounts.IPFS // use default value
		}

		// get default mount points
		nsdir, found, err := req.Option("n").String()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		if !found {
			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
		}

		err = nodeMount.Mount(node, fsdir, nsdir)
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		var output config.Mounts
		output.IPFS = fsdir
		output.IPNS = nsdir
		res.SetOutput(&output)
	},
	Type: config.Mounts{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			mnts, ok := v.(*config.Mounts)
			if !ok {
				return nil, e.TypeErr(mnts, v)
			}

			s := fmt.Sprintf("IPFS mounted at: %s\n", mnts.IPFS)
			s += fmt.Sprintf("IPNS mounted at: %s\n", mnts.IPNS)
			return strings.NewReader(s), nil
		},
	},
}
<commit_msg>fix typo<commit_after>// +build linux darwin freebsd netbsd openbsd
// +build !nofuse

package commands

import (
	"fmt"
	"io"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	e "github.com/ipfs/go-ipfs/core/commands/e"
	nodeMount "github.com/ipfs/go-ipfs/fuse/node"
	config "github.com/ipfs/go-ipfs/repo/config"

	"gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
)

var MountCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only).",
		ShortDescription: `
Mount IPFS at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All IPFS objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

You may have to create /ipfs and /ipns before using 'ipfs mount':

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs daemon &
> ipfs mount
`,
		LongDescription: `
Mount IPFS at a read-only mountpoint on the OS. The default, /ipfs and /ipns,
are set in the configuration file, but can be overriden by the options.
All IPFS objects will be accessible under this directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

You may have to create /ipfs and /ipns before using 'ipfs mount':

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs daemon &
> ipfs mount

Example:

# setup
> mkdir foo
> echo "baz" > foo/bar
> ipfs add -r foo
added QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR foo/bar
added QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC foo
> ipfs ls QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR 12 bar
> ipfs cat QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz

# mount
> ipfs daemon &
> ipfs mount
IPFS mounted at: /ipfs
IPNS mounted at: /ipns
> cd /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
> ls
bar
> cat bar
baz
> cat /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC/bar
baz
> cat /ipfs/QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz
`,
	},
	Options: []cmdkit.Option{
		cmdkit.StringOption("ipfs-path", "f", "The path where IPFS should be mounted."),
		cmdkit.StringOption("ipns-path", "n", "The path where IPNS should be mounted."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		cfg, err := req.InvocContext().GetConfig()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		node, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		// error if we aren't running node in online mode
		if node.LocalMode() {
			res.SetError(errNotOnline, cmdkit.ErrClient)
			return
		}

		fsdir, found, err := req.Option("f").String()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		if !found {
			fsdir = cfg.Mounts.IPFS // use default value
		}

		// get default mount points
		nsdir, found, err := req.Option("n").String()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		if !found {
			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
		}

		err = nodeMount.Mount(node, fsdir, nsdir)
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		var output config.Mounts
		output.IPFS = fsdir
		output.IPNS = nsdir
		res.SetOutput(&output)
	},
	Type: config.Mounts{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			mnts, ok := v.(*config.Mounts)
			if !ok {
				return nil, e.TypeErr(mnts, v)
			}

			s := fmt.Sprintf("IPFS mounted at: %s\n", mnts.IPFS)
			s += fmt.Sprintf("IPNS mounted at: %s\n", mnts.IPNS)
			return strings.NewReader(s), nil
		},
	},
}
<|endoftext|>


<commit_before>package channels

import "testing"

func testChannel(t *testing.T, name string, ch Channel) {
	go func() {
		for i := 0; i < 1000; i++ {
			ch.In() <- i
		}
		ch.Close()
	}()
	for i := 0; i < 1000; i++ {
		val := <-ch.Out()
		if i != val.(int) {
			t.Fatal(name, "expected", i, "but got", val.(int))
		}
	}
}

func testChannelPair(t *testing.T, name string, in InChannel, out OutChannel) {
	go func() {
		for i := 0; i < 1000; i++ {
			in.In() <- i
		}
		in.Close()
	}()
	for i := 0; i < 1000; i++ {
		val := <-out.Out()
		if i != val.(int) {
			t.Fatal("pair", name, "expected", i, "but got", val.(int))
		}
	}
}
<commit_msg>Add test for Pipe()<commit_after>package channels

import "testing"

func testChannel(t *testing.T, name string, ch Channel) {
	go func() {
		for i := 0; i < 1000; i++ {
			ch.In() <- i
		}
		ch.Close()
	}()
	for i := 0; i < 1000; i++ {
		val := <-ch.Out()
		if i != val.(int) {
			t.Fatal(name, "expected", i, "but got", val.(int))
		}
	}
}

func testChannelPair(t *testing.T, name string, in InChannel, out OutChannel) {
	go func() {
		for i := 0; i < 1000; i++ {
			in.In() <- i
		}
		in.Close()
	}()
	for i := 0; i < 1000; i++ {
		val := <-out.Out()
		if i != val.(int) {
			t.Fatal("pair", name, "expected", i, "but got", val.(int))
		}
	}
}

func TestPipe(t *testing.T) {
	a := NewNativeChannel(NoBuffer)
	b := NewNativeChannel(NoBuffer)

	Pipe(a, b)

	testChannelPair(t, "pipe", a, b)
}
<|endoftext|>


<commit_before>package main

import (
	"strconv"
	"time"

	"github.com/MJKWoolnough/byteio"
	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/tabs"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/MJKWoolnough/minewebgen/internal/data"
	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
)

type Server struct {
	data.Server
	row    dom.Node
	name   *dom.HTMLTableCellElement
	status *dom.HTMLTableCellElement
	button *dom.HTMLButtonElement
}

func ServersTab() func(dom.Element) {
	forceUpdate := make(chan struct{})
	ns := xdom.Button()
	ns.AddEventListener("click", false, func(dom.Event) {
		d := xdom.Div()
		o := overlay.New(d)
		d.AppendChild(transferFile("Server", "Upload/Download", 0, o))
		o.OnClose(func() {
			go func() {
				forceUpdate <- struct{}{}
			}()
		})
		xjs.Body().AppendChild(o)
	})
	noneTd := xdom.Td()
	noneTd.ColSpan = 3
	none := xjs.AppendChildren(xdom.Tr(), xjs.SetInnerText(noneTd, "No Servers Found"))
	serverList := xjs.AppendChildren(xdom.Table(),
		xjs.AppendChildren(xdom.Thead(), xjs.AppendChildren(xdom.Tr(),
			xjs.SetInnerText(xdom.Th(), "Server Name"),
			xjs.SetInnerText(xdom.Th(), "Status"),
			xjs.SetInnerText(xdom.Th(), "Controls"),
		)),
		none,
	)
	nodes := xjs.AppendChildren(xdom.Div(),
		xjs.SetInnerText(xdom.H2(), "Servers"),
		xjs.SetInnerText(ns, "New Server"),
		serverList,
	)
	servers := make(map[int]*Server)

	return func(c dom.Element) {
		c.AppendChild(nodes)
		updateStop := make(chan struct{})
		registerUpdateStopper(c, updateStop)
		for {
			servs, err := RPC.ServerList()
			if err != nil {
				xjs.Alert("Error getting server list: %s", err)
				return
			}

			if none.ParentNode() != nil {
				serverList.RemoveChild(none)
			}

			for _, s := range servers {
				s.ID = -1
			}

			for _, s := range servs {
				os, ok := servers[s.ID]
				if ok {
					os.Server = s
				} else {
					name := xdom.Td()
					status := xdom.Td()
					startStop := xdom.Button()
					os = &Server{
						Server: s,
						row: xjs.AppendChildren(xdom.Tr(),
							name,
							status,
							xjs.AppendChildren(xdom.Td(), startStop),
						),
						name:   name,
						status: status,
						button: startStop,
					}
					servers[s.ID] = os
					serverList.AppendChild(os.row)
					name.AddEventListener("click", false, func() func(dom.Event) {
						s := os
						return func(dom.Event) {
							go func() {
								d, err := RPC.ServerEULA(s.ID)
								if err != nil {
									d = ""
								}
								t := []tabs.Tab{
									{"General", serverGeneral(s.Server)},
									{"Properties", serverProperties(s.Server)},
									{"Console", serverConsole(s.Server)},
								}
								if d != "" {
									t = append(t, tabs.Tab{"EULA", serverEULA(s.Server, d)})
								}
								div := xdom.Div()
								o := overlay.New(div)
								t = append(t, tabs.Tab{"Misc.", misc("server", s.Server.ID, o, RPC.RemoveServer)})
								div.AppendChild(tabs.New(t))
								o.OnClose(func() {
									go func() {
										forceUpdate <- struct{}{}
									}()
								})
								xjs.Body().AppendChild(o)
							}()

						}
					}())
					startStop.AddEventListener("click", false, func() func(dom.Event) {
						b := startStop
						s := os
						return func(dom.Event) {
							go func() {
								b.Disabled = true
								switch s.State {
								case data.StateStopped:
									err := RPC.StartServer(s.ID)
									if err != nil {
										xjs.Alert("Error starting server: %s", err)
										return
									}
								case data.StateRunning:
									err := RPC.StopServer(s.ID)
									if err != nil {
										xjs.Alert("Error stopping server: %s", err)
										return
									}
								default:
									return
								}
								go func() {
									forceUpdate <- struct{}{}
								}()
							}()
						}
					}())
				}
				if os.Map >= 0 {
					xjs.SetInnerText(os.status, os.State.String())
					switch os.State {
					case data.StateStopped:
						xjs.SetInnerText(os.button, "Start")
						os.button.Disabled = false
					case data.StateRunning:
						xjs.SetInnerText(os.button, "Stop")
						os.button.Disabled = false
					default:
						xjs.SetInnerText(os.button, "N/A")
						os.button.Disabled = true
					}
				} else {
					xjs.SetInnerText(os.status, "No Map")
					os.button.Disabled = true
					xjs.SetInnerText(os.button, "N/A")
				}
				xjs.SetInnerText(os.name, os.Name)
			}

			for id, s := range servers {
				if s.ID == -1 {
					delete(servers, id)
					serverList.RemoveChild(s.row)
				}
			}

			if len(servers) == 0 {
				serverList.AppendChild(none)
			}

			// Sleep until update
			if !updateSleep(forceUpdate, updateStop) {
				return
			}
		}
	}
}

func serverGeneral(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go func() {
			maps, err := RPC.MapList()
			if err != nil {
				c.AppendChild(xjs.SetInnerText(xdom.Div(), "Error getting map list: "+err.Error()))
				return
			}
			name := xform.InputText("name", s.Name)
			name.Required = true
			opts := make([]xform.Option, 1, len(maps)+1)
			opts[0] = xform.Option{
				Label:    "-- None -- ",
				Value:    "-1",
				Selected: s.Map == -1,
			}
			for i, m := range maps {
				n := m.Name
				if m.Server != -1 {
					if m.ID == s.Map {
						n = "* - " + n
					} else {
						n = "! - " + n
					}
				} else {
					n = "    " + n
				}
				opts = append(opts, xform.Option{
					Label:    n,
					Value:    strconv.Itoa(i),
					Selected: m.ID == s.Map,
				})
			}
			args := xform.InputSizeableList(s.Args...)
			sel := xform.SelectBox("map", opts...)
			submit := xform.InputSubmit("Set")
			submit.AddEventListener("click", false, func(e dom.Event) {
				if s.State != data.StateStopped {
					xjs.Alert("Cannot modify these settings while the server is running")
					return
				}
				if name.Value == "" {
					return
				}
				sID, err := strconv.Atoi(sel.Value)
				if err != nil || sID < -1 || sID >= len(maps) {
					return
				}
				submit.Disabled = true
				e.PreventDefault()
				if sID >= 0 {
					m := maps[sID]
					sID = m.ID
				}
				go func() {
					err = RPC.SetServerMap(s.ID, sID)
					if err != nil {
						xjs.Alert("Error setting server map: %s", err)
						return
					}
					s.Name = name.Value
					s.Args = args.Values()
					err = RPC.SetServer(s)
					if err != nil {
						xjs.Alert("Error setting server data: %s", err)
						return
					}
					span := xdom.Span()
					span.Style().Set("color", "#f00")
					c.AppendChild(xjs.SetInnerText(span, "Saved!"))
					time.Sleep(5 * time.Second)
					c.RemoveChild(span)
					submit.Disabled = false
				}()
			})
			xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(),
				xform.Label("Server Name", "name"),
				name,
				xdom.Br(),
				xform.Label("Arguments", "args"),
				args,
				xdom.Br(),
				xform.Label("Map Name", "map"),
				sel,
				xdom.Br(),
				submit,
			))
		}()
	}
}

type PropertyList [][2]string

func (p PropertyList) Len() int {
	return len(p)
}

func (p PropertyList) Less(i, j int) bool {
	return p[i][0] < p[j][0]
}

func (p PropertyList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func serverProperties(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go editProperties(c, "Server", s.ID, RPC.ServerProperties, RPC.SetServerProperties)
	}
}

func serverConsole(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		log := xform.TextArea("log", "")
		log.ReadOnly = true
		command := xform.InputText("command", "")
		command.Required = true
		send := xform.InputSubmit("Send")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Legend(), "Console"),
			xform.Label("Log", ""), log, xdom.Br(),
			xform.Label("Command", "command"), command, send,
		)))
		if s.State == data.StateStopped {
			send.Disabled = true
			command.Disabled = true
		} else {
			send.AddEventListener("click", false, func(e dom.Event) {
				if command.Value == "" {
					return
				}
				e.PreventDefault()
				send.Disabled = true
				cmd := command.Value
				log.Value += "\n>" + cmd + "\n"
				log.Set("scrollTop", log.Get("scrollHeight"))
				command.Value = ""
				go func() {
					err := RPC.WriteCommand(s.ID, cmd)
					if err != nil {
						xjs.Alert("Error sending command: %s", err)
						return
					}
					send.Disabled = false
				}()
			})
		}
		go func() {
			conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/console")
			if err != nil {
				xjs.Alert("Failed to connect to console: %s", err)
				return
			}
			defer conn.Close()
			w := byteio.StickyWriter{Writer: byteio.LittleEndianWriter{Writer: conn}}
			r := byteio.StickyReader{Reader: byteio.LittleEndianReader{Reader: conn}}
			updateStop := make(chan struct{})
			registerUpdateStopper(c, updateStop)
			done := false
			go func() {
				<-updateStop
				done = true
				conn.Close()
			}()
			w.WriteInt32(int32(s.ID))
			for {
				state := r.ReadUint8()
				switch state {
				case 0:
					if !done {
						err := ReadError(&r)
						if r.Err != nil {
							err = r.Err
						}
						log.Value += "\n\nError reading from console: " + err.Error()
						log.Set("scrollTop", log.Get("scrollHeight"))
					}
					return
				case 1:
					log.Value += ReadString(&r)
					log.Set("scrollTop", log.Get("scrollHeight"))
				}
			}
		}()
	}
}

func serverEULA(s data.Server, d string) func(dom.Element) {
	return func(c dom.Element) {
		t := xform.TextArea("eula", d)
		submit := xform.InputSubmit("Save")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Label(), "End User License Agreement"),
			xform.Label("EULA", "eula"), t, xdom.Br(),
			submit,
		)))
		submit.AddEventListener("click", false, func(e dom.Event) {
			e.PreventDefault()
			submit.Disabled = true
			go func() {
				err := RPC.SetServerEULA(s.ID, t.Value)
				if err != nil {
					xjs.Alert("Error setting server EULA: %s", err)
					return
				}
				span := xdom.Span()
				span.Style().Set("color", "#f00")
				c.AppendChild(xjs.SetInnerText(span, "Saved!"))
				time.Sleep(5 * time.Second)
				c.RemoveChild(span)
				submit.Disabled = false
			}()
		})
	}
}
<commit_msg>Server name now shows as clickable<commit_after>package main

import (
	"strconv"
	"time"

	"github.com/MJKWoolnough/byteio"
	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/style"
	"github.com/MJKWoolnough/gopherjs/tabs"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/MJKWoolnough/minewebgen/internal/data"
	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
)

func init() {
	style.Add(`.serverName {
	background-color : #ccc;
	cursor: pointer;
	cursor: hand;
}
`)
}

type Server struct {
	data.Server
	row    dom.Node
	name   *dom.HTMLTableCellElement
	status *dom.HTMLTableCellElement
	button *dom.HTMLButtonElement
}

func ServersTab() func(dom.Element) {
	forceUpdate := make(chan struct{})
	ns := xdom.Button()
	ns.AddEventListener("click", false, func(dom.Event) {
		d := xdom.Div()
		o := overlay.New(d)
		d.AppendChild(transferFile("Server", "Upload/Download", 0, o))
		o.OnClose(func() {
			go func() {
				forceUpdate <- struct{}{}
			}()
		})
		xjs.Body().AppendChild(o)
	})
	noneTd := xdom.Td()
	noneTd.ColSpan = 3
	none := xjs.AppendChildren(xdom.Tr(), xjs.SetInnerText(noneTd, "No Servers Found"))
	serverList := xjs.AppendChildren(xdom.Table(),
		xjs.AppendChildren(xdom.Thead(), xjs.AppendChildren(xdom.Tr(),
			xjs.SetInnerText(xdom.Th(), "Server Name"),
			xjs.SetInnerText(xdom.Th(), "Status"),
			xjs.SetInnerText(xdom.Th(), "Controls"),
		)),
		none,
	)
	nodes := xjs.AppendChildren(xdom.Div(),
		xjs.SetInnerText(xdom.H2(), "Servers"),
		xjs.SetInnerText(ns, "New Server"),
		serverList,
	)
	servers := make(map[int]*Server)

	return func(c dom.Element) {
		c.AppendChild(nodes)
		updateStop := make(chan struct{})
		registerUpdateStopper(c, updateStop)
		for {
			servs, err := RPC.ServerList()
			if err != nil {
				xjs.Alert("Error getting server list: %s", err)
				return
			}

			if none.ParentNode() != nil {
				serverList.RemoveChild(none)
			}

			for _, s := range servers {
				s.ID = -1
			}

			for _, s := range servs {
				os, ok := servers[s.ID]
				if ok {
					os.Server = s
				} else {
					name := xdom.Td()
					status := xdom.Td()
					startStop := xdom.Button()
					os = &Server{
						Server: s,
						row: xjs.AppendChildren(xdom.Tr(),
							name,
							status,
							xjs.AppendChildren(xdom.Td(), startStop),
						),
						name:   name,
						status: status,
						button: startStop,
					}
					servers[s.ID] = os
					serverList.AppendChild(os.row)
					name.Class().SetString("serverName")
					name.AddEventListener("click", false, func() func(dom.Event) {
						s := os
						return func(dom.Event) {
							go func() {
								d, err := RPC.ServerEULA(s.ID)
								if err != nil {
									d = ""
								}
								t := []tabs.Tab{
									{"General", serverGeneral(s.Server)},
									{"Properties", serverProperties(s.Server)},
									{"Console", serverConsole(s.Server)},
								}
								if d != "" {
									t = append(t, tabs.Tab{"EULA", serverEULA(s.Server, d)})
								}
								div := xdom.Div()
								o := overlay.New(div)
								t = append(t, tabs.Tab{"Misc.", misc("server", s.Server.ID, o, RPC.RemoveServer)})
								div.AppendChild(tabs.New(t))
								o.OnClose(func() {
									go func() {
										forceUpdate <- struct{}{}
									}()
								})
								xjs.Body().AppendChild(o)
							}()

						}
					}())
					startStop.AddEventListener("click", false, func() func(dom.Event) {
						b := startStop
						s := os
						return func(dom.Event) {
							go func() {
								b.Disabled = true
								switch s.State {
								case data.StateStopped:
									err := RPC.StartServer(s.ID)
									if err != nil {
										xjs.Alert("Error starting server: %s", err)
										return
									}
								case data.StateRunning:
									err := RPC.StopServer(s.ID)
									if err != nil {
										xjs.Alert("Error stopping server: %s", err)
										return
									}
								default:
									return
								}
								go func() {
									forceUpdate <- struct{}{}
								}()
							}()
						}
					}())
				}
				if os.Map >= 0 {
					xjs.SetInnerText(os.status, os.State.String())
					switch os.State {
					case data.StateStopped:
						xjs.SetInnerText(os.button, "Start")
						os.button.Disabled = false
					case data.StateRunning:
						xjs.SetInnerText(os.button, "Stop")
						os.button.Disabled = false
					default:
						xjs.SetInnerText(os.button, "N/A")
						os.button.Disabled = true
					}
				} else {
					xjs.SetInnerText(os.status, "No Map")
					os.button.Disabled = true
					xjs.SetInnerText(os.button, "N/A")
				}
				xjs.SetInnerText(os.name, os.Name)
			}

			for id, s := range servers {
				if s.ID == -1 {
					delete(servers, id)
					serverList.RemoveChild(s.row)
				}
			}

			if len(servers) == 0 {
				serverList.AppendChild(none)
			}

			// Sleep until update
			if !updateSleep(forceUpdate, updateStop) {
				return
			}
		}
	}
}

func serverGeneral(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go func() {
			maps, err := RPC.MapList()
			if err != nil {
				c.AppendChild(xjs.SetInnerText(xdom.Div(), "Error getting map list: "+err.Error()))
				return
			}
			name := xform.InputText("name", s.Name)
			name.Required = true
			opts := make([]xform.Option, 1, len(maps)+1)
			opts[0] = xform.Option{
				Label:    "-- None -- ",
				Value:    "-1",
				Selected: s.Map == -1,
			}
			for i, m := range maps {
				n := m.Name
				if m.Server != -1 {
					if m.ID == s.Map {
						n = "* - " + n
					} else {
						n = "! - " + n
					}
				} else {
					n = "    " + n
				}
				opts = append(opts, xform.Option{
					Label:    n,
					Value:    strconv.Itoa(i),
					Selected: m.ID == s.Map,
				})
			}
			args := xform.InputSizeableList(s.Args...)
			sel := xform.SelectBox("map", opts...)
			submit := xform.InputSubmit("Set")
			submit.AddEventListener("click", false, func(e dom.Event) {
				if s.State != data.StateStopped {
					xjs.Alert("Cannot modify these settings while the server is running")
					return
				}
				if name.Value == "" {
					return
				}
				sID, err := strconv.Atoi(sel.Value)
				if err != nil || sID < -1 || sID >= len(maps) {
					return
				}
				submit.Disabled = true
				e.PreventDefault()
				if sID >= 0 {
					m := maps[sID]
					sID = m.ID
				}
				go func() {
					err = RPC.SetServerMap(s.ID, sID)
					if err != nil {
						xjs.Alert("Error setting server map: %s", err)
						return
					}
					s.Name = name.Value
					s.Args = args.Values()
					err = RPC.SetServer(s)
					if err != nil {
						xjs.Alert("Error setting server data: %s", err)
						return
					}
					span := xdom.Span()
					span.Style().Set("color", "#f00")
					c.AppendChild(xjs.SetInnerText(span, "Saved!"))
					time.Sleep(5 * time.Second)
					c.RemoveChild(span)
					submit.Disabled = false
				}()
			})
			xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(),
				xform.Label("Server Name", "name"),
				name,
				xdom.Br(),
				xform.Label("Arguments", "args"),
				args,
				xdom.Br(),
				xform.Label("Map Name", "map"),
				sel,
				xdom.Br(),
				submit,
			))
		}()
	}
}

type PropertyList [][2]string

func (p PropertyList) Len() int {
	return len(p)
}

func (p PropertyList) Less(i, j int) bool {
	return p[i][0] < p[j][0]
}

func (p PropertyList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func serverProperties(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go editProperties(c, "Server", s.ID, RPC.ServerProperties, RPC.SetServerProperties)
	}
}

func serverConsole(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		log := xform.TextArea("log", "")
		log.ReadOnly = true
		command := xform.InputText("command", "")
		command.Required = true
		send := xform.InputSubmit("Send")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Legend(), "Console"),
			xform.Label("Log", ""), log, xdom.Br(),
			xform.Label("Command", "command"), command, send,
		)))
		if s.State == data.StateStopped {
			send.Disabled = true
			command.Disabled = true
		} else {
			send.AddEventListener("click", false, func(e dom.Event) {
				if command.Value == "" {
					return
				}
				e.PreventDefault()
				send.Disabled = true
				cmd := command.Value
				log.Value += "\n>" + cmd + "\n"
				log.Set("scrollTop", log.Get("scrollHeight"))
				command.Value = ""
				go func() {
					err := RPC.WriteCommand(s.ID, cmd)
					if err != nil {
						xjs.Alert("Error sending command: %s", err)
						return
					}
					send.Disabled = false
				}()
			})
		}
		go func() {
			conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/console")
			if err != nil {
				xjs.Alert("Failed to connect to console: %s", err)
				return
			}
			defer conn.Close()
			w := byteio.StickyWriter{Writer: byteio.LittleEndianWriter{Writer: conn}}
			r := byteio.StickyReader{Reader: byteio.LittleEndianReader{Reader: conn}}
			updateStop := make(chan struct{})
			registerUpdateStopper(c, updateStop)
			done := false
			go func() {
				<-updateStop
				done = true
				conn.Close()
			}()
			w.WriteInt32(int32(s.ID))
			for {
				state := r.ReadUint8()
				switch state {
				case 0:
					if !done {
						err := ReadError(&r)
						if r.Err != nil {
							err = r.Err
						}
						log.Value += "\n\nError reading from console: " + err.Error()
						log.Set("scrollTop", log.Get("scrollHeight"))
					}
					return
				case 1:
					log.Value += ReadString(&r)
					log.Set("scrollTop", log.Get("scrollHeight"))
				}
			}
		}()
	}
}

func serverEULA(s data.Server, d string) func(dom.Element) {
	return func(c dom.Element) {
		t := xform.TextArea("eula", d)
		submit := xform.InputSubmit("Save")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Label(), "End User License Agreement"),
			xform.Label("EULA", "eula"), t, xdom.Br(),
			submit,
		)))
		submit.AddEventListener("click", false, func(e dom.Event) {
			e.PreventDefault()
			submit.Disabled = true
			go func() {
				err := RPC.SetServerEULA(s.ID, t.Value)
				if err != nil {
					xjs.Alert("Error setting server EULA: %s", err)
					return
				}
				span := xdom.Span()
				span.Style().Set("color", "#f00")
				c.AppendChild(xjs.SetInnerText(span, "Saved!"))
				time.Sleep(5 * time.Second)
				c.RemoveChild(span)
				submit.Disabled = false
			}()
		})
	}
}
<|endoftext|>