<commit_before>// Copyright 2016 Red Hat, Inc, and individual contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	readline "gopkg.in/readline.v1"

	"k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

func main() {
	cmdutil.BehaviorOnFatal(func(msg string, code int) {
		fmt.Println(msg)
	})

	cmd := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
	l, err := readline.NewEx(&readline.Config{
		Prompt:       ">>> ",
		AutoComplete: &CommandCompleter{cmd},
	})
	if err != nil {
		panic(err)
	}
	defer l.Close()

	for {
		line, err := l.Readline()
		if err == readline.ErrInterrupt {
			if len(line) == 0 {
				break
			} else {
				continue
			}
		} else if err == io.EOF {
			break
		}
		line = strings.TrimSpace(line)
		cmd.ResetFlags()
		cmd.SetArgs(strings.Split(line, " "))
		cmd.Execute()
	}
}

type CommandCompleter struct {
	Root *cobra.Command
}

func (cc *CommandCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
	cmd := cc.Root
	index := strings.LastIndex(string(line[:pos]), " ") + 1
	word := string(line[:pos])
	if index > 0 {
		word = word[index:pos]
		var err error
		cmd, _, err = cc.Root.Find(strings.Split(string(line), " "))
		if err != nil {
			return
		}
	}
	for _, completion := range completions(word, cmd) {
		if len(word) >= len(completion) {
			if len(word) == len(completion) {
				newLine = append(newLine, []rune{' '})
			} else {
				newLine = append(newLine, []rune(completion))
			}
			offset = len(completion)
		} else {
			newLine = append(newLine, []rune(completion)[len(word):])
			offset = len(word)
		}

	}
	return
}

func completions(prefix string, cmd *cobra.Command) (completions []string) {
	if strings.HasPrefix(prefix, "-") {
		completions = flags(cmd)
	} else {
		completions = subCommands(cmd)
		if len(completions) == 0 {
			completions = resourceTypes(cmd)
		}
	}
	return
}

func subCommands(cmd *cobra.Command) []string {
	prefixes := make([]string, len(cmd.Commands()))
	for i, c := range cmd.Commands() {
		prefixes[i] = c.Name()
	}
	return prefixes
}

func resourceTypes(cmd *cobra.Command) []string {
	return cmd.ValidArgs
}

func flags(cmd *cobra.Command) []string {
	flags := []string{}
	fn := func(f *pflag.Flag) {
		flag := "--" + f.Name
		if len(f.NoOptDefVal) == 0 {
			flag += "="
		}
		flags = append(flags, flag)
	}
	cmd.NonInheritedFlags().VisitAll(fn)
	cmd.InheritedFlags().VisitAll(fn)
	return flags
}
<commit_msg>Use a Trie to actually perform the completion<commit_after>// Copyright 2016 Red Hat, Inc, and individual contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/chzyer/readline"
	"github.com/vivekn/autocomplete"

	"k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

func main() {
	cmdutil.BehaviorOnFatal(func(msg string, code int) {
		fmt.Println(msg)
	})

	cmd := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
	l, err := readline.NewEx(&readline.Config{
		Prompt:       ">>> ",
		AutoComplete: &CommandCompleter{cmd},
	})
	if err != nil {
		panic(err)
	}
	defer l.Close()

	for {
		line, err := l.Readline()
		if err == readline.ErrInterrupt {
			if len(line) == 0 {
				break
			} else {
				continue
			}
		} else if err == io.EOF {
			break
		}
		line = strings.TrimSpace(line)
		cmd.ResetFlags()
		cmd.SetArgs(strings.Split(line, " "))
		cmd.Execute()
	}
}

type CommandCompleter struct {
	Root *cobra.Command
}

func (cc *CommandCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
	cmd := cc.Root
	index := strings.LastIndex(string(line[:pos]), " ") + 1
	word := string(line[:pos])
	if index > 0 {
		word = word[index:pos]
		var err error
		cmd, _, err = cc.Root.Find(strings.Split(string(line), " "))
		if err != nil {
			return
		}
	}
	for _, completion := range completions(word, cmd) {
		if len(word) >= len(completion) {
			if len(word) == len(completion) {
				newLine = append(newLine, []rune{' '})
			} else {
				newLine = append(newLine, []rune(completion))
			}
			offset = len(completion)
		} else {
			newLine = append(newLine, []rune(completion)[len(word):])
			offset = len(word)
		}

	}
	return
}

func completions(prefix string, cmd *cobra.Command) (completions []string) {
	if strings.HasPrefix(prefix, "-") {
		completions = flags(cmd)
	} else {
		completions = subCommands(cmd)
		if len(completions) == 0 {
			completions = resourceTypes(cmd)
		}
	}
	trie := trie.NewTrie()
	for _, c := range completions {
		trie.Insert(c)
	}
	completions, _ = trie.AutoComplete(prefix)
	return
}

func subCommands(cmd *cobra.Command) []string {
	prefixes := make([]string, len(cmd.Commands()))
	for i, c := range cmd.Commands() {
		prefixes[i] = c.Name()
	}
	return prefixes
}

func resourceTypes(cmd *cobra.Command) []string {
	return cmd.ValidArgs
}

func flags(cmd *cobra.Command) []string {
	flags := []string{}
	fn := func(f *pflag.Flag) {
		flag := "--" + f.Name
		if len(f.NoOptDefVal) == 0 {
			flag += "="
		}
		flags = append(flags, flag)
	}
	cmd.NonInheritedFlags().VisitAll(fn)
	cmd.InheritedFlags().VisitAll(fn)
	return flags
}
<|endoftext|>


<commit_before>// Copyright 2016 The Gosl 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 fun (functions) implements special functions such as elliptical, orthogonal polynomials,
// Bessel, discrete Fourier transform, polynomial interpolators, and more.
package fun

import (
	"math"

	"github.com/cpmech/gosl/la"
)

// π = 3.141592653589...
const π = math.Pi

// Ss defines a scalar function f(s) of a scalar argument s. Also returns error
//   Scalar scalar
type Ss func(s float64) (float64, error)

// Sv defines a scalar functioin f(v) of a vector argument v. Also returns error
type Sv func(v la.Vector) (float64, error)

// Vs defines a vector function f(s) of a scalar argument s. Also returns error
//   Vector scalar
type Vs func(f la.Vector, s float64) error

// Vv defines a vector function f(v) of a vector argument v. Also returns error
//   Vector vector
type Vv func(f, v la.Vector) error

// Mv defines a matrix function f(v) of a vector argument v. Also returns error
//   Matrix vector
type Mv func(f *la.Matrix, v la.Vector) error

// Mm defines a matrix function f(m) of a matrix argument m. Also returns error
//   Matrix matrix
type Mm func(f, m *la.Matrix) error

// Tv defines a triplet (matrix) function f(v) of a vector argument v. Also returns error
//   Triplet vector
type Tv func(f *la.Triplet, v la.Vector) error

// Tt defines a triplet (matrix) function f(t) of a triplet (matrix) argument t. Also returns error
//   Triplet triplet
type Tt func(f, t *la.Triplet) error
<commit_msg>Add another type of function (vector and scalar)<commit_after>// Copyright 2016 The Gosl 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 fun (functions) implements special functions such as elliptical, orthogonal polynomials,
// Bessel, discrete Fourier transform, polynomial interpolators, and more.
package fun

import (
	"math"

	"github.com/cpmech/gosl/la"
)

// π = 3.141592653589...
const π = math.Pi

// Ss defines a scalar function f(s) of a scalar argument s. Also returns error
//   Scalar scalar
type Ss func(s float64) (float64, error)

// Sv defines a scalar functioin f(v) of a vector argument v. Also returns error
type Sv func(v la.Vector) (float64, error)

// Vs defines a vector function f(s) of a scalar argument s. Also returns error
//   Vector scalar
type Vs func(f la.Vector, s float64) error

// Vv defines a vector function f(v) of a vector argument v. Also returns error
//   Vector vector
type Vv func(f, v la.Vector) error

// Mv defines a matrix function f(v) of a vector argument v. Also returns error
//   Matrix vector
type Mv func(f *la.Matrix, v la.Vector) error

// Mm defines a matrix function f(m) of a matrix argument m. Also returns error
//   Matrix matrix
type Mm func(f, m *la.Matrix) error

// Tv defines a triplet (matrix) function f(v) of a vector argument v. Also returns error
//   Triplet vector
type Tv func(f *la.Triplet, v la.Vector) error

// Tt defines a triplet (matrix) function f(t) of a triplet (matrix) argument t. Also returns error
//   Triplet triplet
type Tt func(f, t *la.Triplet) error

// Svs defines a scalar function f(v,s) of a vector and a scalar
type Svs func(v la.Vector, s float64) (float64, error)
<|endoftext|>


<commit_before>package fuse

import (
	"log"
	"os"
	"strings"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/hanwen/go-fuse/raw"
)

const (
	// The kernel caps writes at 128k.
	MAX_KERNEL_WRITE = 128 * 1024
)

// MountState contains the logic for reading from the FUSE device and
// translating it to RawFileSystem interface calls.
type MountState struct {
	// Empty if unmounted.
	mountPoint string
	fileSystem RawFileSystem

	// I/O with kernel and daemon.
	mountFile *os.File

	// Dump debug info onto stdout.
	Debug bool

	// For efficient reads and writes.
	buffers *BufferPoolImpl

	latencies *LatencyMap

	opts           *MountOptions
	kernelSettings raw.InitIn

	// Number of loops blocked on reading; used to control amount
	// of concurrency.
	readers int32
}

func (ms *MountState) KernelSettings() raw.InitIn {
	return ms.kernelSettings
}

func (ms *MountState) MountPoint() string {
	return ms.mountPoint
}

