<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/graph"
)

const fileChunkSize = 1 << 24

// For each incoming node, use the supplied blob store to ensure that the node
// has a non-nil list of scores. Incoming nodes must be in reverse
// topologically sorted order: children must appear before parents.
func fillInScores(
	ctx context.Context,
	basePath string,
	blobStore blob.Store,
	nodeIn <-chan *fsNode,
	nodesOut chan<- *fsNode) (err error) {
	err = errors.New("TODO")
	return
}

// Create a graph.Visitor for *fsNode that saves to the supplied blob store,
// filling in the node's Scores field when it is nil. All visited nodes are
// then written to nodesOut.
func newVisitor(
	chunkSize int,
	basePath string,
	blobStore blob.Store,
	nodesOut chan<- *fsNode) (v graph.Visitor) {
	v = &visitor{
		chunkSize: chunkSize,
		basePath:  basePath,
		blobStore: blobStore,
		nodesOut:  nodesOut,
	}

	return
}

type visitor struct {
	chunkSize int
	basePath  string
	blobStore blob.Store
	nodesOut  chan<- *fsNode
}

func (v *visitor) Visit(ctx context.Context, untyped graph.Node) (err error) {
	// Check the type of the node.
	n, ok := untyped.(*fsNode)
	if !ok {
		err = fmt.Errorf("Unexpected node type: %T", untyped)
		return
	}

	// Ensure that the node has scores set, if it needs to.
	err = v.setScores(ctx, n)
	if err != nil {
		err = fmt.Errorf("setScores: %v", err)
		return
	}

	// Pass on the node.
	select {
	case v.nodesOut <- n:
	case <-ctx.Done():
		err = ctx.Err()
		return
	}

	return
}

func (v *visitor) setScores(
	ctx context.Context,
	n *fsNode) (err error) {
	// If the node already has scores, we're done.
	if n.Scores != nil {
		return
	}

	// Files and directories are the only interesting cases.
	switch n.Info.Type {
	case fs.TypeFile:
		n.Scores, err = v.saveFile(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveFile: %v", err)
			return
		}

	case fs.TypeDirectory:
		n.Scores, err = v.saveDir(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveDir: %v", err)
			return
		}
	}

	return
}

// Guarantees non-nil result when successful, even for empty list of scores.
func (v *visitor) saveFile(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	scores = make([]blob.Score, 0, 1)

	// Open the file for reading.
	f, err := os.Open(path)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer f.Close()

	// Process a chunk at a time.
	buf := make([]byte, v.chunkSize)
	for {
		// Read some data.
		var n int
		n, err = f.Read(buf)

		switch {
		case err == io.EOF:
			// Ignore EOF.
			err = nil

		case err != nil:
			err = fmt.Errorf("Read: %v", err)
			return
		}

		// Are we done?
		if n == 0 {
			break
		}

		// Write out the blob.
		var s blob.Score
		s, err = v.blobStore.Store(ctx, buf[:n])
		if err != nil {
			err = fmt.Errorf("Store: %v", err)
			return
		}

		scores = append(scores, s)
	}

	return
}

func (v *visitor) saveDir(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	err = errors.New("TODO")
	return
}
<commit_msg>visitor.saveDir<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/graph"
	"github.com/jacobsa/comeback/internal/repr"
)

const fileChunkSize = 1 << 24

// For each incoming node, use the supplied blob store to ensure that the node
// has a non-nil list of scores. Incoming nodes must be in reverse
// topologically sorted order: children must appear before parents.
func fillInScores(
	ctx context.Context,
	basePath string,
	blobStore blob.Store,
	nodeIn <-chan *fsNode,
	nodesOut chan<- *fsNode) (err error) {
	err = errors.New("TODO")
	return
}

// Create a graph.Visitor for *fsNode that saves to the supplied blob store,
// filling in the node's Scores field when it is nil. All visited nodes are
// then written to nodesOut.
func newVisitor(
	chunkSize int,
	basePath string,
	blobStore blob.Store,
	nodesOut chan<- *fsNode) (v graph.Visitor) {
	v = &visitor{
		chunkSize: chunkSize,
		basePath:  basePath,
		blobStore: blobStore,
		nodesOut:  nodesOut,
	}

	return
}

type visitor struct {
	chunkSize int
	basePath  string
	blobStore blob.Store
	nodesOut  chan<- *fsNode
}

func (v *visitor) Visit(ctx context.Context, untyped graph.Node) (err error) {
	// Check the type of the node.
	n, ok := untyped.(*fsNode)
	if !ok {
		err = fmt.Errorf("Unexpected node type: %T", untyped)
		return
	}

	// Ensure that the node has scores set, if it needs to.
	err = v.setScores(ctx, n)
	if err != nil {
		err = fmt.Errorf("setScores: %v", err)
		return
	}

	// Pass on the node.
	select {
	case v.nodesOut <- n:
	case <-ctx.Done():
		err = ctx.Err()
		return
	}

	return
}

func (v *visitor) setScores(
	ctx context.Context,
	n *fsNode) (err error) {
	// If the node already has scores, we're done.
	if n.Scores != nil {
		return
	}

	// Files and directories are the only interesting cases.
	switch n.Info.Type {
	case fs.TypeFile:
		n.Scores, err = v.saveFile(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveFile: %v", err)
			return
		}

	case fs.TypeDirectory:
		n.Scores, err = v.saveDir(ctx, n.Children)
		if err != nil {
			err = fmt.Errorf("saveDir: %v", err)
			return
		}
	}

	return
}

// Guarantees non-nil result when successful, even for empty list of scores.
func (v *visitor) saveFile(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	scores = make([]blob.Score, 0, 1)

	// Open the file for reading.
	f, err := os.Open(path)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer f.Close()

	// Process a chunk at a time.
	buf := make([]byte, v.chunkSize)
	for {
		// Read some data.
		var n int
		n, err = f.Read(buf)

		switch {
		case err == io.EOF:
			// Ignore EOF.
			err = nil

		case err != nil:
			err = fmt.Errorf("Read: %v", err)
			return
		}

		// Are we done?
		if n == 0 {
			break
		}

		// Write out the blob.
		var s blob.Score
		s, err = v.blobStore.Store(ctx, buf[:n])
		if err != nil {
			err = fmt.Errorf("Store: %v", err)
			return
		}

		scores = append(scores, s)
	}

	return
}

func (v *visitor) saveDir(
	ctx context.Context,
	children []*fsNode) (scores []blob.Score, err error) {
	// Set up a list of directory entries.
	var entries []*fs.DirectoryEntry
	for _, child := range children {
		entries = append(entries, &child.Info)
	}

	// Create a blob describing the directory's contents.
	b, err := repr.MarshalDir(entries)
	if err != nil {
		err = fmt.Errorf("MarshalDir: %v", err)
		return
	}

	// Write out the blob.
	s, err := v.blobStore.Store(ctx, b)
	if err != nil {
		err = fmt.Errorf("Store: %v", err)
		return
	}

	scores = []blob.Score{s}
	return
}
<|endoftext|>


<commit_before>package flow

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/google/gopacket"

	"github.com/redhat-cip/skydive/logging"
)

type FlowTable struct {
	lock  sync.RWMutex
	table map[string]*Flow
}

type FlowTableAsyncNotificaionUpdate interface {
	AsyncNotificaionUpdate(every time.Duration)
}

func NewFlowTable() *FlowTable {
	return &FlowTable{table: make(map[string]*Flow)}
}

func (ft *FlowTable) String() string {
	return fmt.Sprintf("%d flows", len(ft.table))
}

func (ft *FlowTable) Update(flows []*Flow) {
	ft.lock.Lock()
	defer ft.lock.Unlock()
	for _, f := range flows {
		_, found := ft.table[f.UUID]
		if !found {
			ft.table[f.UUID] = f
		} else if f.UUID != ft.table[f.UUID].UUID {
			logging.GetLogger().Error("FlowTable Collision ", f.UUID, ft.table[f.UUID].UUID)
		}
	}
}

type ExpireFunc func(f *Flow)

func (ft *FlowTable) AsyncExpire(fn ExpireFunc, every time.Duration) {
	ticker := time.NewTicker(every)
	defer ticker.Stop()
	for {
		now := <-ticker.C
		flowTableSzBefore := len(ft.table)
		expire := now.Unix() - int64((every).Seconds())

		ft.lock.Lock()
		defer ft.lock.Unlock()
		for key, f := range ft.table {
			fs := f.GetStatistics()
			if fs.Last < expire {
				duration := time.Duration(fs.Last - fs.Start)
				logging.GetLogger().Debug("%v Expire flow %s Duration %v", now, f.UUID, duration)
				/* Advise Clients */
				fn(f)
				delete(ft.table, key)
			}
		}
		logging.GetLogger().Debug("%v Expire Flow : removed %v new size %v", now, flowTableSzBefore-len(ft.table), len(ft.table))
	}
}

func (ft *FlowTable) IsExist(f *Flow) bool {
	ft.lock.RLock()
	_, found := ft.table[f.UUID]
	ft.lock.RUnlock()
	return found
}

func (ft *FlowTable) GetFlow(key string, packet *gopacket.Packet) (flow *Flow, new bool) {
	ft.lock.Lock()
	flow, found := ft.table[key]
	if found == false {
		flow = &Flow{}
		ft.table[key] = flow
	}
	ft.lock.Unlock()
	return flow, !found
}

func (ft *FlowTable) JSONFlowConversationEthernetPath() string {
	str := ""
	str += "{"
	//	{"nodes":[{"name":"Myriel","group":1}, ... ],"links":[{"source":1,"target":0,"value":1},...]}

	var strNodes, strLinks string
	strNodes += "\"nodes\":["
	strLinks += "\"links\":["
	pathMap := make(map[string]int)
	ethMap := make(map[string]int)
	for _, f := range ft.table {
		_, found := pathMap[f.LayersPath]
		if !found {
			pathMap[f.LayersPath] = len(pathMap)
		}

		ethFlow := f.GetStatistics().Endpoints[FlowEndpointType_ETHERNET.Value()]
		if _, found := ethMap[ethFlow.AB.Value]; !found {
			ethMap[ethFlow.AB.Value] = len(ethMap)
			strNodes += fmt.Sprintf("{\"name\":\"%s\",\"group\":%d},", ethFlow.AB.Value, pathMap[f.LayersPath])
		}
		if _, found := ethMap[ethFlow.BA.Value]; !found {
			ethMap[ethFlow.BA.Value] = len(ethMap)
			strNodes += fmt.Sprintf("{\"name\":\"%s\",\"group\":%d},", ethFlow.BA.Value, pathMap[f.LayersPath])
		}
		strLinks += fmt.Sprintf("{\"source\":%d,\"target\":%d,\"value\":%d},", ethMap[ethFlow.AB.Value], ethMap[ethFlow.BA.Value], ethFlow.AB.Bytes+ethFlow.BA.Bytes)
	}
	strNodes = strings.TrimRight(strNodes, ",")
	strNodes += "]"
	strLinks = strings.TrimRight(strLinks, ",")
	strLinks += "]"
	str += strNodes + "," + strLinks
	str += "}"
	return str
}

func (ft *FlowTable) NewFlowTableFromFlows(flows []*Flow) *FlowTable {
	nft := NewFlowTable()
	nft.Update(flows)
	return nft
}

