<commit_before>package main

import (
	"fmt"

	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

type initDataNode struct {
	api.ServerPut `yaml:",inline"`
	Networks      []api.NetworksPost     `json:"networks" yaml:"networks"`
	StoragePools  []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"`
	Profiles      []api.ProfilesPost     `json:"profiles" yaml:"profiles"`
}

type initDataCluster struct {
	api.ClusterPut `yaml:",inline"`
}

// Helper to initialize node-specific entities on a LXD instance using the
// definitions from the given initDataNode object.
//
// It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
//
// In case of error, the returned function can be used to revert the changes.
func initDataNodeApply(d lxd.InstanceServer, config initDataNode) (func(), error) {
	revert := revert.New()
	defer revert.Fail()

	// Apply server configuration
	if config.Config != nil && len(config.Config) > 0 {
		// Get current config
		currentServer, etag, err := d.GetServer()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve current server configuration")
		}

		// Setup reverter
		revert.Add(func() { d.UpdateServer(currentServer.Writable(), "") })

		// Prepare the update
		newServer := api.ServerPut{}
		err = shared.DeepCopy(currentServer.Writable(), &newServer)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to copy server configuration")
		}

		for k, v := range config.Config {
			newServer.Config[k] = fmt.Sprintf("%v", v)
		}

		// Apply it
		err = d.UpdateServer(newServer, etag)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to update server configuration")
		}
	}

	// Apply network configuration
	if config.Networks != nil && len(config.Networks) > 0 {
		// Get the list of networks
		networkNames, err := d.GetNetworkNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of networks")
		}

		// Network creator
		createNetwork := func(network api.NetworksPost) error {
			// Create the network if doesn't exist
			err := d.CreateNetwork(network)
			if err != nil {
				return errors.Wrapf(err, "Failed to create network '%s'", network.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteNetwork(network.Name) })
			return nil
		}

		// Network updater
		updateNetwork := func(network api.NetworksPost) error {
			// Get the current network
			currentNetwork, etag, err := d.GetNetwork(network.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "") })

			// Prepare the update
			newNetwork := api.NetworkPut{}
			err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name)
			}

			// Description override
			if network.Description != "" {
				newNetwork.Description = network.Description
			}

			// Config overrides
			for k, v := range network.Config {
				newNetwork.Config[k] = fmt.Sprintf("%v", v)
			}

			// Apply it
			err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update network '%s'", network.Name)
			}

			return nil
		}

		for _, network := range config.Networks {
			// New network
			if !shared.StringInSlice(network.Name, networkNames) {
				err := createNetwork(network)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing network
			err := updateNetwork(network)
			if err != nil {
				return nil, err
			}
		}
	}

	// Apply storage configuration
	if config.StoragePools != nil && len(config.StoragePools) > 0 {
		// Get the list of storagePools
		storagePoolNames, err := d.GetStoragePoolNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of storage pools")
		}

		// StoragePool creator
		createStoragePool := func(storagePool api.StoragePoolsPost) error {
			// Create the storagePool if doesn't exist
			err := d.CreateStoragePool(storagePool)
			if err != nil {
				return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteStoragePool(storagePool.Name) })
			return nil
		}

		// StoragePool updater
		updateStoragePool := func(storagePool api.StoragePoolsPost) error {
			// Get the current storagePool
			currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name)
			}

			// Sanity check
			if currentStoragePool.Driver != storagePool.Driver {
				return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "") })

			// Prepare the update
			newStoragePool := api.StoragePoolPut{}
			err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name)
			}

			// Description override
			if storagePool.Description != "" {
				newStoragePool.Description = storagePool.Description
			}

			// Config overrides
			for k, v := range storagePool.Config {
				newStoragePool.Config[k] = fmt.Sprintf("%v", v)
			}

			// Apply it
			err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name)
			}

			return nil
		}

		for _, storagePool := range config.StoragePools {
			// New storagePool
			if !shared.StringInSlice(storagePool.Name, storagePoolNames) {
				err := createStoragePool(storagePool)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing storagePool
			err := updateStoragePool(storagePool)
			if err != nil {
				return nil, err
			}
		}
	}

	// Apply profile configuration
	if config.Profiles != nil && len(config.Profiles) > 0 {
		// Get the list of profiles
		profileNames, err := d.GetProfileNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of profiles")
		}

		// Profile creator
		createProfile := func(profile api.ProfilesPost) error {
			// Create the profile if doesn't exist
			err := d.CreateProfile(profile)
			if err != nil {
				return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteProfile(profile.Name) })
			return nil
		}

		// Profile updater
		updateProfile := func(profile api.ProfilesPost) error {
			// Get the current profile
			currentProfile, etag, err := d.GetProfile(profile.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "") })

			// Prepare the update
			newProfile := api.ProfilePut{}
			err = shared.DeepCopy(currentProfile.Writable(), &newProfile)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name)
			}

			// Description override
			if profile.Description != "" {
				newProfile.Description = profile.Description
			}

			// Config overrides
			for k, v := range profile.Config {
				newProfile.Config[k] = fmt.Sprintf("%v", v)
			}

			// Device overrides
			for k, v := range profile.Devices {
				// New device
				_, ok := newProfile.Devices[k]
				if !ok {
					newProfile.Devices[k] = v
					continue
				}

				// Existing device
				for configKey, configValue := range v {
					newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue)
				}
			}

			// Apply it
			err = d.UpdateProfile(currentProfile.Name, newProfile, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name)
			}

			return nil
		}

		for _, profile := range config.Profiles {
			// New profile
			if !shared.StringInSlice(profile.Name, profileNames) {
				err := createProfile(profile)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing profile
			err := updateProfile(profile)
			if err != nil {
				return nil, err
			}
		}
	}

	revertExternal := revert.Clone() // Clone before calling revert.Success() so we can return the Fail func.
	revert.Success()
	return revertExternal.Fail, nil
}

// Helper to initialize LXD clustering.
//
// Used by the 'lxd init' command.
func initDataClusterApply(d lxd.InstanceServer, config *initDataCluster) error {
	if config == nil || !config.Enabled {
		return nil
	}

	// Get the current cluster configuration
	currentCluster, etag, err := d.GetCluster()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current cluster config")
	}

	// Check if already enabled
	if !currentCluster.Enabled {
		// Configure the cluster
		op, err := d.UpdateCluster(config.ClusterPut, etag)
		if err != nil {
			return errors.Wrap(err, "Failed to configure cluster")
		}

		err = op.Wait()
		if err != nil {
			return errors.Wrap(err, "Failed to configure cluster")
		}
	}

	return nil
}
<commit_msg>lxd/init: Adds internalClusterPostNetwork to initDataNode<commit_after>package main

import (
	"fmt"

	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

type initDataNode struct {
	api.ServerPut `yaml:",inline"`
	Networks      []internalClusterPostNetwork `json:"networks" yaml:"networks"`
	StoragePools  []api.StoragePoolsPost       `json:"storage_pools" yaml:"storage_pools"`
	Profiles      []api.ProfilesPost           `json:"profiles" yaml:"profiles"`
}

type initDataCluster struct {
	api.ClusterPut `yaml:",inline"`
}

// Helper to initialize node-specific entities on a LXD instance using the
// definitions from the given initDataNode object.
//
// It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
//
// In case of error, the returned function can be used to revert the changes.
func initDataNodeApply(d lxd.InstanceServer, config initDataNode) (func(), error) {
	revert := revert.New()
	defer revert.Fail()

	// Apply server configuration
	if config.Config != nil && len(config.Config) > 0 {
		// Get current config
		currentServer, etag, err := d.GetServer()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve current server configuration")
		}

		// Setup reverter
		revert.Add(func() { d.UpdateServer(currentServer.Writable(), "") })

		// Prepare the update
		newServer := api.ServerPut{}
		err = shared.DeepCopy(currentServer.Writable(), &newServer)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to copy server configuration")
		}

		for k, v := range config.Config {
			newServer.Config[k] = fmt.Sprintf("%v", v)
		}

		// Apply it
		err = d.UpdateServer(newServer, etag)
		if err != nil {
			return nil, errors.Wrap(err, "Failed to update server configuration")
		}
	}

	// Apply network configuration
	if config.Networks != nil && len(config.Networks) > 0 {
		// Get the list of networks
		networkNames, err := d.GetNetworkNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of networks")
		}

		// Network creator
		createNetwork := func(network api.NetworksPost) error {
			// Create the network if doesn't exist
			err := d.CreateNetwork(network)
			if err != nil {
				return errors.Wrapf(err, "Failed to create network '%s'", network.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteNetwork(network.Name) })
			return nil
		}

		// Network updater
		updateNetwork := func(network api.NetworksPost) error {
			// Get the current network
			currentNetwork, etag, err := d.GetNetwork(network.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "") })

			// Prepare the update
			newNetwork := api.NetworkPut{}
			err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name)
			}

			// Description override
			if network.Description != "" {
				newNetwork.Description = network.Description
			}

			// Config overrides
			for k, v := range network.Config {
				newNetwork.Config[k] = fmt.Sprintf("%v", v)
			}

			// Apply it
			err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update network '%s'", network.Name)
			}

			return nil
		}

		for _, network := range config.Networks {
			// New network
			if !shared.StringInSlice(network.Name, networkNames) {
				err := createNetwork(network)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing network
			err := updateNetwork(network)
			if err != nil {
				return nil, err
			}
		}
	}

	// Apply storage configuration
	if config.StoragePools != nil && len(config.StoragePools) > 0 {
		// Get the list of storagePools
		storagePoolNames, err := d.GetStoragePoolNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of storage pools")
		}

		// StoragePool creator
		createStoragePool := func(storagePool api.StoragePoolsPost) error {
			// Create the storagePool if doesn't exist
			err := d.CreateStoragePool(storagePool)
			if err != nil {
				return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteStoragePool(storagePool.Name) })
			return nil
		}

		// StoragePool updater
		updateStoragePool := func(storagePool api.StoragePoolsPost) error {
			// Get the current storagePool
			currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name)
			}

			// Sanity check
			if currentStoragePool.Driver != storagePool.Driver {
				return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "") })

			// Prepare the update
			newStoragePool := api.StoragePoolPut{}
			err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name)
			}

			// Description override
			if storagePool.Description != "" {
				newStoragePool.Description = storagePool.Description
			}

			// Config overrides
			for k, v := range storagePool.Config {
				newStoragePool.Config[k] = fmt.Sprintf("%v", v)
			}

			// Apply it
			err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name)
			}

			return nil
		}

		for _, storagePool := range config.StoragePools {
			// New storagePool
			if !shared.StringInSlice(storagePool.Name, storagePoolNames) {
				err := createStoragePool(storagePool)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing storagePool
			err := updateStoragePool(storagePool)
			if err != nil {
				return nil, err
			}
		}
	}

	// Apply profile configuration
	if config.Profiles != nil && len(config.Profiles) > 0 {
		// Get the list of profiles
		profileNames, err := d.GetProfileNames()
		if err != nil {
			return nil, errors.Wrap(err, "Failed to retrieve list of profiles")
		}

		// Profile creator
		createProfile := func(profile api.ProfilesPost) error {
			// Create the profile if doesn't exist
			err := d.CreateProfile(profile)
			if err != nil {
				return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name)
			}

			// Setup reverter
			revert.Add(func() { d.DeleteProfile(profile.Name) })
			return nil
		}

		// Profile updater
		updateProfile := func(profile api.ProfilesPost) error {
			// Get the current profile
			currentProfile, etag, err := d.GetProfile(profile.Name)
			if err != nil {
				return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name)
			}

			// Setup reverter
			revert.Add(func() { d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "") })

			// Prepare the update
			newProfile := api.ProfilePut{}
			err = shared.DeepCopy(currentProfile.Writable(), &newProfile)
			if err != nil {
				return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name)
			}

			// Description override
			if profile.Description != "" {
				newProfile.Description = profile.Description
			}

			// Config overrides
			for k, v := range profile.Config {
				newProfile.Config[k] = fmt.Sprintf("%v", v)
			}

			// Device overrides
			for k, v := range profile.Devices {
				// New device
				_, ok := newProfile.Devices[k]
				if !ok {
					newProfile.Devices[k] = v
					continue
				}

				// Existing device
				for configKey, configValue := range v {
					newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue)
				}
			}

			// Apply it
			err = d.UpdateProfile(currentProfile.Name, newProfile, etag)
			if err != nil {
				return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name)
			}

			return nil
		}

		for _, profile := range config.Profiles {
			// New profile
			if !shared.StringInSlice(profile.Name, profileNames) {
				err := createProfile(profile)
				if err != nil {
					return nil, err
				}

				continue
			}

			// Existing profile
			err := updateProfile(profile)
			if err != nil {
				return nil, err
			}
		}
	}

	revertExternal := revert.Clone() // Clone before calling revert.Success() so we can return the Fail func.
	revert.Success()
	return revertExternal.Fail, nil
}

