<commit_before>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

package auth0

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"github.com/lestrrat-go/jwx/jwt"
	"github.com/pkg/browser"
	"github.com/vespa-engine/vespa/client/go/auth"
	"github.com/vespa-engine/vespa/client/go/util"
)

const accessTokenExpThreshold = 5 * time.Minute

var errUnauthenticated = errors.New("not logged in. Try 'vespa auth login'")

type configJsonFormat struct {
	Version   int       `json:"version"`
	Providers providers `json:"providers"`
}

type providers struct {
	Config config `json:"auth0"`
}

type config struct {
	Version int                `json:"version"`
	Systems map[string]*System `json:"systems"`
}

type System struct {
	Name        string    `json:"-"`
	AccessToken string    `json:"access_token,omitempty"`
	Scopes      []string  `json:"scopes,omitempty"`
	ExpiresAt   time.Time `json:"expires_at"`
}

type Auth0 struct {
	Authenticator *auth.Authenticator
	system        string
	systemApiUrl  string
	initOnce      sync.Once
	errOnce       error
	Path          string
	config        config
}

type authCfg struct {
	Audience           string `json:"audience"`
	ClientID           string `json:"client-id"`
	DeviceCodeEndpoint string `json:"device-code-endpoint"`
	OauthTokenEndpoint string `json:"oauth-token-endpoint"`
}

func ContextWithCancel() context.Context {
	ctx, cancel := context.WithCancel(context.Background())
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	go func() {
		<-ch
		defer cancel()
		os.Exit(0)
	}()
	return ctx
}

// GetAuth0 will try to initialize the config context, as well as figure out if
// there's a readily available system.
func GetAuth0(configPath string, systemName string, systemApiUrl string) (*Auth0, error) {
	a := Auth0{}
	a.Path = configPath
	a.system = systemName
	a.systemApiUrl = systemApiUrl
	c, err := a.getDeviceFlowConfig()
	if err != nil {
		return nil, fmt.Errorf("cannot get auth config: %w", err)
	}
	a.Authenticator = &auth.Authenticator{
		Audience:           c.Audience,
		ClientID:           c.ClientID,
		DeviceCodeEndpoint: c.DeviceCodeEndpoint,
		OauthTokenEndpoint: c.OauthTokenEndpoint,
	}
	return &a, nil
}

func (a *Auth0) getDeviceFlowConfig() (authCfg, error) {
	systemApiUrl, _ := url.Parse(a.systemApiUrl + "/auth0/v1/device-flow-config")
	r, err := http.Get(systemApiUrl.String())
	if err != nil {
		return authCfg{}, fmt.Errorf("cannot get auth config: %w", err)
	}
	defer r.Body.Close()
	var res authCfg
	err = json.NewDecoder(r.Body).Decode(&res)
	if err != nil {
		return authCfg{}, fmt.Errorf("cannot decode response: %w", err)
	}
	return res, nil
}

// IsLoggedIn encodes the domain logic for determining whether we're
// logged in. This might check our config storage, or just in memory.
func (a *Auth0) IsLoggedIn() bool {
	// No need to check errors for initializing context.
	_ = a.init()

	if a.system == "" {
		return false
	}

	// Parse the access token for the system.
	token, err := jwt.ParseString(a.config.Systems[a.system].AccessToken)
	if err != nil {
		return false
	}

	// Check if token is valid.
	// TODO: Choose issuer based on system
	if err = jwt.Validate(token, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
		return false
	}

	return true
}

// PrepareSystem loads the System, refreshing its token if necessary.
// The System access token needs a refresh if:
// 1. the System scopes are different from the currently required scopes - (auth0 changes).
// 2. the access token is expired.
func (a *Auth0) PrepareSystem(ctx context.Context) (*System, error) {
	if err := a.init(); err != nil {
		return nil, err
	}
	s, err := a.getSystem()
	if err != nil {
		return nil, err
	}

	if s.AccessToken == "" || scopesChanged(s) {
		s, err = RunLogin(ctx, a, true)
		if err != nil {
			return nil, err
		}
	} else if isExpired(s.ExpiresAt, accessTokenExpThreshold) {
		// check if the stored access token is expired:
		// use the refresh token to get a new access token:
		tr := &auth.TokenRetriever{
			Authenticator: a.Authenticator,
			Secrets:       &auth.Keyring{},
			Client:        http.DefaultClient,
		}

		res, err := tr.Refresh(ctx, a.system)
		if err != nil {
			// ask and guide the user through the login process:
			fmt.Println(fmt.Errorf("failed to renew access token, %s", err))
			fmt.Print("\n")
			s, err = RunLogin(ctx, a, true)
			if err != nil {
				return nil, err
			}
		} else {
			// persist the updated system with renewed access token
			s.AccessToken = res.AccessToken
			s.ExpiresAt = time.Now().Add(
				time.Duration(res.ExpiresIn) * time.Second,
			)

			err = a.AddSystem(s)
			if err != nil {
				return nil, err
			}
		}
	}

	return s, nil
}

// isExpired is true if now() + a threshold is after the given date
func isExpired(t time.Time, threshold time.Duration) bool {
	return time.Now().Add(threshold).After(t)
}

// scopesChanged compare the System scopes
// with the currently required scopes.
func scopesChanged(s *System) bool {
	want := auth.RequiredScopes()
	got := s.Scopes

	sort.Strings(want)
	sort.Strings(got)

	if (want == nil) != (got == nil) {
		return true
	}

	if len(want) != len(got) {
		return true
	}

	for i := range s.Scopes {
		if want[i] != got[i] {
			return true
		}
	}

	return false
}

func (a *Auth0) getSystem() (*System, error) {
	if err := a.init(); err != nil {
		return nil, err
	}

	s, ok := a.config.Systems[a.system]
	if !ok {
		return nil, fmt.Errorf("unable to find system: %s; run 'vespa auth login' to configure a new system", a.system)
	}

	return s, nil
}

// HasSystem checks if the system is configured
// TODO: Used to print deprecation warning if we fall back to use tenant API key.
//       Remove when this is not longer needed.
func (a *Auth0) HasSystem() bool {
	if _, err := a.getSystem(); err != nil {
		return false
	}
	return true
}

// AddSystem assigns an existing, or new System. This is expected to be called
// after a login has completed.
func (a *Auth0) AddSystem(s *System) error {
	_ = a.init()

	// If we're dealing with an empty file, we'll need to initialize this map.
	if a.config.Systems == nil {
		a.config.Systems = map[string]*System{}
	}

	a.config.Systems[a.system] = s

	if err := a.persistConfig(); err != nil {
		return fmt.Errorf("unexpected error persisting config: %w", err)
	}

	return nil
}

func (a *Auth0) removeSystem(s string) error {
	_ = a.init()

	// If we're dealing with an empty file, we'll need to initialize this map.
	if a.config.Systems == nil {
		a.config.Systems = map[string]*System{}
	}

	delete(a.config.Systems, s)

	if err := a.persistConfig(); err != nil {
		return fmt.Errorf("unexpected error persisting config: %w", err)
	}

	tr := &auth.TokenRetriever{Secrets: &auth.Keyring{}}
	if err := tr.Delete(s); err != nil {
		return fmt.Errorf("unexpected error clearing system information: %w", err)
	}

	return nil
}

func (a *Auth0) persistConfig() error {
	dir := filepath.Dir(a.Path)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		if err := os.MkdirAll(dir, 0700); err != nil {
			return err
		}
	}

	buf, err := a.configToJson(&a.config)
	if err != nil {
		return err
	}

	if err := ioutil.WriteFile(a.Path, buf, 0600); err != nil {
		return err
	}

	return nil
}

func (a *Auth0) configToJson(cfg *config) ([]byte, error) {
	cfg.Version = 1
	r := configJsonFormat{
		Version: 1,
		Providers: providers{
			Config: *cfg,
		},
	}
	return json.MarshalIndent(r, "", "    ")
}

func (a *Auth0) jsonToConfig(buf []byte) (*config, error) {
	r := configJsonFormat{}
	if err := json.Unmarshal(buf, &r); err != nil {
		return nil, err
	}
	cfg := r.Providers.Config
	if cfg.Systems != nil {
		for n, s := range cfg.Systems {
			s.Name = n
		}
	}
	return &cfg, nil
}

func (a *Auth0) init() error {
	a.initOnce.Do(func() {
		if a.errOnce = a.initContext(); a.errOnce != nil {
			return
		}
	})
	return a.errOnce
}

func (a *Auth0) initContext() (err error) {
	if _, err := os.Stat(a.Path); os.IsNotExist(err) {
		return errUnauthenticated
	}

	var buf []byte
	if buf, err = ioutil.ReadFile(a.Path); err != nil {
		return err
	}

	cfg, err := a.jsonToConfig(buf)
	if err != nil {
		return err
	}
	a.config = *cfg
	return nil
}

// RunLogin runs the login flow guiding the user through the process
// by showing the login instructions, opening the browser.
// Use `expired` to run the login from other commands setup:
// this will only affect the messages.
func RunLogin(ctx context.Context, a *Auth0, expired bool) (*System, error) {
	if expired {
		fmt.Println("Please sign in to re-authorize the CLI.")
	}

	state, err := a.Authenticator.Start(ctx)
	if err != nil {
		return nil, fmt.Errorf("could not start the authentication process: %w", err)
	}

	fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)

	fmt.Println("If you prefer, you can open the URL directly for verification")
	fmt.Printf("Your Verification URL: %s\n\n", state.VerificationURI)

	fmt.Println("Press Enter to open the browser to log in or ^C to quit...")
	fmt.Scanln()

	err = browser.OpenURL(state.VerificationURI)

	if err != nil {
		fmt.Printf("Couldn't open the URL, please do it manually: %s.", state.VerificationURI)
	}

	var res auth.Result
	err = util.Spinner(os.Stderr, "Waiting for login to complete in browser ...", func() error {
		res, err = a.Authenticator.Wait(ctx, state)
		return err
	})

	if err != nil {
		return nil, fmt.Errorf("login error: %w", err)
	}

	fmt.Print("\n")
	fmt.Println("Successfully logged in.")
	fmt.Print("\n")

	// store the refresh token
	secretsStore := &auth.Keyring{}
	err = secretsStore.Set(auth.SecretsNamespace, a.system, res.RefreshToken)
	if err != nil {
		// log the error but move on
		fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
	}

	s := System{
		Name:        a.system,
		AccessToken: res.AccessToken,
		ExpiresAt:   time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
		Scopes:      auth.RequiredScopes(),
	}
	err = a.AddSystem(&s)
	if err != nil {
		return nil, fmt.Errorf("could not add system to config: %w", err)
	}

	return &s, nil
}

func RunLogout(a *Auth0) error {
	s, err := a.getSystem()
	if err != nil {
		return err
	}

	if err := a.removeSystem(s.Name); err != nil {
		return err
	}

	fmt.Print("\n")
	fmt.Println("Successfully logged out.")
	fmt.Print("\n")

	return nil
}
<commit_msg>Avoid implicit re-authentication as this breaks spinner animation<commit_after>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

package auth0

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"github.com/lestrrat-go/jwx/jwt"
	"github.com/pkg/browser"
	"github.com/vespa-engine/vespa/client/go/auth"
	"github.com/vespa-engine/vespa/client/go/util"
)

const accessTokenExpThreshold = 5 * time.Minute

var errUnauthenticated = errors.New("not logged in. Try 'vespa auth login'")

type configJsonFormat struct {
	Version   int       `json:"version"`
	Providers providers `json:"providers"`
}

type providers struct {
	Config config `json:"auth0"`
}

type config struct {
	Version int                `json:"version"`
	Systems map[string]*System `json:"systems"`
}