// Mount filesystem on mountPoint.
func (ms *MountState) Mount(mountPoint string, opts *MountOptions) error {
	if opts == nil {
		opts = &MountOptions{
			MaxBackground: _DEFAULT_BACKGROUND_TASKS,
		}
	}
	o := *opts
	if o.MaxWrite < 0 {
		o.MaxWrite = 0
	}
	if o.MaxWrite == 0 {
		o.MaxWrite = 1 << 16
	}
	if o.MaxWrite > MAX_KERNEL_WRITE {
		o.MaxWrite = MAX_KERNEL_WRITE
	}
	opts = &o
	ms.opts = &o

	optStrs := opts.Options
	if opts.AllowOther {
		optStrs = append(optStrs, "allow_other")
	}

	file, mp, err := mount(mountPoint, strings.Join(optStrs, ","))
	if err != nil {
		return err
	}
	initParams := RawFsInit{
		InodeNotify: func(n *raw.NotifyInvalInodeOut) Status {
			return ms.writeInodeNotify(n)
		},
		EntryNotify: func(parent uint64, n string) Status {
			return ms.writeEntryNotify(parent, n)
		},
	}
	ms.fileSystem.Init(&initParams)
	ms.mountPoint = mp
	ms.mountFile = file
	return nil
}

func (ms *MountState) SetRecordStatistics(record bool) {
	if record {
		ms.latencies = NewLatencyMap()
	} else {
		ms.latencies = nil
	}
}

func (ms *MountState) Unmount() (err error) {
	if ms.mountPoint == "" {
		return nil
	}
	delay := time.Duration(0)
	for try := 0; try < 5; try++ {
		err = unmount(ms.mountPoint)
		if err == nil {
			break
		}

		// Sleep for a bit. This is not pretty, but there is
		// no way we can be certain that the kernel thinks all
		// open files have already been closed.
		delay = 2*delay + 5*time.Millisecond
		time.Sleep(delay)
	}
	ms.mountPoint = ""
	return err
}

func NewMountState(fs RawFileSystem) *MountState {
	ms := new(MountState)
	ms.mountPoint = ""
	ms.fileSystem = fs
	ms.buffers = NewBufferPool()
	return ms
}

func (ms *MountState) Latencies() map[string]float64 {
	if ms.latencies == nil {
		return nil
	}
	return ms.latencies.Latencies(1e-3)
}

func (ms *MountState) OperationCounts() map[string]int {
	if ms.latencies == nil {
		return nil
	}
	return ms.latencies.Counts()
}

func (ms *MountState) BufferPoolStats() string {
	return ms.buffers.String()
}

func (ms *MountState) newRequest() *request {
	r := &request{
		pool:               ms.buffers,
	}
	return r
}

func (ms *MountState) recordStats(req *request) {
	if ms.latencies != nil {
		endNs := time.Now().UnixNano()
		dt := endNs - req.startNs

		opname := operationName(req.inHeader.Opcode)
		ms.latencies.AddMany(
			[]LatencyArg{
				{opname, "", dt},
				{opname + "-write", "", endNs - req.preWriteNs}})
	}
}

// Loop initiates the FUSE loop. Normally, callers should run Loop()
// and wait for it to exit, but tests will want to run this in a
// goroutine.
//
// Each filesystem operation executes in a separate goroutine.
func (ms *MountState) Loop() {
	ms.loop()
	ms.mountFile.Close()
}

const _MAX_READERS = 10
func (ms *MountState) loop() {
	var dest []byte
	var req *request
	for {
		if dest == nil {
			dest = ms.buffers.AllocBuffer(uint32(ms.opts.MaxWrite + 4096))
		}
		if atomic.AddInt32(&ms.readers, 0) > _MAX_READERS {
			break
		}

		atomic.AddInt32(&ms.readers, 1)
		n, err := ms.mountFile.Read(dest)
		readers := atomic.AddInt32(&ms.readers, -1)
		if err != nil {
			errNo := ToStatus(err)

			// Retry.
			if errNo == ENOENT {
				continue
			}

			// Unmount.
			if errNo == ENODEV {
				break
			}

			log.Printf("Failed to read from fuse conn: %v", errNo)
			break
		}

		if readers <= 0 {
			go ms.loop()
		}

		if req == nil {
			req = ms.newRequest()
		}
		if ms.latencies != nil {
			req.startNs = time.Now().UnixNano()
		}
		if req.setInput(dest[:n]) {
			dest = nil
		}

		ms.handleRequest(req)
		req.clear()
	}

	ms.buffers.FreeBuffer(dest)
}

func (ms *MountState) handleRequest(req *request) {
	defer req.Discard()
	defer ms.recordStats(req)

	req.parse()
	if req.handler == nil {
		req.status = ENOSYS
	}

	if req.status.Ok() && ms.Debug {
		log.Println(req.InputDebug())
	}

	if req.status.Ok() && req.handler.Func == nil {
		log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
		req.status = ENOSYS
	}

	if req.status.Ok() {
		req.handler.Func(ms, req)
	}

	errNo := ms.write(req)
	if errNo != 0 {
		log.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
			errNo, operationName(req.inHeader.Opcode))
	}
}

func (ms *MountState) write(req *request) Status {
	// Forget does not wait for reply.
	if req.inHeader.Opcode == _OP_FORGET || req.inHeader.Opcode == _OP_BATCH_FORGET {
		return OK
	}

	header, data := req.serialize()
	if ms.Debug {
		log.Println(req.OutputDebug())
	}

	if ms.latencies != nil {
		req.preWriteNs = time.Now().UnixNano()
	}

	if header == nil {
		return OK
	}
	var err error
	if data == nil {
		_, err = ms.mountFile.Write(header)
	} else {
		_, err = Writev(int(ms.mountFile.Fd()), [][]byte{header, data})
	}

	return ToStatus(err)
}

func (ms *MountState) writeInodeNotify(entry *raw.NotifyInvalInodeOut) Status {
	req := request{
		inHeader: &raw.InHeader{
			Opcode: _OP_NOTIFY_INODE,
		},
		handler: operationHandlers[_OP_NOTIFY_INODE],
		status:  raw.NOTIFY_INVAL_INODE,
	}
	req.outData = unsafe.Pointer(entry)
	result := ms.write(&req)

	if ms.Debug {
		log.Println("Response: INODE_NOTIFY", result)
	}
	return result
}

func (ms *MountState) writeEntryNotify(parent uint64, name string) Status {
	req := request{
		inHeader: &raw.InHeader{
			Opcode: _OP_NOTIFY_ENTRY,
		},
		handler: operationHandlers[_OP_NOTIFY_ENTRY],
		status:  raw.NOTIFY_INVAL_ENTRY,
	}
	entry := &raw.NotifyInvalEntryOut{
		Parent:  parent,
		NameLen: uint32(len(name)),
	}

	// Many versions of FUSE generate stacktraces if the
	// terminating null byte is missing.
	nameBytes := []byte(name + "\000")
	req.outData = unsafe.Pointer(entry)
	req.flatData = nameBytes
	result := ms.write(&req)

	if ms.Debug {
		log.Printf("Response: ENTRY_NOTIFY: %v", result)
	}
	return result
}
<commit_msg>Use waitgroup to shutdown connection cleanly.<commit_after>package fuse

import (
	"log"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/hanwen/go-fuse/raw"
)

const (
	// The kernel caps writes at 128k.
	MAX_KERNEL_WRITE = 128 * 1024
)

// MountState contains the logic for reading from the FUSE device and
// translating it to RawFileSystem interface calls.
type MountState struct {
	// Empty if unmounted.
	mountPoint string
	fileSystem RawFileSystem

	// I/O with kernel and daemon.
	mountFile *os.File

	// Dump debug info onto stdout.
	Debug bool

	// For efficient reads and writes.
	buffers *BufferPoolImpl

	latencies *LatencyMap

	opts           *MountOptions
	kernelSettings raw.InitIn

	// Number of loops blocked on reading; used to control amount
	// of concurrency.
	readers int32

	// Number of loops total. Needed for clean shutdown.
	loops sync.WaitGroup
}

func (ms *MountState) KernelSettings() raw.InitIn {
	return ms.kernelSettings
}

func (ms *MountState) MountPoint() string {
	return ms.mountPoint
}

// Mount filesystem on mountPoint.
func (ms *MountState) Mount(mountPoint string, opts *MountOptions) error {
	if opts == nil {
		opts = &MountOptions{
			MaxBackground: _DEFAULT_BACKGROUND_TASKS,
		}
	}
	o := *opts
	if o.MaxWrite < 0 {
		o.MaxWrite = 0
	}
	if o.MaxWrite == 0 {
		o.MaxWrite = 1 << 16
	}
	if o.MaxWrite > MAX_KERNEL_WRITE {
		o.MaxWrite = MAX_KERNEL_WRITE
	}
	opts = &o
	ms.opts = &o

	optStrs := opts.Options
	if opts.AllowOther {
		optStrs = append(optStrs, "allow_other")
	}

	file, mp, err := mount(mountPoint, strings.Join(optStrs, ","))
	if err != nil {
		return err
	}
	initParams := RawFsInit{
		InodeNotify: func(n *raw.NotifyInvalInodeOut) Status {
			return ms.writeInodeNotify(n)
		},
		EntryNotify: func(parent uint64, n string) Status {
			return ms.writeEntryNotify(parent, n)
		},
	}
	ms.fileSystem.Init(&initParams)
	ms.mountPoint = mp
	ms.mountFile = file
	return nil
}

func (ms *MountState) SetRecordStatistics(record bool) {
	if record {
		ms.latencies = NewLatencyMap()
	} else {
		ms.latencies = nil
	}
}