/* Return a new FlowTable that contain <last> active flows */
func (ft *FlowTable) FilterLast(last time.Duration) []*Flow {
	ft.lock.RLock()
	defer ft.lock.RUnlock()
	var flows []*Flow
	selected := time.Now().Unix() - int64((last).Seconds())
	for _, f := range ft.table {
		fs := f.GetStatistics()
		if fs.Last >= selected {
			flows = append(flows, f)
		}
	}
	return flows
}
<commit_msg>[FlowTable] fixup race condition on AsyncExpire<commit_after>package flow

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/google/gopacket"

	"github.com/redhat-cip/skydive/logging"
)

type FlowTable struct {
	lock  sync.RWMutex
	table map[string]*Flow
}

type FlowTableAsyncNotificaionUpdate interface {
	AsyncNotificaionUpdate(every time.Duration)
}

func NewFlowTable() *FlowTable {
	return &FlowTable{table: make(map[string]*Flow)}
}

func (ft *FlowTable) String() string {
	return fmt.Sprintf("%d flows", len(ft.table))
}

func (ft *FlowTable) Update(flows []*Flow) {
	ft.lock.Lock()
	defer ft.lock.Unlock()
	for _, f := range flows {
		_, found := ft.table[f.UUID]
		if !found {
			ft.table[f.UUID] = f
		} else if f.UUID != ft.table[f.UUID].UUID {
			logging.GetLogger().Error("FlowTable Collision ", f.UUID, ft.table[f.UUID].UUID)
		}
	}
}

type ExpireFunc func(f *Flow)

func (ft *FlowTable) AsyncExpire(fn ExpireFunc, every time.Duration) {
	ticker := time.NewTicker(every)
	defer ticker.Stop()
	for {
		now := <-ticker.C
		flowTableSzBefore := len(ft.table)
		expire := now.Unix() - int64((every).Seconds())

		ft.lock.Lock()
		for key, f := range ft.table {
			fs := f.GetStatistics()
			if fs.Last < expire {
				duration := time.Duration(fs.Last - fs.Start)
				logging.GetLogger().Debug("%v Expire flow %s Duration %v", now, f.UUID, duration)
				/* Advise Clients */
				fn(f)
				delete(ft.table, key)
			}
		}
		ft.lock.Unlock()
		logging.GetLogger().Debug("%v Expire Flow : removed %v new size %v", now, flowTableSzBefore-len(ft.table), len(ft.table))
	}
}

func (ft *FlowTable) IsExist(f *Flow) bool {
	ft.lock.RLock()
	_, found := ft.table[f.UUID]
	ft.lock.RUnlock()
	return found
}

func (ft *FlowTable) GetFlow(key string, packet *gopacket.Packet) (flow *Flow, new bool) {
	ft.lock.Lock()
	flow, found := ft.table[key]
	if found == false {
		flow = &Flow{}
		ft.table[key] = flow
	}
	ft.lock.Unlock()
	return flow, !found
}

func (ft *FlowTable) JSONFlowConversationEthernetPath() string {
	str := ""
	str += "{"
	//	{"nodes":[{"name":"Myriel","group":1}, ... ],"links":[{"source":1,"target":0,"value":1},...]}

	var strNodes, strLinks string
	strNodes += "\"nodes\":["
	strLinks += "\"links\":["
	pathMap := make(map[string]int)
	ethMap := make(map[string]int)
	for _, f := range ft.table {
		_, found := pathMap[f.LayersPath]
		if !found {
			pathMap[f.LayersPath] = len(pathMap)
		}

		ethFlow := f.GetStatistics().Endpoints[FlowEndpointType_ETHERNET.Value()]
		if _, found := ethMap[ethFlow.AB.Value]; !found {
			ethMap[ethFlow.AB.Value] = len(ethMap)
			strNodes += fmt.Sprintf("{\"name\":\"%s\",\"group\":%d},", ethFlow.AB.Value, pathMap[f.LayersPath])
		}
		if _, found := ethMap[ethFlow.BA.Value]; !found {
			ethMap[ethFlow.BA.Value] = len(ethMap)
			strNodes += fmt.Sprintf("{\"name\":\"%s\",\"group\":%d},", ethFlow.BA.Value, pathMap[f.LayersPath])
		}
		strLinks += fmt.Sprintf("{\"source\":%d,\"target\":%d,\"value\":%d},", ethMap[ethFlow.AB.Value], ethMap[ethFlow.BA.Value], ethFlow.AB.Bytes+ethFlow.BA.Bytes)
	}
	strNodes = strings.TrimRight(strNodes, ",")
	strNodes += "]"
	strLinks = strings.TrimRight(strLinks, ",")
	strLinks += "]"
	str += strNodes + "," + strLinks
	str += "}"
	return str
}

func (ft *FlowTable) NewFlowTableFromFlows(flows []*Flow) *FlowTable {
	nft := NewFlowTable()
	nft.Update(flows)
	return nft
}

/* Return a new FlowTable that contain <last> active flows */
func (ft *FlowTable) FilterLast(last time.Duration) []*Flow {
	ft.lock.RLock()
	defer ft.lock.RUnlock()
	var flows []*Flow
	selected := time.Now().Unix() - int64((last).Seconds())
	for _, f := range ft.table {
		fs := f.GetStatistics()
		if fs.Last >= selected {
			flows = append(flows, f)
		}
	}
	return flows
}
<|endoftext|>


<commit_before>package root

import (
	"context"
	"fmt"

	"github.com/gopasspw/gopass/internal/backend"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/internal/store/leaf"
	"github.com/gopasspw/gopass/pkg/debug"
	"github.com/gopasspw/gopass/pkg/fsutil"
)

// IsInitialized checks on disk if .gpg-id was generated and thus returns true.
func (r *Store) IsInitialized(ctx context.Context) (bool, error) {
	if r.store == nil {
		debug.Log("initializing store and possible sub-stores")

		if err := r.initialize(ctx); err != nil {
			return false, fmt.Errorf("failed to initialize stores: %w", err)
		}
	}

	debug.Log("root store is initialized")

	return r.store.IsInitialized(ctx), nil
}

// Init tries to initialize a new password store location matching the object.
func (r *Store) Init(ctx context.Context, alias, path string, ids ...string) error {
	debug.Log("Instantiating new sub store %s at %s for %+v", alias, path, ids)

	if !backend.HasCryptoBackend(ctx) {
		ctx = backend.WithCryptoBackend(ctx, backend.GPGCLI)
	}

	if !backend.HasStorageBackend(ctx) {
		ctx = backend.WithStorageBackend(ctx, backend.GitFS)
	}

	sub, err := leaf.New(ctx, alias, path)
	if err != nil {
		return fmt.Errorf("failed to instantiate new sub store: %w", err)
	}

	if !r.store.IsInitialized(ctx) && alias == "" {
		r.store = sub
	}

	debug.Log("Initializing sub store at %s for %+v", path, ids)

	if err := sub.Init(ctx, path, ids...); err != nil {
		return fmt.Errorf("failed to initialize new sub store: %w", err)
	}

	if alias == "" {
		debug.Log("initialized root at %s", path)
		r.cfg.Path = path
	} else {
		debug.Log("mounted %s at %s", alias, path)
		r.cfg.Mounts[alias] = path
	}

	return nil
}