// Helper to initialize LXD clustering.
//
// Used by the 'lxd init' command.
func initDataClusterApply(d lxd.InstanceServer, config *initDataCluster) error {
	if config == nil || !config.Enabled {
		return nil
	}

	// Get the current cluster configuration
	currentCluster, etag, err := d.GetCluster()
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve current cluster config")
	}

	// Check if already enabled
	if !currentCluster.Enabled {
		// Configure the cluster
		op, err := d.UpdateCluster(config.ClusterPut, etag)
		if err != nil {
			return errors.Wrap(err, "Failed to configure cluster")
		}

		err = op.Wait()
		if err != nil {
			return errors.Wrap(err, "Failed to configure cluster")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

//go:generate stringer -type=Action

// The Actor struct holds info about an actor I.E the Lad or a Rock
type Actor struct {
	Type       int
	Y          int
	X          int
	Ch         byte
	Dir        Action
	DirRequest Action
	JumpStep   int
}

type Action int

// The Action constans define what the Actor currently is, or is requested to be, doing
const (
	STOPPED Action = iota
	UP
	UPRIGHT
	RIGHT
	DOWNRIGHT
	DOWN
	DOWNLEFT
	LEFT
	UPLEFT
	FALLING
	JUMP // Generic jump set by keyhandler
	JUMPRIGHT
	JUMPUP
	JUMPLEFT
)

var jumpLeft = []Action{UPLEFT, UPLEFT, LEFT, LEFT, DOWNLEFT, DOWNLEFT}
var jumpRight = []Action{UPRIGHT, UPRIGHT, RIGHT, RIGHT, DOWNRIGHT, DOWNRIGHT}
var jumpUp = []Action{UP, UP, STOPPED, DOWN, DOWN}

var jumpPaths = map[Action][]Action{
	JUMPUP:    jumpUp,
	JUMPLEFT:  jumpLeft,
	JUMPRIGHT: jumpRight,
}

var dirs = map[Action]struct {
	x, y int
}{
	STOPPED:   {0, 0},
	UP:        {0, -1},
	UPRIGHT:   {1, -1},
	RIGHT:     {1, 0},
	DOWNRIGHT: {1, 1},
	DOWN:      {0, 1},
	DOWNLEFT:  {-1, 1},
	LEFT:      {-1, 0},
	UPLEFT:    {-1, -1},
	FALLING:   {0, 1},
	JUMP:      {0, 0},
	JUMPRIGHT: {0, 0},
	JUMPLEFT:  {0, 0},
	JUMPUP:    {0, 0},
}

//
// A moving jummp is UR/UR/R/R/DR/DR
// or                UL/UL/L/L/DL/DL
// A standing jump is U/U/-/D/D
//
// 	 ====================
//   ----234-----23------
//   ---1---5----14------
//   --0-----6---05------
//   ====================
//

//
//
//
func MoveActor(a Actor, m MapData) Actor {

loopAgain: // If just started falling we need to retest all conditions

	// If stopped or going left or going right and requst to do something else, then try to do it
	if (a.Dir == STOPPED && a.DirRequest == LEFT) ||
		(a.Dir == STOPPED && a.DirRequest == RIGHT) ||
		(a.Dir == LEFT && a.DirRequest == RIGHT) ||
		(a.Dir == RIGHT && a.DirRequest == LEFT) {
		a.Dir = a.DirRequest
		a.DirRequest = STOPPED

	}

	// Handle starting of jumps
	if a.DirRequest == JUMP {
		switch a.Dir {
		case STOPPED:
			a.DirRequest = a.Dir
			a.Dir = JUMPUP
			a.JumpStep = 0
		case LEFT:
			a.DirRequest = a.Dir
			a.Dir = JUMPLEFT
			a.JumpStep = 0
		case RIGHT:
			a.DirRequest = a.Dir
			a.Dir = JUMPRIGHT
			a.JumpStep = 0
		}
	}

	// Do the jumping
	if a.Dir == JUMPUP || a.Dir == JUMPLEFT || a.Dir == JUMPRIGHT {
		jd := jumpPaths[a.Dir][a.JumpStep]
		if m.Field[a.Y+dirs[jd].y][a.X+dirs[jd].x] == ' ' {
			a.X += dirs[jd].x
			a.Y += dirs[jd].y
			a.JumpStep++
			if a.JumpStep >= len(jumpPaths[a.Dir]) {
				a.Dir = a.DirRequest
			}
		} else {
			// If bumped into something try falling
			a.Dir = FALLING
		}
	}

	// Don't allow player to end up outside of the playfeild
	ClampToPlayfield(&a)

	// If at a ladder and want to go up
	if (a.DirRequest == UP && m.Field[a.Y][a.X] == 'H') ||
		(a.DirRequest == DOWN && m.Field[a.Y][a.X] == 'H') {
		a.Dir = a.DirRequest
		a.DirRequest = STOPPED
	}

	// If falling then continue doing that until not in free space anymore,
	// then continue the previous direction (if any)
	if a.Dir == FALLING {
		if OnSolid(a, m) {
			a.Dir = a.DirRequest
		} else {
			a.Y++
		}
		return a
	}

	// Climb up until ladder is no more
	if a.Dir == UP {
		if m.Field[a.Y-1][a.X] == 'H' {
			a.Y--
		} else {
			a.Dir = STOPPED
		}
	}

	// Climb down until ladder is no more
	if a.Dir == DOWN {
		if m.Field[a.Y+1][a.X] == 'H' {
			a.Y++
		} else {
			a.Dir = STOPPED
		}
	}

	if a.Dir == LEFT && (m.Field[a.Y][a.X] == ' ' || m.Field[a.Y][a.X] == 'H' || m.Field[a.Y][a.X] == '*') {
		// Stepped out into the void?, the start falling, but remember the previous direction
		if m.Field[a.Y+1][a.X] == ' ' {
			a.DirRequest = a.Dir
			a.Dir = FALLING
			goto loopAgain
		}
		a.X--
	}

	if a.Dir == RIGHT && (m.Field[a.Y][a.X] == ' ' || m.Field[a.Y][a.X] == 'H' || m.Field[a.Y][a.X] == '*') {
		// Stepped out into the void?, the start falling, but remember the previous direction
		if m.Field[a.Y+1][a.X] == ' ' {
			a.DirRequest = a.Dir
			a.Dir = FALLING
			goto loopAgain
		}
		a.X++
	}

	// Don't allow player to end up outside of the playfeild
	ClampToPlayfield(&a)

	return a
}

//
// If walking or jumping of the playfield edges then set actor mode to FALLING
// and make sure the actor stays inside the playfield
//
func ClampToPlayfield(a *Actor) {
	if a.X <= 0 && (a.Dir == LEFT || a.Dir == JUMPLEFT) {
		a.X = 0
		a.Dir = FALLING
		a.DirRequest = STOPPED
	}

	if a.X >= 78 && (a.Dir == RIGHT || a.Dir == JUMPRIGHT) {
		a.X = 78
		a.Dir = FALLING
		a.DirRequest = STOPPED
	}

}

//
// Return true if standing on something solid I.E Floor, Disaperaring floor
// or a Ladder
//
func OnSolid(a Actor, m MapData) bool {
	switch m.Field[a.Y+1][a.X] {
	case '=', '-', 'H':
		return true
	}
	return false
}
<commit_msg>Allow jumping onto a ladder. Fixes #2<commit_after>package main

//go:generate stringer -type=Action

// The Actor struct holds info about an actor I.E the Lad or a Rock
type Actor struct {
	Type       int
	Y          int
	X          int
	Ch         byte
	Dir        Action
	DirRequest Action
	JumpStep   int
}

type Action int

// The Action constans define what the Actor currently is, or is requested to be, doing
const (
	STOPPED Action = iota
	UP
	UPRIGHT
	RIGHT
	DOWNRIGHT
	DOWN
	DOWNLEFT
	LEFT
	UPLEFT
	FALLING
	JUMP // Generic jump set by keyhandler
	JUMPRIGHT
	JUMPUP
	JUMPLEFT
)

var jumpLeft = []Action{UPLEFT, UPLEFT, LEFT, LEFT, DOWNLEFT, DOWNLEFT}
var jumpRight = []Action{UPRIGHT, UPRIGHT, RIGHT, RIGHT, DOWNRIGHT, DOWNRIGHT}
var jumpUp = []Action{UP, UP, STOPPED, DOWN, DOWN}

var jumpPaths = map[Action][]Action{
	JUMPUP:    jumpUp,
	JUMPLEFT:  jumpLeft,
	JUMPRIGHT: jumpRight,
}

var dirs = map[Action]struct {
	x, y int
}{
	STOPPED:   {0, 0},
	UP:        {0, -1},
	UPRIGHT:   {1, -1},
	RIGHT:     {1, 0},
	DOWNRIGHT: {1, 1},
	DOWN:      {0, 1},
	DOWNLEFT:  {-1, 1},
	LEFT:      {-1, 0},
	UPLEFT:    {-1, -1},
	FALLING:   {0, 1},
	JUMP:      {0, 0},
	JUMPRIGHT: {0, 0},
	JUMPLEFT:  {0, 0},
	JUMPUP:    {0, 0},
}

//
// A moving jummp is UR/UR/R/R/DR/DR
// or                UL/UL/L/L/DL/DL
// A standing jump is U/U/-/D/D
//
// 	 ====================
//   ----234-----23------
//   ---1---5----14------
//   --0-----6---05------
//   ====================
//

//
//
//
func MoveActor(a Actor, m MapData) Actor {

loopAgain: // If just started falling we need to retest all conditions

	// If stopped or going left or going right and requst to do something else, then try to do it
	if (a.Dir == STOPPED && a.DirRequest == LEFT) ||
		(a.Dir == STOPPED && a.DirRequest == RIGHT) ||
		(a.Dir == LEFT && a.DirRequest == RIGHT) ||
		(a.Dir == RIGHT && a.DirRequest == LEFT) {
		a.Dir = a.DirRequest
		a.DirRequest = STOPPED

	}

	// Handle starting of jumps
	if a.DirRequest == JUMP {
		switch a.Dir {
		case STOPPED:
			a.DirRequest = a.Dir
			a.Dir = JUMPUP
			a.JumpStep = 0
		case LEFT:
			a.DirRequest = a.Dir
			a.Dir = JUMPLEFT
			a.JumpStep = 0
		case RIGHT:
			a.DirRequest = a.Dir
			a.Dir = JUMPRIGHT
			a.JumpStep = 0
		}
	}

	// Do the jumping
	if a.Dir == JUMPUP || a.Dir == JUMPLEFT || a.Dir == JUMPRIGHT {
		jd := jumpPaths[a.Dir][a.JumpStep]
		switch m.Field[a.Y+dirs[jd].y][a.X+dirs[jd].x] {
		case ' ': // Just jumping in air
			a.X += dirs[jd].x
			a.Y += dirs[jd].y
			a.JumpStep++
			if a.JumpStep >= len(jumpPaths[a.Dir]) {
				a.Dir = a.DirRequest
			}
		case 'H': // Jumping onto a ladder
			a.Y += dirs[jd].y
			a.X += dirs[jd].x
			a.Dir = STOPPED
			a.DirRequest = STOPPED
		default: // If bumped into something try falling
			a.Dir = FALLING
		}
	}

	// Don't allow player to end up outside of the playfield
	ClampToPlayfield(&a)

	// If at a ladder and want to go up
	if (a.DirRequest == UP && m.Field[a.Y][a.X] == 'H') ||
		(a.DirRequest == DOWN && m.Field[a.Y][a.X] == 'H') {
		a.Dir = a.DirRequest
		a.DirRequest = STOPPED
	}

	// If falling then continue doing that until not in free space anymore,
	// then continue the previous direction (if any)
	if a.Dir == FALLING {
		if OnSolid(a, m) {
			a.Dir = a.DirRequest
		} else {
			a.Y++
		}
		return a
	}

	// Climb up until ladder is no more
	if a.Dir == UP {
		if m.Field[a.Y-1][a.X] == 'H' {
			a.Y--
		} else {
			a.Dir = STOPPED
		}
	}

	// Climb down until ladder is no more
	if a.Dir == DOWN {
		if m.Field[a.Y+1][a.X] == 'H' {
			a.Y++
		} else {
			a.Dir = STOPPED
		}
	}

	if a.Dir == LEFT && (m.Field[a.Y][a.X] == ' ' || m.Field[a.Y][a.X] == 'H' || m.Field[a.Y][a.X] == '*') {
		// Stepped out into the void?, the start falling, but remember the previous direction
		if m.Field[a.Y+1][a.X] == ' ' {
			a.DirRequest = a.Dir
			a.Dir = FALLING
			goto loopAgain
		}
		a.X--
	}

	if a.Dir == RIGHT && (m.Field[a.Y][a.X] == ' ' || m.Field[a.Y][a.X] == 'H' || m.Field[a.Y][a.X] == '*') {
		// Stepped out into the void?, the start falling, but remember the previous direction
		if m.Field[a.Y+1][a.X] == ' ' {
			a.DirRequest = a.Dir
			a.Dir = FALLING
			goto loopAgain
		}
		a.X++
	}

	// Don't allow player to end up outside of the playfeild
	ClampToPlayfield(&a)

	return a
}

//
// If walking or jumping of the playfield edges then set actor mode to FALLING
// and make sure the actor stays inside the playfield
//
func ClampToPlayfield(a *Actor) {
	if a.X <= 0 && (a.Dir == LEFT || a.Dir == JUMPLEFT) {
		a.X = 0
		a.Dir = FALLING
		a.DirRequest = STOPPED
	}

	if a.X >= 78 && (a.Dir == RIGHT || a.Dir == JUMPRIGHT) {
		a.X = 78
		a.Dir = FALLING
		a.DirRequest = STOPPED
	}

}

//
// Return true if standing on something solid I.E Floor, Disaperaring floor
// or a Ladder
//
func OnSolid(a Actor, m MapData) bool {
	switch m.Field[a.Y+1][a.X] {
	case '=', '-', 'H':
		return true
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2015-2017 Geofrey Ernest <geofreyernest@live.com>. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package resume has resumes handlers for zedlist.
package resume

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"

	"github.com/zedio/zedlist/models"
	"github.com/zedio/zedlist/modules/db"
	"github.com/zedio/zedlist/modules/flash"
	"github.com/zedio/zedlist/modules/log"
	"github.com/zedio/zedlist/modules/query"
	"github.com/zedio/zedlist/modules/tmpl"
	"github.com/zedio/zedlist/modules/utils"

	"github.com/labstack/echo"
)

// Home shows the resumes home page.
//
//		Method           GET
//
//		Route            /resume/
//
//		Restrictions     Yes
//
// 		Template         resume/home.html
func Home(ctx echo.Context) error {
	user := ctx.Get("User").(*models.Person)
	if res, err := query.GetAllPersonResumes(db.Conn, user); err == nil {
		utils.SetData(ctx, "resumes", res)
	}
	return ctx.Render(http.StatusOK, tmpl.ResumeHomeTpl, utils.GetData(ctx))
}

// View displays the resume.
//
//		Method           GET
//
//		Route            /resume/view
//
//		Restrictions     Yes
//
// 		Template         resume/view.html
func View(ctx echo.Context) error {
	iid, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}
	resume, err := query.GetResumeByID(db.Conn, iid)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.NotFoundMessage)
		return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, tmpl.NotFoundMessage)
	}
	utils.SetData(ctx, "resume", resume)
	return ctx.Render(http.StatusOK, tmpl.ResumeViewTpl, utils.GetData(ctx))
}