func (ms *MountState) Unmount() (err error) {
	if ms.mountPoint == "" {
		return nil
	}
	delay := time.Duration(0)
	for try := 0; try < 5; try++ {
		err = unmount(ms.mountPoint)
		if err == nil {
			break
		}

		// Sleep for a bit. This is not pretty, but there is
		// no way we can be certain that the kernel thinks all
		// open files have already been closed.
		delay = 2*delay + 5*time.Millisecond
		time.Sleep(delay)
	}
	ms.mountPoint = ""
	return err
}

func NewMountState(fs RawFileSystem) *MountState {
	ms := new(MountState)
	ms.mountPoint = ""
	ms.fileSystem = fs
	ms.buffers = NewBufferPool()
	return ms
}

func (ms *MountState) Latencies() map[string]float64 {
	if ms.latencies == nil {
		return nil
	}
	return ms.latencies.Latencies(1e-3)
}

func (ms *MountState) OperationCounts() map[string]int {
	if ms.latencies == nil {
		return nil
	}
	return ms.latencies.Counts()
}

func (ms *MountState) BufferPoolStats() string {
	return ms.buffers.String()
}

func (ms *MountState) newRequest() *request {
	r := &request{
		pool:               ms.buffers,
	}
	return r
}

func (ms *MountState) recordStats(req *request) {
	if ms.latencies != nil {
		endNs := time.Now().UnixNano()
		dt := endNs - req.startNs

		opname := operationName(req.inHeader.Opcode)
		ms.latencies.AddMany(
			[]LatencyArg{
				{opname, "", dt},
				{opname + "-write", "", endNs - req.preWriteNs}})
	}
}

// Loop initiates the FUSE loop. Normally, callers should run Loop()
// and wait for it to exit, but tests will want to run this in a
// goroutine.
//
// Each filesystem operation executes in a separate goroutine.
func (ms *MountState) Loop() {
	ms.loops.Add(1)
	ms.loop()
	ms.loops.Wait()
	ms.mountFile.Close()
}

const _MAX_READERS = 10
func (ms *MountState) loop() {
	var dest []byte
	var req *request
	for {
		if dest == nil {
			dest = ms.buffers.AllocBuffer(uint32(ms.opts.MaxWrite + 4096))
		}
		if atomic.AddInt32(&ms.readers, 0) > _MAX_READERS {
			break
		}

		atomic.AddInt32(&ms.readers, 1)
		n, err := ms.mountFile.Read(dest)
		readers := atomic.AddInt32(&ms.readers, -1)
		if err != nil {
			errNo := ToStatus(err)

			// Retry.
			if errNo == ENOENT {
				continue
			}

			// Unmount.
			if errNo == ENODEV {
				break
			}

			log.Printf("Failed to read from fuse conn: %v", errNo)
			break
		}

		if readers <= 0 {
			ms.loops.Add(1)
			go ms.loop()
		}

		if req == nil {
			req = ms.newRequest()
		}
		if ms.latencies != nil {
			req.startNs = time.Now().UnixNano()
		}
		if req.setInput(dest[:n]) {
			dest = nil
		}

		ms.handleRequest(req)
		req.clear()
	}

	ms.buffers.FreeBuffer(dest)
	ms.loops.Done()
}

func (ms *MountState) handleRequest(req *request) {
	defer req.Discard()
	defer ms.recordStats(req)

	req.parse()
	if req.handler == nil {
		req.status = ENOSYS
	}

	if req.status.Ok() && ms.Debug {
		log.Println(req.InputDebug())
	}

	if req.status.Ok() && req.handler.Func == nil {
		log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
		req.status = ENOSYS
	}

	if req.status.Ok() {
		req.handler.Func(ms, req)
	}

	errNo := ms.write(req)
	if errNo != 0 {
		log.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
			errNo, operationName(req.inHeader.Opcode))
	}
}

func (ms *MountState) write(req *request) Status {
	// Forget does not wait for reply.
	if req.inHeader.Opcode == _OP_FORGET || req.inHeader.Opcode == _OP_BATCH_FORGET {
		return OK
	}

	header, data := req.serialize()
	if ms.Debug {
		log.Println(req.OutputDebug())
	}

	if ms.latencies != nil {
		req.preWriteNs = time.Now().UnixNano()
	}

	if header == nil {
		return OK
	}
	var err error
	if data == nil {
		_, err = ms.mountFile.Write(header)
	} else {
		_, err = Writev(int(ms.mountFile.Fd()), [][]byte{header, data})
	}

	return ToStatus(err)
}

func (ms *MountState) writeInodeNotify(entry *raw.NotifyInvalInodeOut) Status {
	req := request{
		inHeader: &raw.InHeader{
			Opcode: _OP_NOTIFY_INODE,
		},
		handler: operationHandlers[_OP_NOTIFY_INODE],
		status:  raw.NOTIFY_INVAL_INODE,
	}
	req.outData = unsafe.Pointer(entry)
	result := ms.write(&req)

	if ms.Debug {
		log.Println("Response: INODE_NOTIFY", result)
	}
	return result
}

func (ms *MountState) writeEntryNotify(parent uint64, name string) Status {
	req := request{
		inHeader: &raw.InHeader{
			Opcode: _OP_NOTIFY_ENTRY,
		},
		handler: operationHandlers[_OP_NOTIFY_ENTRY],
		status:  raw.NOTIFY_INVAL_ENTRY,
	}
	entry := &raw.NotifyInvalEntryOut{
		Parent:  parent,
		NameLen: uint32(len(name)),
	}

	// Many versions of FUSE generate stacktraces if the
	// terminating null byte is missing.
	nameBytes := []byte(name + "\000")
	req.outData = unsafe.Pointer(entry)
	req.flatData = nameBytes
	result := ms.write(&req)

	if ms.Debug {
		log.Printf("Response: ENTRY_NOTIFY: %v", result)
	}
	return result
}
<|endoftext|>


<commit_before>/*
Implementation of the Api for executing commands locally
*/

package main

import (
	"fmt"
	"github.com/ActiveState/tail"
	"strings"
)

type Actions struct {
	path string
}

/*
List all jobs
*/
func (a *Actions) ListJobs() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, job := range setup.Jobs {
		fmt.Println(job.Id)
	}
}

/*
List all machines
*/
func (a *Actions) ListMachines() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, machine := range setup.Machines {
		fmt.Println(machine.Id)
	}
}

/*
List all scripts
*/
func (a *Actions) ListScripts() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, script := range setup.Scripts {
		fmt.Println(script)
	}
}

/*
List all existing logs stored locally
*/
func (a *Actions) ListLogs() {
	logs, err := loadLogs(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	fmt.Printf("%-20s\t%-20s\t%-20s\t%-32s\t%-32s\n", "Id", "Job", "Status", "Start", "End")
	for _, log := range logs {
		fmt.Printf("%-20s\t%-20s\t%-20s\t%-32s\t%-32s\n", log.Id, log.JobId, log.Status, log.StartTime, log.EndTime)
	}
}

/*
Execute the job with the given id locally
*/
func (a *Actions) RunJob(jobId string) {
	log := newLog(jobId)

	pipeline, err := buildPipeline(a.path, jobId, log)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	go func() {
		pipeline.Run(a.path)
	}()

	fmt.Println(log.Id)

	// Tail the log, ensuring the program does not terminate
	a.GetLogOutput(log.Id)
}

/*
Get the output stored locally in the log with the given id
*/
func (a *Actions) GetLogOutput(logId string) {
	// If the log id given is not full, search for the first log that
	// matches the id prefix
	if len(logId) < 16 {
		match := ""
		logs, err := loadLogs(a.path)
		if err != nil {
			fmt.Println("ERROR: " + err.Error())
		}

		for _, log := range logs {
			if strings.HasPrefix(log.Id, logId) {
				match = log.Id
				break
			}
		}

		// If no match, inform the user
		if match == "" {
			fmt.Println("ERROR: Log not found")
			return
		}

		logId = match

	}
	t, err := tail.TailFile(a.path+"/logs/"+logId, tail.Config{Follow: true})
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
		return
	}

	for line := range t.Lines {
		if line.Text == "-----Finished-----" || line.Text == "-----Error-----" {
			break
		}
		fmt.Println(line.Text)
	}
}
<commit_msg>Improved listing of jobs and machines<commit_after>/*
Implementation of the Api for executing commands locally
*/

package main

import (
	"fmt"
	"github.com/ActiveState/tail"
	"strings"
)

type Actions struct {
	path string
}

/*
List all jobs
*/
func (a *Actions) ListJobs() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, job := range setup.Jobs {
		fmt.Println(job.Id)
		for _, ex := range job.Pipeline {
			fmt.Printf("\t%s -> %s\n", ex.Machine, ex.Script)
		}
	}
}

/*
List all machines
*/
func (a *Actions) ListMachines() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, machine := range setup.Machines {
		fmt.Println(machine.Id)
		fmt.Printf("\t%s@%s:%s (%s)\n", machine.User, machine.Address, machine.Port, machine.PrivateKey)
	}
}

/*
List all scripts
*/
func (a *Actions) ListScripts() {
	setup, err := loadSetup(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	for _, script := range setup.Scripts {
		fmt.Println(script)
	}
}

/*
List all existing logs stored locally
*/
func (a *Actions) ListLogs() {
	logs, err := loadLogs(a.path)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	fmt.Printf("%-20s\t%-20s\t%-20s\t%-32s\t%-32s\n", "Id", "Job", "Status", "Start", "End")
	for _, log := range logs {
		fmt.Printf("%-20s\t%-20s\t%-20s\t%-32s\t%-32s\n", log.Id, log.JobId, log.Status, log.StartTime, log.EndTime)
	}
}

/*
Execute the job with the given id locally
*/
func (a *Actions) RunJob(jobId string) {
	log := newLog(jobId)

	pipeline, err := buildPipeline(a.path, jobId, log)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
	}

	go func() {
		pipeline.Run(a.path)
	}()

	fmt.Println(log.Id)

	// Tail the log, ensuring the program does not terminate
	a.GetLogOutput(log.Id)
}