func (r *Store) initialize(ctx context.Context) error {
	// already initialized?
	if r.store != nil {
		return nil
	}

	// create the base store
	path := fsutil.CleanPath(r.cfg.Path)
	debug.Log("initialize - %s", path)

	s, err := leaf.New(ctx, "", path)
	if err != nil {
		return fmt.Errorf("failed to initialize the root store at %q: %w", r.cfg.Path, err)
	}

	debug.Log("Root Store initialized at %s", path)

	r.store = s

	// initialize all mounts
	for alias, path := range r.cfg.Mounts {
		path := fsutil.CleanPath(path)
		if err := r.addMount(ctx, alias, path); err != nil {
			out.Errorf(ctx, "Failed to initialize mount %s (%s). Ignoring: %s", alias, path, err)

			continue
		}

		debug.Log("Sub-Store mounted at %s from %s", alias, path)
	}

	// check for duplicate mounts
	if err := r.checkMounts(); err != nil {
		return fmt.Errorf("checking mounts failed: %w", err)
	}

	return nil
}
<commit_msg>Honor PASSWORD_STORE_DIR (#2272)<commit_after>package root

import (
	"context"
	"fmt"
	"os"

	"github.com/gopasspw/gopass/internal/backend"
	"github.com/gopasspw/gopass/internal/out"
	"github.com/gopasspw/gopass/internal/store/leaf"
	"github.com/gopasspw/gopass/pkg/debug"
	"github.com/gopasspw/gopass/pkg/fsutil"
)

// IsInitialized checks on disk if .gpg-id was generated and thus returns true.
func (r *Store) IsInitialized(ctx context.Context) (bool, error) {
	if r.store == nil {
		debug.Log("initializing store and possible sub-stores")

		if err := r.initialize(ctx); err != nil {
			return false, fmt.Errorf("failed to initialize stores: %w", err)
		}
	}

	debug.Log("root store is initialized")

	return r.store.IsInitialized(ctx), nil
}

// Init tries to initialize a new password store location matching the object.
func (r *Store) Init(ctx context.Context, alias, path string, ids ...string) error {
	debug.Log("Instantiating new sub store %s at %s for %+v", alias, path, ids)

	if !backend.HasCryptoBackend(ctx) {
		ctx = backend.WithCryptoBackend(ctx, backend.GPGCLI)
	}

	if !backend.HasStorageBackend(ctx) {
		ctx = backend.WithStorageBackend(ctx, backend.GitFS)
	}

	sub, err := leaf.New(ctx, alias, path)
	if err != nil {
		return fmt.Errorf("failed to instantiate new sub store: %w", err)
	}

	if !r.store.IsInitialized(ctx) && alias == "" {
		r.store = sub
	}

	debug.Log("Initializing sub store at %s for %+v", path, ids)

	if err := sub.Init(ctx, path, ids...); err != nil {
		return fmt.Errorf("failed to initialize new sub store: %w", err)
	}

	if alias == "" {
		debug.Log("initialized root at %s", path)
		r.cfg.Path = path
	} else {
		debug.Log("mounted %s at %s", alias, path)
		r.cfg.Mounts[alias] = path
	}

	return nil
}

func (r *Store) initialize(ctx context.Context) error {
	// already initialized?
	if r.store != nil {
		return nil
	}

	// create the base store
	path := fsutil.CleanPath(r.cfg.Path)
	if sv := os.Getenv("PASSWORD_STORE_DIR"); sv != "" {
		path = fsutil.CleanPath(sv)
	}
	debug.Log("initialize - %s", path)

	s, err := leaf.New(ctx, "", path)
	if err != nil {
		return fmt.Errorf("failed to initialize the root store at %q: %w", r.cfg.Path, err)
	}

	debug.Log("Root Store initialized at %s", path)

	r.store = s

	// initialize all mounts
	for alias, path := range r.cfg.Mounts {
		path := fsutil.CleanPath(path)
		if err := r.addMount(ctx, alias, path); err != nil {
			out.Errorf(ctx, "Failed to initialize mount %s (%s). Ignoring: %s", alias, path, err)

			continue
		}

		debug.Log("Sub-Store mounted at %s from %s", alias, path)
	}

	// check for duplicate mounts
	if err := r.checkMounts(); err != nil {
		return fmt.Errorf("checking mounts failed: %w", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/signal"
	"time"

	"github.com/polydawn/refmt"
	"github.com/polydawn/refmt/json"
	"gopkg.in/alecthomas/kingpin.v2"

	tar "go.polydawn.net/rio/transmat/tar"
	"go.polydawn.net/timeless-api"
	"go.polydawn.net/timeless-api/rio"
)

/*
	Output serialization formats
*/
const (
	FmtJson = "json"
	FmtDumb = "dumb"
)

type baseCLI struct {
	Deadline       string        // Deadline time (RFC3339 or @UNIX)
	Format         string        // Output api format, eg. json
	ProgressEnable bool          // Emit progress notification yes/no
	ProgressRate   time.Duration // How frequently to emit progress notification
	Test           string        // Testmode
	Timeout        time.Duration // Timeout duration (exclusive with deadline) eg. "60s"
	PackCLI        struct {
		Filters             api.FilesetFilters // TODO: file filters for pack/unpack
		Path                string             // Pack target path
		TargetWarehouseAddr string             // Warehouse address to push to
	}
	UnpackCLI struct {
		Filters              api.FilesetFilters
		Path                 string   // Unpack target path
		SourcesWarehouseAddr []string // Warehouse address to push to
		WareID               string   // Ware id string "<kind>:<hash>"
	}
	MirrorCLI struct {
		SourcesWarehouseAddr []string // Warehouse addresses to fetch from
		TargetWarehouseAddr  string   // Warehouse address to push to
		WareID               string   // Ware id string "<kind>:<hash>"
	}
}

func configurePack(cli *baseCLI, appPack *kingpin.CmdClause) {
	// Non-filter flags
	appPack.Flag("path", "Target path").
		StringVar(&cli.PackCLI.Path)
	appPack.Flag("target", "Warehouse in which to place the ware").
		StringVar(&cli.PackCLI.TargetWarehouseAddr)

	// Filter flags
	appPack.Flag("uid", "Set UID filter [keep, <int>]").
		StringVar(&cli.PackCLI.Filters.Uid)
	appPack.Flag("gid", "Set GID filter [keep, <int>]").
		StringVar(&cli.PackCLI.Filters.Gid)
	appPack.Flag("mtime", "Set mtime filter [keep, <@UNIX>, <RFC3339>]. Will be set to a date if not specified.").
		StringVar(&cli.PackCLI.Filters.Mtime)
	// Sticky flag not used for pack
}

func configureUnpack(cli *baseCLI, appUnpack *kingpin.CmdClause) {
	// Non-filter flags
	appUnpack.Flag("path", "Target path").
		StringVar(&cli.UnpackCLI.Path)
	appUnpack.Flag("source", "Warehouses from which to fetch the ware").
		StringsVar(&cli.UnpackCLI.SourcesWarehouseAddr)
	appUnpack.Flag("ware", "Ware ID").
		StringVar(&cli.UnpackCLI.WareID)

	// Filter flags
	appUnpack.Flag("uid", "Set UID filter [keep, mine, <int>]").
		Default("mine").
		StringVar(&cli.UnpackCLI.Filters.Uid)
	appUnpack.Flag("gid", "Set GID filter [keep, mine, <int>]").
		Default("mine").
		StringVar(&cli.UnpackCLI.Filters.Gid)
	appUnpack.Flag("mtime", "Set mtime filter [keep, <@UNIX>, <RFC3339>]").
		Default("keep").
		StringVar(&cli.UnpackCLI.Filters.Mtime)
	appUnpack.Flag("sticky", "Keep setuid, setgid, and sticky bits").
		BoolVar(&cli.UnpackCLI.Filters.Sticky)
}

func configureMirror(cli *baseCLI, appMirror *kingpin.CmdClause) {
	appMirror.Flag("ware", "Ware ID").
		StringVar(&cli.MirrorCLI.WareID)
	appMirror.Flag("target", "Warehouse in which to place the ware").
		StringVar(&cli.MirrorCLI.TargetWarehouseAddr)
	appMirror.Flag("source", "Warehouses from which to fetch the ware").
		StringsVar(&cli.MirrorCLI.SourcesWarehouseAddr)
}

/*
	Blocks until a sigint is received, then calls cancel.
*/
func CancelOnInterrupt(cancel context.CancelFunc) {
	signalChan := make(chan os.Signal)
	signal.Notify(signalChan, os.Interrupt)
	<-signalChan
	cancel()
	close(signalChan)
}

func main() {
	ctx := context.Background()
	exitCode := Main(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr)
	os.Exit(int(exitCode))
}

func Main(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) rio.ExitCode {
	exitCode := rio.ExitSuccess
	ctx, cancel := context.WithCancel(ctx)
	go CancelOnInterrupt(cancel)

	cli := baseCLI{}

	app := kingpin.New("rio", "Repeatable I/O")
	app.HelpFlag.Short('h')

	app.UsageWriter(stderr)
	app.ErrorWriter(stderr)

	app.Flag("deadline", "Deadline (RFC3339)").
		StringVar(&cli.Deadline)
	app.Flag("timeout", "Timeout for command").
		DurationVar(&cli.Timeout)
	app.Flag("format", "Output api format").
		Default(FmtDumb).
		EnumVar(&cli.Format, FmtJson, FmtDumb)
	app.Flag("progress-rate", "How frequently to emit progress notification").
		DurationVar(&cli.ProgressRate)
	app.Flag("progress", "Emit progress notification").
		BoolVar(&cli.ProgressEnable)
	app.Flag("test", "Testmodes").
		StringVar(&cli.Test)

	appPack := app.Command("pack", "pack a fileset into a ware")
	configurePack(&cli, appPack)

	appUnpack := app.Command("unpack", "fetch a ware")
	configureUnpack(&cli, appUnpack)

	appMirror := app.Command("mirror", "mirror a ware to another warehouse")
	configureUnpack(&cli, appMirror)

	cmd, err := Parse(app, args)
	if err != nil {
		fmt.Fprintln(stderr, err)
		return rio.ExitUsage
	}
	var wareID api.WareID
	// FIXME: We'll need to support more than tar eventually
	switch cmd {
	case appPack.FullCommand():
		wareID, err = executePack(ctx, cli, rio.PackFunc(tar.Pack))
		SerializeResult(cli.Format, wareID, err, stdout, stderr)
	case appUnpack.FullCommand():
		wareID, err = executeUnpack(ctx, cli, rio.UnpackFunc(tar.Unpack))
		SerializeResult(cli.Format, wareID, err, stdout, stderr)
	case appMirror.FullCommand():
		fmt.Fprintln(stderr, "not implemented")
		return rio.ExitNotImplemented
	}

	return exitCode
}

/*
	Kingpin really expects terminate to terminate.
	So we make it panic and handle it. Passing errors back up.
*/
func Parse(app *kingpin.Application, args []string) (cmd string, err error) {
	defer func() {
		r := recover()
		if e, ok := r.(error); ok {
			err = e
		}
	}()
	app.Terminate(func(status int) {
		termErr := fmt.Errorf("rio: parse error %d\n", status)
		panic(termErr)
	})
	return app.Parse(args[1:])
}

func SerializeResult(format string, wareID api.WareID, resultErr error, stdout io.Writer, stderr io.Writer) {
	ev := rio.Event{Result: &rio.Event_Result{WareID: wareID, Error: resultErr}}
	switch format {
	case FmtJson:
		marshaller := refmt.NewMarshallerAtlased(json.EncodeOptions{}, stdout, rio.Atlas)
		err := marshaller.Marshal(&ev)
		if err != nil {
			panic(err)
		}
	case FmtDumb:
		if resultErr != nil {
			fmt.Fprintln(stderr, resultErr)
		} else {
			fmt.Fprintln(stdout, wareID)
		}
	default:
		panic(fmt.Errorf("rio: invalid format %s", format))
	}
}

func convertWarehouseSlice(slice []string) []api.WarehouseAddr {
	result := make([]api.WarehouseAddr, len(slice))
	for idx, item := range slice {
		result[idx] = api.WarehouseAddr(item)
	}
	return result
}

func executeUnpack(ctx context.Context, cli baseCLI, unpackFn rio.UnpackFunc) (api.WareID, error) {
	monitor := rio.Monitor{}
	wareID, err := api.ParseWareID(cli.UnpackCLI.WareID)
	if err != nil {
		return api.WareID{}, err
	}
	path := cli.UnpackCLI.Path
	warehouses := convertWarehouseSlice(cli.UnpackCLI.SourcesWarehouseAddr)
	return unpackFn(ctx, wareID, path, cli.UnpackCLI.Filters, warehouses, monitor)
}

func executePack(ctx context.Context, cli baseCLI, packFn rio.PackFunc) (api.WareID, error) {
	monitor := rio.Monitor{}
	warehouse := api.WarehouseAddr(cli.PackCLI.TargetWarehouseAddr)

	return packFn(ctx, cli.PackCLI.Path, cli.PackCLI.Filters, warehouse, monitor)
}
<commit_msg>Improve handling of parse errors<commit_after>package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/signal"
	"time"

	"github.com/polydawn/refmt"
	"github.com/polydawn/refmt/json"
	"gopkg.in/alecthomas/kingpin.v2"

	tar "go.polydawn.net/rio/transmat/tar"
	"go.polydawn.net/timeless-api"
	"go.polydawn.net/timeless-api/rio"
)

/*
	Output serialization formats
*/
const (
	FmtJson = "json"
	FmtDumb = "dumb"
)

type baseCLI struct {
	Deadline       string        // Deadline time (RFC3339 or @UNIX)
	Format         string        // Output api format, eg. json
	ProgressEnable bool          // Emit progress notification yes/no
	ProgressRate   time.Duration // How frequently to emit progress notification
	Test           string        // Testmode
	Timeout        time.Duration // Timeout duration (exclusive with deadline) eg. "60s"
	PackCLI        struct {
		Filters             api.FilesetFilters // TODO: file filters for pack/unpack
		Path                string             // Pack target path
		TargetWarehouseAddr string             // Warehouse address to push to
	}
	UnpackCLI struct {
		Filters              api.FilesetFilters
		Path                 string   // Unpack target path
		SourcesWarehouseAddr []string // Warehouse address to push to
		WareID               string   // Ware id string "<kind>:<hash>"
	}
	MirrorCLI struct {
		SourcesWarehouseAddr []string // Warehouse addresses to fetch from
		TargetWarehouseAddr  string   // Warehouse address to push to
		WareID               string   // Ware id string "<kind>:<hash>"
	}
}

func configurePack(cli *baseCLI, appPack *kingpin.CmdClause) {
	// Non-filter flags
	appPack.Flag("path", "Target path").
		Required().
		StringVar(&cli.PackCLI.Path)
	appPack.Flag("target", "Warehouse in which to place the ware").
		Required().
		StringVar(&cli.PackCLI.TargetWarehouseAddr)

	// Filter flags
	appPack.Flag("uid", "Set UID filter [keep, <int>]").
		StringVar(&cli.PackCLI.Filters.Uid)
	appPack.Flag("gid", "Set GID filter [keep, <int>]").
		StringVar(&cli.PackCLI.Filters.Gid)
	appPack.Flag("mtime", "Set mtime filter [keep, <@UNIX>, <RFC3339>]. Will be set to a date if not specified.").
		StringVar(&cli.PackCLI.Filters.Mtime)
	// Sticky flag not used for pack
}

func configureUnpack(cli *baseCLI, appUnpack *kingpin.CmdClause) {
	// Non-filter flags
	appUnpack.Flag("path", "Target path").
		Required().
		StringVar(&cli.UnpackCLI.Path)
	appUnpack.Flag("source", "Warehouses from which to fetch the ware").
		Required().
		StringsVar(&cli.UnpackCLI.SourcesWarehouseAddr)
	appUnpack.Flag("ware", "Ware ID").
		Required().
		StringVar(&cli.UnpackCLI.WareID)

	// Filter flags
	appUnpack.Flag("uid", "Set UID filter [keep, mine, <int>]").
		Default("mine").
		StringVar(&cli.UnpackCLI.Filters.Uid)
	appUnpack.Flag("gid", "Set GID filter [keep, mine, <int>]").
		Default("mine").
		StringVar(&cli.UnpackCLI.Filters.Gid)
	appUnpack.Flag("mtime", "Set mtime filter [keep, <@UNIX>, <RFC3339>]").
		Default("keep").
		StringVar(&cli.UnpackCLI.Filters.Mtime)
	appUnpack.Flag("sticky", "Keep setuid, setgid, and sticky bits").
		BoolVar(&cli.UnpackCLI.Filters.Sticky)
}

func configureMirror(cli *baseCLI, appMirror *kingpin.CmdClause) {
	appMirror.Flag("ware", "Ware ID").
		Required().
		StringVar(&cli.MirrorCLI.WareID)
	appMirror.Flag("target", "Warehouse in which to place the ware").
		Required().
		StringVar(&cli.MirrorCLI.TargetWarehouseAddr)
	appMirror.Flag("source", "Warehouses from which to fetch the ware").
		Required().
		StringsVar(&cli.MirrorCLI.SourcesWarehouseAddr)
}

/*
	Blocks until a sigint is received, then calls cancel.
*/
func CancelOnInterrupt(cancel context.CancelFunc) {
	signalChan := make(chan os.Signal)
	signal.Notify(signalChan, os.Interrupt)
	<-signalChan
	cancel()
	close(signalChan)
}

func main() {
	ctx := context.Background()
	exitCode := Main(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr)
	os.Exit(int(exitCode))
}

func Main(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) rio.ExitCode {
	exitCode := rio.ExitSuccess
	ctx, cancel := context.WithCancel(ctx)
	go CancelOnInterrupt(cancel)

	cli := baseCLI{}

	app := kingpin.New("rio", "Repeatable I/O")
	app.HelpFlag.Short('h')

	app.UsageWriter(stderr)
	app.ErrorWriter(stderr)

	app.Flag("deadline", "Deadline (RFC3339)").
		StringVar(&cli.Deadline)
	app.Flag("timeout", "Timeout for command").
		DurationVar(&cli.Timeout)
	app.Flag("format", "Output api format").
		Default(FmtDumb).
		EnumVar(&cli.Format, FmtJson, FmtDumb)
	app.Flag("progress-rate", "How frequently to emit progress notification").
		DurationVar(&cli.ProgressRate)
	app.Flag("progress", "Emit progress notification").
		BoolVar(&cli.ProgressEnable)
	app.Flag("test", "Testmodes").
		StringVar(&cli.Test)

	appPack := app.Command("pack", "pack a fileset into a ware")
	configurePack(&cli, appPack)

	appUnpack := app.Command("unpack", "fetch a ware")
	configureUnpack(&cli, appUnpack)

	appMirror := app.Command("mirror", "mirror a ware to another warehouse")
	configureUnpack(&cli, appMirror)

	cmd, err, terminated := Parse(app, args)
	if err != nil {
		fmt.Fprintln(stderr, err)
		return rio.ExitUsage
	} else if terminated {
		return rio.ExitUsage
	}
	var wareID api.WareID
	// FIXME: We'll need to support more than tar eventually
	switch cmd {
	case appPack.FullCommand():
		wareID, err = executePack(ctx, cli, rio.PackFunc(tar.Pack))
		SerializeResult(cli.Format, wareID, err, stdout, stderr)
	case appUnpack.FullCommand():
		wareID, err = executeUnpack(ctx, cli, rio.UnpackFunc(tar.Unpack))
		SerializeResult(cli.Format, wareID, err, stdout, stderr)
	case appMirror.FullCommand():
		fmt.Fprintln(stderr, "not implemented")
		return rio.ExitNotImplemented
	}

	return exitCode
}

/*
	Kingpin really expects terminate to terminate.
	So we make it panic and handle it. Passing errors back up.
*/
func Parse(app *kingpin.Application, args []string) (cmd string, err error, terminated bool) {
	defer func() {
		recover()
	}()
	app.Terminate(func(status int) {
		terminated = true
		panic("")
	})
	cmd, err = app.Parse(args[1:])
	return
}

func SerializeResult(format string, wareID api.WareID, resultErr error, stdout io.Writer, stderr io.Writer) {
	ev := rio.Event{Result: &rio.Event_Result{WareID: wareID, Error: resultErr}}
	switch format {
	case FmtJson:
		marshaller := refmt.NewMarshallerAtlased(json.EncodeOptions{}, stdout, rio.Atlas)
		err := marshaller.Marshal(&ev)
		if err != nil {
			panic(err)
		}
	case FmtDumb:
		if resultErr != nil {
			fmt.Fprintln(stderr, resultErr)
		} else {
			fmt.Fprintln(stdout, wareID)
		}
	default:
		panic(fmt.Errorf("rio: invalid format %s", format))
	}
}

func convertWarehouseSlice(slice []string) []api.WarehouseAddr {
	result := make([]api.WarehouseAddr, len(slice))
	for idx, item := range slice {
		result[idx] = api.WarehouseAddr(item)
	}
	return result
}

func executeUnpack(ctx context.Context, cli baseCLI, unpackFn rio.UnpackFunc) (api.WareID, error) {
	monitor := rio.Monitor{}
	wareID, err := api.ParseWareID(cli.UnpackCLI.WareID)
	if err != nil {
		return api.WareID{}, err
	}
	path := cli.UnpackCLI.Path
	warehouses := convertWarehouseSlice(cli.UnpackCLI.SourcesWarehouseAddr)
	return unpackFn(ctx, wareID, path, cli.UnpackCLI.Filters, warehouses, monitor)
}

func executePack(ctx context.Context, cli baseCLI, packFn rio.PackFunc) (api.WareID, error) {
	monitor := rio.Monitor{}
	warehouse := api.WarehouseAddr(cli.PackCLI.TargetWarehouseAddr)

	return packFn(ctx, cli.PackCLI.Path, cli.PackCLI.Filters, warehouse, monitor)
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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 tfengine

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/hcl"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/jsonschema"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/template"
	"github.com/ghodss/yaml"
	"github.com/hashicorp/hcl/v2/hclsimple"
	"github.com/zclconf/go-cty/cty"
)

// Config is the user supplied config for the engine.
// HCL struct tags are documented at https://pkg.go.dev/github.com/hashicorp/hcl2/gohcl.
type Config struct {
	// HCL decoder can't unmarshal into map[string]interface{},
	// so make it unmarshal to a cty.Value and manually convert to map.
	// TODO(https://github.com/hashicorp/hcl/issues/291): Remove the need for DataCty.
	DataCty *cty.Value             `hcl:"data,optional" json:"-"`
	Data    map[string]interface{} `json:"data,omitempty"`

	SchemaCty *cty.Value             `hcl:"schema,optional" json:"-"`
	Schema    map[string]interface{} `json:"schema,omitempty"`

	Templates []*templateInfo `hcl:"template,block" json:"template,omitempty"`
}

type templateInfo struct {
	Name          string                  `hcl:",label" json:"name"`
	Source        string                  `hcl:"source,optional" json:"source,omitempty"`
	ComponentPath string                  `hcl:"component_path,optional" json:"component_path,omitempty"`
	RecipePath    string                  `hcl:"recipe_path,optional" json:"recipe_path,omitempty"`
	OutputPath    string                  `hcl:"output_path,optional" json:"output_path,omitempty"`
	Flatten       []*template.FlattenInfo `hcl:"flatten,block" json:"flatten,omitempty"`

	DataCty *cty.Value             `hcl:"data,optional" json:"-"`
	Data    map[string]interface{} `json:"data,omitempty"`
}

// Init initializes and validates the config.
func (c *Config) Init() error {
	var err error
	if c.DataCty != nil {
		c.Data, err = hcl.CtyValueToMap(c.DataCty)
		if err != nil {
			return fmt.Errorf("failed to convert %v to map: %v", c.DataCty, err)
		}
	}

	if c.Data == nil {
		c.Data = make(map[string]interface{})
	}

	if c.SchemaCty != nil {
		c.Schema, err = hcl.CtyValueToMap(c.SchemaCty)
		if err != nil {
			return fmt.Errorf("failed to convert schema %v to map: %v", c.SchemaCty, err)
		}
		// Add output_path to be a valid field in schema. It is set for each template below.
		props := c.Schema["properties"].(map[string]interface{})
		props["output_path"] = make(map[string]interface{})
	}

	for _, t := range c.Templates {
		if t.DataCty != nil {
			t.Data, err = hcl.CtyValueToMap(t.DataCty)
			if err != nil {
				return fmt.Errorf("failed to convert data %v to map: %v", t.DataCty, err)
			}
		}
		if t.Data == nil {
			t.Data = make(map[string]interface{})
		}
		if t.OutputPath != "" {
			t.Data["output_path"] = filepath.Clean(t.OutputPath)
		}
	}
	return c.validate()
}

func (c *Config) validate() error {
	sj, err := hcl.ToJSON([]byte(Schema))
	if err != nil {
		return err
	}
	cj, err := json.Marshal(c)
	if err != nil {
		return err
	}

	return jsonschema.ValidateJSONBytes(sj, cj)
}

func loadConfig(path string, data map[string]interface{}) (*Config, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	buf, err := template.WriteBuffer(string(b), data)
	if err != nil {
		return nil, err
	}

	// Convert yaml to json so hcl decoder can parse it.
	cj := buf.Bytes()
	if filepath.Ext(path) == ".yaml" {
		cj, err = yaml.YAMLToJSON(cj)
		if err != nil {
			return nil, err
		}
		// hclsimple.Decode doesn't actually use the path for anything other
		// than its extension, so just pass in any file name ending with json so
		// the library knows to treat these bytes as json and not yaml.
		path = "file.json"
	}

	c := new(Config)
	if err := hclsimple.Decode(path, cj, nil, c); err != nil {
		return nil, err
	}
	if err := c.Init(); err != nil {
		return nil, err
	}
	return c, nil
}
<commit_msg>Add more information to loadConfig errors (#552)<commit_after>// Copyright 2020 Google LLC
//
// 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 tfengine

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/hcl"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/jsonschema"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/template"
	"github.com/ghodss/yaml"
	"github.com/hashicorp/hcl/v2/hclsimple"
	"github.com/zclconf/go-cty/cty"
)

// Config is the user supplied config for the engine.
// HCL struct tags are documented at https://pkg.go.dev/github.com/hashicorp/hcl2/gohcl.
type Config struct {
	// HCL decoder can't unmarshal into map[string]interface{},
	// so make it unmarshal to a cty.Value and manually convert to map.
	// TODO(https://github.com/hashicorp/hcl/issues/291): Remove the need for DataCty.
	DataCty *cty.Value             `hcl:"data,optional" json:"-"`
	Data    map[string]interface{} `json:"data,omitempty"`

	SchemaCty *cty.Value             `hcl:"schema,optional" json:"-"`
	Schema    map[string]interface{} `json:"schema,omitempty"`

	Templates []*templateInfo `hcl:"template,block" json:"template,omitempty"`
}

type templateInfo struct {
	Name          string                  `hcl:",label" json:"name"`
	Source        string                  `hcl:"source,optional" json:"source,omitempty"`
	ComponentPath string                  `hcl:"component_path,optional" json:"component_path,omitempty"`
	RecipePath    string                  `hcl:"recipe_path,optional" json:"recipe_path,omitempty"`
	OutputPath    string                  `hcl:"output_path,optional" json:"output_path,omitempty"`
	Flatten       []*template.FlattenInfo `hcl:"flatten,block" json:"flatten,omitempty"`

	DataCty *cty.Value             `hcl:"data,optional" json:"-"`
	Data    map[string]interface{} `json:"data,omitempty"`
}

// Init initializes and validates the config.
func (c *Config) Init() error {
	var err error
	if c.DataCty != nil {
		c.Data, err = hcl.CtyValueToMap(c.DataCty)
		if err != nil {
			return fmt.Errorf("failed to convert %v to map: %v", c.DataCty, err)
		}
	}

	if c.Data == nil {
		c.Data = make(map[string]interface{})
	}

	if c.SchemaCty != nil {
		c.Schema, err = hcl.CtyValueToMap(c.SchemaCty)
		if err != nil {
			return fmt.Errorf("failed to convert schema %v to map: %v", c.SchemaCty, err)
		}
		// Add output_path to be a valid field in schema. It is set for each template below.
		props := c.Schema["properties"].(map[string]interface{})
		props["output_path"] = make(map[string]interface{})
	}

	for _, t := range c.Templates {
		if t.DataCty != nil {
			t.Data, err = hcl.CtyValueToMap(t.DataCty)
			if err != nil {
				return fmt.Errorf("failed to convert data %v to map: %v", t.DataCty, err)
			}
		}
		if t.Data == nil {
			t.Data = make(map[string]interface{})
		}
		if t.OutputPath != "" {
			t.Data["output_path"] = filepath.Clean(t.OutputPath)
		}
	}
	return c.validate()
}

func (c *Config) validate() error {
	sj, err := hcl.ToJSON([]byte(Schema))
	if err != nil {
		return err
	}
	cj, err := json.Marshal(c)
	if err != nil {
		return err
	}

	return jsonschema.ValidateJSONBytes(sj, cj)
}

func loadConfig(path string, data map[string]interface{}) (*Config, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read file %v: %v", path, err)
	}
	buf, err := template.WriteBuffer(string(b), data)
	if err != nil {
		return nil, fmt.Errorf("parsing template %v: %v", string(b), err)
	}

	// Convert yaml to json so hcl decoder can parse it.
	cj := buf.Bytes()
	if filepath.Ext(path) == ".yaml" {
		cj, err = yaml.YAMLToJSON(cj)
		if err != nil {
			return nil, fmt.Errorf("converting yaml to json %v: %v", string(cj), err)
		}
		// hclsimple.Decode doesn't actually use the path for anything other
		// than its extension, so just pass in any file name ending with json so
		// the library knows to treat these bytes as json and not yaml.
		path = "file.json"
	}

	c := new(Config)
	if err := hclsimple.Decode(path, cj, nil, c); err != nil {
		return nil, fmt.Errorf("decode as hcl %v: %v", string(cj), err)
	}
	if err := c.Init(); err != nil {
		return nil, fmt.Errorf("init config %v: %v", c, err)
	}
	return c, nil
}
<|endoftext|>


<commit_before>package ov

import (
	"encoding/json"
	"fmt"
	"github.com/HewlettPackard/oneview-golang/rest"
	"github.com/HewlettPackard/oneview-golang/utils"
	"github.com/docker/machine/libmachine/log"
)

type EthernetNetwork struct {
	Category              string        `json:"category,omitempty"`              // "category": "ethernet-networks",
	ConnectionTemplateUri utils.Nstring `json:"connectionTemplateUri,omitempty"` // "connectionTemplateUri": "/rest/connection-templates/7769cae0-b680-435b-9b87-9b864c81657f",
	Created               string        `json:"created,omitempty"`               // "created": "20150831T154835.250Z",
	Description           utils.Nstring `json:"description,omitempty"`           // "description": "Ethernet network 1",
	ETAG                  string        `json:"eTag,omitempty"`                  // "eTag": "1441036118675/8",
	EthernetNetworkType   string        `json:"ethernetNetworkType,omitempty"`   // "ethernetNetworkType": "Tagged",
	FabricUri             utils.Nstring `json:"fabricUri,omitempty"`             // "fabricUri": "/rest/fabrics/9b8f7ec0-52b3-475e-84f4-c4eac51c2c20",
	Modified              string        `json:"modified,omitempty"`              // "modified": "20150831T154835.250Z",
	Name                  string        `json:"name,omitempty"`                  // "name": "Ethernet Network 1",
	PrivateNetwork        bool          `json:"privateNetwork"`                  // "privateNetwork": false,
	Purpose               string        `json:"purpose,omitempty"`               // "purpose": "General",
	SmartLink             bool          `json:"smartLink"`                       // "smartLink": false,
	State                 string        `json:"state,omitempty"`                 // "state": "Normal",
	Status                string        `json:"status,omitempty"`                // "status": "Critical",
	Type                  string        `json:"type,omitempty"`                  // "type": "ethernet-networkV3",
	URI                   utils.Nstring `json:"uri,omitempty"`                   // "uri": "/rest/ethernet-networks/e2f0031b-52bd-4223-9ac1-d91cb519d548"
	VlanId                int           `json:"vlanId,omitempty"`                // "vlanId": 1,
}

type EthernetNetworkList struct {
	Total       int               `json:"total,omitempty"`       // "total": 1,
	Count       int               `json:"count,omitempty"`       // "count": 1,
	Start       int               `json:"start,omitempty"`       // "start": 0,
	PrevPageURI utils.Nstring     `json:"prevPageUri,omitempty"` // "prevPageUri": null,
	NextPageURI utils.Nstring     `json:"nextPageUri,omitempty"` // "nextPageUri": null,
	URI         utils.Nstring     `json:"uri,omitempty"`         // "uri": "/rest/ethernet-networks?filter=connectionTemplateUri%20matches%7769cae0-b680-435b-9b87-9b864c81657fsort=name:asc"
	Members     []EthernetNetwork `json:"members,omitempty"`     // "members":[]
}

type Bandwidth struct {
	MaximumBandwidth int `json:"maximumBandwidth"` //"maximumBandwidth":10000
	TypicalBandwidth int `json:"typicalBandwidth"` //"typicalBandwidth":2000
}

type BulkEthernetNetwork struct {
	VlanIdRange    string    `json:"vlanIdRange"`    // "vlanIdRange":"1-500",
	Purpose        string    `json:"purpose"`        // "purpose":"General",
	NamePrefix     string    `json:"namePrefix"`     // "namePrefix":"TestNetwork",
	SmartLink      bool      `json:"smartLink"`      // "smartLink":false,
	PrivateNetwork bool      `json:"privateNetwork"` // "privateNetwork":false,
	Bandwidth      Bandwidth `json:"bandwidth"`      // "bandwidth":10000,2000
	Type           string    `json:"type"`           // "type":"bulk-ethernet-network",
}

func (c *OVClient) GetEthernetNetworkByName(name string) (EthernetNetwork, error) {
	var (
		eNet EthernetNetwork
	)
	eNets, err := c.GetEthernetNetworks(fmt.Sprintf("name matches '%s'", name), "name:asc")
	if eNets.Total > 0 {
		return eNets.Members[0], err
	} else {
		return eNet, err
	}
}

func (c *OVClient) GetEthernetNetworks(filter string, sort string) (EthernetNetworkList, error) {
	var (
		uri              = "/rest/ethernet-networks"
		q                map[string]interface{}
		ethernetNetworks EthernetNetworkList
	)
	q = make(map[string]interface{})
	if len(filter) > 0 {
		q["filter"] = filter
	}

	if sort != "" {
		q["sort"] = sort
	}

	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	// Setup query
	if len(q) > 0 {
		c.SetQueryString(q)
	}

	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return ethernetNetworks, err
	}

	log.Debugf("GetEthernetNetworks %s", data)
	if err := json.Unmarshal([]byte(data), &ethernetNetworks); err != nil {
		return ethernetNetworks, err
	}
	return ethernetNetworks, nil
}