func New(ctx echo.Context) error {
	utils.SetData(ctx, "PageTitle", "new resume")
	utils.SetData(ctx, "Scripts", []template.HTML{
		template.HTML(`/static/js/moon.min.js`),
		template.HTML(`/static/js/monx.min.js`),
		template.HTML(`/static/js/resume.js`),
	})
	return ctx.Render(http.StatusOK, tmpl.ResumeNewTpl, utils.GetData(ctx))
}

type resumeReq struct {
	ID        int64  `json:"id,omitempty"`
	ProfileID int64  `json:"profileID"`
	Title     string `json:"title"`
}

func Update(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	req := &resumeReq{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, req)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	rs, err := query.GetResumeByID(db.Conn, req.ID)
	if err != nil {
		log.Error(ctx, err)
		if query.NotFound(err) {
			return ctx.JSON(http.StatusNotFound, models.NewJSONErr(http.StatusText(
				http.StatusNotFound,
			)))
		}
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSON(http.StatusOK, rs)
}

func NewPost(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	req := &resumeReq{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, req)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	rs := &models.Resume{
		PersonID: req.ProfileID,
		Title:    req.Title,
	}
	if err = query.Create(db.Conn, rs); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSON(http.StatusOK, rs)
}

func BasicPut(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	b := &models.Basic{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, b)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	if err = query.Update(db.Conn, b); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusOK, b, "\t")
}

func BasicPost(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		ctx.Echo().DefaultHTTPErrorHandler(echo.ErrUnsupportedMediaType, ctx)
		return nil
	}
	b := &models.Basic{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, b)
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	if err = query.Create(db.Conn, b); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusCreated, b, "\t")
}

func BasicGet(ctx echo.Context) error {
	id, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	b, err := query.GetBasicResumeByID(db.Conn, id)
	if err != nil {
		log.Error(ctx, err)
		if query.NotFound(err) {
			return ctx.JSON(http.StatusNotFound, models.NewJSONErr(http.StatusText(
				http.StatusNotFound,
			)))
		}
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusOK, b, "\t")
}