/*
Get the output stored locally in the log with the given id
*/
func (a *Actions) GetLogOutput(logId string) {
	// If the log id given is not full, search for the first log that
	// matches the id prefix
	if len(logId) < 16 {
		match := ""
		logs, err := loadLogs(a.path)
		if err != nil {
			fmt.Println("ERROR: " + err.Error())
		}

		for _, log := range logs {
			if strings.HasPrefix(log.Id, logId) {
				match = log.Id
				break
			}
		}

		// If no match, inform the user
		if match == "" {
			fmt.Println("ERROR: Log not found")
			return
		}

		logId = match

	}
	t, err := tail.TailFile(a.path+"/logs/"+logId, tail.Config{Follow: true})
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
		return
	}

	for line := range t.Lines {
		if line.Text == "-----Finished-----" || line.Text == "-----Error-----" {
			break
		}
		fmt.Println(line.Text)
	}
}
<|endoftext|>


<commit_before>package trdsql

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestNewImporter(t *testing.T) {
	type args struct {
		inputFormat Format
	}
	tests := []struct {
		name string
		args args
		want Format
	}{
		{
			name: "test1",
			args: args{inputFormat: CSV},
			want: CSV,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := NewImporter(InFormat(CSV)); !reflect.DeepEqual(got.ReadOpts.InFormat, tt.want) {
				t.Errorf("NewImporter() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestImporter_Import(t *testing.T) {
	type fields struct {
		ReadOpts *ReadOpts
	}
	tests := []struct {
		name    string
		fields  fields
		query   string
		want    string
		wantErr bool
	}{
		{
			name:    "test1",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT 1",
			want:    "SELECT 1",
			wantErr: false,
		},
		{
			name:    "testNoFile",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM ttttnofile.csv",
			want:    "SELECT * FROM ttttnofile.csv",
			wantErr: false,
		},
		{
			name:    "testGenerate_series",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM generate_series(1,10)",
			want:    "SELECT * FROM generate_series(1,10)",
			wantErr: false,
		},
		{
			name:    "testGenerate_series2",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM generate_series(1,10,2)",
			want:    "SELECT * FROM generate_series(1,10,2)",
			wantErr: false,
		},
		{
			name:    "test3",
			fields:  fields{ReadOpts: NewReadOpts(InDelimiter("ddd"))},
			query:   "SELECT * FROM testdata/test.csv",
			want:    "SELECT * FROM testdata/test.csv",
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db, err := Connect("sqlite3", "")
			if err != nil {
				t.Fatal(err)
			}
			db.Tx, err = db.Begin()
			if err != nil {
				t.Fatal(err)
			}
			i := &ReadFormat{
				ReadOpts: tt.fields.ReadOpts,
			}
			got, err := i.Import(db, tt.query)
			if (err != nil) != tt.wantErr {
				t.Errorf("Importer.Import() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("Importer.Import() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_listTable(t *testing.T) {
	tests := []struct {
		name    string
		query   string
		wantTbl map[string]string
		wantIdx []int
	}{
		{
			name:    "noTable",
			query:   "SELECT 1;",
			wantTbl: map[string]string{},
			wantIdx: []int{},
		},
		{
			name:    "testTable",
			query:   "SELECT * FROM test;",
			wantTbl: map[string]string{"test": "test"},
			wantIdx: []int{6},
		},
		{
			name:    "testJoin",
			query:   "SELECT test.a FROM test LEFT JOIN test2 ON (test.b = test2.b);",
			wantTbl: map[string]string{"test": "test", "test2": "test2"},
			wantIdx: []int{6, 12},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotTbl, gotIdx := TableNames(SQLFields(tt.query))
			if !reflect.DeepEqual(gotTbl, tt.wantTbl) {
				t.Errorf("TableNames() Table Map = %v, want %v", gotTbl, tt.wantTbl)
			}
			if !reflect.DeepEqual(gotIdx, tt.wantIdx) {
				t.Errorf("TableNames() Table Index = %v, want %v", gotIdx, tt.wantIdx)
			}
		})
	}
}

func Test_SQLFields(t *testing.T) {
	type args struct {
		query string
	}
	tests := []struct {
		name string
		args args
		want []string
	}{
		{
			name: "testNo",
			args: args{query: ""},
			want: []string{},
		},
		{
			name: "testDoubleQuote",
			args: args{query: `SELECT * FROM "C:\file with a space.csv"`},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, `"C:\file with a space.csv"`},
		},
		{
			name: "testSingleQuote",
			args: args{query: `SELECT * FROM 'C:\file with a space.csv'`},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, `'C:\file with a space.csv'`},
		},
		{
			name: "testSingleQuote2",
			args: args{query: "SELECT * FROM jame's.csv"},
			want: []string{"SELECT", " ", "*", " ", "FROM", " ", "jame's.csv"},
		},
		{
			name: "testBackQuote",
			args: args{query: "SELECT * FROM `C:\file with a space.csv`;"},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, "`C:\file with a space.csv`", `;`},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := SQLFields(tt.args.query); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("SQLFields() = %#v, want %#v", got, tt.want)
			}
		})
	}
}

func newDBTestSqlite3() *DB {
	db, err := Connect("sqlite3", "")
	if err != nil {
		return nil
	}
	return db
}

func newDBTestPostgres() *DB {
	db, err := Connect("postgres", "dbname=trdsql_test")
	if err != nil {
		return nil
	}
	err = db.Ping()
	if err != nil {
		return nil
	}
	return db
}

func newDBTestMysql() *DB {
	db, err := Connect("mysql", "root@/trdsql_test")
	if err != nil {
		return nil
	}
	err = db.Ping()
	if err != nil {
		return nil
	}
	return db
}

func csvReadOpts() *ReadOpts {
	opts := NewReadOpts()
	opts.InFormat = CSV
	return opts
}

func TestImportFile(t *testing.T) {
	type args struct {
		db       *DB
		fileName string
		opts     *ReadOpts
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		{
			name: "testNoFile",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "nofile",
				opts:     NewReadOpts(),
			},
			want:    "",
			wantErr: false,
		},
		{
			name: "testGlobFile",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test*.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test*.csv`",
			wantErr: false,
		},
		{
			name: "testNoMatch",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/testtttttt*.csv",
				opts:     NewReadOpts(),
			},
			want:    "",
			wantErr: false,
		},
		{
			name: "testCSV",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test.csv",
				opts:     csvReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
		{
			name: "testSqlite",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
		{
			name: "testPostgres",
			args: args{
				db:       newDBTestPostgres(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "\"testdata/test.csv\"",
			wantErr: false,
		},
		{
			name: "testMysql",
			args: args{
				db:       newDBTestMysql(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.args.db == nil {
				t.SkipNow()
			}
			var err error
			tt.args.db.Tx, err = tt.args.db.Begin()
			if err != nil {
				t.Fatal(err)
			}
			got, err := ImportFile(tt.args.db, tt.args.fileName, tt.args.opts)
			if (err != nil) != tt.wantErr {
				t.Errorf("ImportFile() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ImportFile() = %v, want %v", got, tt.want)
			}
			err = tt.args.db.Tx.Commit()
			if err != nil {
				t.Fatal(err)
			}
			err = tt.args.db.Disconnect()
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

func Test_guessExtension(t *testing.T) {
	tests := []struct {
		name      string
		tableName string
		want      Format
	}{
		{name: "testCSV", tableName: "test.csv", want: CSV},
		{name: "testLTSV", tableName: "test.ltsv", want: LTSV},
		{name: "testLTSV2", tableName: "test.ltsv.gz", want: LTSV},
		{name: "testJSON", tableName: "test.json", want: JSON},
		{name: "testTBLN", tableName: "test.tbln", want: TBLN},
		{name: "testunknown", tableName: "test.go", want: CSV},
		{name: "testunknown2", tableName: "testltsv", want: CSV},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := guessExtension(tt.tableName); got != tt.want {
				t.Errorf("guessExtension() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_trimQuote(t *testing.T) {
	type args struct {
		fileName string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "test1",
			args: args{"test"},
			want: "test",
		},
		{
			name: "test2",
			args: args{"`test`"},
			want: "test",
		},
		{
			name: "test3",
			args: args{"\"test\""},
			want: "test",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := trimQuote(tt.args.fileName); got != tt.want {
				t.Errorf("trimQuote() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_extFileReader(t *testing.T) {
	tests := []struct {
		name     string
		fileName string
		want     []byte
	}{
		{
			name:     "testGzFile",
			fileName: filepath.Join("testdata", "test.csv.gz"),
			want:     []byte("1,Orange\n2,Melon\n3,Apple\n"),
		},
		{
			name:     "testNoGzFile",
			fileName: filepath.Join("testdata", "testNoGzFile.gz"),
			want:     []byte("1,Orange\n2,Melon\n3,Apple"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			file, err := os.Open(tt.fileName)
			if err != nil {
				t.Fatalf("extFileReader() file open error %s:%s", tt.fileName, err)
			}
			got := extFileReader(tt.fileName, file)
			r, err := ioutil.ReadAll(got)
			if err != nil {
				t.Fatalf("extFileReader() read error %s:%s", tt.fileName, err)
			}
			if !reflect.DeepEqual(r, tt.want) {
				t.Errorf("extFileReader() = %v, want %v", string(r), string(tt.want))
			}
		})
	}
}
<commit_msg>Change to substring comparison<commit_after>package trdsql

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestNewImporter(t *testing.T) {
	type args struct {
		inputFormat Format
	}
	tests := []struct {
		name string
		args args
		want Format
	}{
		{
			name: "test1",
			args: args{inputFormat: CSV},
			want: CSV,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := NewImporter(InFormat(CSV)); !reflect.DeepEqual(got.ReadOpts.InFormat, tt.want) {
				t.Errorf("NewImporter() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestImporter_Import(t *testing.T) {
	type fields struct {
		ReadOpts *ReadOpts
	}
	tests := []struct {
		name    string
		fields  fields
		query   string
		want    string
		wantErr bool
	}{
		{
			name:    "test1",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT 1",
			want:    "SELECT 1",
			wantErr: false,
		},
		{
			name:    "testNoFile",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM ttttnofile.csv",
			want:    "SELECT * FROM ttttnofile.csv",
			wantErr: false,
		},
		{
			name:    "testGenerate_series",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM generate_series(1,10)",
			want:    "SELECT * FROM generate_series(1,10)",
			wantErr: false,
		},
		{
			name:    "testGenerate_series2",
			fields:  fields{ReadOpts: NewReadOpts()},
			query:   "SELECT * FROM generate_series(1,10,2)",
			want:    "SELECT * FROM generate_series(1,10,2)",
			wantErr: false,
		},
		{
			name:    "test3",
			fields:  fields{ReadOpts: NewReadOpts(InDelimiter("ddd"))},
			query:   "SELECT * FROM testdata/test.csv",
			want:    "SELECT * FROM testdata/test.csv",
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db, err := Connect("sqlite3", "")
			if err != nil {
				t.Fatal(err)
			}
			db.Tx, err = db.Begin()
			if err != nil {
				t.Fatal(err)
			}
			i := &ReadFormat{
				ReadOpts: tt.fields.ReadOpts,
			}
			got, err := i.Import(db, tt.query)
			if (err != nil) != tt.wantErr {
				t.Errorf("Importer.Import() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("Importer.Import() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_listTable(t *testing.T) {
	tests := []struct {
		name    string
		query   string
		wantTbl map[string]string
		wantIdx []int
	}{
		{
			name:    "noTable",
			query:   "SELECT 1;",
			wantTbl: map[string]string{},
			wantIdx: []int{},
		},
		{
			name:    "testTable",
			query:   "SELECT * FROM test;",
			wantTbl: map[string]string{"test": "test"},
			wantIdx: []int{6},
		},
		{
			name:    "testJoin",
			query:   "SELECT test.a FROM test LEFT JOIN test2 ON (test.b = test2.b);",
			wantTbl: map[string]string{"test": "test", "test2": "test2"},
			wantIdx: []int{6, 12},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotTbl, gotIdx := TableNames(SQLFields(tt.query))
			if !reflect.DeepEqual(gotTbl, tt.wantTbl) {
				t.Errorf("TableNames() Table Map = %v, want %v", gotTbl, tt.wantTbl)
			}
			if !reflect.DeepEqual(gotIdx, tt.wantIdx) {
				t.Errorf("TableNames() Table Index = %v, want %v", gotIdx, tt.wantIdx)
			}
		})
	}
}

func Test_SQLFields(t *testing.T) {
	type args struct {
		query string
	}
	tests := []struct {
		name string
		args args
		want []string
	}{
		{
			name: "testNo",
			args: args{query: ""},
			want: []string{},
		},
		{
			name: "testDoubleQuote",
			args: args{query: `SELECT * FROM "C:\file with a space.csv"`},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, `"C:\file with a space.csv"`},
		},
		{
			name: "testSingleQuote",
			args: args{query: `SELECT * FROM 'C:\file with a space.csv'`},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, `'C:\file with a space.csv'`},
		},
		{
			name: "testSingleQuote2",
			args: args{query: "SELECT * FROM jame's.csv"},
			want: []string{"SELECT", " ", "*", " ", "FROM", " ", "jame's.csv"},
		},
		{
			name: "testBackQuote",
			args: args{query: "SELECT * FROM `C:\file with a space.csv`;"},
			want: []string{`SELECT`, ` `, `*`, ` `, `FROM`, ` `, "`C:\file with a space.csv`", `;`},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := SQLFields(tt.args.query); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("SQLFields() = %#v, want %#v", got, tt.want)
			}
		})
	}
}

func newDBTestSqlite3() *DB {
	db, err := Connect("sqlite3", "")
	if err != nil {
		return nil
	}
	return db
}

func newDBTestPostgres() *DB {
	db, err := Connect("postgres", "dbname=trdsql_test")
	if err != nil {
		return nil
	}
	err = db.Ping()
	if err != nil {
		return nil
	}
	return db
}

func newDBTestMysql() *DB {
	db, err := Connect("mysql", "root@/trdsql_test")
	if err != nil {
		return nil
	}
	err = db.Ping()
	if err != nil {
		return nil
	}
	return db
}

func csvReadOpts() *ReadOpts {
	opts := NewReadOpts()
	opts.InFormat = CSV
	return opts
}

func TestImportFile(t *testing.T) {
	type args struct {
		db       *DB
		fileName string
		opts     *ReadOpts
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		{
			name: "testNoFile",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "nofile",
				opts:     NewReadOpts(),
			},
			want:    "",
			wantErr: false,
		},
		{
			name: "testGlobFile",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test*.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test*.csv`",
			wantErr: false,
		},
		{
			name: "testNoMatch",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/testtttttt*.csv",
				opts:     NewReadOpts(),
			},
			want:    "",
			wantErr: false,
		},
		{
			name: "testCSV",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test.csv",
				opts:     csvReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
		{
			name: "testSqlite",
			args: args{
				db:       newDBTestSqlite3(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
		{
			name: "testPostgres",
			args: args{
				db:       newDBTestPostgres(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "\"testdata/test.csv\"",
			wantErr: false,
		},
		{
			name: "testMysql",
			args: args{
				db:       newDBTestMysql(),
				fileName: "testdata/test.csv",
				opts:     NewReadOpts(),
			},
			want:    "`testdata/test.csv`",
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.args.db == nil {
				t.SkipNow()
			}
			var err error
			tt.args.db.Tx, err = tt.args.db.Begin()
			if err != nil {
				t.Fatal(err)
			}
			got, err := ImportFile(tt.args.db, tt.args.fileName, tt.args.opts)
			if (err != nil) != tt.wantErr {
				t.Errorf("ImportFile() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ImportFile() = %v, want %v", got, tt.want)
			}
			err = tt.args.db.Tx.Commit()
			if err != nil {
				t.Fatal(err)
			}
			err = tt.args.db.Disconnect()
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

func Test_guessExtension(t *testing.T) {
	tests := []struct {
		name      string
		tableName string
		want      Format
	}{
		{name: "testCSV", tableName: "test.csv", want: CSV},
		{name: "testLTSV", tableName: "test.ltsv", want: LTSV},
		{name: "testLTSV2", tableName: "test.ltsv.gz", want: LTSV},
		{name: "testJSON", tableName: "test.json", want: JSON},
		{name: "testTBLN", tableName: "test.tbln", want: TBLN},
		{name: "testunknown", tableName: "test.go", want: CSV},
		{name: "testunknown2", tableName: "testltsv", want: CSV},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := guessExtension(tt.tableName); got != tt.want {
				t.Errorf("guessExtension() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_trimQuote(t *testing.T) {
	type args struct {
		fileName string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "test1",
			args: args{"test"},
			want: "test",
		},
		{
			name: "test2",
			args: args{"`test`"},
			want: "test",
		},
		{
			name: "test3",
			args: args{"\"test\""},
			want: "test",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := trimQuote(tt.args.fileName); got != tt.want {
				t.Errorf("trimQuote() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_extFileReader(t *testing.T) {
	tests := []struct {
		name     string
		fileName string
		want     string
	}{
		{
			name:     "testGzFile",
			fileName: filepath.Join("testdata", "test.csv.gz"),
			want:     "1,Orange\n2,Melon\n3,Apple\n",
		},
		{
			name:     "testNoGzFile",
			fileName: filepath.Join("testdata", "testNoGzFile.gz"),
			want:     "1,Orange\n2,Melon\n3,Apple",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			file, err := os.Open(tt.fileName)
			if err != nil {
				t.Fatalf("extFileReader() file open error %s:%s", tt.fileName, err)
			}
			got := extFileReader(tt.fileName, file)
			r, err := ioutil.ReadAll(got)
			if err != nil {
				t.Fatalf("extFileReader() read error %s:%s", tt.fileName, err)
			}
			if !reflect.DeepEqual(string(r)[0:7], tt.want[0:7]) {
				t.Errorf("extFileReader() = %v, want %v", string(r)[0:7], string(tt.want[0:7]))
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-vm 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 vmrun

import (
	"bytes"
	"fmt"
	"os/exec"
	"regexp"
	"strings"
	"syscall"

	"github.com/go-vm/vmware/internal/vmwareutil"
)

var vmrunPath = vmwareutil.LookPath("vmrun")

// vmrun run the vmrun command with the app name and args, return the stdout result and cmd error.
func vmrun(app string, arg ...string) (string, error) {
	// vmrun with nogui on VMware Fusion through at least 8.0.1 doesn't work right
	// if the umask is set to not allow world-readable permissions
	_ = syscall.Umask(022)

	cmd := exec.Command(vmrunPath, "-T", app)
	cmd.Args = append(cmd.Args, arg...)

	var stdout bytes.Buffer
	cmd.Stdout = &stdout

	err := cmd.Run()
	if err != nil {
		if runErr := err.(*exec.ExitError); runErr != nil {
			return "", fmt.Errorf(stdout.String())
		}
	}

	return stdout.String(), err
}

// Start start a VM or Team.
func Start(app, vmx string, gui bool) error {
	flag := "nogui"
	if gui {
		flag = "gui"
	}

	if _, err := vmrun(app, "start", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Stop stop a VM or Team.
func Stop(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "stop", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Reset reset a VM or Team.
func Reset(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "reset", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Suspend Suspend a VM or Team.
func Suspend(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "suspend", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Pause pause a VM.
func Pause(app, vmx string) error {
	if _, err := vmrun(app, "pause", vmx); err != nil {
		return err
	}

	return nil
}

// Unpause unpause a VM.
func Unpause(app, vmx string) error {
	if _, err := vmrun(app, "unpause", vmx); err != nil {
		return err
	}

	return nil
}

// SNAPSHOT COMMANDS        PARAMETERS           DESCRIPTION
// -----------------        ----------           -----------
// listSnapshots            Path to vmx file     List all snapshots in a VM
//                          [showTree]
//
// snapshot                 Path to vmx file     Create a snapshot of a VM
//                          Snapshot name
//
// deleteSnapshot           Path to vmx file     Remove a snapshot from a VM
//                          Snapshot name
//                          [andDeleteChildren]
//
// revertToSnapshot         Path to vmx file     Set VM state to a snapshot
//                          Snapshot name

var listSnapshotsRe = regexp.MustCompile(`[^Total snapshots: \d](\w+)`)

// ListSnapshots list all snapshots in a VM.
func ListSnapshots(app, vmx string) ([]string, int, error) {
	stdout, err := vmrun(app, "listSnapshots", vmx)
	if err != nil {
		return nil, 0, err
	}

	list := listSnapshotsRe.FindAllString(stdout, -1)
	// TODO(zchee): need re-append with TrimSpace?
	var snapshotList []string
	for _, snapshotName := range list {
		snapshotList = append(snapshotList, strings.TrimSpace(snapshotName))
	}

	return snapshotList, len(snapshotList), nil
}

// Snapshot create a snapshot of a VM.
func Snapshot(app, vmx, snapshotName string) error {
	if _, err := vmrun(app, "snapshot", vmx, snapshotName); err != nil {
		return err
	}

	return nil
}

// DeleteSnapshot remove a snapshot from a VM.
func DeleteSnapshot(app, vmx, snapshotName string, deleteChildren bool) error {
	args := []string{"deleteSnapshot", vmx, snapshotName}
	if deleteChildren {
		args = append(args, "andDeleteChildren")
	}

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}

// RevertToSnapshot set VM state to a snapshot.
func RevertToSnapshot(app, vmx, snapshotName string) error {
	if _, err := vmrun(app, "revertToSnapshot", vmx, snapshotName); err != nil {
		return err
	}

	return nil
}

// GUEST OS COMMANDS        PARAMETERS           DESCRIPTION
// -----------------        ----------           -----------
// runProgramInGuest        Path to vmx file     Run a program in Guest OS
//                          [-noWait]
//                          [-activeWindow]
//                          [-interactive]
//                          Complete-Path-To-Program
//                          [Program arguments]
//
// fileExistsInGuest        Path to vmx file     Check if a file exists in Guest OS
//                          Path to file in guest
//
// directoryExistsInGuest   Path to vmx file     Check if a directory exists in Guest OS
//                          Path to directory in guest
//
// setSharedFolderState     Path to vmx file     Modify a Host-Guest shared folder
//                          Share name
//                          Host path
//                          writable | readonly
//
// addSharedFolder          Path to vmx file     Add a Host-Guest shared folder
//                          Share name
//                          New host path
//
// removeSharedFolder       Path to vmx file     Remove a Host-Guest shared folder
//                          Share name
//
// enableSharedFolders      Path to vmx file     Enable shared folders in Guest
//                          [runtime]
//
// disableSharedFolders     Path to vmx file     Disable shared folders in Guest
//                          [runtime]
//
// listProcessesInGuest     Path to vmx file     List running processes in Guest OS
//
// killProcessInGuest       Path to vmx file     Kill a process in Guest OS
//                          process id
//
// runScriptInGuest         Path to vmx file     Run a script in Guest OS
//                          [-noWait]
//                          [-activeWindow]
//                          [-interactive]
//                          Interpreter path
//                          Script text
//
// deleteFileInGuest        Path to vmx file     Delete a file in Guest OS
// Path in guest
//
// createDirectoryInGuest   Path to vmx file     Create a directory in Guest OS
// Directory path in guest
//
// deleteDirectoryInGuest   Path to vmx file     Delete a directory in Guest OS
// Directory path in guest
//
// CreateTempfileInGuest    Path to vmx file     Create a temporary file in Guest OS
//
// listDirectoryInGuest     Path to vmx file     List a directory in Guest OS
//                          Directory path in guest
//
// CopyFileFromHostToGuest  Path to vmx file     Copy a file from host OS to guest OS
// Path on host             Path in guest
//
//
// CopyFileFromGuestToHost  Path to vmx file     Copy a file from guest OS to host OS
// Path in guest            Path on host
//
//
// renameFileInGuest        Path to vmx file     Rename a file in Guest OS
//                          Original name
//                          New name
//
// captureScreen            Path to vmx file     Capture the screen of the VM to a local file
// Path on host
//
// writeVariable            Path to vmx file     Write a variable in the VM state
//                          [runtimeConfig|guestEnv|guestVar]
//                          variable name
//                          variable value
//
// readVariable             Path to vmx file     Read a variable in the VM state
//                          [runtimeConfig|guestEnv|guestVar]
//                          variable name
//
// getGuestIPAddress        Path to vmx file     Gets the IP address of the guest
//                          [-wait]

// RunProgramInGuestConfig represents a runProgramInGuest command flags.
type RunProgramInGuestConfig int

const (
	// NoWait returns a prompt immediately after the program starts in the guest, rather than waiting for it to finish.
	// This option is useful for interactive programs.
	NoWait RunProgramInGuestConfig = 1 << iota
	// ActiveWindow ensures that the Windows GUI is visible, not minimized.
	// It has no effect on Linux.
	ActiveWindow
	// Interactive forces interactive guest login.
	// It is useful for Vista and Windows 7 guests to make the program visible in the console window.
	Interactive
)

// RunProgramInGuest run a program in Guest OS.
func RunProgramInGuest(app, vmx string, username, password string, config RunProgramInGuestConfig, cmdPath string, cmdArgs ...string) error {
	args := []string{"-gu", username, "-gp", password, "runProgramInGuest", vmx}

	if config&NoWait > 0 {
		args = append(args, "-noWait")
	}
	if config&ActiveWindow > 0 {
		args = append(args, "-activeWindow")
	}
	if config&Interactive > 0 {
		args = append(args, "-interactive")
	}

	args = append(args, cmdPath)
	args = append(args, cmdArgs...)

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}

// FileExistsInGuest check if a file exists in Guest OS.
func FileExistsInGuest(app, vmx string, username, password string, filename string) bool {
	if _, err := vmrun(app, "-gu", username, "-gp", password, "fileExistsInGuest", vmx, filename); err != nil {
		return false
	}

	return true
}

// DirectoryExistsInGuest check if a directory exists in Guest OS.
func DirectoryExistsInGuest(app, vmx string, username, password string, dir string) bool {
	if _, err := vmrun(app, "-gu", username, "-gp", password, "directoryExistsInGuest", vmx, dir); err != nil {
		return false
	}

	return true
}

// SetSharedFolderState modify a Host-Guest shared folder.
func SetSharedFolderState(app, vmx string, shareName, hostPath string, writable bool) bool {
	flag := "readonly"
	if writable {
		flag = "writable"
	}
	if _, err := vmrun(app, "setSharedFolderState", vmx, shareName, hostPath, flag); err != nil {
		return false
	}

	return true
}

// AddSharedFolder add a Host-Guest shared folder.
func AddSharedFolder(app, vmx string, shareName, newHostPath string) error {
	if _, err := vmrun(app, "addSharedFolder", vmx, shareName, newHostPath); err != nil {
		return err
	}

	return nil
}

// RemoveSharedFolder remove a Host-Guest shared folder.
func RemoveSharedFolder(app, vmx, shareName string) error {
	if _, err := vmrun(app, "removeSharedFolder", vmx, shareName); err != nil {
		return err
	}

	return nil
}

// EnableSharedFolders enable shared folders in Guest.
// The optional runtime argument means to share folders only until the virtual machine is powered off.
// Otherwise, the setting persists at next power on.
func EnableSharedFolders(app, vmx string, runtime bool) error {
	args := []string{"enableSharedFolders", vmx}
	if runtime {
		args = append(args, "runtime")
	}

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}
<commit_msg>vmrun: add DisableSharedFolders function<commit_after>// Copyright 2017 The go-vm 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 vmrun

import (
	"bytes"
	"fmt"
	"os/exec"
	"regexp"
	"strings"
	"syscall"

	"github.com/go-vm/vmware/internal/vmwareutil"
)

var vmrunPath = vmwareutil.LookPath("vmrun")

// vmrun run the vmrun command with the app name and args, return the stdout result and cmd error.
func vmrun(app string, arg ...string) (string, error) {
	// vmrun with nogui on VMware Fusion through at least 8.0.1 doesn't work right
	// if the umask is set to not allow world-readable permissions
	_ = syscall.Umask(022)

	cmd := exec.Command(vmrunPath, "-T", app)
	cmd.Args = append(cmd.Args, arg...)

	var stdout bytes.Buffer
	cmd.Stdout = &stdout

	err := cmd.Run()
	if err != nil {
		if runErr := err.(*exec.ExitError); runErr != nil {
			return "", fmt.Errorf(stdout.String())
		}
	}

	return stdout.String(), err
}

// Start start a VM or Team.
func Start(app, vmx string, gui bool) error {
	flag := "nogui"
	if gui {
		flag = "gui"
	}

	if _, err := vmrun(app, "start", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Stop stop a VM or Team.
func Stop(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "stop", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Reset reset a VM or Team.
func Reset(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "reset", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Suspend Suspend a VM or Team.
func Suspend(app, vmx string, hard bool) error {
	flag := "soft"
	if hard {
		flag = "hard"
	}

	if _, err := vmrun(app, "suspend", vmx, flag); err != nil {
		return err
	}

	return nil
}

// Pause pause a VM.
func Pause(app, vmx string) error {
	if _, err := vmrun(app, "pause", vmx); err != nil {
		return err
	}

	return nil
}

// Unpause unpause a VM.
func Unpause(app, vmx string) error {
	if _, err := vmrun(app, "unpause", vmx); err != nil {
		return err
	}

	return nil
}

// SNAPSHOT COMMANDS        PARAMETERS           DESCRIPTION
// -----------------        ----------           -----------
// listSnapshots            Path to vmx file     List all snapshots in a VM
//                          [showTree]
//
// snapshot                 Path to vmx file     Create a snapshot of a VM
//                          Snapshot name
//
// deleteSnapshot           Path to vmx file     Remove a snapshot from a VM
//                          Snapshot name
//                          [andDeleteChildren]
//
// revertToSnapshot         Path to vmx file     Set VM state to a snapshot
//                          Snapshot name

var listSnapshotsRe = regexp.MustCompile(`[^Total snapshots: \d](\w+)`)

// ListSnapshots list all snapshots in a VM.
func ListSnapshots(app, vmx string) ([]string, int, error) {
	stdout, err := vmrun(app, "listSnapshots", vmx)
	if err != nil {
		return nil, 0, err
	}

	list := listSnapshotsRe.FindAllString(stdout, -1)
	// TODO(zchee): need re-append with TrimSpace?
	var snapshotList []string
	for _, snapshotName := range list {
		snapshotList = append(snapshotList, strings.TrimSpace(snapshotName))
	}

	return snapshotList, len(snapshotList), nil
}

// Snapshot create a snapshot of a VM.
func Snapshot(app, vmx, snapshotName string) error {
	if _, err := vmrun(app, "snapshot", vmx, snapshotName); err != nil {
		return err
	}

	return nil
}

// DeleteSnapshot remove a snapshot from a VM.
func DeleteSnapshot(app, vmx, snapshotName string, deleteChildren bool) error {
	args := []string{"deleteSnapshot", vmx, snapshotName}
	if deleteChildren {
		args = append(args, "andDeleteChildren")
	}

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}

// RevertToSnapshot set VM state to a snapshot.
func RevertToSnapshot(app, vmx, snapshotName string) error {
	if _, err := vmrun(app, "revertToSnapshot", vmx, snapshotName); err != nil {
		return err
	}

	return nil
}

// GUEST OS COMMANDS        PARAMETERS           DESCRIPTION
// -----------------        ----------           -----------
// runProgramInGuest        Path to vmx file     Run a program in Guest OS
//                          [-noWait]
//                          [-activeWindow]
//                          [-interactive]
//                          Complete-Path-To-Program
//                          [Program arguments]
//
// fileExistsInGuest        Path to vmx file     Check if a file exists in Guest OS
//                          Path to file in guest
//
// directoryExistsInGuest   Path to vmx file     Check if a directory exists in Guest OS
//                          Path to directory in guest
//
// setSharedFolderState     Path to vmx file     Modify a Host-Guest shared folder
//                          Share name
//                          Host path
//                          writable | readonly
//
// addSharedFolder          Path to vmx file     Add a Host-Guest shared folder
//                          Share name
//                          New host path
//
// removeSharedFolder       Path to vmx file     Remove a Host-Guest shared folder
//                          Share name
//
// enableSharedFolders      Path to vmx file     Enable shared folders in Guest
//                          [runtime]
//
// disableSharedFolders     Path to vmx file     Disable shared folders in Guest
//                          [runtime]
//
// listProcessesInGuest     Path to vmx file     List running processes in Guest OS
//
// killProcessInGuest       Path to vmx file     Kill a process in Guest OS
//                          process id
//
// runScriptInGuest         Path to vmx file     Run a script in Guest OS
//                          [-noWait]
//                          [-activeWindow]
//                          [-interactive]
//                          Interpreter path
//                          Script text
//
// deleteFileInGuest        Path to vmx file     Delete a file in Guest OS
// Path in guest
//
// createDirectoryInGuest   Path to vmx file     Create a directory in Guest OS
// Directory path in guest
//
// deleteDirectoryInGuest   Path to vmx file     Delete a directory in Guest OS
// Directory path in guest
//
// CreateTempfileInGuest    Path to vmx file     Create a temporary file in Guest OS
//
// listDirectoryInGuest     Path to vmx file     List a directory in Guest OS
//                          Directory path in guest
//
// CopyFileFromHostToGuest  Path to vmx file     Copy a file from host OS to guest OS
// Path on host             Path in guest
//
//
// CopyFileFromGuestToHost  Path to vmx file     Copy a file from guest OS to host OS
// Path in guest            Path on host
//
//
// renameFileInGuest        Path to vmx file     Rename a file in Guest OS
//                          Original name
//                          New name
//
// captureScreen            Path to vmx file     Capture the screen of the VM to a local file
// Path on host
//
// writeVariable            Path to vmx file     Write a variable in the VM state
//                          [runtimeConfig|guestEnv|guestVar]
//                          variable name
//                          variable value
//
// readVariable             Path to vmx file     Read a variable in the VM state
//                          [runtimeConfig|guestEnv|guestVar]
//                          variable name
//
// getGuestIPAddress        Path to vmx file     Gets the IP address of the guest
//                          [-wait]

// RunProgramInGuestConfig represents a runProgramInGuest command flags.
type RunProgramInGuestConfig int

const (
	// NoWait returns a prompt immediately after the program starts in the guest, rather than waiting for it to finish.
	// This option is useful for interactive programs.
	NoWait RunProgramInGuestConfig = 1 << iota
	// ActiveWindow ensures that the Windows GUI is visible, not minimized.
	// It has no effect on Linux.
	ActiveWindow
	// Interactive forces interactive guest login.
	// It is useful for Vista and Windows 7 guests to make the program visible in the console window.
	Interactive
)

// RunProgramInGuest run a program in Guest OS.
func RunProgramInGuest(app, vmx string, username, password string, config RunProgramInGuestConfig, cmdPath string, cmdArgs ...string) error {
	args := []string{"-gu", username, "-gp", password, "runProgramInGuest", vmx}

	if config&NoWait > 0 {
		args = append(args, "-noWait")
	}
	if config&ActiveWindow > 0 {
		args = append(args, "-activeWindow")
	}
	if config&Interactive > 0 {
		args = append(args, "-interactive")
	}

	args = append(args, cmdPath)
	args = append(args, cmdArgs...)

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}

// FileExistsInGuest check if a file exists in Guest OS.
func FileExistsInGuest(app, vmx string, username, password string, filename string) bool {
	if _, err := vmrun(app, "-gu", username, "-gp", password, "fileExistsInGuest", vmx, filename); err != nil {
		return false
	}

	return true
}

// DirectoryExistsInGuest check if a directory exists in Guest OS.
func DirectoryExistsInGuest(app, vmx string, username, password string, dir string) bool {
	if _, err := vmrun(app, "-gu", username, "-gp", password, "directoryExistsInGuest", vmx, dir); err != nil {
		return false
	}

	return true
}

// SetSharedFolderState modify a Host-Guest shared folder.
func SetSharedFolderState(app, vmx string, shareName, hostPath string, writable bool) bool {
	flag := "readonly"
	if writable {
		flag = "writable"
	}
	if _, err := vmrun(app, "setSharedFolderState", vmx, shareName, hostPath, flag); err != nil {
		return false
	}

	return true
}

// AddSharedFolder add a Host-Guest shared folder.
func AddSharedFolder(app, vmx string, shareName, newHostPath string) error {
	if _, err := vmrun(app, "addSharedFolder", vmx, shareName, newHostPath); err != nil {
		return err
	}

	return nil
}

// RemoveSharedFolder remove a Host-Guest shared folder.
func RemoveSharedFolder(app, vmx, shareName string) error {
	if _, err := vmrun(app, "removeSharedFolder", vmx, shareName); err != nil {
		return err
	}

	return nil
}

// EnableSharedFolders enable shared folders in Guest.
// The optional runtime argument means to share folders only until the virtual machine is powered off.
// Otherwise, the setting persists at next power on.
func EnableSharedFolders(app, vmx string, runtime bool) error {
	args := []string{"enableSharedFolders", vmx}
	if runtime {
		args = append(args, "runtime")
	}

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}

// DisableSharedFolders disable shared folders in Guest.
// Stops the guest virtual machine, specified by .vmx file, from sharing folders with its host.
// The optional runtime argument means to stop sharing folders only until the virtual machine is powered off.
// Otherwise, the setting persists at next power on.
func DisableSharedFolders(app, vmx string, runtime bool) error {
	args := []string{"disableSharedFolders", vmx}
	if runtime {
		args = append(args, "runtime")
	}

	if _, err := vmrun(app, args...); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// pulse-simple wraps PulseAudio's Simple API using CGo,
// for easy audio playback and capture via PulseAudio.
//
// Basic usage is to request a playback or capture stream,
// then write bytes to or read bytes from it.
//
// Reading and writing will block until the given byte slice
// is completely consumed or filled, or an error occurs.
//
// The format of the data will be as requested on stream creation.
//
//  ss := pulse.SampleSpec{pulse.SAMPLE_S16LE, 44100, 2}
//  stream, _ := pulse.Playback("my app", "my stream", &ss)
//  defer stream.Free()
//  defer stream.Drain()
//  stream.Write(data)
//
// More example usage can be found in the examples folder.
//
// For more information, see the PulseAudio Simple API documentation at
// http://www.freedesktop.org/software/pulseaudio/doxygen/simple.html
package pulse

import (
	"errors"
	"unsafe"
)

/*
#cgo pkg-config: libpulse-simple

#include <stdlib.h>
#include <pulse/simple.h>
*/
import "C"

type StreamDirection C.pa_stream_direction_t

const (
	STREAM_NODIRECTION StreamDirection = C.PA_STREAM_NODIRECTION
	STREAM_PLAYBACK    StreamDirection = C.PA_STREAM_PLAYBACK
	STREAM_RECORD      StreamDirection = C.PA_STREAM_RECORD
	STREAM_UPLOAD      StreamDirection = C.PA_STREAM_UPLOAD
)

type Stream struct {
	simple *C.pa_simple
}

// Capture creates a new stream for recording and returns its pointer.
func Capture(clientName, streamName string, spec *SampleSpec) (*Stream, error) {
	return NewStream("", clientName, STREAM_RECORD, "", streamName, spec, nil, nil)
}

// Playback creates a new stream for playback and returns its pointer.
func Playback(clientName, streamName string, spec *SampleSpec) (*Stream, error) {
	return NewStream("", clientName, STREAM_PLAYBACK, "", streamName, spec, nil, nil)
}

func NewStream(
	serverName, clientName string,
	dir StreamDirection,
	deviceName, streamName string,
	spec *SampleSpec,
	cmap *ChannelMap,
	battr *BufferAttr,
) (*Stream, error) {

	s := new(Stream)

	var server *C.char
	if serverName != "" {
		server = C.CString(serverName)
		defer C.free(unsafe.Pointer(server))
	}

	var dev *C.char
	if deviceName != "" {
		dev = C.CString(deviceName)
		defer C.free(unsafe.Pointer(dev))
	}

	name := C.CString(clientName)
	defer C.free(unsafe.Pointer(name))
	stream_name := C.CString(streamName)
	defer C.free(unsafe.Pointer(stream_name))

	var err C.int

	s.simple = C.pa_simple_new(
		server,
		name,
		C.pa_stream_direction_t(dir),
		dev,
		stream_name,
		spec.toC(),
		nil,
		nil,
		&err,
	)

	if err == C.PA_OK {
		return s, nil
	}
	return s, errors.New("whatever")
}

// Stream.Free closes the stream and frees the associated memory.
// The stream becomes invalid after this has been called.
// This should usually be deferred immediately after obtaining a stream.
func (s *Stream) Free() {
	C.pa_simple_free(s.simple)
}

// Stream.Drain blocks until all buffered data has finished playing.
func (s *Stream) Drain() (int, error) {
	var err C.int
	written := C.pa_simple_drain(s.simple, &err)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Flush flushes the playback buffer, discarding any audio therein
func (s *Stream) Flush() (int, error) {
	var err C.int
	flushed := C.pa_simple_flush(s.simple, &err)
	if err == C.PA_OK {
		return int(flushed), nil
	}
	return int(flushed), errors.New("summt went wrong")
}

// Stream.Write writes the given data to the stream,
// blocking until the data has been written.
func (s *Stream) Write(data []byte) (int, error) {
	var err C.int
	written := C.pa_simple_write(
		s.simple,
		unsafe.Pointer(&data[0]),
		C.size_t(len(data)),
		&err,
	)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Read reads data from the stream,
// blocking until it has filled the provided slice.
func (s *Stream) Read(data []byte) (int, error) {
	var err C.int
	written := C.pa_simple_read(
		s.simple,
		unsafe.Pointer(&data[0]),
		C.size_t(len(data)),
		&err,
	)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Latency returns the playback latency in microseconds.
func (s *Stream) Latency() (uint64, error) {
	var err C.int
	lat := C.pa_simple_get_latency(s.simple, &err)
	if err == C.PA_OK {
		return uint64(lat), nil
	}
	return uint64(lat), errors.New("summt went wrong")
}
<commit_msg>Use given ChannelMap and BufferAttr in NewStream().<commit_after>// pulse-simple wraps PulseAudio's Simple API using CGo,
// for easy audio playback and capture via PulseAudio.
//
// Basic usage is to request a playback or capture stream,
// then write bytes to or read bytes from it.
//
// Reading and writing will block until the given byte slice
// is completely consumed or filled, or an error occurs.
//
// The format of the data will be as requested on stream creation.
//
//  ss := pulse.SampleSpec{pulse.SAMPLE_S16LE, 44100, 2}
//  stream, _ := pulse.Playback("my app", "my stream", &ss)
//  defer stream.Free()
//  defer stream.Drain()
//  stream.Write(data)
//
// More example usage can be found in the examples folder.
//
// For more information, see the PulseAudio Simple API documentation at
// http://www.freedesktop.org/software/pulseaudio/doxygen/simple.html
package pulse

import (
	"errors"
	"unsafe"
)

/*
#cgo pkg-config: libpulse-simple

#include <stdlib.h>
#include <pulse/simple.h>
*/
import "C"

type StreamDirection C.pa_stream_direction_t

const (
	STREAM_NODIRECTION StreamDirection = C.PA_STREAM_NODIRECTION
	STREAM_PLAYBACK    StreamDirection = C.PA_STREAM_PLAYBACK
	STREAM_RECORD      StreamDirection = C.PA_STREAM_RECORD
	STREAM_UPLOAD      StreamDirection = C.PA_STREAM_UPLOAD
)

type Stream struct {
	simple *C.pa_simple
}

// Capture creates a new stream for recording and returns its pointer.
func Capture(clientName, streamName string, spec *SampleSpec) (*Stream, error) {
	return NewStream("", clientName, STREAM_RECORD, "", streamName, spec, nil, nil)
}

// Playback creates a new stream for playback and returns its pointer.
func Playback(clientName, streamName string, spec *SampleSpec) (*Stream, error) {
	return NewStream("", clientName, STREAM_PLAYBACK, "", streamName, spec, nil, nil)
}

func NewStream(
	serverName, clientName string,
	dir StreamDirection,
	deviceName, streamName string,
	spec *SampleSpec,
	cmap *ChannelMap,
	battr *BufferAttr,
) (*Stream, error) {

	s := new(Stream)

	var server *C.char
	if serverName != "" {
		server = C.CString(serverName)
		defer C.free(unsafe.Pointer(server))
	}

	var dev *C.char
	if deviceName != "" {
		dev = C.CString(deviceName)
		defer C.free(unsafe.Pointer(dev))
	}

	name := C.CString(clientName)
	defer C.free(unsafe.Pointer(name))
	stream_name := C.CString(streamName)
	defer C.free(unsafe.Pointer(stream_name))

	var err C.int

	s.simple = C.pa_simple_new(
		server,
		name,
		C.pa_stream_direction_t(dir),
		dev,
		stream_name,
		spec.toC(),
		cmap.toC(),
		battr.toC(),
		&err,
	)

	if err == C.PA_OK {
		return s, nil
	}
	return s, errors.New("whatever")
}

// Stream.Free closes the stream and frees the associated memory.
// The stream becomes invalid after this has been called.
// This should usually be deferred immediately after obtaining a stream.
func (s *Stream) Free() {
	C.pa_simple_free(s.simple)
}

// Stream.Drain blocks until all buffered data has finished playing.
func (s *Stream) Drain() (int, error) {
	var err C.int
	written := C.pa_simple_drain(s.simple, &err)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Flush flushes the playback buffer, discarding any audio therein
func (s *Stream) Flush() (int, error) {
	var err C.int
	flushed := C.pa_simple_flush(s.simple, &err)
	if err == C.PA_OK {
		return int(flushed), nil
	}
	return int(flushed), errors.New("summt went wrong")
}

// Stream.Write writes the given data to the stream,
// blocking until the data has been written.
func (s *Stream) Write(data []byte) (int, error) {
	var err C.int
	written := C.pa_simple_write(
		s.simple,
		unsafe.Pointer(&data[0]),
		C.size_t(len(data)),
		&err,
	)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Read reads data from the stream,
// blocking until it has filled the provided slice.
func (s *Stream) Read(data []byte) (int, error) {
	var err C.int
	written := C.pa_simple_read(
		s.simple,
		unsafe.Pointer(&data[0]),
		C.size_t(len(data)),
		&err,
	)
	if err == C.PA_OK {
		return int(written), nil
	}
	return int(written), errors.New("summt went wrong")
}

// Stream.Latency returns the playback latency in microseconds.
func (s *Stream) Latency() (uint64, error) {
	var err C.int
	lat := C.pa_simple_get_latency(s.simple, &err)
	if err == C.PA_OK {
		return uint64(lat), nil
	}
	return uint64(lat), errors.New("summt went wrong")
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"context"
	"fmt"
	"io"
	"time"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/utils/clock"
)

const (
	// PluginName indicates the name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

// Admit makes an admission decision based on the request attributes
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}

		klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
		if apierr, ok := err.(*errors.StatusError); ok {
			apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
				Type:    v1.NamespaceTerminatingCause,
				Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
				Field:   "metadata.namespace",
			})
		}
		return err
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<commit_msg>UPSTREAM: <carry>: kube-apiserver: add our immortal namespaces directly to admission plugin<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"context"
	"fmt"
	"io"
	"time"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/utils/clock"
)

const (
	// PluginName indicates the name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic,
			// user specified configuration that cannot be rebuilt
			"openshift-config",
			// cluster generated configuration that cannot be rebuilt (etcd encryption keys)
			"openshift-config-managed",
			// the CVO which is the root we use to rebuild all the rest
			"openshift-cluster-version",
			// contains a namespaced list of all nodes in the cluster (yeah, weird.  they do it for multi-tenant management I think?)
			"openshift-machine-api",
		))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

// Admit makes an admission decision based on the request attributes
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}

		klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
		if apierr, ok := err.(*errors.StatusError); ok {
			apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
				Type:    v1.NamespaceTerminatingCause,
				Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
				Field:   "metadata.namespace",
			})
		}
		return err
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<|endoftext|>