func (c *OVClient) GetAssociatedProfile(id string) ([]string, error) {
	var (
		uri            = "/rest/ethernet-networks/"
		serverProfiles = new([]string)
	)
	uri = uri + id + "/associatedProfiles"
	fmt.Println(uri)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return *serverProfiles, err
	}
	log.Infof("GetAssociatedProfile %s", data)
	if err := json.Unmarshal([]byte(data), serverProfiles); err != nil {
		return *serverProfiles, err
	}
	return *serverProfiles, nil
}

func (c *OVClient) GetAssociatedUplinkGroup(id string) ([]string, error) {
	var (
		uri          = "/rest/ethernet-networks/"
		uplinkGroups = new([]string)
	)
	uri = uri + id + "/associatedUplinkGroups"
	fmt.Println(uri)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return *uplinkGroups, err
	}
	log.Infof("GetAssociatedUplinkGroups %s", data)
	if err := json.Unmarshal([]byte(data), uplinkGroups); err != nil {
		return *uplinkGroups, err
	}
	return *uplinkGroups, nil
}

func (c *OVClient) CreateEthernetNetwork(eNet EthernetNetwork) error {
	log.Infof("Initializing creation of ethernet network for %s.", eNet.Name)
	var (
		uri = "/rest/ethernet-networks"
		t   *Task
	)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())

	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST : %s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.POST, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting new ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response New EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return nil
}