// Create creates a new resume.
//
//		Method           POST
//
//		Route            /resume/new
//
//		Restrictions     Yes
//
// 		Template         None
func Create(ctx echo.Context) error {
	var flashMessages = flash.New()
	r := ctx.Request()
	r.ParseForm()
	name := r.Form.Get("resume_name")

	user := ctx.Get("User").(*models.Person)

	resume := models.SampleResume()
	resume.Title = name
	err := query.CreateResume(db.Conn, user, resume)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.ServerErrorMessage)
		return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx))
	}

	flashMessages.Success("successful created a new resume")
	flashMessages.Save(ctx)

	// Redirect to the update page for further updating of the resume.
	ctx.Redirect(http.StatusFound, fmt.Sprintf("/resume/update/%d", resume.ID))
	return nil
}

// Delete deletes the resume.
//
//		Method           POST
//
//		Route            /resume/delete/:id
//
//		Restrictions     Yes
//
// 		Template         None
func Delete(ctx echo.Context) error {
	var flashMessages = flash.New()
	id, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}
	user := ctx.Get("User").(*models.Person)

	resume, err := query.GetResumeByID(db.Conn, id)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.NotFoundMessage)
		return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx))
	}

	// Users are allowed to delete resumes that they don't own.
	if resume.PersonID != user.ID {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}

	err = query.Delete(db.Conn, resume)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.ServerErrorMessage)
		return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx))
	}

	flashMessages.Success("resume successful deleted")
	flashMessages.Save(ctx)
	ctx.Redirect(http.StatusFound, "/resume/")
	return nil
}
<commit_msg>Remove monx<commit_after>// Copyright 2015-2017 Geofrey Ernest <geofreyernest@live.com>. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package resume has resumes handlers for zedlist.
package resume

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"

	"github.com/zedio/zedlist/models"
	"github.com/zedio/zedlist/modules/db"
	"github.com/zedio/zedlist/modules/flash"
	"github.com/zedio/zedlist/modules/log"
	"github.com/zedio/zedlist/modules/query"
	"github.com/zedio/zedlist/modules/tmpl"
	"github.com/zedio/zedlist/modules/utils"

	"github.com/labstack/echo"
)

// Home shows the resumes home page.
//
//		Method           GET
//
//		Route            /resume/
//
//		Restrictions     Yes
//
// 		Template         resume/home.html
func Home(ctx echo.Context) error {
	user := ctx.Get("User").(*models.Person)
	if res, err := query.GetAllPersonResumes(db.Conn, user); err == nil {
		utils.SetData(ctx, "resumes", res)
	}
	return ctx.Render(http.StatusOK, tmpl.ResumeHomeTpl, utils.GetData(ctx))
}

// View displays the resume.
//
//		Method           GET
//
//		Route            /resume/view
//
//		Restrictions     Yes
//
// 		Template         resume/view.html
func View(ctx echo.Context) error {
	iid, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}
	resume, err := query.GetResumeByID(db.Conn, iid)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.NotFoundMessage)
		return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, tmpl.NotFoundMessage)
	}
	utils.SetData(ctx, "resume", resume)
	return ctx.Render(http.StatusOK, tmpl.ResumeViewTpl, utils.GetData(ctx))
}

func New(ctx echo.Context) error {
	utils.SetData(ctx, "PageTitle", "new resume")
	utils.SetData(ctx, "Scripts", []template.HTML{
		template.HTML(`/static/js/moon.min.js`),
		template.HTML(`/static/js/resume.js`),
	})
	return ctx.Render(http.StatusOK, tmpl.ResumeNewTpl, utils.GetData(ctx))
}

type resumeReq struct {
	ID        int64  `json:"id,omitempty"`
	ProfileID int64  `json:"profileID"`
	Title     string `json:"title"`
}

func Update(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	req := &resumeReq{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, req)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	rs, err := query.GetResumeByID(db.Conn, req.ID)
	if err != nil {
		log.Error(ctx, err)
		if query.NotFound(err) {
			return ctx.JSON(http.StatusNotFound, models.NewJSONErr(http.StatusText(
				http.StatusNotFound,
			)))
		}
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSON(http.StatusOK, rs)
}

func NewPost(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	req := &resumeReq{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, req)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	rs := &models.Resume{
		PersonID: req.ProfileID,
		Title:    req.Title,
	}
	if err = query.Create(db.Conn, rs); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSON(http.StatusOK, rs)
}

func BasicPut(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	b := &models.Basic{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, b)
	if err != nil {
		return ctx.JSON(http.StatusUnprocessableEntity, models.NewJSONErr(http.StatusText(
			http.StatusUnprocessableEntity,
		)))
	}
	if err = query.Update(db.Conn, b); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusOK, b, "\t")
}

func BasicPost(ctx echo.Context) error {
	r := ctx.Request()
	c := r.Header.Get(echo.HeaderContentType)
	if c != echo.MIMEApplicationJSON {
		ctx.Echo().DefaultHTTPErrorHandler(echo.ErrUnsupportedMediaType, ctx)
		return nil
	}
	b := &models.Basic{}
	o, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	err = json.Unmarshal(o, b)
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	if err = query.Create(db.Conn, b); err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusCreated, b, "\t")
}

func BasicGet(ctx echo.Context) error {
	id, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		log.Error(ctx, err)
		return ctx.JSON(http.StatusBadRequest, models.NewJSONErr(http.StatusText(
			http.StatusBadRequest,
		)))
	}
	b, err := query.GetBasicResumeByID(db.Conn, id)
	if err != nil {
		log.Error(ctx, err)
		if query.NotFound(err) {
			return ctx.JSON(http.StatusNotFound, models.NewJSONErr(http.StatusText(
				http.StatusNotFound,
			)))
		}
		return ctx.JSON(http.StatusInternalServerError, models.NewJSONErr(http.StatusText(
			http.StatusInternalServerError,
		)))
	}
	return ctx.JSONPretty(http.StatusOK, b, "\t")
}

// Create creates a new resume.
//
//		Method           POST
//
//		Route            /resume/new
//
//		Restrictions     Yes
//
// 		Template         None
func Create(ctx echo.Context) error {
	var flashMessages = flash.New()
	r := ctx.Request()
	r.ParseForm()
	name := r.Form.Get("resume_name")

	user := ctx.Get("User").(*models.Person)

	resume := models.SampleResume()
	resume.Title = name
	err := query.CreateResume(db.Conn, user, resume)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.ServerErrorMessage)
		return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx))
	}

	flashMessages.Success("successful created a new resume")
	flashMessages.Save(ctx)

	// Redirect to the update page for further updating of the resume.
	ctx.Redirect(http.StatusFound, fmt.Sprintf("/resume/update/%d", resume.ID))
	return nil
}

// Delete deletes the resume.
//
//		Method           POST
//
//		Route            /resume/delete/:id
//
//		Restrictions     Yes
//
// 		Template         None
func Delete(ctx echo.Context) error {
	var flashMessages = flash.New()
	id, err := utils.GetInt64(ctx.Param("id"))
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}
	user := ctx.Get("User").(*models.Person)

	resume, err := query.GetResumeByID(db.Conn, id)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.NotFoundMessage)
		return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx))
	}

	// Users are allowed to delete resumes that they don't own.
	if resume.PersonID != user.ID {
		utils.SetData(ctx, "Message", tmpl.BadRequestMessage)
		return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx))
	}

	err = query.Delete(db.Conn, resume)
	if err != nil {
		utils.SetData(ctx, "Message", tmpl.ServerErrorMessage)
		return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx))
	}

	flashMessages.Success("resume successful deleted")
	flashMessages.Save(ctx)
	ctx.Redirect(http.StatusFound, "/resume/")
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/kubernetes-incubator/external-storage/lib/controller"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/resource"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/util/uuid"
	"k8s.io/client-go/pkg/util/wait"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/dustin/go-humanize"
	"github.com/kolyshkin/goploop-cli"
	"github.com/virtuozzo/ploop-flexvol/vstorage"
)

const (
	resyncPeriod              = 15 * time.Second
	provisionerName           = "kubernetes.io/virtuozzo-storage"
	exponentialBackOffOnError = false
	failedRetryThreshold      = 5
	parentProvisionerAnn      = "vzFSParentProvisioner"
	vzShareAnn                = "vzShare"
)

type provisionOutput struct {
	Path string `json:"path"`
}

type vzFSProvisioner struct {
	// Kubernetes Client. Use to retrieve secrets with Virtuozzo Storage credentials
	client kubernetes.Interface
}

func newVzFSProvisioner(client kubernetes.Interface) controller.Provisioner {
	return &vzFSProvisioner{
		client:   client,
	}
}

var _ controller.Provisioner = &vzFSProvisioner{}

const ProvisionerDir = "/export/virtuozzo-provisioner/"
const MountDir = ProvisionerDir + "mnt/"

func prepareVstorage(options map[string]string, clusterName string, clusterPassword string) error {
	mount := MountDir + clusterName
	mounted, _ := vstorage.IsVstorage(mount)
	if mounted {
		return nil
	}

	if err := os.MkdirAll(mount, 0755); err != nil {
		return err
	}

	v := vstorage.Vstorage{clusterName}
	p, _ := v.Mountpoint()
	if p != "" {
		return syscall.Mount(p, mount, "", syscall.MS_BIND, "")
	}

	if err := v.Auth(clusterPassword); err != nil {
		return err
	}
	if err := v.Mount(mount); err != nil {
		return err
	}

	return nil
}

func createPloop(mount string, options map[string]string) error {
	var (
		volumePath, volumeId, size string
	)

	for k, v := range options {
		switch k {
		case "volumePath":
			volumePath = v
		case "volumeId":
			volumeId = v
		case "size":
			size = v
		case "vzsReplicas":
		case "vzsFailureDomain":
		case "vzsEncoding":
		case "vzsTier":
		case "kubernetes.io/readwrite":
		case "kubernetes.io/fsType":
		default:
		}
	}

	if volumePath == "" {
		return fmt.Errorf("volumePath isn't specified")
	}

	if volumeId == "" {
		return fmt.Errorf("volumeId isn't specified")
	}

	if size == "" {
		return fmt.Errorf("size isn't specified")
	}

	// get a human readable size from the map
	bytes, _ := humanize.ParseBytes(size)

	// ploop driver takes kilobytes, so convert it
	volume_size := bytes / 1024

	ploop_path := mount + "/" + options["volumePath"] + "/" + options["volumeId"]

	// make the base directory where the volume will go
	err := os.MkdirAll(ploop_path, 0700)
	if err != nil {
		return err
	}

	for k, v := range options {
		var err error
		attr := ""
		switch k {
		case "vzsReplicas":
			attr = "replicas"
		case "vzsTier":
			attr = "tier"
		case "vzsEncoding":
			attr = "encoding"
		case "vzsFailureDomain":
			attr = "failure-domain"
		}
		if attr != "" {
			cmd := "vstorage"
			args := []string{"set-attr", "-R", ploop_path,
				fmt.Sprintf("%s=%s", attr, v)}
			err = exec.Command(cmd, args...).Run()
		}

		if err != nil {
			os.RemoveAll(ploop_path)
			return fmt.Errorf("Unable to set %s to %s: %v", attr, v, err)
		}
	}

	// Create the ploop volume
	cp := ploop.CreateParam{Size: volume_size, File: ploop_path + "/" + options["volumeId"]}
	if err := ploop.Create(&cp); err != nil {
		return err
	}

	return nil
}