type System struct {
	Name        string    `json:"-"`
	AccessToken string    `json:"access_token,omitempty"`
	Scopes      []string  `json:"scopes,omitempty"`
	ExpiresAt   time.Time `json:"expires_at"`
}

type Auth0 struct {
	Authenticator *auth.Authenticator
	system        string
	systemApiUrl  string
	initOnce      sync.Once
	errOnce       error
	Path          string
	config        config
}

type authCfg struct {
	Audience           string `json:"audience"`
	ClientID           string `json:"client-id"`
	DeviceCodeEndpoint string `json:"device-code-endpoint"`
	OauthTokenEndpoint string `json:"oauth-token-endpoint"`
}

func ContextWithCancel() context.Context {
	ctx, cancel := context.WithCancel(context.Background())
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	go func() {
		<-ch
		defer cancel()
		os.Exit(0)
	}()
	return ctx
}

// GetAuth0 will try to initialize the config context, as well as figure out if
// there's a readily available system.
func GetAuth0(configPath string, systemName string, systemApiUrl string) (*Auth0, error) {
	a := Auth0{}
	a.Path = configPath
	a.system = systemName
	a.systemApiUrl = systemApiUrl
	c, err := a.getDeviceFlowConfig()
	if err != nil {
		return nil, fmt.Errorf("cannot get auth config: %w", err)
	}
	a.Authenticator = &auth.Authenticator{
		Audience:           c.Audience,
		ClientID:           c.ClientID,
		DeviceCodeEndpoint: c.DeviceCodeEndpoint,
		OauthTokenEndpoint: c.OauthTokenEndpoint,
	}
	return &a, nil
}

func (a *Auth0) getDeviceFlowConfig() (authCfg, error) {
	systemApiUrl, _ := url.Parse(a.systemApiUrl + "/auth0/v1/device-flow-config")
	r, err := http.Get(systemApiUrl.String())
	if err != nil {
		return authCfg{}, fmt.Errorf("cannot get auth config: %w", err)
	}
	defer r.Body.Close()
	var res authCfg
	err = json.NewDecoder(r.Body).Decode(&res)
	if err != nil {
		return authCfg{}, fmt.Errorf("cannot decode response: %w", err)
	}
	return res, nil
}

// IsLoggedIn encodes the domain logic for determining whether we're
// logged in. This might check our config storage, or just in memory.
func (a *Auth0) IsLoggedIn() bool {
	// No need to check errors for initializing context.
	_ = a.init()

	if a.system == "" {
		return false
	}

	// Parse the access token for the system.
	token, err := jwt.ParseString(a.config.Systems[a.system].AccessToken)
	if err != nil {
		return false
	}

	// Check if token is valid.
	// TODO: Choose issuer based on system
	if err = jwt.Validate(token, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil {
		return false
	}

	return true
}

// PrepareSystem loads the System, refreshing its token if necessary.
// The System access token needs a refresh if:
// 1. the System scopes are different from the currently required scopes - (auth0 changes).
// 2. the access token is expired.
func (a *Auth0) PrepareSystem(ctx context.Context) (*System, error) {
	if err := a.init(); err != nil {
		return nil, err
	}
	s, err := a.getSystem()
	if err != nil {
		return nil, err
	}

	if s.AccessToken == "" || scopesChanged(s) {
		s, err = RunLogin(ctx, a, true)
		if err != nil {
			return nil, err
		}
	} else if isExpired(s.ExpiresAt, accessTokenExpThreshold) {
		// check if the stored access token is expired:
		// use the refresh token to get a new access token:
		tr := &auth.TokenRetriever{
			Authenticator: a.Authenticator,
			Secrets:       &auth.Keyring{},
			Client:        http.DefaultClient,
		}

		res, err := tr.Refresh(ctx, a.system)
		if err != nil {
			return nil, fmt.Errorf("failed to renew access token: %w: %s", err, "re-authenticate with 'vespa auth login'")
		} else {
			// persist the updated system with renewed access token
			s.AccessToken = res.AccessToken
			s.ExpiresAt = time.Now().Add(
				time.Duration(res.ExpiresIn) * time.Second,
			)

			err = a.AddSystem(s)
			if err != nil {
				return nil, err
			}
		}
	}

	return s, nil
}

// isExpired is true if now() + a threshold is after the given date
func isExpired(t time.Time, threshold time.Duration) bool {
	return time.Now().Add(threshold).After(t)
}

// scopesChanged compare the System scopes
// with the currently required scopes.
func scopesChanged(s *System) bool {
	want := auth.RequiredScopes()
	got := s.Scopes

	sort.Strings(want)
	sort.Strings(got)

	if (want == nil) != (got == nil) {
		return true
	}

	if len(want) != len(got) {
		return true
	}

	for i := range s.Scopes {
		if want[i] != got[i] {
			return true
		}
	}

	return false
}

func (a *Auth0) getSystem() (*System, error) {
	if err := a.init(); err != nil {
		return nil, err
	}

	s, ok := a.config.Systems[a.system]
	if !ok {
		return nil, fmt.Errorf("unable to find system: %s; run 'vespa auth login' to configure a new system", a.system)
	}

	return s, nil
}

// HasSystem checks if the system is configured
// TODO: Used to print deprecation warning if we fall back to use tenant API key.
//       Remove when this is not longer needed.
func (a *Auth0) HasSystem() bool {
	if _, err := a.getSystem(); err != nil {
		return false
	}
	return true
}

// AddSystem assigns an existing, or new System. This is expected to be called
// after a login has completed.
func (a *Auth0) AddSystem(s *System) error {
	_ = a.init()

	// If we're dealing with an empty file, we'll need to initialize this map.
	if a.config.Systems == nil {
		a.config.Systems = map[string]*System{}
	}

	a.config.Systems[a.system] = s

	if err := a.persistConfig(); err != nil {
		return fmt.Errorf("unexpected error persisting config: %w", err)
	}

	return nil
}

func (a *Auth0) removeSystem(s string) error {
	_ = a.init()

	// If we're dealing with an empty file, we'll need to initialize this map.
	if a.config.Systems == nil {
		a.config.Systems = map[string]*System{}
	}

	delete(a.config.Systems, s)

	if err := a.persistConfig(); err != nil {
		return fmt.Errorf("unexpected error persisting config: %w", err)
	}

	tr := &auth.TokenRetriever{Secrets: &auth.Keyring{}}
	if err := tr.Delete(s); err != nil {
		return fmt.Errorf("unexpected error clearing system information: %w", err)
	}

	return nil
}

func (a *Auth0) persistConfig() error {
	dir := filepath.Dir(a.Path)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		if err := os.MkdirAll(dir, 0700); err != nil {
			return err
		}
	}

	buf, err := a.configToJson(&a.config)
	if err != nil {
		return err
	}

	if err := ioutil.WriteFile(a.Path, buf, 0600); err != nil {
		return err
	}

	return nil
}

func (a *Auth0) configToJson(cfg *config) ([]byte, error) {
	cfg.Version = 1
	r := configJsonFormat{
		Version: 1,
		Providers: providers{
			Config: *cfg,
		},
	}
	return json.MarshalIndent(r, "", "    ")
}

func (a *Auth0) jsonToConfig(buf []byte) (*config, error) {
	r := configJsonFormat{}
	if err := json.Unmarshal(buf, &r); err != nil {
		return nil, err
	}
	cfg := r.Providers.Config
	if cfg.Systems != nil {
		for n, s := range cfg.Systems {
			s.Name = n
		}
	}
	return &cfg, nil
}

func (a *Auth0) init() error {
	a.initOnce.Do(func() {
		if a.errOnce = a.initContext(); a.errOnce != nil {
			return
		}
	})
	return a.errOnce
}

func (a *Auth0) initContext() (err error) {
	if _, err := os.Stat(a.Path); os.IsNotExist(err) {
		return errUnauthenticated
	}

	var buf []byte
	if buf, err = ioutil.ReadFile(a.Path); err != nil {
		return err
	}

	cfg, err := a.jsonToConfig(buf)
	if err != nil {
		return err
	}
	a.config = *cfg
	return nil
}

// RunLogin runs the login flow guiding the user through the process
// by showing the login instructions, opening the browser.
// Use `expired` to run the login from other commands setup:
// this will only affect the messages.
func RunLogin(ctx context.Context, a *Auth0, expired bool) (*System, error) {
	if expired {
		fmt.Println("Please sign in to re-authorize the CLI.")
	}

	state, err := a.Authenticator.Start(ctx)
	if err != nil {
		return nil, fmt.Errorf("could not start the authentication process: %w", err)
	}

	fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)

	fmt.Println("If you prefer, you can open the URL directly for verification")
	fmt.Printf("Your Verification URL: %s\n\n", state.VerificationURI)

	fmt.Println("Press Enter to open the browser to log in or ^C to quit...")
	fmt.Scanln()

	err = browser.OpenURL(state.VerificationURI)

	if err != nil {
		fmt.Printf("Couldn't open the URL, please do it manually: %s.", state.VerificationURI)
	}

	var res auth.Result
	err = util.Spinner(os.Stderr, "Waiting for login to complete in browser ...", func() error {
		res, err = a.Authenticator.Wait(ctx, state)
		return err
	})

	if err != nil {
		return nil, fmt.Errorf("login error: %w", err)
	}

	fmt.Print("\n")
	fmt.Println("Successfully logged in.")
	fmt.Print("\n")

	// store the refresh token
	secretsStore := &auth.Keyring{}
	err = secretsStore.Set(auth.SecretsNamespace, a.system, res.RefreshToken)
	if err != nil {
		// log the error but move on
		fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
	}

	s := System{
		Name:        a.system,
		AccessToken: res.AccessToken,
		ExpiresAt:   time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
		Scopes:      auth.RequiredScopes(),
	}
	err = a.AddSystem(&s)
	if err != nil {
		return nil, fmt.Errorf("could not add system to config: %w", err)
	}

	return &s, nil
}

func RunLogout(a *Auth0) error {
	s, err := a.getSystem()
	if err != nil {
		return err
	}

	if err := a.removeSystem(s.Name); err != nil {
		return err
	}

	fmt.Print("\n")
	fmt.Println("Successfully logged out.")
	fmt.Print("\n")

	return nil
}
<|endoftext|>


<commit_before>package wal

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
)

type WriteOptions struct {
	MaxSize     int64
	MaxSegments int
}

var DefaultWriteOptions = WriteOptions{
	MaxSize:     16 * (1024 * 1024),
	MaxSegments: 10,
}

type tagCache struct {
	Tags map[string]Position `json:"tags"`
}

type WAL struct {
	opts WriteOptions

	lock    sync.Mutex
	root    string
	current string

	first int
	index int

	segment *SegmentWriter

	cache     tagCache
	cacheFile *os.File
	cacheEnc  *json.Encoder
}

func rangeSegments(path string) (int, int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	files, err := f.Readdirnames(-1)
	if err != nil {
		return 0, 0, err
	}

	var (
		first = -1
		last  = -1
	)

	for _, file := range files {
		i, err := strconv.Atoi(file)
		if err == nil {
			if first == -1 || i < first {
				first = i
			}

			if last == -1 || i > last {
				last = i
			}
		}
	}

	return first, last, nil
}

func New(root string) (*WAL, error) {
	return NewWithOptions(root, DefaultWriteOptions)
}