func (c *OVClient) CreateBulkEthernetNetwork(eNet BulkEthernetNetwork) error {
	log.Infof("Initializing creation of bulk ethernet network")
	var (
		uri = "rest/ethernet-networks/bulk"
		t   *Task
	)
	//refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST :%s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.POST, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting new bulk ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response New Bulk EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}
	return nil
}

func (c *OVClient) DeleteEthernetNetwork(name string) error {
	var (
		eNet EthernetNetwork
		err  error
		t    *Task
		uri  string
	)

	eNet, err = c.GetEthernetNetworkByName(name)
	if err != nil {
		return err
	}
	if eNet.Name != "" {
		t = t.NewProfileTask(c)
		t.ResetTask()
		log.Debugf("REST : %s \n %+v\n", eNet.URI, eNet)
		log.Debugf("task -> %+v", t)
		uri = eNet.URI.String()
		if uri == "" {
			log.Warn("Unable to post delete, no uri found.")
			t.TaskIsDone = true
			return err
		}
		data, err := c.RestAPICall(rest.DELETE, uri, nil)
		if err != nil {
			log.Errorf("Error submitting delete ethernet network request: %s", err)
			t.TaskIsDone = true
			return err
		}

		log.Debugf("Response delete ethernet network %s", data)
		if err := json.Unmarshal([]byte(data), &t); err != nil {
			t.TaskIsDone = true
			log.Errorf("Error with task un-marshal: %s", err)
			return err
		}
		err = t.Wait()
		if err != nil {
			return err
		}
		return nil
	} else {
		log.Infof("EthernetNetwork could not be found to delete, %s, skipping delete ...", name)
	}
	return nil
}

func (c *OVClient) UpdateEthernetNetwork(eNet EthernetNetwork) error {
	log.Infof("Initializing update of ethernet network for %s.", eNet.Name)
	var (
		uri = eNet.URI.String()
		t   *Task
	)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())

	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST : %s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.PUT, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting update ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response update EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>removed unwanted print<commit_after>package ov

import (
	"encoding/json"
	"fmt"
	"github.com/HewlettPackard/oneview-golang/rest"
	"github.com/HewlettPackard/oneview-golang/utils"
	"github.com/docker/machine/libmachine/log"
)