// Provision creates a storage asset and returns a PV object representing it.
func (p *vzFSProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
	var (
		capacity resource.Quantity
	)

	capacity = options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
	bytes := capacity.Value()

	if options.PVC.Spec.Selector != nil {
		return nil, fmt.Errorf("claim Selector is not supported")
	}
	share := fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())

	glog.Infof("Add %s %s", share, humanize.Bytes(uint64(bytes)))

	storage_class_options := map[string]string{}
	for k, v := range options.Parameters {
		storage_class_options[k] = v
	}

	storage_class_options["volumeId"] = share
	storage_class_options["size"] = fmt.Sprintf("%d", bytes)
	secretName := storage_class_options["secretName"]
	delete(storage_class_options, "secretName")

	secret, err := p.client.Core().Secrets(options.PVC.Namespace).Get(secretName)
	if err != nil {
		return nil, err
	}

	name := string(secret.Data["clusterName"][:len(secret.Data["clusterName"])])
	password := string(secret.Data["clusterPassword"][:len(secret.Data["clusterPassword"])])
	if err := prepareVstorage(storage_class_options, name, password); err != nil {
		return nil, err
	}
	defer syscall.Unmount(MountDir+name, syscall.MNT_DETACH)

	if err := createPloop(MountDir+name, storage_class_options); err != nil {
		return nil, err
	}

	pv := &v1.PersistentVolume{
		ObjectMeta: v1.ObjectMeta{
			Name: options.PVName,
			Annotations: map[string]string{
				parentProvisionerAnn: *provisionerId,
				vzShareAnn:           share,
			},
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
			AccessModes:                   options.PVC.Spec.AccessModes,
			Capacity: v1.ResourceList{
				v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
			},
			PersistentVolumeSource: v1.PersistentVolumeSource{
				FlexVolume: &v1.FlexVolumeSource{
					Driver:    "virtuozzo/ploop",
					SecretRef: &v1.LocalObjectReference{Name: secretName},
					Options:   storage_class_options,
				},
			},
		},
	}

	glog.Infof("successfully created virtuozzo storage share: %s", share)

	return pv, nil
}

// Delete removes the storage asset that was created by Provision represented
// by the given PV.
func (p *vzFSProvisioner) Delete(volume *v1.PersistentVolume) error {
	ann, ok := volume.Annotations[parentProvisionerAnn]
	if !ok {
		return errors.New("Parent provisioner name annotation not found on PV")
	}
	if ann != *provisionerId {
		return &controller.IgnoredError{"parent provisioner name annotation on PV does not match ours"}
	}
	share, ok := volume.Annotations[vzShareAnn]
	if !ok {
		return errors.New("vz share annotation not found on PV")
	}

	secretName := volume.Spec.PersistentVolumeSource.FlexVolume.SecretRef.Name
	options := volume.Spec.PersistentVolumeSource.FlexVolume.Options

	secret, err := p.client.Core().Secrets(volume.Spec.ClaimRef.Namespace).Get(secretName)
	if err != nil {
		return err
	}

	name := string(secret.Data["clusterName"][:len(secret.Data["clusterName"])])
	password := string(secret.Data["clusterPassword"][:len(secret.Data["clusterPassword"])])
	mount := MountDir + name
	if err := prepareVstorage(options, name, password); err != nil {
		return err
	}
	defer syscall.Unmount(mount, syscall.MNT_DETACH)

	path := mount + "/" + options["volumePath"] + "/" + options["volumeId"]
	glog.Infof("Delete: %s", path)
	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	glog.Infof("successfully delete virtuozzo storage share: %s", share)

	return nil
}

var (
	master     = flag.String("master", "", "Master URL")
	kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig")
	provisionerId = flag.String("name", "", "Unique provisioner name")
)

func main() {
	flag.Parse()
	flag.Set("logtostderr", "true")
	if *provisionerId == "" {
		glog.Fatalf("You should provide unique provisioner name!")
	}

	var config *rest.Config
	var err error
	if *master != "" || *kubeconfig != "" {
		config, err = clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
	} else {
		config, err = rest.InClusterConfig()
	}

	if err != nil {
		glog.Fatalf("Failed to create config: %v", err)
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		glog.Fatalf("Failed to create client: %v", err)
	}

	// The controller needs to know what the server version is because out-of-tree
	// provisioners aren't officially supported until 1.5
	serverVersion, err := clientset.Discovery().ServerVersion()
	if err != nil {
		glog.Fatalf("Error getting server version: %v", err)
	}

	// Create the provisioner: it implements the Provisioner interface expected by
	// the controller
	vzFSProvisioner := newVzFSProvisioner(clientset)

	// Start the provision controller which will dynamically provision Virtuozzo Storage PVs
	pc := controller.NewProvisionController(clientset, resyncPeriod, provisionerName, vzFSProvisioner, serverVersion.GitVersion, exponentialBackOffOnError, failedRetryThreshold, 2*resyncPeriod, resyncPeriod, resyncPeriod/2, 2*resyncPeriod)

	pc.Run(wait.NeverStop)
}
<commit_msg>Update vzstorage-pd to use a new NewProvisionController()<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/kubernetes-incubator/external-storage/lib/controller"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/dustin/go-humanize"
	"github.com/kolyshkin/goploop-cli"
	"github.com/virtuozzo/ploop-flexvol/vstorage"
)

const (
	resyncPeriod              = 15 * time.Second
	provisionerName           = "kubernetes.io/virtuozzo-storage"
	exponentialBackOffOnError = false
	failedRetryThreshold      = 5
	parentProvisionerAnn      = "vzFSParentProvisioner"
	vzShareAnn                = "vzShare"
)

type provisionOutput struct {
	Path string `json:"path"`
}

type vzFSProvisioner struct {
	// Kubernetes Client. Use to retrieve secrets with Virtuozzo Storage credentials
	client kubernetes.Interface
}

func newVzFSProvisioner(client kubernetes.Interface) controller.Provisioner {
	return &vzFSProvisioner{
		client: client,
	}
}

var _ controller.Provisioner = &vzFSProvisioner{}

const ProvisionerDir = "/export/virtuozzo-provisioner/"
const MountDir = ProvisionerDir + "mnt/"

func prepareVstorage(options map[string]string, clusterName string, clusterPassword string) error {
	mount := MountDir + clusterName
	mounted, _ := vstorage.IsVstorage(mount)
	if mounted {
		return nil
	}

	if err := os.MkdirAll(mount, 0755); err != nil {
		return err
	}

	v := vstorage.Vstorage{clusterName}
	p, _ := v.Mountpoint()
	if p != "" {
		return syscall.Mount(p, mount, "", syscall.MS_BIND, "")
	}

	if err := v.Auth(clusterPassword); err != nil {
		return err
	}
	if err := v.Mount(mount); err != nil {
		return err
	}

	return nil
}

func createPloop(mount string, options map[string]string) error {
	var (
		volumePath, volumeId, size string
	)

	for k, v := range options {
		switch k {
		case "volumePath":
			volumePath = v
		case "volumeId":
			volumeId = v
		case "size":
			size = v
		case "vzsReplicas":
		case "vzsFailureDomain":
		case "vzsEncoding":
		case "vzsTier":
		case "kubernetes.io/readwrite":
		case "kubernetes.io/fsType":
		default:
		}
	}

	if volumePath == "" {
		return fmt.Errorf("volumePath isn't specified")
	}

	if volumeId == "" {
		return fmt.Errorf("volumeId isn't specified")
	}

	if size == "" {
		return fmt.Errorf("size isn't specified")
	}

	// get a human readable size from the map
	bytes, _ := humanize.ParseBytes(size)

	// ploop driver takes kilobytes, so convert it
	volume_size := bytes / 1024

	ploop_path := mount + "/" + options["volumePath"] + "/" + options["volumeId"]

	// make the base directory where the volume will go
	err := os.MkdirAll(ploop_path, 0700)
	if err != nil {
		return err
	}

	for k, v := range options {
		var err error
		attr := ""
		switch k {
		case "vzsReplicas":
			attr = "replicas"
		case "vzsTier":
			attr = "tier"
		case "vzsEncoding":
			attr = "encoding"
		case "vzsFailureDomain":
			attr = "failure-domain"
		}
		if attr != "" {
			cmd := "vstorage"
			args := []string{"set-attr", "-R", ploop_path,
				fmt.Sprintf("%s=%s", attr, v)}
			err = exec.Command(cmd, args...).Run()
		}

		if err != nil {
			os.RemoveAll(ploop_path)
			return fmt.Errorf("Unable to set %s to %s: %v", attr, v, err)
		}
	}

	// Create the ploop volume
	cp := ploop.CreateParam{Size: volume_size, File: ploop_path + "/" + options["volumeId"]}
	if err := ploop.Create(&cp); err != nil {
		return err
	}

	return nil
}