func NewWithOptions(root string, opts WriteOptions) (*WAL, error) {
	err := os.Mkdir(root, 0755)
	if err != nil {
		if !os.IsExist(err) {
			return nil, err
		}
	}

	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if last == -1 {
		last = 0
	}

	cache, err := os.Create(filepath.Join(root, "tags"))
	if err != nil {
		return nil, err
	}

	wal := &WAL{
		root:      root,
		current:   filepath.Join(root, fmt.Sprintf("%d", last)),
		first:     first,
		index:     last,
		opts:      opts,
		cacheFile: cache,
		cacheEnc:  json.NewEncoder(cache),
	}

	wal.cache.Tags = make(map[string]Position)

	seg, err := OpenSegment(wal.current)
	if err != nil {
		return nil, err
	}

	wal.segment = seg

	return wal, nil
}

func (wal *WAL) rotateSegment() error {
	err := wal.segment.Close()
	if err != nil {
		return err
	}

	wal.index++

	wal.current = filepath.Join(wal.root, fmt.Sprintf("%d", wal.index))

	seg, err := OpenSegment(wal.current)
	if err != nil {
		return err
	}

	wal.segment = seg

	return nil
}

func (wal *WAL) pruneSegments(total int) error {
	startAt := wal.index - total

	for i := startAt; i >= wal.first; i-- {
		err := os.Remove(filepath.Join(wal.root, fmt.Sprintf("%d", i)))
		if err != nil {
			return err
		}
	}

	return nil
}

const averageOverhead = 4 + 1 + 2

func (wal *WAL) Write(data []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	newSize := int64(len(data)) + averageOverhead + wal.segment.Size()

	if newSize > wal.opts.MaxSize {
		err := wal.rotateSegment()
		if err != nil {
			return err
		}

		err = wal.pruneSegments(wal.opts.MaxSegments)
		if err != nil {
			return err
		}
	}

	_, err := wal.segment.Write(data)
	return err
}

type Position struct {
	Segment int   `json:"segment"`
	Offset  int64 `json:"offset"`
}

func (wal *WAL) Pos() (Position, error) {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	pos := wal.segment.Pos()

	return Position{wal.index, pos}, nil
}

func (wal *WAL) WriteTag(tag []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	// We truncate the cache and rewrite it after the segment
	// has confirmed the tag so the cache is either absent
	// or correct, never present but out of date.
	truncErr := wal.cacheFile.Truncate(0)

	segPos := wal.segment.Pos()

	err := wal.segment.WriteTag(tag)
	if err != nil {
		return err
	}

	if truncErr == nil {
		key := base64.URLEncoding.EncodeToString(tag)
		wal.cache.Tags[key] = Position{wal.index, segPos}

		err = wal.cacheEnc.Encode(&wal.cache)
		if err == nil {
			wal.cacheFile.Sync()
		}
	}

	return nil
}

func (wal *WAL) Close() error {
	return wal.segment.Close()
}

type WALReader struct {
	root    string
	current string

	first int
	last  int
	index int

	seg *SegmentReader

	err error
}

var ErrNoSegments = errors.New("no segments")

func NewReader(root string) (*WALReader, error) {
	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if first == -1 {
		return nil, ErrNoSegments
	}

	cur := filepath.Join(root, fmt.Sprintf("%d", first))

	r, err := NewSegmentReader(cur)
	if err != nil {
		return nil, err
	}

	return &WALReader{
		root:    root,
		current: cur,
		first:   first,
		last:    last,
		index:   first,
		seg:     r,
	}, nil
}

func (wal *WALReader) Seek(p Position) error {
	path := filepath.Join(wal.root, fmt.Sprintf("%d", p.Segment))

	seg, err := NewSegmentReader(path)
	if err != nil {
		return err
	}

	err = seg.Seek(p.Offset)
	if err != nil {
		return err
	}

	wal.seg.Close()

	wal.index = p.Segment
	wal.seg = seg

	return nil
}