type EthernetNetwork struct {
	Category              string        `json:"category,omitempty"`              // "category": "ethernet-networks",
	ConnectionTemplateUri utils.Nstring `json:"connectionTemplateUri,omitempty"` // "connectionTemplateUri": "/rest/connection-templates/7769cae0-b680-435b-9b87-9b864c81657f",
	Created               string        `json:"created,omitempty"`               // "created": "20150831T154835.250Z",
	Description           utils.Nstring `json:"description,omitempty"`           // "description": "Ethernet network 1",
	ETAG                  string        `json:"eTag,omitempty"`                  // "eTag": "1441036118675/8",
	EthernetNetworkType   string        `json:"ethernetNetworkType,omitempty"`   // "ethernetNetworkType": "Tagged",
	FabricUri             utils.Nstring `json:"fabricUri,omitempty"`             // "fabricUri": "/rest/fabrics/9b8f7ec0-52b3-475e-84f4-c4eac51c2c20",
	Modified              string        `json:"modified,omitempty"`              // "modified": "20150831T154835.250Z",
	Name                  string        `json:"name,omitempty"`                  // "name": "Ethernet Network 1",
	PrivateNetwork        bool          `json:"privateNetwork"`                  // "privateNetwork": false,
	Purpose               string        `json:"purpose,omitempty"`               // "purpose": "General",
	SmartLink             bool          `json:"smartLink"`                       // "smartLink": false,
	State                 string        `json:"state,omitempty"`                 // "state": "Normal",
	Status                string        `json:"status,omitempty"`                // "status": "Critical",
	Type                  string        `json:"type,omitempty"`                  // "type": "ethernet-networkV3",
	URI                   utils.Nstring `json:"uri,omitempty"`                   // "uri": "/rest/ethernet-networks/e2f0031b-52bd-4223-9ac1-d91cb519d548"
	VlanId                int           `json:"vlanId,omitempty"`                // "vlanId": 1,
}

type EthernetNetworkList struct {
	Total       int               `json:"total,omitempty"`       // "total": 1,
	Count       int               `json:"count,omitempty"`       // "count": 1,
	Start       int               `json:"start,omitempty"`       // "start": 0,
	PrevPageURI utils.Nstring     `json:"prevPageUri,omitempty"` // "prevPageUri": null,
	NextPageURI utils.Nstring     `json:"nextPageUri,omitempty"` // "nextPageUri": null,
	URI         utils.Nstring     `json:"uri,omitempty"`         // "uri": "/rest/ethernet-networks?filter=connectionTemplateUri%20matches%7769cae0-b680-435b-9b87-9b864c81657fsort=name:asc"
	Members     []EthernetNetwork `json:"members,omitempty"`     // "members":[]
}

type Bandwidth struct {
	MaximumBandwidth int `json:"maximumBandwidth"` //"maximumBandwidth":10000
	TypicalBandwidth int `json:"typicalBandwidth"` //"typicalBandwidth":2000
}

type BulkEthernetNetwork struct {
	VlanIdRange    string    `json:"vlanIdRange"`    // "vlanIdRange":"1-500",
	Purpose        string    `json:"purpose"`        // "purpose":"General",
	NamePrefix     string    `json:"namePrefix"`     // "namePrefix":"TestNetwork",
	SmartLink      bool      `json:"smartLink"`      // "smartLink":false,
	PrivateNetwork bool      `json:"privateNetwork"` // "privateNetwork":false,
	Bandwidth      Bandwidth `json:"bandwidth"`      // "bandwidth":10000,2000
	Type           string    `json:"type"`           // "type":"bulk-ethernet-network",
}

func (c *OVClient) GetEthernetNetworkByName(name string) (EthernetNetwork, error) {
	var (
		eNet EthernetNetwork
	)
	eNets, err := c.GetEthernetNetworks(fmt.Sprintf("name matches '%s'", name), "name:asc")
	if eNets.Total > 0 {
		return eNets.Members[0], err
	} else {
		return eNet, err
	}
}

func (c *OVClient) GetEthernetNetworks(filter string, sort string) (EthernetNetworkList, error) {
	var (
		uri              = "/rest/ethernet-networks"
		q                map[string]interface{}
		ethernetNetworks EthernetNetworkList
	)
	q = make(map[string]interface{})
	if len(filter) > 0 {
		q["filter"] = filter
	}

	if sort != "" {
		q["sort"] = sort
	}

	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	// Setup query
	if len(q) > 0 {
		c.SetQueryString(q)
	}

	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return ethernetNetworks, err
	}

	log.Debugf("GetEthernetNetworks %s", data)
	if err := json.Unmarshal([]byte(data), &ethernetNetworks); err != nil {
		return ethernetNetworks, err
	}
	return ethernetNetworks, nil
}

func (c *OVClient) GetAssociatedProfile(id string) ([]string, error) {
	var (
		uri            = "/rest/ethernet-networks/"
		serverProfiles = new([]string)
	)
	uri = uri + id + "/associatedProfiles"
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return *serverProfiles, err
	}
	log.Infof("GetAssociatedProfile %s", data)
	if err := json.Unmarshal([]byte(data), serverProfiles); err != nil {
		return *serverProfiles, err
	}
	return *serverProfiles, nil
}

func (c *OVClient) GetAssociatedUplinkGroup(id string) ([]string, error) {
	var (
		uri          = "/rest/ethernet-networks/"
		uplinkGroups = new([]string)
	)
	uri = uri + id + "/associatedUplinkGroups"
	fmt.Println(uri)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	data, err := c.RestAPICall(rest.GET, uri, nil)
	if err != nil {
		return *uplinkGroups, err
	}
	log.Infof("GetAssociatedUplinkGroups %s", data)
	if err := json.Unmarshal([]byte(data), uplinkGroups); err != nil {
		return *uplinkGroups, err
	}
	return *uplinkGroups, nil
}

func (c *OVClient) CreateEthernetNetwork(eNet EthernetNetwork) error {
	log.Infof("Initializing creation of ethernet network for %s.", eNet.Name)
	var (
		uri = "/rest/ethernet-networks"
		t   *Task
	)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())

	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST : %s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.POST, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting new ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response New EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return nil
}

func (c *OVClient) CreateBulkEthernetNetwork(eNet BulkEthernetNetwork) error {
	log.Infof("Initializing creation of bulk ethernet network")
	var (
		uri = "rest/ethernet-networks/bulk"
		t   *Task
	)
	//refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())
	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST :%s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.POST, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting new bulk ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response New Bulk EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}
	return nil
}

func (c *OVClient) DeleteEthernetNetwork(name string) error {
	var (
		eNet EthernetNetwork
		err  error
		t    *Task
		uri  string
	)

	eNet, err = c.GetEthernetNetworkByName(name)
	if err != nil {
		return err
	}
	if eNet.Name != "" {
		t = t.NewProfileTask(c)
		t.ResetTask()
		log.Debugf("REST : %s \n %+v\n", eNet.URI, eNet)
		log.Debugf("task -> %+v", t)
		uri = eNet.URI.String()
		if uri == "" {
			log.Warn("Unable to post delete, no uri found.")
			t.TaskIsDone = true
			return err
		}
		data, err := c.RestAPICall(rest.DELETE, uri, nil)
		if err != nil {
			log.Errorf("Error submitting delete ethernet network request: %s", err)
			t.TaskIsDone = true
			return err
		}

		log.Debugf("Response delete ethernet network %s", data)
		if err := json.Unmarshal([]byte(data), &t); err != nil {
			t.TaskIsDone = true
			log.Errorf("Error with task un-marshal: %s", err)
			return err
		}
		err = t.Wait()
		if err != nil {
			return err
		}
		return nil
	} else {
		log.Infof("EthernetNetwork could not be found to delete, %s, skipping delete ...", name)
	}
	return nil
}