// Provision creates a storage asset and returns a PV object representing it.
func (p *vzFSProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
	capacity := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
	bytes := capacity.Value()

	if options.PVC.Spec.Selector != nil {
		return nil, fmt.Errorf("claim Selector is not supported")
	}
	share := fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())

	glog.Infof("Add %s %s", share, humanize.Bytes(uint64(bytes)))

	storage_class_options := map[string]string{}
	for k, v := range options.Parameters {
		storage_class_options[k] = v
	}

	storage_class_options["volumeId"] = share
	storage_class_options["size"] = fmt.Sprintf("%d", bytes)
	secretName := storage_class_options["secretName"]
	delete(storage_class_options, "secretName")

	secret, err := p.client.Core().Secrets(options.PVC.Namespace).Get(secretName, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}

	name := string(secret.Data["clusterName"][:len(secret.Data["clusterName"])])
	password := string(secret.Data["clusterPassword"][:len(secret.Data["clusterPassword"])])
	if err := prepareVstorage(storage_class_options, name, password); err != nil {
		return nil, err
	}
	defer syscall.Unmount(MountDir+name, syscall.MNT_DETACH)

	if err := createPloop(MountDir+name, storage_class_options); err != nil {
		return nil, err
	}

	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: options.PVName,
			Annotations: map[string]string{
				parentProvisionerAnn: *provisionerId,
				vzShareAnn:           share,
			},
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
			AccessModes:                   options.PVC.Spec.AccessModes,
			Capacity: v1.ResourceList{
				v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
			},
			PersistentVolumeSource: v1.PersistentVolumeSource{
				FlexVolume: &v1.FlexVolumeSource{
					Driver:    "virtuozzo/ploop",
					SecretRef: &v1.LocalObjectReference{Name: secretName},
					Options:   storage_class_options,
				},
			},
		},
	}

	glog.Infof("successfully created virtuozzo storage share: %s", share)

	return pv, nil
}

// Delete removes the storage asset that was created by Provision represented
// by the given PV.
func (p *vzFSProvisioner) Delete(volume *v1.PersistentVolume) error {
	ann, ok := volume.Annotations[parentProvisionerAnn]
	if !ok {
		return errors.New("Parent provisioner name annotation not found on PV")
	}
	if ann != *provisionerId {
		return &controller.IgnoredError{"parent provisioner name annotation on PV does not match ours"}
	}
	share, ok := volume.Annotations[vzShareAnn]
	if !ok {
		return errors.New("vz share annotation not found on PV")
	}

	secretName := volume.Spec.PersistentVolumeSource.FlexVolume.SecretRef.Name
	options := volume.Spec.PersistentVolumeSource.FlexVolume.Options

	secret, err := p.client.Core().Secrets(volume.Spec.ClaimRef.Namespace).Get(secretName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	name := string(secret.Data["clusterName"][:len(secret.Data["clusterName"])])
	password := string(secret.Data["clusterPassword"][:len(secret.Data["clusterPassword"])])
	mount := MountDir + name
	if err := prepareVstorage(options, name, password); err != nil {
		return err
	}
	defer syscall.Unmount(mount, syscall.MNT_DETACH)

	path := mount + "/" + options["volumePath"] + "/" + options["volumeId"]
	glog.Infof("Delete: %s", path)
	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	glog.Infof("successfully delete virtuozzo storage share: %s", share)

	return nil
}

var (
	master        = flag.String("master", "", "Master URL")
	kubeconfig    = flag.String("kubeconfig", "", "Absolute path to the kubeconfig")
	provisionerId = flag.String("name", "", "Unique provisioner name")
)

func main() {
	flag.Parse()
	flag.Set("logtostderr", "true")
	if *provisionerId == "" {
		glog.Fatalf("You should provide unique provisioner name!")
	}

	var config *rest.Config
	var err error
	if *master != "" || *kubeconfig != "" {
		config, err = clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
	} else {
		config, err = rest.InClusterConfig()
	}

	if err != nil {
		glog.Fatalf("Failed to create config: %v", err)
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		glog.Fatalf("Failed to create client: %v", err)
	}

	// The controller needs to know what the server version is because out-of-tree
	// provisioners aren't officially supported until 1.5
	serverVersion, err := clientset.Discovery().ServerVersion()
	if err != nil {
		glog.Fatalf("Error getting server version: %v", err)
	}

	// Create the provisioner: it implements the Provisioner interface expected by
	// the controller
	vzFSProvisioner := newVzFSProvisioner(clientset)

	// Start the provision controller which will dynamically provision Virtuozzo Storage PVs
	pc := controller.NewProvisionController(clientset,
		provisionerName,
		vzFSProvisioner,
		serverVersion.GitVersion,
	)

	pc.Run(wait.NeverStop)
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/julienschmidt/httprouter"
)

// HttpGET is a utility function for making http get requests to sia with a whitelisted user-agent
func HttpGET(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", "Sia-Agent")
	return new(http.Client).Do(req)
}

// HttpPOST is a utility function for making post requests to sia with a whitelisted user-agent
func HttpPOST(url string, data string) (resp *http.Response, err error) {
	req, err := http.NewRequest("POST", url, strings.NewReader(data))
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", "Sia-Agent")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	return new(http.Client).Do(req)
}

// requireUserAgent is middleware that requires all requests to set a
// UserAgent that contains the specified string.
func requireUserAgent(h http.Handler, ua string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if !strings.Contains(req.UserAgent(), ua) {
			writeError(w, "Browser access disabled due to security vulnerability. Use Sia-UI or siac.", http.StatusBadRequest)
			return
		}
		h.ServeHTTP(w, req)
	})
}

// initAPI determines which functions handle each API call.
func (srv *Server) initAPI() {
	router := httprouter.New()
	router.NotFound = http.HandlerFunc(srv.unrecognizedCallHandler) // custom 404

	// Daemon API Calls
	router.GET("/daemon/constants", srv.daemonConstantsHandler)
	router.GET("/daemon/version", srv.daemonVersionHandler)
	router.GET("/daemon/stop", srv.daemonStopHandler)

	// Consensus API Calls
	if srv.cs != nil {
		router.GET("/consensus", srv.consensusHandler)
	}

	// Explorer API Calls
	if srv.explorer != nil {
		router.GET("/explorer", srv.explorerHandler)
		router.GET("/explorer/blocks/:height", srv.explorerBlocksHandler)
		router.GET("/explorer/hashes/:hash", srv.explorerHashHandler)
	}

	// Gateway API Calls
	if srv.gateway != nil {
		router.GET("/gateway", srv.gatewayHandler)
		router.POST("/gateway/add/:netaddress", srv.gatewayAddHandler)
		router.POST("/gateway/remove/:netaddress", srv.gatewayRemoveHandler)
	}

	// Host API Calls
	if srv.host != nil {
		router.GET("/host", srv.hostHandlerGET)
		router.POST("/host", srv.hostHandlerPOST)
		router.POST("/host/announce", srv.hostAnnounceHandler)
	}

	// Miner API Calls
	if srv.miner != nil {
		router.GET("/miner", srv.minerHandler)
		router.GET("/miner/header", srv.minerHeaderHandlerGET)
		router.POST("/miner/header", srv.minerHeaderHandlerPOST)
		router.GET("/miner/start", srv.minerStartHandler)
		router.GET("/miner/stop", srv.minerStopHandler)
		router.GET("/miner/headerforwork", srv.minerHeaderHandlerGET) // COMPATv0.4.8
		router.GET("/miner/submitheader", srv.minerHeaderHandlerPOST) // COMPATv0.4.8
	}

	// Renter API Calls
	if srv.renter != nil {
		router.GET("/renter/downloads", srv.renterDownloadsHandler)
		router.GET("/renter/files", srv.renterFilesHandler)

		router.POST("/renter/load", srv.renterLoadHandler)
		router.POST("/renter/loadascii", srv.renterLoadAsciiHandler)
		router.GET("/renter/share", srv.renterShareHandler)
		router.GET("/renter/shareascii", srv.renterShareAsciiHandler)

		router.POST("/renter/delete/*siapath", srv.renterDeleteHandler)
		router.GET("/renter/download/*siapath", srv.renterDownloadHandler)
		router.POST("/renter/rename/*siapath", srv.renterRenameHandler)
		router.POST("/renter/upload/*siapath", srv.renterUploadHandler)

		router.GET("/renter/hosts/active", srv.renterHostsActiveHandler)
		router.GET("/renter/hosts/all", srv.renterHostsAllHandler)
	}

	// TransactionPool API Calls
	if srv.tpool != nil {
		router.GET("/transactionpool/transactions", srv.transactionpoolTransactionsHandler)
	}

	// Wallet API Calls
	if srv.wallet != nil {
		router.GET("/wallet", srv.walletHandler)
		router.POST("/wallet/033x", srv.wallet033xHandler)
		router.GET("/wallet/address", srv.walletAddressHandler)
		router.GET("/wallet/addresses", srv.walletAddressesHandler)
		router.GET("/wallet/backup", srv.walletBackupHandler)
		router.POST("/wallet/init", srv.walletInitHandler)
		router.POST("/wallet/lock", srv.walletLockHandler)
		router.POST("/wallet/seed", srv.walletSeedHandler)
		router.GET("/wallet/seeds", srv.walletSeedsHandler)
		router.POST("/wallet/siacoins", srv.walletSiacoinsHandler)
		router.POST("/wallet/siafunds", srv.walletSiafundsHandler)
		router.POST("/wallet/siagkey", srv.walletSiagkeyHandler)
		router.GET("/wallet/transaction/:id", srv.walletTransactionHandler)
		router.GET("/wallet/transactions", srv.walletTransactionsHandler)
		router.GET("/wallet/transactions/:addr", srv.walletTransactionsAddrHandler)
		router.POST("/wallet/unlock", srv.walletUnlockHandler)
		router.POST("/wallet/encrypt", srv.walletInitHandler) // COMPATv0.4.0
	}

	// Apply UserAgent middleware and create HTTP server
	uaRouter := requireUserAgent(router, srv.requiredUserAgent)
	srv.apiServer = &http.Server{Handler: uaRouter}
}

// unrecognizedCallHandler handles calls to unknown pages (404).
func (srv *Server) unrecognizedCallHandler(w http.ResponseWriter, req *http.Request) {
	http.Error(w, "404 - Refer to API.md", http.StatusNotFound)
}

// writeError an error to the API caller.
func writeError(w http.ResponseWriter, msg string, err int) {
	http.Error(w, msg, err)
}

// writeJSON writes the object to the ResponseWriter. If the encoding fails, an
// error is written instead.
func writeJSON(w http.ResponseWriter, obj interface{}) {
	if json.NewEncoder(w).Encode(obj) != nil {
		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
	}
}