func (wal *WALReader) SeekTag(tag []byte) (Position, error) {
	lastPos := Position{-1, -1}

	index := wal.first

	for {
		path := filepath.Join(wal.root, fmt.Sprintf("%d", index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			if os.IsNotExist(err) {
				return lastPos, nil
			}

			return lastPos, err
		}

		wal.seg = seg

		pos, err := seg.SeekTag(tag)
		if err != nil {
			return lastPos, err
		}

		lastPos = Position{index, pos}

		index++
	}

	return lastPos, nil
}

func (r *WALReader) Close() error {
	if r.seg == nil {
		return nil
	}

	return r.seg.Close()
}

func (r *WALReader) Next() bool {
	if r.seg == nil {
		return false
	}

	if r.seg.Next() {
		return true
	}

	r.seg.Close()
	r.seg = nil

	for {
		r.index++
		if r.index > r.last {
			return false
		}

		path := filepath.Join(r.root, fmt.Sprintf("%d", r.index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			r.err = err
			return false
		}

		if seg.Next() {
			r.seg = seg
			break
		}
	}

	return true
}

func (r *WALReader) Value() []byte {
	if r.seg == nil {
		return nil
	}

	return r.seg.Value()
}

func (r *WALReader) Error() error {
	if r.err != nil {
		return r.err
	}

	if r.seg != nil {
		return r.seg.Error()
	}

	return nil
}
<commit_msg>WAL => WALWriter<commit_after>package wal

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
)

type WriteOptions struct {
	MaxSize     int64
	MaxSegments int
}

var DefaultWriteOptions = WriteOptions{
	MaxSize:     16 * (1024 * 1024),
	MaxSegments: 10,
}

type tagCache struct {
	Tags map[string]Position `json:"tags"`
}

type WALWriter struct {
	opts WriteOptions

	lock    sync.Mutex
	root    string
	current string

	first int
	index int

	segment *SegmentWriter

	cache     tagCache
	cacheFile *os.File
	cacheEnc  *json.Encoder
}

func rangeSegments(path string) (int, int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	files, err := f.Readdirnames(-1)
	if err != nil {
		return 0, 0, err
	}

	var (
		first = -1
		last  = -1
	)

	for _, file := range files {
		i, err := strconv.Atoi(file)
		if err == nil {
			if first == -1 || i < first {
				first = i
			}

			if last == -1 || i > last {
				last = i
			}
		}
	}

	return first, last, nil
}

func New(root string) (*WALWriter, error) {
	return NewWithOptions(root, DefaultWriteOptions)
}

func NewWithOptions(root string, opts WriteOptions) (*WALWriter, error) {
	err := os.Mkdir(root, 0755)
	if err != nil {
		if !os.IsExist(err) {
			return nil, err
		}
	}

	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if last == -1 {
		last = 0
	}

	if first == -1 {
		first = 0
	}

	cache, err := os.Create(filepath.Join(root, "tags"))
	if err != nil {
		return nil, err
	}

	wal := &WALWriter{
		root:      root,
		current:   filepath.Join(root, fmt.Sprintf("%d", last)),
		first:     first,
		index:     last,
		opts:      opts,
		cacheFile: cache,
		cacheEnc:  json.NewEncoder(cache),
	}

	wal.cache.Tags = make(map[string]Position)

	seg, err := OpenSegment(wal.current)
	if err != nil {
		return nil, err
	}

	wal.segment = seg

	return wal, nil
}

func (wal *WALWriter) rotateSegment() error {
	err := wal.segment.Close()
	if err != nil {
		return err
	}

	wal.index++

	wal.current = filepath.Join(wal.root, fmt.Sprintf("%d", wal.index))

	seg, err := OpenSegment(wal.current)
	if err != nil {
		return err
	}

	wal.segment = seg

	return nil
}

func (wal *WALWriter) pruneSegments(total int) error {
	startAt := wal.index - total

	for i := startAt; i >= wal.first; i-- {
		err := os.Remove(filepath.Join(wal.root, fmt.Sprintf("%d", i)))
		if err != nil {
			return err
		}
	}

	return nil
}

const averageOverhead = 4 + 1 + 2

func (wal *WALWriter) Write(data []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	newSize := int64(len(data)) + averageOverhead + wal.segment.Size()

	if newSize > wal.opts.MaxSize {
		err := wal.rotateSegment()
		if err != nil {
			return err
		}

		err = wal.pruneSegments(wal.opts.MaxSegments)
		if err != nil {
			return err
		}
	}

	_, err := wal.segment.Write(data)
	return err
}

type Position struct {
	Segment int   `json:"segment"`
	Offset  int64 `json:"offset"`
}

func (wal *WALWriter) Pos() (Position, error) {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	pos := wal.segment.Pos()

	return Position{wal.index, pos}, nil
}

func (wal *WALWriter) WriteTag(tag []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	// We truncate the cache and rewrite it after the segment
	// has confirmed the tag so the cache is either absent
	// or correct, never present but out of date.
	truncErr := wal.cacheFile.Truncate(0)

	segPos := wal.segment.Pos()

	err := wal.segment.WriteTag(tag)
	if err != nil {
		return err
	}

	if truncErr == nil {
		key := base64.URLEncoding.EncodeToString(tag)
		wal.cache.Tags[key] = Position{wal.index, segPos}

		err = wal.cacheEnc.Encode(&wal.cache)
		if err == nil {
			wal.cacheFile.Sync()
		}
	}

	return nil
}

func (wal *WALWriter) Close() error {
	return wal.segment.Close()
}

type WALReader struct {
	root    string
	current string

	first int
	last  int
	index int

	seg *SegmentReader

	err error
}

var ErrNoSegments = errors.New("no segments")

func NewReader(root string) (*WALReader, error) {
	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if first == -1 {
		return nil, ErrNoSegments
	}

	cur := filepath.Join(root, fmt.Sprintf("%d", first))

	r, err := NewSegmentReader(cur)
	if err != nil {
		return nil, err
	}

	return &WALReader{
		root:    root,
		current: cur,
		first:   first,
		last:    last,
		index:   first,
		seg:     r,
	}, nil
}

func (wal *WALReader) Seek(p Position) error {
	path := filepath.Join(wal.root, fmt.Sprintf("%d", p.Segment))

	seg, err := NewSegmentReader(path)
	if err != nil {
		return err
	}

	err = seg.Seek(p.Offset)
	if err != nil {
		return err
	}

	wal.seg.Close()

	wal.index = p.Segment
	wal.seg = seg

	return nil
}

func (wal *WALReader) SeekTag(tag []byte) (Position, error) {
	lastPos := Position{-1, -1}

	index := wal.first

	for {
		path := filepath.Join(wal.root, fmt.Sprintf("%d", index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			if os.IsNotExist(err) {
				return lastPos, nil
			}

			return lastPos, err
		}

		wal.seg = seg

		pos, err := seg.SeekTag(tag)
		if err != nil {
			return lastPos, err
		}

		lastPos = Position{index, pos}

		index++
	}

	return lastPos, nil
}

func (r *WALReader) Close() error {
	if r.seg == nil {
		return nil
	}

	return r.seg.Close()
}

func (r *WALReader) Next() bool {
	if r.seg == nil {
		return false
	}

	if r.seg.Next() {
		return true
	}

	r.seg.Close()
	r.seg = nil

	for {
		r.index++
		if r.index > r.last {
			return false
		}

		path := filepath.Join(r.root, fmt.Sprintf("%d", r.index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			r.err = err
			return false
		}

		if seg.Next() {
			r.seg = seg
			break
		}
	}

	return true
}

func (r *WALReader) Value() []byte {
	if r.seg == nil {
		return nil
	}

	return r.seg.Value()
}

func (r *WALReader) Error() error {
	if r.err != nil {
		return r.err
	}

	if r.seg != nil {
		return r.seg.Error()
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"errors"
	"net/http"
	"os"
	"os/signal"

	"github.com/issue9/logs"
	"github.com/issue9/middleware"

	"github.com/issue9/web/context"
	"github.com/issue9/web/internal/app"
	"github.com/issue9/web/module"
	"github.com/issue9/web/result"
)

var defaultApp *app.App

// Init 初始化整个应用环境
//
// configDir 表示配置文件的目录；
func Init(configDir string) (err error) {
	if defaultApp != nil {
		return errors.New("不能重复调用 Init")
	}

	defaultApp, err = app.New(configDir)
	return
}

// Grace 指定触发 Shutdown() 的信号，若为空，则任意信号都触发。
//
// 多次调用，则每次指定的信号都会起作用，如果由传递了相同的值，
// 则有可能多次触发 Shutdown()。
//
// NOTE: 传递空值，与不调用，其结果是不同的。
// 若是不调用，则不会处理任何信号；若是传递空值调用，则是处理任何要信号。
func Grace(sig ...os.Signal) {
	go func() {
		signalChannel := make(chan os.Signal)
		signal.Notify(signalChannel, sig...)

		<-signalChannel
		signal.Stop(signalChannel)

		if err := Shutdown(); err != nil {
			logs.Error(err)
		}
	}()
}

// SetMiddleware 设置一个全局的中间件，多次设置，只有最后一次会启作用。
func SetMiddleware(m middleware.Middleware) {
	defaultApp.SetMiddleware(m)
}

// IsDebug 是否处在调试模式
func IsDebug() bool {
	return defaultApp.Debug()
}

// Handler 将当前实例当作一个 http.Handler 返回。一般用于测试。
// 比如在 httptest.NewServer 中使用。
func Handler() (http.Handler, error) {
	return defaultApp.Handler()
}

// Run 运行路由，执行监听程序。
func Run() error {
	return defaultApp.Serve()
}

// Install 执行指定版本的安装功能
func Install(version string) error {
	return defaultApp.Install(version)
}

// Close 关闭服务。
//
// 无论配置文件如果设置，此函数都是直接关闭服务，不会等待。
func Close() error {
	return defaultApp.Close()
}

// Shutdown 关闭所有服务。
//
// 根据配置文件中的配置项，决定当前是直接关闭还是延时之后关闭。
func Shutdown() error {
	return defaultApp.Shutdown()
}

// File 获取配置目录下的文件。
func File(path string) string {
	return defaultApp.File(path)
}

// URL 构建一条完整 URL
func URL(path string) string {
	return defaultApp.URL(path)
}

// Modules 当前系统使用的所有模块信息
func Modules() []*module.Module {
	return defaultApp.Modules()
}

// NewModule 注册一个模块
func NewModule(name, desc string, deps ...string) *module.Module {
	return defaultApp.NewModule(name, desc, deps...)
}

// NewContext 根据当前配置，生成 context.Context 对象，若是出错则 panic
func NewContext(w http.ResponseWriter, r *http.Request) *context.Context {
	return context.New(w, r, logs.ERROR())
}

// NewResult 生成一个 *result.Result 对象
func NewResult(code int) *result.Result {
	return &result.Result{Code: code}
}
<commit_msg>将部分子包类型引用到 web 中，方便调用方使用<commit_after>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"errors"
	"net/http"
	"os"
	"os/signal"

	"github.com/issue9/logs"
	"github.com/issue9/middleware"

	"github.com/issue9/web/context"
	"github.com/issue9/web/internal/app"
	"github.com/issue9/web/module"
	"github.com/issue9/web/result"
)

type (
	// Context 等同于 context.Context，方便调用者使用
	Context = context.Context

	// Result 等同于 result.Result，方便调用者使用
	Result = result.Result

	// Module 等同于 module.Module，方便调用者使用
	Module = module.Module
)

var defaultApp *app.App

// Init 初始化整个应用环境
//
// configDir 表示配置文件的目录；
func Init(configDir string) (err error) {
	if defaultApp != nil {
		return errors.New("不能重复调用 Init")
	}

	defaultApp, err = app.New(configDir)
	return
}

// Grace 指定触发 Shutdown() 的信号，若为空，则任意信号都触发。
//
// 多次调用，则每次指定的信号都会起作用，如果由传递了相同的值，
// 则有可能多次触发 Shutdown()。
//
// NOTE: 传递空值，与不调用，其结果是不同的。
// 若是不调用，则不会处理任何信号；若是传递空值调用，则是处理任何要信号。
func Grace(sig ...os.Signal) {
	go func() {
		signalChannel := make(chan os.Signal)
		signal.Notify(signalChannel, sig...)

		<-signalChannel
		signal.Stop(signalChannel)

		if err := Shutdown(); err != nil {
			logs.Error(err)
		}
	}()
}

// SetMiddleware 设置一个全局的中间件，多次设置，只有最后一次会启作用。
func SetMiddleware(m middleware.Middleware) {
	defaultApp.SetMiddleware(m)
}

// IsDebug 是否处在调试模式
func IsDebug() bool {
	return defaultApp.Debug()
}

// Handler 将当前实例当作一个 http.Handler 返回。一般用于测试。
// 比如在 httptest.NewServer 中使用。
func Handler() (http.Handler, error) {
	return defaultApp.Handler()
}

// Run 运行路由，执行监听程序。
func Run() error {
	return defaultApp.Serve()
}

// Install 执行指定版本的安装功能
func Install(version string) error {
	return defaultApp.Install(version)
}

// Close 关闭服务。
//
// 无论配置文件如果设置，此函数都是直接关闭服务，不会等待。
func Close() error {
	return defaultApp.Close()
}

// Shutdown 关闭所有服务。
//
// 根据配置文件中的配置项，决定当前是直接关闭还是延时之后关闭。
func Shutdown() error {
	return defaultApp.Shutdown()
}

// File 获取配置目录下的文件。
func File(path string) string {
	return defaultApp.File(path)
}

// URL 构建一条完整 URL
func URL(path string) string {
	return defaultApp.URL(path)
}

// Modules 当前系统使用的所有模块信息
func Modules() []*module.Module {
	return defaultApp.Modules()
}

// NewModule 注册一个模块
func NewModule(name, desc string, deps ...string) *Module {
	return defaultApp.NewModule(name, desc, deps...)
}

// NewContext 根据当前配置，生成 context.Context 对象，若是出错则 panic
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
	return context.New(w, r, logs.ERROR())
}

// NewResult 生成一个 *result.Result 对象
func NewResult(code int) *Result {
	return &result.Result{Code: code}
}
<|endoftext|>


<commit_before>package main

// Import formatting and IO libraries
import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

// Define a structure to hold our page
type Page struct {
	Title string
	Body  []byte // IO libs expect a byte slice rather than a string
}

// Add a save method to our Page struct so we can persist our data
// This method's signature reads: "This is a method named save that takes as its receiver p, a pointer to Page . It takes no parameters, and returns a value of type error."
func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

// Load pages too
func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	t, _ := template.ParseFiles(tmpl + ".html")
	t.Execute(w, p)
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/view/"):]
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/edit/"):]
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func main() {
	// p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
	// p1.save()
	// p2, _ := loadPage("TestPage")
	// fmt.Println(string(p2.Body))
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>Save pages.<commit_after>package main

// Import formatting and IO libraries
import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

// Define a structure to hold our page
type Page struct {
	Title string
	Body  []byte // IO libs expect a byte slice rather than a string
}

// Add a save method to our Page struct so we can persist our data
// This method's signature reads: "This is a method named save that takes as its receiver p, a pointer to Page . It takes no parameters, and returns a value of type error."
func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

// Load pages too
func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	t, _ := template.ParseFiles(tmpl + ".html")
	t.Execute(w, p)
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/view/"):]
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/edit/"):]
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/save/"):]
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	p.save()
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func main() {
	// p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
	// p1.save()
	// p2, _ := loadPage("TestPage")
	// fmt.Println(string(p2.Body))
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


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

package pkix

import (
	"encoding/asn1"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

type jsonName struct {
	CommonName         []string
	SerialNumber       []string
	Country            []string
	Locality           []string
	Province           []string
	StreetAddress      []string
	Organization       []string
	OrganizationalUnit []string
	PostalCode         []string
	DomainComponent    []string //technically deprecated, but yolo
	UnknownAttributes  []AttributeTypeAndValue
}

func (jn *jsonName) MarshalJSON() ([]byte, error) {
	enc := make(map[string]interface{})
	if len(jn.CommonName) > 0 {
		enc["common_name"] = jn.CommonName
	}
	if len(jn.SerialNumber) > 0 {
		enc["serial_number"] = jn.SerialNumber
	}
	if len(jn.Country) > 0 {
		enc["country"] = jn.Country
	}
	if len(jn.Locality) > 0 {
		enc["locality"] = jn.Locality
	}
	if len(jn.Province) > 0 {
		enc["province"] = jn.Province
	}
	if len(jn.StreetAddress) > 0 {
		enc["street_address"] = jn.StreetAddress
	}
	if len(jn.Organization) > 0 {
		enc["organization"] = jn.Organization
	}
	if len(jn.OrganizationalUnit) > 0 {
		enc["organizational_unit"] = jn.OrganizationalUnit
	}
	if len(jn.PostalCode) > 0 {
		enc["postal_code"] = jn.PostalCode
	}
	if len(jn.DomainComponent) > 0 {
		enc["domain_component"] = jn.DomainComponent
	}
	for _, a := range jn.UnknownAttributes {
		enc[a.Type.String()] = a.Value
	}
	return json.Marshal(enc)
}

func convertToStrArray(i interface{}) []string {

	var strArray []string

	arr, _ := i.([]interface{})
	for _, val := range arr {
		if str, ok := val.(string); ok {
			strArray = append(strArray, str)
		}
	}

	return strArray
}

func (jn *jsonName) UnmarshalJSON(b []byte) error {
	nameMap := make(map[string]interface{})

	if err := json.Unmarshal(b, &nameMap); err != nil {
		return err
	}

	for key, val := range nameMap {
		switch key {
		case "common_name":
			jn.CommonName = convertToStrArray(val)
		case "serial_number":
			jn.SerialNumber = convertToStrArray(val)
		case "country":
			jn.Country = convertToStrArray(val)
		case "locality":
			jn.Locality = convertToStrArray(val)
		case "province":
			jn.Province = convertToStrArray(val)
		case "street_address":
			jn.StreetAddress = convertToStrArray(val)
		case "organization":
			jn.Organization = convertToStrArray(val)
		case "organizational_unit":
			jn.OrganizationalUnit = convertToStrArray(val)
		case "postal_code":
			jn.PostalCode = convertToStrArray(val)
		case "domain_component":
			jn.DomainComponent = convertToStrArray(val)
		default:
			attributeType := asn1.ObjectIdentifier{}
			valStr, okStr := val.(string)
			if !okStr {
				return fmt.Errorf("Expected string value for field %s, got %T", key, val)
			}

			for _, oidString := range strings.Split(valStr, ".") {
				oidInt, err := strconv.Atoi(oidString)
				if err != nil {
					return err
				}
				attributeType = append(attributeType, oidInt)
			}

			atv := AttributeTypeAndValue{
				Type:  attributeType,
				Value: val,
			}

			jn.UnknownAttributes = append(jn.UnknownAttributes, atv)
		}

	}

	return nil
}

type jsonAttributeTypeAndValue struct {
	Type  string      `json:"type"`
	Value interface{} `json:"value"`
}

func (a *AttributeTypeAndValue) MarshalJSON() ([]byte, error) {
	var enc jsonAttributeTypeAndValue
	enc.Type = a.Type.String()
	enc.Value = a.Value
	return json.Marshal(&enc)
}

type jsonExtension struct {
	Id       string `json:"id"`
	Critical bool   `json:"critical"`
	Value    []byte `json:"value"`
}

func (e *Extension) MarshalJSON() ([]byte, error) {
	ext := jsonExtension{
		Id:       e.Id.String(),
		Critical: e.Critical,
		Value:    e.Value,
	}
	return json.Marshal(ext)
}

type jsonOtherName struct {
	Id    string `json:"id"`
	Value []byte `json:"value"`
}

func (o *OtherName) MarshalJSON() ([]byte, error) {
	oName := jsonOtherName{
		Id:    o.Typeid.String(),
		Value: o.Value.Bytes,
	}
	return json.Marshal(oName)
}

func (o *OtherName) UnmarshalJSON(b []byte) (err error) {
	var oName jsonOtherName

	if err = json.Unmarshal(b, &oName); err != nil {
		return
	}

	arcs := strings.Split(oName.Id, ".")
	oid := make(asn1.ObjectIdentifier, len(arcs))

	for i, s := range arcs {
		var tmp int64
		tmp, err = strconv.ParseInt(s, 10, 32)
		if err != nil {
			return
		}
		oid[i] = int(tmp)
	}
	o.Typeid = oid

	o.Value = asn1.RawValue{
		Tag:        0,
		Class:      asn1.ClassContextSpecific,
		IsCompound: true,
		Bytes:      oName.Value,
	}
	o.Value.FullBytes, err = asn1.Marshal(o.Value)
	if err != nil {
		return
	}
	return nil
}

func (n *Name) MarshalJSON() ([]byte, error) {
	var enc jsonName
	attrs := n.ToRDNSequence()
	for _, attrSet := range attrs {
		for _, a := range attrSet {
			s, _ := a.Value.(string)
			if a.Type.Equal(oidCommonName) {
				enc.CommonName = append(enc.CommonName, s)
			} else if a.Type.Equal(oidSerialNumber) {
				enc.SerialNumber = append(enc.SerialNumber, s)
			} else if a.Type.Equal(oidCountry) {
				enc.Country = append(enc.Country, s)
			} else if a.Type.Equal(oidLocality) {
				enc.Locality = append(enc.Locality, s)
			} else if a.Type.Equal(oidProvince) {
				enc.Province = append(enc.Province, s)
			} else if a.Type.Equal(oidStreetAddress) {
				enc.StreetAddress = append(enc.StreetAddress, s)
			} else if a.Type.Equal(oidOrganization) {
				enc.Organization = append(enc.Organization, s)
			} else if a.Type.Equal(oidOrganizationalUnit) {
				enc.OrganizationalUnit = append(enc.OrganizationalUnit, s)
			} else if a.Type.Equal(oidPostalCode) {
				enc.PostalCode = append(enc.PostalCode, s)
			} else if a.Type.Equal(oidDomainComponent) {
				enc.DomainComponent = append(enc.DomainComponent, s)
			} else {
				enc.UnknownAttributes = append(enc.UnknownAttributes, a)
			}
		}
	}
	return json.Marshal(&enc)
}

func appendATV(names []AttributeTypeAndValue, fieldVals []string, asn1Id asn1.ObjectIdentifier) []AttributeTypeAndValue {
	if len(fieldVals) == 0 {
		return names
	}

	for _, val := range fieldVals {
		names = append(names, AttributeTypeAndValue{Type: asn1Id, Value: val})
	}

	return names
}

func (n *Name) UnmarshalJSON(b []byte) error {
	var jName jsonName

	if err := jName.UnmarshalJSON(b); err != nil {
		return err
	}

	// add everything to names
	n.Names = appendATV(n.Names, jName.Country, oidCountry)
	n.Names = appendATV(n.Names, jName.Organization, oidOrganization)
	n.Names = appendATV(n.Names, jName.OrganizationalUnit, oidOrganizationalUnit)
	n.Names = appendATV(n.Names, jName.Locality, oidLocality)
	n.Names = appendATV(n.Names, jName.Province, oidProvince)
	n.Names = appendATV(n.Names, jName.StreetAddress, oidStreetAddress)
	n.Names = appendATV(n.Names, jName.PostalCode, oidPostalCode)
	n.Names = appendATV(n.Names, jName.DomainComponent, oidDomainComponent)

	// populate specific fields
	n.Country = jName.Country
	n.Organization = jName.Organization
	n.OrganizationalUnit = jName.OrganizationalUnit
	n.Locality = jName.Locality
	n.Province = jName.Province
	n.StreetAddress = jName.StreetAddress
	n.PostalCode = jName.PostalCode
	n.DomainComponent = jName.DomainComponent

	// add first commonNames and serialNumbers to struct and Names
	if len(jName.CommonName) > 0 {
		n.CommonName = jName.CommonName[0]
		n.Names = append(n.Names, AttributeTypeAndValue{Type: oidCommonName, Value: jName.CommonName[0]})
	}
	if len(jName.SerialNumber) > 0 {
		n.SerialNumber = jName.SerialNumber[0]
		n.Names = append(n.Names, AttributeTypeAndValue{Type: oidSerialNumber, Value: jName.SerialNumber[0]})
	}

	// add extra commonNames and serialNumbers to extraNames
	if len(jName.CommonName) > 1 {
		for _, val := range jName.CommonName[1:] {
			n.ExtraNames = append(n.ExtraNames, AttributeTypeAndValue{Type: oidCommonName, Value: val})
		}
	}

	if len(jName.SerialNumber) > 1 {
		for _, val := range jName.SerialNumber[1:] {
			n.ExtraNames = append(n.ExtraNames, AttributeTypeAndValue{Type: oidSerialNumber, Value: val})
		}
	}

	return nil
}
<commit_msg>simplified return logic in UnmarshalJSON for OtherName<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pkix

import (
	"encoding/asn1"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

type jsonName struct {
	CommonName         []string
	SerialNumber       []string
	Country            []string
	Locality           []string
	Province           []string
	StreetAddress      []string
	Organization       []string
	OrganizationalUnit []string
	PostalCode         []string
	DomainComponent    []string //technically deprecated, but yolo
	UnknownAttributes  []AttributeTypeAndValue
}

func (jn *jsonName) MarshalJSON() ([]byte, error) {
	enc := make(map[string]interface{})
	if len(jn.CommonName) > 0 {
		enc["common_name"] = jn.CommonName
	}
	if len(jn.SerialNumber) > 0 {
		enc["serial_number"] = jn.SerialNumber
	}
	if len(jn.Country) > 0 {
		enc["country"] = jn.Country
	}
	if len(jn.Locality) > 0 {
		enc["locality"] = jn.Locality
	}
	if len(jn.Province) > 0 {
		enc["province"] = jn.Province
	}
	if len(jn.StreetAddress) > 0 {
		enc["street_address"] = jn.StreetAddress
	}
	if len(jn.Organization) > 0 {
		enc["organization"] = jn.Organization
	}
	if len(jn.OrganizationalUnit) > 0 {
		enc["organizational_unit"] = jn.OrganizationalUnit
	}
	if len(jn.PostalCode) > 0 {
		enc["postal_code"] = jn.PostalCode
	}
	if len(jn.DomainComponent) > 0 {
		enc["domain_component"] = jn.DomainComponent
	}
	for _, a := range jn.UnknownAttributes {
		enc[a.Type.String()] = a.Value
	}
	return json.Marshal(enc)
}

func convertToStrArray(i interface{}) []string {

	var strArray []string

	arr, _ := i.([]interface{})
	for _, val := range arr {
		if str, ok := val.(string); ok {
			strArray = append(strArray, str)
		}
	}

	return strArray
}

func (jn *jsonName) UnmarshalJSON(b []byte) error {
	nameMap := make(map[string]interface{})

	if err := json.Unmarshal(b, &nameMap); err != nil {
		return err
	}

	for key, val := range nameMap {
		switch key {
		case "common_name":
			jn.CommonName = convertToStrArray(val)
		case "serial_number":
			jn.SerialNumber = convertToStrArray(val)
		case "country":
			jn.Country = convertToStrArray(val)
		case "locality":
			jn.Locality = convertToStrArray(val)
		case "province":
			jn.Province = convertToStrArray(val)
		case "street_address":
			jn.StreetAddress = convertToStrArray(val)
		case "organization":
			jn.Organization = convertToStrArray(val)
		case "organizational_unit":
			jn.OrganizationalUnit = convertToStrArray(val)
		case "postal_code":
			jn.PostalCode = convertToStrArray(val)
		case "domain_component":
			jn.DomainComponent = convertToStrArray(val)
		default:
			attributeType := asn1.ObjectIdentifier{}
			valStr, okStr := val.(string)
			if !okStr {
				return fmt.Errorf("Expected string value for field %s, got %T", key, val)
			}

			for _, oidString := range strings.Split(valStr, ".") {
				oidInt, err := strconv.Atoi(oidString)
				if err != nil {
					return err
				}
				attributeType = append(attributeType, oidInt)
			}

			atv := AttributeTypeAndValue{
				Type:  attributeType,
				Value: val,
			}

			jn.UnknownAttributes = append(jn.UnknownAttributes, atv)
		}

	}

	return nil
}

type jsonAttributeTypeAndValue struct {
	Type  string      `json:"type"`
	Value interface{} `json:"value"`
}

func (a *AttributeTypeAndValue) MarshalJSON() ([]byte, error) {
	var enc jsonAttributeTypeAndValue
	enc.Type = a.Type.String()
	enc.Value = a.Value
	return json.Marshal(&enc)
}

type jsonExtension struct {
	Id       string `json:"id"`
	Critical bool   `json:"critical"`
	Value    []byte `json:"value"`
}

func (e *Extension) MarshalJSON() ([]byte, error) {
	ext := jsonExtension{
		Id:       e.Id.String(),
		Critical: e.Critical,
		Value:    e.Value,
	}
	return json.Marshal(ext)
}

type jsonOtherName struct {
	Id    string `json:"id"`
	Value []byte `json:"value"`
}

func (o *OtherName) MarshalJSON() ([]byte, error) {
	oName := jsonOtherName{
		Id:    o.Typeid.String(),
		Value: o.Value.Bytes,
	}
	return json.Marshal(oName)
}

func (o *OtherName) UnmarshalJSON(b []byte) (err error) {
	var oName jsonOtherName

	if err = json.Unmarshal(b, &oName); err != nil {
		return
	}

	arcs := strings.Split(oName.Id, ".")
	oid := make(asn1.ObjectIdentifier, len(arcs))

	for i, s := range arcs {
		var tmp int64
		tmp, err = strconv.ParseInt(s, 10, 32)
		if err != nil {
			return
		}
		oid[i] = int(tmp)
	}
	o.Typeid = oid

	o.Value = asn1.RawValue{
		Tag:        0,
		Class:      asn1.ClassContextSpecific,
		IsCompound: true,
		Bytes:      oName.Value,
	}
	o.Value.FullBytes, err = asn1.Marshal(o.Value)
	return
}

func (n *Name) MarshalJSON() ([]byte, error) {
	var enc jsonName
	attrs := n.ToRDNSequence()
	for _, attrSet := range attrs {
		for _, a := range attrSet {
			s, _ := a.Value.(string)
			if a.Type.Equal(oidCommonName) {
				enc.CommonName = append(enc.CommonName, s)
			} else if a.Type.Equal(oidSerialNumber) {
				enc.SerialNumber = append(enc.SerialNumber, s)
			} else if a.Type.Equal(oidCountry) {
				enc.Country = append(enc.Country, s)
			} else if a.Type.Equal(oidLocality) {
				enc.Locality = append(enc.Locality, s)
			} else if a.Type.Equal(oidProvince) {
				enc.Province = append(enc.Province, s)
			} else if a.Type.Equal(oidStreetAddress) {
				enc.StreetAddress = append(enc.StreetAddress, s)
			} else if a.Type.Equal(oidOrganization) {
				enc.Organization = append(enc.Organization, s)
			} else if a.Type.Equal(oidOrganizationalUnit) {
				enc.OrganizationalUnit = append(enc.OrganizationalUnit, s)
			} else if a.Type.Equal(oidPostalCode) {
				enc.PostalCode = append(enc.PostalCode, s)
			} else if a.Type.Equal(oidDomainComponent) {
				enc.DomainComponent = append(enc.DomainComponent, s)
			} else {
				enc.UnknownAttributes = append(enc.UnknownAttributes, a)
			}
		}
	}
	return json.Marshal(&enc)
}

func appendATV(names []AttributeTypeAndValue, fieldVals []string, asn1Id asn1.ObjectIdentifier) []AttributeTypeAndValue {
	if len(fieldVals) == 0 {
		return names
	}

	for _, val := range fieldVals {
		names = append(names, AttributeTypeAndValue{Type: asn1Id, Value: val})
	}

	return names
}

func (n *Name) UnmarshalJSON(b []byte) error {
	var jName jsonName

	if err := jName.UnmarshalJSON(b); err != nil {
		return err
	}

	// add everything to names
	n.Names = appendATV(n.Names, jName.Country, oidCountry)
	n.Names = appendATV(n.Names, jName.Organization, oidOrganization)
	n.Names = appendATV(n.Names, jName.OrganizationalUnit, oidOrganizationalUnit)
	n.Names = appendATV(n.Names, jName.Locality, oidLocality)
	n.Names = appendATV(n.Names, jName.Province, oidProvince)
	n.Names = appendATV(n.Names, jName.StreetAddress, oidStreetAddress)
	n.Names = appendATV(n.Names, jName.PostalCode, oidPostalCode)
	n.Names = appendATV(n.Names, jName.DomainComponent, oidDomainComponent)

	// populate specific fields
	n.Country = jName.Country
	n.Organization = jName.Organization
	n.OrganizationalUnit = jName.OrganizationalUnit
	n.Locality = jName.Locality
	n.Province = jName.Province
	n.StreetAddress = jName.StreetAddress
	n.PostalCode = jName.PostalCode
	n.DomainComponent = jName.DomainComponent

	// add first commonNames and serialNumbers to struct and Names
	if len(jName.CommonName) > 0 {
		n.CommonName = jName.CommonName[0]
		n.Names = append(n.Names, AttributeTypeAndValue{Type: oidCommonName, Value: jName.CommonName[0]})
	}
	if len(jName.SerialNumber) > 0 {
		n.SerialNumber = jName.SerialNumber[0]
		n.Names = append(n.Names, AttributeTypeAndValue{Type: oidSerialNumber, Value: jName.SerialNumber[0]})
	}

	// add extra commonNames and serialNumbers to extraNames
	if len(jName.CommonName) > 1 {
		for _, val := range jName.CommonName[1:] {
			n.ExtraNames = append(n.ExtraNames, AttributeTypeAndValue{Type: oidCommonName, Value: val})
		}
	}

	if len(jName.SerialNumber) > 1 {
		for _, val := range jName.SerialNumber[1:] {
			n.ExtraNames = append(n.ExtraNames, AttributeTypeAndValue{Type: oidSerialNumber, Value: val})
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

// infoCmd represents the info command
var infoCmd = &cobra.Command{
	Use:   "info",
	Short: "Prints off diagnostic information useful for debugging.",
	RunE: func(cmd *cobra.Command, args []string) error {
		bb := os.Stdout

		bb.WriteString(fmt.Sprintf("### Buffalo Version\n%s\n", Version))

		bb.WriteString("\n### Go Version\n")
		c := exec.Command("go", "version")
		c.Stdout = bb
		err := c.Run()
		if err != nil {
			return errors.WithStack(err)
		}

		bb.WriteString("\n### Go Env\n")
		c = exec.Command("go", "env")
		c.Stdout = bb
		c.Stderr = bb
		c.Run()

		bb.WriteString("\n### Node Version\n")
		if _, err := exec.LookPath("node"); err == nil {
			c = exec.Command("node", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("Node Not Found\n")
		}

		bb.WriteString("\n### NPM Version\n")
		if _, err := exec.LookPath("npm"); err == nil {
			c = exec.Command("npm", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("NPM Not Found\n")
		}

		bb.WriteString("\n### Dep Status\n")
		if _, err := exec.LookPath("dep"); err == nil {
			c = exec.Command("dep", "status")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("dep Not Found\n")
		}

		bb.WriteString("\n### PostgreSQL Version\n")
		if _, err := exec.LookPath("pg_ctl"); err == nil {
			c = exec.Command("pg_ctl", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("PostgreSQL Not Found\n")
		}

		bb.WriteString("\n### MySQL Version\n")
		if _, err := exec.LookPath("mysql"); err == nil {
			c = exec.Command("mysql", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("MySQL Not Found\n")
		}

		bb.WriteString("\n### SQLite Version\n")
		if _, err := exec.LookPath("sqlite3"); err == nil {
			c = exec.Command("sqlite3", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("MySQL Not Found\n")
		}

		return nil
	},
}

func init() {
	decorate("info", RootCmd)
	RootCmd.AddCommand(infoCmd)
}
<commit_msg>fixed typo<commit_after>package cmd

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

// infoCmd represents the info command
var infoCmd = &cobra.Command{
	Use:   "info",
	Short: "Prints off diagnostic information useful for debugging.",
	RunE: func(cmd *cobra.Command, args []string) error {
		bb := os.Stdout

		bb.WriteString(fmt.Sprintf("### Buffalo Version\n%s\n", Version))

		bb.WriteString("\n### Go Version\n")
		c := exec.Command("go", "version")
		c.Stdout = bb
		err := c.Run()
		if err != nil {
			return errors.WithStack(err)
		}

		bb.WriteString("\n### Go Env\n")
		c = exec.Command("go", "env")
		c.Stdout = bb
		c.Stderr = bb
		c.Run()

		bb.WriteString("\n### Node Version\n")
		if _, err := exec.LookPath("node"); err == nil {
			c = exec.Command("node", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("Node Not Found\n")
		}

		bb.WriteString("\n### NPM Version\n")
		if _, err := exec.LookPath("npm"); err == nil {
			c = exec.Command("npm", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("NPM Not Found\n")
		}

		bb.WriteString("\n### Dep Status\n")
		if _, err := exec.LookPath("dep"); err == nil {
			c = exec.Command("dep", "status")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("dep Not Found\n")
		}

		bb.WriteString("\n### PostgreSQL Version\n")
		if _, err := exec.LookPath("pg_ctl"); err == nil {
			c = exec.Command("pg_ctl", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("PostgreSQL Not Found\n")
		}

		bb.WriteString("\n### MySQL Version\n")
		if _, err := exec.LookPath("mysql"); err == nil {
			c = exec.Command("mysql", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("MySQL Not Found\n")
		}

		bb.WriteString("\n### SQLite Version\n")
		if _, err := exec.LookPath("sqlite3"); err == nil {
			c = exec.Command("sqlite3", "--version")
			c.Stdout = bb
			c.Stderr = bb
			c.Run()
		} else {
			bb.WriteString("SQLite Not Found\n")
		}

		return nil
	},
}

func init() {
	decorate("info", RootCmd)
	RootCmd.AddCommand(infoCmd)
}
<|endoftext|>


<commit_before>package buffer_list

import (
	"testing"
)

type TestData struct {
	a int64
	b int32
	c int64
}

func createList() *List {
	list := New(TestData{}, 4096)
	data := (*TestData)(list.Front().Value())
	data.a = 1
	data.b = 11

	return list
}

func TestBufferListiCreate(t *testing.T) {

	list := createList()

	if list.Len != 1 {
		t.Error("list.Len != 1")
	}
}

func TestBufferListInsertNewElem(t *testing.T) {

	list := createList()

	e := list.InsertNewElem(list.Front())
	data := (*TestData)(e.Value())

	data.a = 2
	data.b = 22

	if list.Len != 2 {
		t.Error("list.Len != 2")
	}

	data2 := (*TestData)(list.Back().Value())

	if data2.a != 2 {
		t.Error("data2.a != 2")
	}
}

func TestBufferListCreate10(t *testing.T) {

	list := createList()
	var data *TestData
	var e *Element
	for i := 1; i < 10; i++ {
		e = list.InsertNewElem(list.Back())
		data = (*TestData)(e.Value())
		data.a = int64(i) * 1
		data.b = int32(i) * 11
	}

	if list.Len != 10 {
		t.Error("list.len != 10")
	}

	data = (*TestData)(list.Back().Prev().Value())

	if data.b != 88 {
		t.Error("data.b != 88", data.b)
	}
}
<commit_msg> add test of concurrent creation<commit_after>package buffer_list

import (
	"testing"
	"time"
)

type TestData struct {
	a int64
	b int32
	c int64
}

func createList() *List {
	list := New(TestData{}, 4096)
	data := (*TestData)(list.Front().Value())
	data.a = 1
	data.b = 11

	return list
}

func TestBufferListiCreate(t *testing.T) {

	list := createList()

	if list.Len != 1 {
		t.Error("list.Len != 1")
	}
}

func TestBufferListInsertNewElem(t *testing.T) {

	list := createList()

	e := list.InsertNewElem(list.Front())
	data := (*TestData)(e.Value())

	data.a = 2
	data.b = 22

	if list.Len != 2 {
		t.Error("list.Len != 2")
	}

	data2 := (*TestData)(list.Back().Value())

	if data2.a != 2 {
		t.Error("data2.a != 2")
	}
}

func TestBufferListCreate10(t *testing.T) {

	list := createList()
	var data *TestData
	var e *Element
	for i := 1; i < 10; i++ {
		e = list.InsertNewElem(list.Back())
		data = (*TestData)(e.Value())
		data.a = int64(i) * 1
		data.b = int32(i) * 11
	}

	if list.Len != 10 {
		t.Error("list.len != 10")
	}

	data = (*TestData)(list.Back().Prev().Value())

	if data.b != 88 {
		t.Error("data.b != 88", data.b)
	}
}

func TestBufferListConcurrentCreate10(t *testing.T) {

	list := createList()
	for i := 1; i < 10; i++ {
		go func(list *List, i int) {
			ee := list.InsertNewElem(list.Back())
			tdata := (*TestData)(ee.Value())
			tdata.a = int64(i) * 1
			tdata.b = int32(i) * 11
		}(list, i)
	}

	time.Sleep(time.Second)

	if list.Len != 10 {
		t.Error("list.len != 10")
	}
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Kubicorn 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 resources

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/digitalocean/godo"
	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/cloud"
	"github.com/kris-nova/kubicorn/cutil/compare"
	"github.com/kris-nova/kubicorn/cutil/logger"
)

// Firewall holds all the data for DO firewalls.
// We preserve the same tags as DO apis for json marshal and unmarhsalling data.
type Firewall struct {
	Shared
	InboundRules  []InboundRule  `json:"inbound_rules,omitempty"`
	OutboundRules []OutboundRule `json:"outbound_rules,omitempty"`
	DropletIDs    []int          `json:"droplet_ids,omitempty"`
	Tags          []string       `json:"tags,omitempty"` // Droplet tags
	FirewallID    string         `json:"id,omitempty"`
	Status        string         `json:"status,omitempty"`
	Created       string         `json:"created_at,omitempty"`
	ServerPool    *cluster.ServerPool
}

// InboundRule DO Firewall InboundRule rule.
type InboundRule struct {
	Protocol  string   `json:"protocol,omitempty"`
	PortRange string   `json:"ports,omitempty"`
	Source    *Sources `json:"sources,omitempty"`
}

// OutboundRule DO Firewall outbound rule.
type OutboundRule struct {
	Protocol     string        `json:"protocol,omitempty"`
	PortRange    string        `json:"ports,omitempty"`
	Destinations *Destinations `json:"destinations,omitempty"`
}

// Sources DO Firewall Source parameters.
type Sources struct {
	Addresses        []string `json:"addresses,omitempty"`
	Tags             []string `json:"tags,omitempty"`
	DropletIDs       []int    `json:"droplet_ids,omitempty"`
	LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}

// Destinations DO Firewall destination  parameters.
type Destinations struct {
	Addresses        []string `json:"addresses,omitempty"`
	Tags             []string `json:"tags,omitempty"`
	DropletIDs       []int    `json:"droplet_ids,omitempty"`
	LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}

// Actual calls DO firewall Api and returns the actual state of firewall in the cloud.
func (f *Firewall) Actual(known *cluster.Cluster) (cloud.Resource, error) {
	logger.Info("Firewall Actual [%s]", f.Name)
	if cached := f.getCachedActual(); cached != nil {
		logger.Debug("Using cached firewall [actual]")
		return cached, nil
	}

	actualFirewall := defaultFirewallStruct()
	// Digital Firewalls.Get requires firewall ID, which we will not always have.thats why using List.
	firewalls, _, err := Sdk.Client.Firewalls.List(context.TODO(), &godo.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to get firwalls info")
	}
	for _, firewall := range firewalls {
		if firewall.Name == f.Name { // In digitalOcean Firwall names are unique.
			// gotcha get all details from this firewall and populate actual.
			firewallBytes, err := json.Marshal(firewall)
			if err != nil {
				return nil, fmt.Errorf("failed to marshal DO firewall details err: %v", err)
			}
			if err := json.Unmarshal(firewallBytes, actualFirewall); err != nil {
				return nil, fmt.Errorf("failed to unmarhal DO firewall details err: %v", err)
			}
			// hack: DO api doesn't take "0" as portRange, but returns "0" for port range in firewall.List.
			for i := 0; i < len(actualFirewall.OutboundRules); i++ {
				if actualFirewall.OutboundRules[i].PortRange == "0" {
					actualFirewall.OutboundRules[i].PortRange = "all"
				}
			}
			logger.Info("Actual firewall returned is %+v", actualFirewall)
			return actualFirewall, nil
		}
	}
	return &Firewall{}, nil
}

// Expected returns the Firewall structure of what is Expected.
func (f *Firewall) Expected(known *cluster.Cluster) (cloud.Resource, error) {

	logger.Info("Firewall Expected [%s]", f.Name)
	if cached := f.getCachedExpected(); cached != nil {
		logger.Debug("Using Expected cached firewall [%s]", f.Name)
		return cached, nil
	}
	expected := &Firewall{
		Shared: Shared{
			Name:    f.Name,
			CloudID: f.ServerPool.Identifier,
		},

		InboundRules:  f.InboundRules,
		OutboundRules: f.OutboundRules,
		DropletIDs:    f.DropletIDs,
		Tags:          f.Tags,
		FirewallID:    f.FirewallID,
		Status:        f.Status,
		Created:       f.Created,
	}
	f.CachedExpected = expected
	logger.Info("Expected firewall returned is %+v", expected)
	return expected, nil

}

// Apply will compare the actual and expected firewall config, if needed it will create the firewall.
func (f *Firewall) Apply(actual, expected cloud.Resource, applyCluster *cluster.Cluster) (cloud.Resource, error) {
	logger.Debug("Firewall.Apply")
	expectedResource, ok := expected.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("Failed to type convert expected Firewall type ")
	}
	actualResource, ok := actual.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("Failed to type convert actual Firewall type ")
	}

	isEqual, err := compare.IsEqual(actualResource, expectedResource)
	if err != nil {
		return nil, err
	}
	if isEqual {
		return expectedResource, nil
	}

	firewallRequest := godo.FirewallRequest{
		Name:          expectedResource.Name,
		InboundRules:  convertInRuleType(expectedResource.InboundRules),
		OutboundRules: convertOutRuleType(expectedResource.OutboundRules),
		DropletIDs:    expectedResource.DropletIDs,
		Tags:          expectedResource.Tags,
	}

	firewall, _, err := Sdk.Client.Firewalls.Create(context.TODO(), &firewallRequest)
	if err != nil {
		return nil, fmt.Errorf("failed to create the firewall err: %v", err)
	}
	f.FirewallID = firewall.ID
	return f, nil
}

// Delete removes the firewall
func (f *Firewall) Delete(actual cloud.Resource, known *cluster.Cluster) (cloud.Resource, error) {
	logger.Debug("firewall.Delete")
	deleteResource, ok := actual.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("failed to type convert actual Firewall type ")
	}
	if deleteResource.Name == "" {
		return nil, fmt.Errorf("Unable to delete droplet resource without Name [%s]", deleteResource.Name)
	}
	if _, err := Sdk.Client.Firewalls.Delete(context.TODO(), deleteResource.FirewallID); err != nil {
		return nil, fmt.Errorf("failed to delete firewall [%s] err: %v", deleteResource.Name, err)
	}
	newResource := &Firewall{}
	newResource.Name = deleteResource.Name
	newResource.Tags = deleteResource.Tags
	newResource.FirewallID = deleteResource.FirewallID
	newResource.DropletIDs = deleteResource.DropletIDs
	newResource.Tags = deleteResource.Tags
	newResource.OutboundRules = deleteResource.OutboundRules
	newResource.InboundRules = deleteResource.InboundRules
	return newResource, nil
}

// Render the firewall to the cluster object
func (f *Firewall) Render(renderResource cloud.Resource, renderCluster *cluster.Cluster) (*cluster.Cluster, error) {

	logger.Debug("Firewall.Render")
	found := false
	for i := 0; i < len(renderCluster.ServerPools); i++ {
		for j := 0; j < len(renderCluster.ServerPools[i].Firewalls); j++ {
			firewall, ok := renderResource.(*Firewall)

			if !ok {
				return nil, fmt.Errorf("failed type convert renderResource Firewall type")
			}
			if renderCluster.ServerPools[i].Firewalls[j].Name == firewall.Name {
				found = true
				renderCluster.ServerPools[i].Firewalls[j].Name = firewall.Name
				renderCluster.ServerPools[i].Firewalls[j].Identifier = firewall.CloudID
				renderCluster.ServerPools[i].Firewalls[j].IngressRules = make([]*cluster.IngressRule, len(firewall.InboundRules))
				for k, renderRule := range firewall.InboundRules {
					renderCluster.ServerPools[i].Firewalls[j].IngressRules[k] = &cluster.IngressRule{
						IngressProtocol: renderRule.Protocol,
						IngressToPort:   renderRule.PortRange,
						IngressSource:   renderRule.Source.Addresses[0],
					}
				}
				renderCluster.ServerPools[i].Firewalls[j].EgressRules = make([]*cluster.EgressRule, len(firewall.OutboundRules))
				for k, renderRule := range firewall.OutboundRules {
					renderCluster.ServerPools[i].Firewalls[j].EgressRules[k] = &cluster.EgressRule{
						EgressProtocol:    renderRule.Protocol,
						EgressToPort:      renderRule.PortRange,
						EgressDestination: renderRule.Destinations.Addresses[0],
					}
				}
			}
		}
	}

	if !found {
		for i := 0; i < len(renderCluster.ServerPools); i++ {
			if renderCluster.ServerPools[i].Name == f.ServerPool.Name {
				found = true
				var inRules []*cluster.IngressRule
				var egRules []*cluster.EgressRule
				firewall, ok := renderResource.(*Firewall)
				if !ok {
					return nil, fmt.Errorf("failed type convert renderResource Firewall type")
				}
				for _, renderRule := range firewall.InboundRules {
					inRules = append(inRules, &cluster.IngressRule{
						IngressProtocol: renderRule.Protocol,
						IngressToPort:   renderRule.PortRange,
						IngressSource:   renderRule.Source.Addresses[0],
					})
				}
				for _, renderRule := range firewall.OutboundRules {
					egRules = append(egRules, &cluster.EgressRule{
						EgressProtocol:    renderRule.Protocol,
						EgressToPort:      renderRule.PortRange,
						EgressDestination: renderRule.Destinations.Addresses[0],
					})
				}
				renderCluster.ServerPools[i].Firewalls = append(renderCluster.ServerPools[i].Firewalls, &cluster.Firewall{
					Name:         firewall.Name,
					Identifier:   firewall.CloudID,
					IngressRules: inRules,
					EgressRules:  egRules,
				})
			}
		}
	}
	if !found {
		var inRules []*cluster.IngressRule
		var egRules []*cluster.EgressRule
		firewall, ok := renderResource.(*Firewall)
		if !ok {
			return nil, fmt.Errorf("failed type convert renderResource Firewall type")
		}
		for _, renderRule := range firewall.InboundRules {
			inRules = append(inRules, &cluster.IngressRule{
				IngressProtocol: renderRule.Protocol,
				IngressToPort:   renderRule.PortRange,
				IngressSource:   renderRule.Source.Addresses[0],
			})
		}
		for _, renderRule := range firewall.OutboundRules {
			egRules = append(egRules, &cluster.EgressRule{
				EgressProtocol:    renderRule.Protocol,
				EgressToPort:      renderRule.PortRange,
				EgressDestination: renderRule.Destinations.Addresses[0],
			})
		}
		firewalls := []*cluster.Firewall{
			{
				Name:         firewall.Name,
				Identifier:   firewall.CloudID,
				IngressRules: inRules,
				EgressRules:  egRules,
			},
		}
		renderCluster.ServerPools = append(renderCluster.ServerPools, &cluster.ServerPool{
			Name:       f.ServerPool.Name,
			Identifier: f.ServerPool.Identifier,
			Firewalls:  firewalls,
		})
	}
	return renderCluster, nil
}

// Tag not used currently.
func (f *Firewall) Tag(tags map[string]string) error {
	return nil
}

func defaultFirewallStruct() *Firewall {
	return &Firewall{
		DropletIDs:    make([]int, 0),
		Tags:          make([]string, 0),
		InboundRules:  make([]InboundRule, 0),
		OutboundRules: make([]OutboundRule, 0),
	}
}

func convertInRuleType(rules []InboundRule) []godo.InboundRule {
	inRule := make([]godo.InboundRule, 0)
	for _, rule := range rules {
		source := godo.Sources(*rule.Source)
		godoRule := godo.InboundRule{
			Protocol:  rule.Protocol,
			PortRange: rule.PortRange,
			Sources:   &source,
		}
		inRule = append(inRule, godoRule)
	}
	return inRule
}
func convertOutRuleType(rules []OutboundRule) []godo.OutboundRule {
	outRule := make([]godo.OutboundRule, 0)
	for _, rule := range rules {
		destination := godo.Destinations(*rule.Destinations)
		godoRule := godo.OutboundRule{
			Protocol:     rule.Protocol,
			PortRange:    rule.PortRange,
			Destinations: &destination,
		}
		outRule = append(outRule, godoRule)
	}
	return outRule
}
<commit_msg>fixes fmt<commit_after>// Copyright © 2017 The Kubicorn 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 resources

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/digitalocean/godo"
	"github.com/kris-nova/kubicorn/apis/cluster"
	"github.com/kris-nova/kubicorn/cloud"
	"github.com/kris-nova/kubicorn/cutil/compare"
	"github.com/kris-nova/kubicorn/cutil/logger"
)

// Firewall holds all the data for DO firewalls.
// We preserve the same tags as DO apis for json marshal and unmarhsalling data.
type Firewall struct {
	Shared
	InboundRules  []InboundRule  `json:"inbound_rules,omitempty"`
	OutboundRules []OutboundRule `json:"outbound_rules,omitempty"`
	DropletIDs    []int          `json:"droplet_ids,omitempty"`
	Tags          []string       `json:"tags,omitempty"` // Droplet tags
	FirewallID    string         `json:"id,omitempty"`
	Status        string         `json:"status,omitempty"`
	Created       string         `json:"created_at,omitempty"`
	ServerPool    *cluster.ServerPool
}

// InboundRule DO Firewall InboundRule rule.
type InboundRule struct {
	Protocol  string   `json:"protocol,omitempty"`
	PortRange string   `json:"ports,omitempty"`
	Source    *Sources `json:"sources,omitempty"`
}

// OutboundRule DO Firewall outbound rule.
type OutboundRule struct {
	Protocol     string        `json:"protocol,omitempty"`
	PortRange    string        `json:"ports,omitempty"`
	Destinations *Destinations `json:"destinations,omitempty"`
}

// Sources DO Firewall Source parameters.
type Sources struct {
	Addresses        []string `json:"addresses,omitempty"`
	Tags             []string `json:"tags,omitempty"`
	DropletIDs       []int    `json:"droplet_ids,omitempty"`
	LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}

// Destinations DO Firewall destination  parameters.
type Destinations struct {
	Addresses        []string `json:"addresses,omitempty"`
	Tags             []string `json:"tags,omitempty"`
	DropletIDs       []int    `json:"droplet_ids,omitempty"`
	LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
}

// Actual calls DO firewall Api and returns the actual state of firewall in the cloud.
func (f *Firewall) Actual(known *cluster.Cluster) (cloud.Resource, error) {
	logger.Info("Firewall Actual [%s]", f.Name)
	if cached := f.getCachedActual(); cached != nil {
		logger.Debug("Using cached firewall [actual]")
		return cached, nil
	}

	actualFirewall := defaultFirewallStruct()
	// Digital Firewalls.Get requires firewall ID, which we will not always have.thats why using List.
	firewalls, _, err := Sdk.Client.Firewalls.List(context.TODO(), &godo.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to get firwalls info")
	}
	for _, firewall := range firewalls {
		if firewall.Name == f.Name { // In digitalOcean Firwall names are unique.
			// gotcha get all details from this firewall and populate actual.
			firewallBytes, err := json.Marshal(firewall)
			if err != nil {
				return nil, fmt.Errorf("failed to marshal DO firewall details err: %v", err)
			}
			if err := json.Unmarshal(firewallBytes, actualFirewall); err != nil {
				return nil, fmt.Errorf("failed to unmarhal DO firewall details err: %v", err)
			}
			// hack: DO api doesn't take "0" as portRange, but returns "0" for port range in firewall.List.
			for i := 0; i < len(actualFirewall.OutboundRules); i++ {
				if actualFirewall.OutboundRules[i].PortRange == "0" {
					actualFirewall.OutboundRules[i].PortRange = "all"
				}
			}
			logger.Info("Actual firewall returned is %+v", actualFirewall)
			return actualFirewall, nil
		}
	}
	return &Firewall{}, nil
}

// Expected returns the Firewall structure of what is Expected.
func (f *Firewall) Expected(known *cluster.Cluster) (cloud.Resource, error) {

	logger.Info("Firewall Expected [%s]", f.Name)
	if cached := f.getCachedExpected(); cached != nil {
		logger.Debug("Using Expected cached firewall [%s]", f.Name)
		return cached, nil
	}
	expected := &Firewall{
		Shared: Shared{
			Name:    f.Name,
			CloudID: f.ServerPool.Identifier,
		},

		InboundRules:  f.InboundRules,
		OutboundRules: f.OutboundRules,
		DropletIDs:    f.DropletIDs,
		Tags:          f.Tags,
		FirewallID:    f.FirewallID,
		Status:        f.Status,
		Created:       f.Created,
	}
	f.CachedExpected = expected
	logger.Info("Expected firewall returned is %+v", expected)
	return expected, nil

}

// Apply will compare the actual and expected firewall config, if needed it will create the firewall.
func (f *Firewall) Apply(actual, expected cloud.Resource, applyCluster *cluster.Cluster) (cloud.Resource, error) {
	logger.Debug("Firewall.Apply")
	expectedResource, ok := expected.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("Failed to type convert expected Firewall type ")
	}
	actualResource, ok := actual.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("Failed to type convert actual Firewall type ")
	}

	isEqual, err := compare.IsEqual(actualResource, expectedResource)
	if err != nil {
		return nil, err
	}
	if isEqual {
		return expectedResource, nil
	}

	firewallRequest := godo.FirewallRequest{
		Name:          expectedResource.Name,
		InboundRules:  convertInRuleType(expectedResource.InboundRules),
		OutboundRules: convertOutRuleType(expectedResource.OutboundRules),
		DropletIDs:    expectedResource.DropletIDs,
		Tags:          expectedResource.Tags,
	}

	firewall, _, err := Sdk.Client.Firewalls.Create(context.TODO(), &firewallRequest)
	if err != nil {
		return nil, fmt.Errorf("failed to create the firewall err: %v", err)
	}
	f.FirewallID = firewall.ID
	return f, nil
}

// Delete removes the firewall
func (f *Firewall) Delete(actual cloud.Resource, known *cluster.Cluster) (cloud.Resource, error) {
	logger.Debug("firewall.Delete")
	deleteResource, ok := actual.(*Firewall)
	if !ok {
		return nil, fmt.Errorf("failed to type convert actual Firewall type ")
	}
	if deleteResource.Name == "" {
		return nil, fmt.Errorf("Unable to delete droplet resource without Name [%s]", deleteResource.Name)
	}
	if _, err := Sdk.Client.Firewalls.Delete(context.TODO(), deleteResource.FirewallID); err != nil {
		return nil, fmt.Errorf("failed to delete firewall [%s] err: %v", deleteResource.Name, err)
	}
	newResource := &Firewall{}
	newResource.Name = deleteResource.Name
	newResource.Tags = deleteResource.Tags
	newResource.FirewallID = deleteResource.FirewallID
	newResource.DropletIDs = deleteResource.DropletIDs
	newResource.Tags = deleteResource.Tags
	newResource.OutboundRules = deleteResource.OutboundRules
	newResource.InboundRules = deleteResource.InboundRules
	return newResource, nil
}

// Render the firewall to the cluster object
func (f *Firewall) Render(renderResource cloud.Resource, renderCluster *cluster.Cluster) (*cluster.Cluster, error) {

	logger.Debug("Firewall.Render")
	found := false
	for i := 0; i < len(renderCluster.ServerPools); i++ {
		for j := 0; j < len(renderCluster.ServerPools[i].Firewalls); j++ {
			firewall, ok := renderResource.(*Firewall)

			if !ok {
				return nil, fmt.Errorf("failed type convert renderResource Firewall type")
			}
			if renderCluster.ServerPools[i].Firewalls[j].Name == firewall.Name {
				found = true
				renderCluster.ServerPools[i].Firewalls[j].Name = firewall.Name
				renderCluster.ServerPools[i].Firewalls[j].Identifier = firewall.CloudID
				renderCluster.ServerPools[i].Firewalls[j].IngressRules = make([]*cluster.IngressRule, len(firewall.InboundRules))
				for k, renderRule := range firewall.InboundRules {
					renderCluster.ServerPools[i].Firewalls[j].IngressRules[k] = &cluster.IngressRule{
						IngressProtocol: renderRule.Protocol,
						IngressToPort:   renderRule.PortRange,
						IngressSource:   renderRule.Source.Addresses[0],
					}
				}
				renderCluster.ServerPools[i].Firewalls[j].EgressRules = make([]*cluster.EgressRule, len(firewall.OutboundRules))
				for k, renderRule := range firewall.OutboundRules {
					renderCluster.ServerPools[i].Firewalls[j].EgressRules[k] = &cluster.EgressRule{
						EgressProtocol:    renderRule.Protocol,
						EgressToPort:      renderRule.PortRange,
						EgressDestination: renderRule.Destinations.Addresses[0],
					}
				}
			}
		}
	}

	if !found {
		for i := 0; i < len(renderCluster.ServerPools); i++ {
			if renderCluster.ServerPools[i].Name == f.ServerPool.Name {
				found = true
				var inRules []*cluster.IngressRule
				var egRules []*cluster.EgressRule
				firewall, ok := renderResource.(*Firewall)
				if !ok {
					return nil, fmt.Errorf("failed type convert renderResource Firewall type")
				}
				for _, renderRule := range firewall.InboundRules {
					inRules = append(inRules, &cluster.IngressRule{
						IngressProtocol: renderRule.Protocol,
						IngressToPort:   renderRule.PortRange,
						IngressSource:   renderRule.Source.Addresses[0],
					})
				}
				for _, renderRule := range firewall.OutboundRules {
					egRules = append(egRules, &cluster.EgressRule{
						EgressProtocol:    renderRule.Protocol,
						EgressToPort:      renderRule.PortRange,
						EgressDestination: renderRule.Destinations.Addresses[0],
					})
				}
				renderCluster.ServerPools[i].Firewalls = append(renderCluster.ServerPools[i].Firewalls, &cluster.Firewall{
					Name:         firewall.Name,
					Identifier:   firewall.CloudID,
					IngressRules: inRules,
					EgressRules:  egRules,
				})
			}
		}
	}
	if !found {
		var inRules []*cluster.IngressRule
		var egRules []*cluster.EgressRule
		firewall, ok := renderResource.(*Firewall)
		if !ok {
			return nil, fmt.Errorf("failed type convert renderResource Firewall type")
		}
		for _, renderRule := range firewall.InboundRules {
			inRules = append(inRules, &cluster.IngressRule{
				IngressProtocol: renderRule.Protocol,
				IngressToPort:   renderRule.PortRange,
				IngressSource:   renderRule.Source.Addresses[0],
			})
		}
		for _, renderRule := range firewall.OutboundRules {
			egRules = append(egRules, &cluster.EgressRule{
				EgressProtocol:    renderRule.Protocol,
				EgressToPort:      renderRule.PortRange,
				EgressDestination: renderRule.Destinations.Addresses[0],
			})
		}
		firewalls := []*cluster.Firewall{
			{
				Name:         firewall.Name,
				Identifier:   firewall.CloudID,
				IngressRules: inRules,
				EgressRules:  egRules,
			},
		}
		renderCluster.ServerPools = append(renderCluster.ServerPools, &cluster.ServerPool{
			Name:       f.ServerPool.Name,
			Identifier: f.ServerPool.Identifier,
			Firewalls:  firewalls,
		})
	}
	return renderCluster, nil
}

// Tag not used currently.
func (f *Firewall) Tag(tags map[string]string) error {
	return nil
}

func defaultFirewallStruct() *Firewall {
	return &Firewall{
		DropletIDs:    make([]int, 0),
		Tags:          make([]string, 0),
		InboundRules:  make([]InboundRule, 0),
		OutboundRules: make([]OutboundRule, 0),
	}
}

func convertInRuleType(rules []InboundRule) []godo.InboundRule {
	inRule := make([]godo.InboundRule, 0)
	for _, rule := range rules {
		source := godo.Sources(*rule.Source)
		godoRule := godo.InboundRule{
			Protocol:  rule.Protocol,
			PortRange: rule.PortRange,
			Sources:   &source,
		}
		inRule = append(inRule, godoRule)
	}
	return inRule
}
func convertOutRuleType(rules []OutboundRule) []godo.OutboundRule {
	outRule := make([]godo.OutboundRule, 0)
	for _, rule := range rules {
		destination := godo.Destinations(*rule.Destinations)
		godoRule := godo.OutboundRule{
			Protocol:     rule.Protocol,
			PortRange:    rule.PortRange,
			Destinations: &destination,
		}
		outRule = append(outRule, godoRule)
	}
	return outRule
}
<|endoftext|>