func (c *OVClient) UpdateEthernetNetwork(eNet EthernetNetwork) error {
	log.Infof("Initializing update of ethernet network for %s.", eNet.Name)
	var (
		uri = eNet.URI.String()
		t   *Task
	)
	// refresh login
	c.RefreshLogin()
	c.SetAuthHeaderOptions(c.GetAuthHeaderMap())

	t = t.NewProfileTask(c)
	t.ResetTask()
	log.Debugf("REST : %s \n %+v\n", uri, eNet)
	log.Debugf("task -> %+v", t)
	data, err := c.RestAPICall(rest.PUT, uri, eNet)
	if err != nil {
		t.TaskIsDone = true
		log.Errorf("Error submitting update ethernet network request: %s", err)
		return err
	}

	log.Debugf("Response update EthernetNetwork %s", data)
	if err := json.Unmarshal([]byte(data), &t); err != nil {
		t.TaskIsDone = true
		log.Errorf("Error with task un-marshal: %s", err)
		return err
	}

	err = t.Wait()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package channels

import "github.com/eapache/queue"

// OverflowingChannel implements the Channel interface in a way that never blocks the writer.
// Specifically, if a value is written to an OverflowingChannel when its buffer is full
// (or, in an unbuffered case, when the recipient is not ready) then that value is simply discarded.
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
// the writer before the reader, so caveat emptor.
// For the opposite behaviour (discarding the oldest element, not the newest) see RingChannel.
type OverflowingChannel struct {
	input, output chan interface{}
	buffer        *queue.Queue
	size          BufferCap
}

func NewOverflowingChannel(size BufferCap) *OverflowingChannel {
	if size < 0 && size != Infinity {
		panic("channels: invalid negative size in NewOverflowingChannel")
	}
	ch := &OverflowingChannel{
		input:  make(chan interface{}),
		output: make(chan interface{}),
		size:   size,
	}
	if size == None {
		go ch.overflowingDirect()
	} else {
		ch.buffer = queue.New()
		go ch.overflowingBuffer()
	}
	return ch
}

func (ch *OverflowingChannel) In() chan<- interface{} {
	return ch.input
}

func (ch *OverflowingChannel) Out() <-chan interface{} {
	return ch.output
}

func (ch *OverflowingChannel) Len() int {
	return ch.buffer.Length()
}

func (ch *OverflowingChannel) Cap() BufferCap {
	return ch.size
}

func (ch *OverflowingChannel) Close() {
	close(ch.input)
}

func (ch *OverflowingChannel) shutdown() {
	for ch.buffer.Length() > 0 {
		ch.output <- ch.buffer.Peek()
		ch.buffer.Remove()
	}
	close(ch.output)
}

// for entirely unbuffered cases
func (ch *OverflowingChannel) overflowingDirect() {
	for elem := range ch.input {
		// if we can't write it immediately, drop it and move on
		select {
		case ch.output <- elem:
		default:
		}
	}
	close(ch.output)
}

// for all buffered cases
func (ch *OverflowingChannel) overflowingBuffer() {
	for {
		if ch.buffer.Length() == 0 {
			elem, open := <-ch.input
			if open {
				ch.buffer.Add(elem)
			} else {
				ch.shutdown()
				return
			}
		} else if ch.size != Infinity && ch.buffer.Length() >= int(ch.size) {
			select {
			// Prefer to write if possible, which is surprisingly effective in reducing
			// dropped elements due to overflow. The naive read/write select chooses randomly
			// when both channels are ready, which produces unnecessary drops 50% of the time.
			case ch.output <- ch.buffer.Peek():
				ch.buffer.Remove()
			default:
				select {
				case _, open := <-ch.input: // discard new inputs
					if !open {
						ch.shutdown()
						return
					}
				case ch.output <- ch.buffer.Peek():
					ch.buffer.Remove()
				}
			}
		} else {
			select {
			case elem, open := <-ch.input:
				if open {
					ch.buffer.Add(elem)
				} else {
					ch.shutdown()
					return
				}
			case ch.output <- ch.buffer.Peek():
				ch.buffer.Remove()
			}
		}
	}
}
<commit_msg>Fix data race in OverflowingChannel<commit_after>package channels

import "github.com/eapache/queue"

// OverflowingChannel implements the Channel interface in a way that never blocks the writer.
// Specifically, if a value is written to an OverflowingChannel when its buffer is full
// (or, in an unbuffered case, when the recipient is not ready) then that value is simply discarded.
// Note that Go's scheduler can cause discarded values when they could be avoided, simply by scheduling
// the writer before the reader, so caveat emptor.
// For the opposite behaviour (discarding the oldest element, not the newest) see RingChannel.
type OverflowingChannel struct {
	input, output chan interface{}
	length        chan int
	buffer        *queue.Queue
	size          BufferCap
}

func NewOverflowingChannel(size BufferCap) *OverflowingChannel {
	if size < 0 && size != Infinity {
		panic("channels: invalid negative size in NewOverflowingChannel")
	}
	ch := &OverflowingChannel{
		input:  make(chan interface{}),
		output: make(chan interface{}),
		length: make(chan int),
		size:   size,
	}
	if size == None {
		go ch.overflowingDirect()
	} else {
		ch.buffer = queue.New()
		go ch.overflowingBuffer()
	}
	return ch
}

func (ch *OverflowingChannel) In() chan<- interface{} {
	return ch.input
}

func (ch *OverflowingChannel) Out() <-chan interface{} {
	return ch.output
}

func (ch *OverflowingChannel) Len() int {
	if ch.size == None {
		return 0
	} else {
		return <-ch.length
	}
}

func (ch *OverflowingChannel) Cap() BufferCap {
	return ch.size
}

func (ch *OverflowingChannel) Close() {
	close(ch.input)
}

func (ch *OverflowingChannel) shutdown() {
	for ch.buffer.Length() > 0 {
		select {
		case ch.output <- ch.buffer.Peek():
			ch.buffer.Remove()
		case ch.length <- ch.buffer.Length():
		}
	}
	close(ch.output)
}

// for entirely unbuffered cases
func (ch *OverflowingChannel) overflowingDirect() {
	for elem := range ch.input {
		// if we can't write it immediately, drop it and move on
		select {
		case ch.output <- elem:
		default:
		}
	}
	close(ch.output)
}

// for all buffered cases
func (ch *OverflowingChannel) overflowingBuffer() {
	for {
		if ch.buffer.Length() == 0 {
			select {
			case elem, open := <-ch.input:
				if open {
					ch.buffer.Add(elem)
				} else {
					ch.shutdown()
					return
				}
			case ch.length <- ch.buffer.Length():
			}
		} else if ch.size != Infinity && ch.buffer.Length() >= int(ch.size) {
			select {
			// Prefer to write if possible, which is surprisingly effective in reducing
			// dropped elements due to overflow. The naive read/write select chooses randomly
			// when both channels are ready, which produces unnecessary drops 50% of the time.
			case ch.output <- ch.buffer.Peek():
				ch.buffer.Remove()
			case ch.length <- ch.buffer.Length():
			default:
				select {
				case _, open := <-ch.input: // discard new inputs
					if !open {
						ch.shutdown()
						return
					}
				case ch.output <- ch.buffer.Peek():
					ch.buffer.Remove()
				case ch.length <- ch.buffer.Length():
				}
			}
		} else {
			select {
			case elem, open := <-ch.input:
				if open {
					ch.buffer.Add(elem)
				} else {
					ch.shutdown()
					return
				}
			case ch.output <- ch.buffer.Peek():
				ch.buffer.Remove()
			case ch.length <- ch.buffer.Length():
			}
		}
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errSettingsMissingDomain = errors.New("Missing --domain flag")

var flagSettingsDomain string

var settingsCmd = &cobra.Command{
	Use:   "settings [settings]",
	Short: "Display and update settings",
	Long: `
cozy-stack settings displays the settings.

It can also take a list of settings to update.
`,
	Example: "$ cozy-stack settings --domain cozy.tools:8080 context:beta,public_name:John",
	RunE: func(cmd *cobra.Command, args []string) error {
		if flagSettingsDomain == "" {
			errPrintfln("%s", errSettingsMissingDomain)
			return cmd.Usage()
		}
		c := newClient(flagSettingsDomain, consts.Settings)
		res, err := c.Req(&request.Options{
			Method: "GET",
			Path:   "/settings/instance",
		})
		if err != nil {
			return err
		}
		defer res.Body.Close()
		var obj map[string]interface{}
		if err = json.NewDecoder(res.Body).Decode(&obj); err != nil {
			return err
		}
		if len(args) > 0 {
			obj, err = updateSettings(c, obj, args[0])
			if err != nil {
				return err
			}
		}
		printSettings(obj)
		return nil
	},
}

func printSettings(obj map[string]interface{}) {
	data, ok := obj["data"].(map[string]interface{})
	if !ok {
		return
	}
	attrs, ok := data["attributes"].(map[string]interface{})
	if !ok {
		return
	}
	for k, v := range attrs {
		fmt.Printf("- %s: %v\n", k, v)
	}
}

func updateSettings(c *client.Client, obj map[string]interface{}, args string) (map[string]interface{}, error) {
	data, ok := obj["data"].(map[string]interface{})
	if !ok {
		return nil, errors.New("data not found")
	}
	attrs, ok := data["attributes"].(map[string]interface{})
	if !ok {
		return nil, errors.New("attributes not found")
	}
	for _, arg := range strings.Split(args, ",") {
		parts := strings.SplitN(arg, ":", 2)
		k := parts[0]
		if len(parts) < 2 || parts[1] == "" {
			delete(attrs, k)
		} else {
			attrs[k] = parts[1]
		}
	}
	delete(obj, "links")
	buf, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	body := bytes.NewReader(buf)
	res, err := c.Req(&request.Options{
		Method: "PUT",
		Path:   "/settings/instance",
		Body:   body,
	})
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	err = json.NewDecoder(res.Body).Decode(&obj)
	return obj, err
}

func init() {
	domain := os.Getenv("COZY_DOMAIN")
	if domain == "" && config.IsDevRelease() {
		domain = "cozy.tools:8080"
	}

	settingsCmd.PersistentFlags().StringVar(&flagSettingsDomain, "domain", domain, "specify the domain name of the instance")
	RootCmd.AddCommand(settingsCmd)
}
<commit_msg>Sort instance settings in CLI<commit_after>package cmd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errSettingsMissingDomain = errors.New("Missing --domain flag")

var flagSettingsDomain string

var settingsCmd = &cobra.Command{
	Use:   "settings [settings]",
	Short: "Display and update settings",
	Long: `
cozy-stack settings displays the settings.

It can also take a list of settings to update.
`,
	Example: "$ cozy-stack settings --domain cozy.tools:8080 context:beta,public_name:John",
	RunE: func(cmd *cobra.Command, args []string) error {
		if flagSettingsDomain == "" {
			errPrintfln("%s", errSettingsMissingDomain)
			return cmd.Usage()
		}
		c := newClient(flagSettingsDomain, consts.Settings)
		res, err := c.Req(&request.Options{
			Method: "GET",
			Path:   "/settings/instance",
		})
		if err != nil {
			return err
		}
		defer res.Body.Close()
		var obj map[string]interface{}
		if err = json.NewDecoder(res.Body).Decode(&obj); err != nil {
			return err
		}
		if len(args) > 0 {
			obj, err = updateSettings(c, obj, args[0])
			if err != nil {
				return err
			}
		}
		printSettings(obj)
		return nil
	},
}

func printSettings(obj map[string]interface{}) {
	data, ok := obj["data"].(map[string]interface{})
	if !ok {
		return
	}
	attrs, ok := data["attributes"].(map[string]interface{})
	if !ok {
		return
	}
	keys := make([]string, 0, len(attrs))
	for k := range attrs {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		fmt.Printf("- %s: %v\n", k, attrs[k])
	}
}

func updateSettings(c *client.Client, obj map[string]interface{}, args string) (map[string]interface{}, error) {
	data, ok := obj["data"].(map[string]interface{})
	if !ok {
		return nil, errors.New("data not found")
	}
	attrs, ok := data["attributes"].(map[string]interface{})
	if !ok {
		return nil, errors.New("attributes not found")
	}
	for _, arg := range strings.Split(args, ",") {
		parts := strings.SplitN(arg, ":", 2)
		k := parts[0]
		if len(parts) < 2 || parts[1] == "" {
			delete(attrs, k)
		} else {
			attrs[k] = parts[1]
		}
	}
	delete(obj, "links")
	buf, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	body := bytes.NewReader(buf)
	res, err := c.Req(&request.Options{
		Method: "PUT",
		Path:   "/settings/instance",
		Body:   body,
	})
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	err = json.NewDecoder(res.Body).Decode(&obj)
	return obj, err
}

func init() {
	domain := os.Getenv("COZY_DOMAIN")
	if domain == "" && config.IsDevRelease() {
		domain = "cozy.tools:8080"
	}

	settingsCmd.PersistentFlags().StringVar(&flagSettingsDomain, "domain", domain, "specify the domain name of the instance")
	RootCmd.AddCommand(settingsCmd)
}
<|endoftext|>


<commit_before>package tracker

import (
	"bytes"
	"encoding/binary"
	"errors"
	"net"
)

type CompactPeer struct {
	IP   [net.IPv4len]byte
	Port uint16
}

func NewCompactPeer(addr *net.TCPAddr) CompactPeer {
	p := CompactPeer{Port: uint16(addr.Port)}
	copy(p.IP[:], addr.IP)
	return p
}

func (p CompactPeer) Addr() *net.TCPAddr {
	return &net.TCPAddr{IP: p.IP[:], Port: int(p.Port)}
}

func (p CompactPeer) MarshalBinary() ([]byte, error) {
	buf := bytes.NewBuffer(make([]byte, 0, 6))
	err := binary.Write(buf, binary.BigEndian, p)
	return buf.Bytes(), err
}

func (p *CompactPeer) UnmarshalBinary(data []byte) error {
	if len(data) != 6 {
		return errors.New("invalid compact peer length")
	}
	return binary.Read(bytes.NewReader(data), binary.BigEndian, p)
}

func DecodePeersCompact(b []byte) ([]*net.TCPAddr, error) {
	if len(b)%6 != 0 {
		return nil, errors.New("invalid peer list length")
	}
	count := len(b) / 6
	addrs := make([]*net.TCPAddr, 0, count)
	for i := 0; i < len(b); i += 6 {
		var peer CompactPeer
		err := peer.UnmarshalBinary(b[i : i+6])
		if err != nil {
			return nil, err
		}
		addrs = append(addrs, peer.Addr())
	}
	return addrs, nil
}
<commit_msg>fix compact peer representation<commit_after>package tracker

import (
	"bytes"
	"encoding/binary"
	"errors"
	"net"
)

type CompactPeer struct {
	IP   [net.IPv4len]byte
	Port uint16
}

func NewCompactPeer(addr *net.TCPAddr) CompactPeer {
	p := CompactPeer{Port: uint16(addr.Port)}
	copy(p.IP[:], addr.IP.To4())
	return p
}

func (p CompactPeer) Addr() *net.TCPAddr {
	return &net.TCPAddr{IP: p.IP[:], Port: int(p.Port)}
}

func (p CompactPeer) MarshalBinary() ([]byte, error) {
	buf := bytes.NewBuffer(make([]byte, 0, 6))
	err := binary.Write(buf, binary.BigEndian, p)
	return buf.Bytes(), err
}

func (p *CompactPeer) UnmarshalBinary(data []byte) error {
	if len(data) != 6 {
		return errors.New("invalid compact peer length")
	}
	return binary.Read(bytes.NewReader(data), binary.BigEndian, p)
}

func DecodePeersCompact(b []byte) ([]*net.TCPAddr, error) {
	if len(b)%6 != 0 {
		return nil, errors.New("invalid peer list length")
	}
	count := len(b) / 6
	addrs := make([]*net.TCPAddr, 0, count)
	for i := 0; i < len(b); i += 6 {
		var peer CompactPeer
		err := peer.UnmarshalBinary(b[i : i+6])
		if err != nil {
			return nil, err
		}
		addrs = append(addrs, peer.Addr())
	}
	return addrs, nil
}
<|endoftext|>


<commit_before>package version

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"text/tabwriter"

	"github.com/blang/semver"
	"github.com/cri-o/cri-o/utils"
	"github.com/google/renameio"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// Version is the version of the build.
const Version = "1.19.0-dev"

// Variables injected during build-time
var (
	gitCommit    string // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string // state of git tree, either "clean" or "dirty"
	buildDate    string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

type Info struct {
	Version      string `json:"version,omitempty"`
	GitCommit    string `json:"gitCommit,omitempty"`
	GitTreeState string `json:"gitTreeState,omitempty"`
	BuildDate    string `json:"buildDate,omitempty"`
	GoVersion    string `json:"goVersion,omitempty"`
	Compiler     string `json:"compiler,omitempty"`
	Platform     string `json:"platform,omitempty"`
	Linkmode     string `json:"linkmode,omitempty"`
}

// ShouldCrioWipe opens the version file, and parses it and the version string
// If there is a parsing error, then crio should wipe, and the error is returned.
// if parsing is successful, it compares the major and minor versions
// and returns whether the major and minor versions are the same.
// If they differ, then crio should wipe.
func ShouldCrioWipe(versionFileName string) (bool, error) {
	return shouldCrioWipe(versionFileName, Version)
}

// shouldCrioWipe is an internal function for testing purposes
func shouldCrioWipe(versionFileName, versionString string) (bool, error) {
	f, err := os.Open(versionFileName)
	if err != nil {
		return true, errors.Errorf("version file %s not found: %v", versionFileName, err)
	}
	r := bufio.NewReader(f)
	versionBytes, err := ioutil.ReadAll(r)
	if err != nil {
		return true, errors.Errorf("reading version file %s failed: %v", versionFileName, err)
	}

	// parse the version that was laid down by a previous invocation of crio
	var oldVersion semver.Version
	if err := oldVersion.UnmarshalJSON(versionBytes); err != nil {
		return true, errors.Errorf("version file %s malformatted: %v", versionFileName, err)
	}

	// parse the version of the current binary
	newVersion, err := parseVersionConstant(versionString, "")
	if err != nil {
		return true, errors.Errorf("version constant %s malformatted: %v", versionString, err)
	}

	// in every case that the minor and major version are out of sync,
	// we want to preform a {down,up}grade. The common case here is newVersion > oldVersion,
	// but even in the opposite case, images are out of date and could be wiped
	return newVersion.Major != oldVersion.Major || newVersion.Minor != oldVersion.Minor, nil
}

// WriteVersionFile writes the version information to a given file
// file is the location of the old version file
// gitCommit is the current git commit version. It will be added to the file
// to aid in debugging, but will not be used to compare versions
func WriteVersionFile(file string) error {
	return writeVersionFile(file, gitCommit, Version)
}

// writeVersionFile is an internal function for testing purposes
func writeVersionFile(file, gitCommit, version string) error {
	current, err := parseVersionConstant(version, gitCommit)
	// Sanity check-this should never happen
	if err != nil {
		return err
	}
	j, err := current.MarshalJSON()
	// Sanity check-this should never happen
	if err != nil {
		return err
	}

	// Create the top level directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
		return err
	}

	return renameio.WriteFile(file, j, 0644)
}

// parseVersionConstant parses the Version variable above
// a const crioVersion would be kept, but golang doesn't support
// const structs. We will instead spend some runtime on CRI-O startup
// Because the version string doesn't keep track of the git commit,
// but it could be useful for debugging, we pass it in here
// If our version constant is properly formatted, this should never error
func parseVersionConstant(versionString, gitCommit string) (*semver.Version, error) {
	v, err := semver.Make(versionString)
	if err != nil {
		return nil, err
	}
	if gitCommit != "" {
		gitBuild, err := semver.NewBuildVersion(strings.Trim(gitCommit, "\""))
		// If gitCommit is empty, silently error, as it's helpful, but not needed.
		if err == nil {
			v.Build = append(v.Build, gitBuild)
		}
	}
	return &v, nil
}

func Get() *Info {
	return &Info{
		Version:      Version,
		GitCommit:    gitCommit,
		GitTreeState: gitTreeState,
		BuildDate:    buildDate,
		GoVersion:    runtime.Version(),
		Compiler:     runtime.Compiler,
		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
		Linkmode:     getLinkmode(),
	}
}

// String returns the string representation of the version info
func (i *Info) String() string {
	b := strings.Builder{}
	w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0)

	v := reflect.ValueOf(*i)
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.FieldByName(field.Name).String()
		if value != "" {
			fmt.Fprintf(w, "%s:\t%s", field.Name, value)
			if i+1 < t.NumField() {
				fmt.Fprintf(w, "\n")
			}
		}
	}

	w.Flush()
	return b.String()
}

func getLinkmode() string {
	abspath, err := os.Executable()
	if err != nil {
		logrus.Warnf("Encountered error finding binary to detect link mode: %v", err)
		return ""
	}

	if _, err = exec.LookPath("ldd"); err != nil {
		return ""
	}
	if _, err = utils.ExecCmd("ldd", abspath); err != nil {
		if strings.Contains(err.Error(), "not a dynamic executable") {
			return "static"
		}
		logrus.Warnf("Encountered error detecting link mode of binary: %v", err)
		return ""
	}

	return "dynamic"
}

// JSONString returns the JSON representation of the version info
func (i *Info) JSONString() (string, error) {
	b, err := json.MarshalIndent(i, "", "  ")
	if err != nil {
		return "", err
	}
	return string(b), nil
}
<commit_msg>Fix static binary mode retrieval for musl toolchains<commit_after>package version

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"text/tabwriter"

	"github.com/blang/semver"
	"github.com/cri-o/cri-o/utils"
	"github.com/google/renameio"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// Version is the version of the build.
const Version = "1.19.0-dev"

// Variables injected during build-time
var (
	gitCommit    string // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string // state of git tree, either "clean" or "dirty"
	buildDate    string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

type Info struct {
	Version      string `json:"version,omitempty"`
	GitCommit    string `json:"gitCommit,omitempty"`
	GitTreeState string `json:"gitTreeState,omitempty"`
	BuildDate    string `json:"buildDate,omitempty"`
	GoVersion    string `json:"goVersion,omitempty"`
	Compiler     string `json:"compiler,omitempty"`
	Platform     string `json:"platform,omitempty"`
	Linkmode     string `json:"linkmode,omitempty"`
}

// ShouldCrioWipe opens the version file, and parses it and the version string
// If there is a parsing error, then crio should wipe, and the error is returned.
// if parsing is successful, it compares the major and minor versions
// and returns whether the major and minor versions are the same.
// If they differ, then crio should wipe.
func ShouldCrioWipe(versionFileName string) (bool, error) {
	return shouldCrioWipe(versionFileName, Version)
}

// shouldCrioWipe is an internal function for testing purposes
func shouldCrioWipe(versionFileName, versionString string) (bool, error) {
	f, err := os.Open(versionFileName)
	if err != nil {
		return true, errors.Errorf("version file %s not found: %v", versionFileName, err)
	}
	r := bufio.NewReader(f)
	versionBytes, err := ioutil.ReadAll(r)
	if err != nil {
		return true, errors.Errorf("reading version file %s failed: %v", versionFileName, err)
	}

	// parse the version that was laid down by a previous invocation of crio
	var oldVersion semver.Version
	if err := oldVersion.UnmarshalJSON(versionBytes); err != nil {
		return true, errors.Errorf("version file %s malformatted: %v", versionFileName, err)
	}

	// parse the version of the current binary
	newVersion, err := parseVersionConstant(versionString, "")
	if err != nil {
		return true, errors.Errorf("version constant %s malformatted: %v", versionString, err)
	}

	// in every case that the minor and major version are out of sync,
	// we want to preform a {down,up}grade. The common case here is newVersion > oldVersion,
	// but even in the opposite case, images are out of date and could be wiped
	return newVersion.Major != oldVersion.Major || newVersion.Minor != oldVersion.Minor, nil
}

// WriteVersionFile writes the version information to a given file
// file is the location of the old version file
// gitCommit is the current git commit version. It will be added to the file
// to aid in debugging, but will not be used to compare versions
func WriteVersionFile(file string) error {
	return writeVersionFile(file, gitCommit, Version)
}

// writeVersionFile is an internal function for testing purposes
func writeVersionFile(file, gitCommit, version string) error {
	current, err := parseVersionConstant(version, gitCommit)
	// Sanity check-this should never happen
	if err != nil {
		return err
	}
	j, err := current.MarshalJSON()
	// Sanity check-this should never happen
	if err != nil {
		return err
	}

	// Create the top level directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
		return err
	}

	return renameio.WriteFile(file, j, 0644)
}

// parseVersionConstant parses the Version variable above
// a const crioVersion would be kept, but golang doesn't support
// const structs. We will instead spend some runtime on CRI-O startup
// Because the version string doesn't keep track of the git commit,
// but it could be useful for debugging, we pass it in here
// If our version constant is properly formatted, this should never error
func parseVersionConstant(versionString, gitCommit string) (*semver.Version, error) {
	v, err := semver.Make(versionString)
	if err != nil {
		return nil, err
	}
	if gitCommit != "" {
		gitBuild, err := semver.NewBuildVersion(strings.Trim(gitCommit, "\""))
		// If gitCommit is empty, silently error, as it's helpful, but not needed.
		if err == nil {
			v.Build = append(v.Build, gitBuild)
		}
	}
	return &v, nil
}

func Get() *Info {
	return &Info{
		Version:      Version,
		GitCommit:    gitCommit,
		GitTreeState: gitTreeState,
		BuildDate:    buildDate,
		GoVersion:    runtime.Version(),
		Compiler:     runtime.Compiler,
		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
		Linkmode:     getLinkmode(),
	}
}

// String returns the string representation of the version info
func (i *Info) String() string {
	b := strings.Builder{}
	w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0)

	v := reflect.ValueOf(*i)
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.FieldByName(field.Name).String()
		if value != "" {
			fmt.Fprintf(w, "%s:\t%s", field.Name, value)
			if i+1 < t.NumField() {
				fmt.Fprintf(w, "\n")
			}
		}
	}

	w.Flush()
	return b.String()
}

func getLinkmode() string {
	abspath, err := os.Executable()
	if err != nil {
		logrus.Warnf("Encountered error finding binary to detect link mode: %v", err)
		return ""
	}

	if _, err = exec.LookPath("ldd"); err != nil {
		return ""
	}
	if _, err = utils.ExecCmd("ldd", abspath); err != nil {
		if strings.Contains(err.Error(), "not a dynamic executable") ||
			strings.Contains(strings.ToLower(err.Error()), "not a valid dynamic program") {
			return "static"
		}
		logrus.Warnf("Encountered error detecting link mode of binary: %v", err)
		return ""
	}

	return "dynamic"
}

// JSONString returns the JSON representation of the version info
func (i *Info) JSONString() (string, error) {
	b, err := json.MarshalIndent(i, "", "  ")
	if err != nil {
		return "", err
	}
	return string(b), nil
}
<|endoftext|>