// writeSuccess writes the success json object ({"Success":true}) to the
// ResponseWriter
func writeSuccess(w http.ResponseWriter) {
	writeJSON(w, struct{ Success bool }{true})
}
<commit_msg>set /miner/submitheader to a POST request<commit_after>package api

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/julienschmidt/httprouter"
)

// HttpGET is a utility function for making http get requests to sia with a whitelisted user-agent
func HttpGET(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", "Sia-Agent")
	return new(http.Client).Do(req)
}

// HttpPOST is a utility function for making post requests to sia with a whitelisted user-agent
func HttpPOST(url string, data string) (resp *http.Response, err error) {
	req, err := http.NewRequest("POST", url, strings.NewReader(data))
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", "Sia-Agent")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	return new(http.Client).Do(req)
}

// requireUserAgent is middleware that requires all requests to set a
// UserAgent that contains the specified string.
func requireUserAgent(h http.Handler, ua string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if !strings.Contains(req.UserAgent(), ua) {
			writeError(w, "Browser access disabled due to security vulnerability. Use Sia-UI or siac.", http.StatusBadRequest)
			return
		}
		h.ServeHTTP(w, req)
	})
}

// initAPI determines which functions handle each API call.
func (srv *Server) initAPI() {
	router := httprouter.New()
	router.NotFound = http.HandlerFunc(srv.unrecognizedCallHandler) // custom 404

	// Daemon API Calls
	router.GET("/daemon/constants", srv.daemonConstantsHandler)
	router.GET("/daemon/version", srv.daemonVersionHandler)
	router.GET("/daemon/stop", srv.daemonStopHandler)

	// Consensus API Calls
	if srv.cs != nil {
		router.GET("/consensus", srv.consensusHandler)
	}

	// Explorer API Calls
	if srv.explorer != nil {
		router.GET("/explorer", srv.explorerHandler)
		router.GET("/explorer/blocks/:height", srv.explorerBlocksHandler)
		router.GET("/explorer/hashes/:hash", srv.explorerHashHandler)
	}

	// Gateway API Calls
	if srv.gateway != nil {
		router.GET("/gateway", srv.gatewayHandler)
		router.POST("/gateway/add/:netaddress", srv.gatewayAddHandler)
		router.POST("/gateway/remove/:netaddress", srv.gatewayRemoveHandler)
	}

	// Host API Calls
	if srv.host != nil {
		router.GET("/host", srv.hostHandlerGET)
		router.POST("/host", srv.hostHandlerPOST)
		router.POST("/host/announce", srv.hostAnnounceHandler)
	}

	// Miner API Calls
	if srv.miner != nil {
		router.GET("/miner", srv.minerHandler)
		router.GET("/miner/header", srv.minerHeaderHandlerGET)
		router.POST("/miner/header", srv.minerHeaderHandlerPOST)
		router.GET("/miner/start", srv.minerStartHandler)
		router.GET("/miner/stop", srv.minerStopHandler)
		router.GET("/miner/headerforwork", srv.minerHeaderHandlerGET)  // COMPATv0.4.8
		router.POST("/miner/submitheader", srv.minerHeaderHandlerPOST) // COMPATv0.4.8
	}

	// Renter API Calls
	if srv.renter != nil {
		router.GET("/renter/downloads", srv.renterDownloadsHandler)
		router.GET("/renter/files", srv.renterFilesHandler)

		router.POST("/renter/load", srv.renterLoadHandler)
		router.POST("/renter/loadascii", srv.renterLoadAsciiHandler)
		router.GET("/renter/share", srv.renterShareHandler)
		router.GET("/renter/shareascii", srv.renterShareAsciiHandler)

		router.POST("/renter/delete/*siapath", srv.renterDeleteHandler)
		router.GET("/renter/download/*siapath", srv.renterDownloadHandler)
		router.POST("/renter/rename/*siapath", srv.renterRenameHandler)
		router.POST("/renter/upload/*siapath", srv.renterUploadHandler)

		router.GET("/renter/hosts/active", srv.renterHostsActiveHandler)
		router.GET("/renter/hosts/all", srv.renterHostsAllHandler)
	}

	// TransactionPool API Calls
	if srv.tpool != nil {
		router.GET("/transactionpool/transactions", srv.transactionpoolTransactionsHandler)
	}

	// Wallet API Calls
	if srv.wallet != nil {
		router.GET("/wallet", srv.walletHandler)
		router.POST("/wallet/033x", srv.wallet033xHandler)
		router.GET("/wallet/address", srv.walletAddressHandler)
		router.GET("/wallet/addresses", srv.walletAddressesHandler)
		router.GET("/wallet/backup", srv.walletBackupHandler)
		router.POST("/wallet/init", srv.walletInitHandler)
		router.POST("/wallet/lock", srv.walletLockHandler)
		router.POST("/wallet/seed", srv.walletSeedHandler)
		router.GET("/wallet/seeds", srv.walletSeedsHandler)
		router.POST("/wallet/siacoins", srv.walletSiacoinsHandler)
		router.POST("/wallet/siafunds", srv.walletSiafundsHandler)
		router.POST("/wallet/siagkey", srv.walletSiagkeyHandler)
		router.GET("/wallet/transaction/:id", srv.walletTransactionHandler)
		router.GET("/wallet/transactions", srv.walletTransactionsHandler)
		router.GET("/wallet/transactions/:addr", srv.walletTransactionsAddrHandler)
		router.POST("/wallet/unlock", srv.walletUnlockHandler)
		router.POST("/wallet/encrypt", srv.walletInitHandler) // COMPATv0.4.0
	}

	// Apply UserAgent middleware and create HTTP server
	uaRouter := requireUserAgent(router, srv.requiredUserAgent)
	srv.apiServer = &http.Server{Handler: uaRouter}
}

// unrecognizedCallHandler handles calls to unknown pages (404).
func (srv *Server) unrecognizedCallHandler(w http.ResponseWriter, req *http.Request) {
	http.Error(w, "404 - Refer to API.md", http.StatusNotFound)
}

// writeError an error to the API caller.
func writeError(w http.ResponseWriter, msg string, err int) {
	http.Error(w, msg, err)
}

// writeJSON writes the object to the ResponseWriter. If the encoding fails, an
// error is written instead.
func writeJSON(w http.ResponseWriter, obj interface{}) {
	if json.NewEncoder(w).Encode(obj) != nil {
		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
	}
}

// writeSuccess writes the success json object ({"Success":true}) to the
// ResponseWriter
func writeSuccess(w http.ResponseWriter) {
	writeJSON(w, struct{ Success bool }{true})
}
<|endoftext|>


<commit_before>// Copyright Istio 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 sds implements secret discovery service in NodeAgent.
package sds

import (
	"context"
	"fmt"
	"time"

	"github.com/cenkalti/backoff"
	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	sds "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pilot/pkg/util/sets"
	"istio.io/istio/pilot/pkg/xds"
	v3 "istio.io/istio/pilot/pkg/xds/v3"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/security"
	"istio.io/pkg/log"
)

var sdsServiceLog = log.RegisterScope("sds", "SDS service debugging", 0)

type sdsservice struct {
	st security.SecretManager

	XdsServer *xds.DiscoveryServer
	stop      chan struct{}
}

// Assert we implement the generator interface
var _ model.XdsResourceGenerator = &sdsservice{}

func NewXdsServer(stop chan struct{}, gen model.XdsResourceGenerator) *xds.DiscoveryServer {
	s := xds.NewXDS(stop)
	s.DiscoveryServer.Generators = map[string]model.XdsResourceGenerator{
		v3.SecretType: gen,
	}
	s.DiscoveryServer.ProxyNeedsPush = func(proxy *model.Proxy, req *model.PushRequest) bool {
		// Empty changes means "all"
		if len(req.ConfigsUpdated) == 0 {
			return true
		}
		proxy.RLock()
		wr := proxy.WatchedResources[v3.SecretType]
		proxy.RUnlock()

		if wr == nil {
			return false
		}

		names := sets.NewSet(wr.ResourceNames...)
		found := false
		for name := range model.ConfigsOfKind(req.ConfigsUpdated, gvk.Secret) {
			if names.Contains(name.Name) {
				found = true
			}
		}
		return found
	}
	s.DiscoveryServer.Start(stop)
	return s.DiscoveryServer
}

// newSDSService creates Secret Discovery Service which implements envoy SDS API.
func newSDSService(st security.SecretManager, options *security.Options) *sdsservice {
	ret := &sdsservice{
		st:   st,
		stop: make(chan struct{}),
	}
	ret.XdsServer = NewXdsServer(ret.stop, ret)

	if options.FileMountedCerts {
		return ret
	}

	// Pre-generate workload certificates to improve startup latency and ensure that for OUTPUT_CERTS
	// case we always write a certificate. A workload can technically run without any mTLS/CA
	// configured, in which case this will fail; if it becomes noisy we should disable the entire SDS
	// server in these cases.
	go func() {
		b := backoff.NewExponentialBackOff()
		b.MaxElapsedTime = 0
		for {
			_, err := st.GenerateSecret(security.WorkloadKeyCertResourceName)
			if err == nil {
				break
			}
			sdsServiceLog.Warnf("failed to warm certificate: %v", err)
			select {
			case <-ret.stop:
				return
			case <-time.After(b.NextBackOff()):
			}
		}
		for {
			_, err := st.GenerateSecret(security.RootCertReqResourceName)
			if err == nil {
				break
			}
			sdsServiceLog.Warnf("failed to warm root certificate: %v", err)
			select {
			case <-ret.stop:
				return
			case <-time.After(b.NextBackOff()):
			}
		}
	}()

	return ret
}

func (s *sdsservice) generate(resourceNames []string) (model.Resources, error) {
	resources := model.Resources{}
	for _, resourceName := range resourceNames {
		secret, err := s.st.GenerateSecret(resourceName)
		if err != nil {
			// Typically, in Istiod, we do not return an error for a failure to generate a resource
			// However, here it makes sense, because we are generally streaming a single resource,
			// so sending an error will not cause a single failure to prevent the entire multiplex stream
			// of resources, and failures here are generally due to temporary networking issues to the CA
			// rather than a result of configuration issues, which trigger updates in Istiod when resolved.
			// Instead, we rely on the client to retry (with backoff) on failures.
			return nil, fmt.Errorf("failed to generate secret for %v: %v", resourceName, err)
		}

		res := util.MessageToAny(toEnvoySecret(secret))
		resources = append(resources, &discovery.Resource{
			Name:     resourceName,
			Resource: res,
		})
	}
	return resources, nil
}

// Generate implements the XDS Generator interface. This allows the XDS server to dispatch requests
// for SecretTypeV3 to our server to generate the Envoy response.
func (s *sdsservice) Generate(_ *model.Proxy, _ *model.PushContext, w *model.WatchedResource,
	updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
	// updates.Full indicates we should do a complete push of all updated resources
	// In practice, all pushes should be incremental (ie, if the `default` cert changes we won't push
	// all file certs).
	if updates.Full {
		resp, err := s.generate(w.ResourceNames)
		pushLog(w.ResourceNames, err)
		return resp, model.DefaultXdsLogDetails, err
	}
	names := []string{}
	watched := sets.NewSet(w.ResourceNames...)
	for i := range updates.ConfigsUpdated {
		if i.Kind == gvk.Secret && watched.Contains(i.Name) {
			names = append(names, i.Name)
		}
	}
	resp, err := s.generate(names)
	pushLog(names, err)
	return resp, model.DefaultXdsLogDetails, err
}

// register adds the SDS handle to the grpc server
func (s *sdsservice) register(rpcs *grpc.Server) {
	sds.RegisterSecretDiscoveryServiceServer(rpcs, s)
}

// StreamSecrets serves SDS discovery requests and SDS push requests
func (s *sdsservice) StreamSecrets(stream sds.SecretDiscoveryService_StreamSecretsServer) error {
	return s.XdsServer.Stream(stream)
}

func (s *sdsservice) DeltaSecrets(stream sds.SecretDiscoveryService_DeltaSecretsServer) error {
	return status.Error(codes.Unimplemented, "DeltaSecrets not implemented")
}

func (s *sdsservice) FetchSecrets(ctx context.Context, discReq *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) {
	return nil, status.Error(codes.Unimplemented, "FetchSecrets not implemented")
}

func (s *sdsservice) Close() {
	close(s.stop)
	s.XdsServer.Shutdown()
}

// toEnvoySecret converts a security.SecretItem to an Envoy tls.Secret
func toEnvoySecret(s *security.SecretItem) *tls.Secret {
	secret := &tls.Secret{
		Name: s.ResourceName,
	}

	cfg, ok := model.SdsCertificateConfigFromResourceName(s.ResourceName)
	if s.ResourceName == security.RootCertReqResourceName || (ok && cfg.IsRootCertificate()) {
		secret.Type = &tls.Secret_ValidationContext{
			ValidationContext: &tls.CertificateValidationContext{
				TrustedCa: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.RootCert,
					},
				},
			},
		}
	} else {
		secret.Type = &tls.Secret_TlsCertificate{
			TlsCertificate: &tls.TlsCertificate{
				CertificateChain: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.CertificateChain,
					},
				},
				PrivateKey: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.PrivateKey,
					},
				},
			},
		}
	}

	return secret
}

func pushLog(names []string, err error) {
	if err != nil {
		return
	}
	if len(names) == 1 {
		sdsServiceLog.WithLabels("resource", names[0]).Infof("SDS: PUSH")
	} else {
		sdsServiceLog.WithLabels("resources", len(names)).Infof("SDS: PUSH")
	}
}
<commit_msg>Fix double logging in agent SDS (#33267)<commit_after>// Copyright Istio 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 sds implements secret discovery service in NodeAgent.
package sds

import (
	"context"
	"fmt"
	"time"

	"github.com/cenkalti/backoff"
	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	sds "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pilot/pkg/util/sets"
	"istio.io/istio/pilot/pkg/xds"
	v3 "istio.io/istio/pilot/pkg/xds/v3"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/security"
	"istio.io/pkg/log"
)

var sdsServiceLog = log.RegisterScope("sds", "SDS service debugging", 0)

type sdsservice struct {
	st security.SecretManager

	XdsServer *xds.DiscoveryServer
	stop      chan struct{}
}

// Assert we implement the generator interface
var _ model.XdsResourceGenerator = &sdsservice{}

func NewXdsServer(stop chan struct{}, gen model.XdsResourceGenerator) *xds.DiscoveryServer {
	s := xds.NewXDS(stop)
	s.DiscoveryServer.Generators = map[string]model.XdsResourceGenerator{
		v3.SecretType: gen,
	}
	s.DiscoveryServer.ProxyNeedsPush = func(proxy *model.Proxy, req *model.PushRequest) bool {
		// Empty changes means "all"
		if len(req.ConfigsUpdated) == 0 {
			return true
		}
		proxy.RLock()
		wr := proxy.WatchedResources[v3.SecretType]
		proxy.RUnlock()

		if wr == nil {
			return false
		}

		names := sets.NewSet(wr.ResourceNames...)
		found := false
		for name := range model.ConfigsOfKind(req.ConfigsUpdated, gvk.Secret) {
			if names.Contains(name.Name) {
				found = true
			}
		}
		return found
	}
	s.DiscoveryServer.Start(stop)
	return s.DiscoveryServer
}

// newSDSService creates Secret Discovery Service which implements envoy SDS API.
func newSDSService(st security.SecretManager, options *security.Options) *sdsservice {
	ret := &sdsservice{
		st:   st,
		stop: make(chan struct{}),
	}
	ret.XdsServer = NewXdsServer(ret.stop, ret)

	if options.FileMountedCerts {
		return ret
	}

	// Pre-generate workload certificates to improve startup latency and ensure that for OUTPUT_CERTS
	// case we always write a certificate. A workload can technically run without any mTLS/CA
	// configured, in which case this will fail; if it becomes noisy we should disable the entire SDS
	// server in these cases.
	go func() {
		b := backoff.NewExponentialBackOff()
		b.MaxElapsedTime = 0
		for {
			_, err := st.GenerateSecret(security.WorkloadKeyCertResourceName)
			if err == nil {
				break
			}
			sdsServiceLog.Warnf("failed to warm certificate: %v", err)
			select {
			case <-ret.stop:
				return
			case <-time.After(b.NextBackOff()):
			}
		}
		for {
			_, err := st.GenerateSecret(security.RootCertReqResourceName)
			if err == nil {
				break
			}
			sdsServiceLog.Warnf("failed to warm root certificate: %v", err)
			select {
			case <-ret.stop:
				return
			case <-time.After(b.NextBackOff()):
			}
		}
	}()

	return ret
}

func (s *sdsservice) generate(resourceNames []string) (model.Resources, error) {
	resources := model.Resources{}
	for _, resourceName := range resourceNames {
		secret, err := s.st.GenerateSecret(resourceName)
		if err != nil {
			// Typically, in Istiod, we do not return an error for a failure to generate a resource
			// However, here it makes sense, because we are generally streaming a single resource,
			// so sending an error will not cause a single failure to prevent the entire multiplex stream
			// of resources, and failures here are generally due to temporary networking issues to the CA
			// rather than a result of configuration issues, which trigger updates in Istiod when resolved.
			// Instead, we rely on the client to retry (with backoff) on failures.
			return nil, fmt.Errorf("failed to generate secret for %v: %v", resourceName, err)
		}

		res := util.MessageToAny(toEnvoySecret(secret))
		resources = append(resources, &discovery.Resource{
			Name:     resourceName,
			Resource: res,
		})
	}
	return resources, nil
}

// Generate implements the XDS Generator interface. This allows the XDS server to dispatch requests
// for SecretTypeV3 to our server to generate the Envoy response.
func (s *sdsservice) Generate(_ *model.Proxy, _ *model.PushContext, w *model.WatchedResource,
	updates *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
	// updates.Full indicates we should do a complete push of all updated resources
	// In practice, all pushes should be incremental (ie, if the `default` cert changes we won't push
	// all file certs).
	if updates.Full {
		resp, err := s.generate(w.ResourceNames)
		return resp, pushLog(w.ResourceNames), err
	}
	names := []string{}
	watched := sets.NewSet(w.ResourceNames...)
	for i := range updates.ConfigsUpdated {
		if i.Kind == gvk.Secret && watched.Contains(i.Name) {
			names = append(names, i.Name)
		}
	}
	resp, err := s.generate(names)
	return resp, pushLog(names), err
}

// register adds the SDS handle to the grpc server
func (s *sdsservice) register(rpcs *grpc.Server) {
	sds.RegisterSecretDiscoveryServiceServer(rpcs, s)
}

// StreamSecrets serves SDS discovery requests and SDS push requests
func (s *sdsservice) StreamSecrets(stream sds.SecretDiscoveryService_StreamSecretsServer) error {
	return s.XdsServer.Stream(stream)
}

func (s *sdsservice) DeltaSecrets(stream sds.SecretDiscoveryService_DeltaSecretsServer) error {
	return status.Error(codes.Unimplemented, "DeltaSecrets not implemented")
}

func (s *sdsservice) FetchSecrets(ctx context.Context, discReq *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) {
	return nil, status.Error(codes.Unimplemented, "FetchSecrets not implemented")
}

func (s *sdsservice) Close() {
	close(s.stop)
	s.XdsServer.Shutdown()
}

// toEnvoySecret converts a security.SecretItem to an Envoy tls.Secret
func toEnvoySecret(s *security.SecretItem) *tls.Secret {
	secret := &tls.Secret{
		Name: s.ResourceName,
	}

	cfg, ok := model.SdsCertificateConfigFromResourceName(s.ResourceName)
	if s.ResourceName == security.RootCertReqResourceName || (ok && cfg.IsRootCertificate()) {
		secret.Type = &tls.Secret_ValidationContext{
			ValidationContext: &tls.CertificateValidationContext{
				TrustedCa: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.RootCert,
					},
				},
			},
		}
	} else {
		secret.Type = &tls.Secret_TlsCertificate{
			TlsCertificate: &tls.TlsCertificate{
				CertificateChain: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.CertificateChain,
					},
				},
				PrivateKey: &core.DataSource{
					Specifier: &core.DataSource_InlineBytes{
						InlineBytes: s.PrivateKey,
					},
				},
			},
		}
	}

	return secret
}

func pushLog(names []string) model.XdsLogDetails {
	if len(names) == 1 {
		// For common case of single resource, show which resource it was
		return model.XdsLogDetails{AdditionalInfo: "resource:" + names[0]}
	}
	return model.DefaultXdsLogDetails
}
<|endoftext|>