<commit_before>package mount

import (
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/hanwen/go-fuse/v2/fuse"
	"os"
	"sync"
)

type InodeToPath struct {
	sync.RWMutex
	nextInodeId uint64
	inode2path  map[uint64]*InodeEntry
	path2inode  map[util.FullPath]uint64
}
type InodeEntry struct {
	util.FullPath
	nlookup          uint64
	isDirectory      bool
	isChildrenCached bool
}

func NewInodeToPath() *InodeToPath {
	t := &InodeToPath{
		inode2path: make(map[uint64]*InodeEntry),
		path2inode: make(map[util.FullPath]uint64),
	}
	t.inode2path[1] = &InodeEntry{"/", 1, true, false}
	t.path2inode["/"] = 1
	return t
}

func (i *InodeToPath) Lookup(path util.FullPath, mode os.FileMode, isHardlink bool, possibleInode uint64, isLookup bool) uint64 {
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if !found {
		if possibleInode == 0 {
			inode = path.AsInode(mode)
		} else {
			inode = possibleInode
		}
		if !isHardlink {
			for _, found := i.inode2path[inode]; found; inode++ {
				_, found = i.inode2path[inode]
			}
		}
	}
	i.path2inode[path] = inode

	if _, found := i.inode2path[inode]; found {
		if isLookup {
			i.inode2path[inode].nlookup++
		}
	} else {
		if !isLookup {
			i.inode2path[inode] = &InodeEntry{path, 0, mode&os.ModeDir > 0, false}
		} else {
			i.inode2path[inode] = &InodeEntry{path, 1, mode&os.ModeDir > 0, false}
		}
	}

	return inode
}

func (i *InodeToPath) AllocateInode(path util.FullPath, mode os.FileMode) uint64 {
	if path == "/" {
		return 1
	}
	i.Lock()
	defer i.Unlock()
	inode := path.AsInode(mode)
	for _, found := i.inode2path[inode]; found; inode++ {
		_, found = i.inode2path[inode]
	}
	return inode
}

func (i *InodeToPath) GetInode(path util.FullPath) uint64 {
	if path == "/" {
		return 1
	}
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if !found {
		// glog.Fatalf("GetInode unknown inode for %s", path)
		// this could be the parent for mount point
	}
	return inode
}

func (i *InodeToPath) GetPath(inode uint64) (util.FullPath, fuse.Status) {
	i.RLock()
	defer i.RUnlock()
	path, found := i.inode2path[inode]
	if !found {
		return "", fuse.ENOENT
	}
	return path.FullPath, fuse.OK
}

func (i *InodeToPath) HasPath(path util.FullPath) bool {
	i.RLock()
	defer i.RUnlock()
	_, found := i.path2inode[path]
	return found
}

func (i *InodeToPath) MarkChildrenCached(fullpath util.FullPath) {
	i.RLock()
	defer i.RUnlock()
	inode, found := i.path2inode[fullpath]
	if !found {
		glog.Fatalf("MarkChildrenCached not found inode %v", fullpath)
	}
	path, found := i.inode2path[inode]
	path.isChildrenCached = true
}

func (i *InodeToPath) IsChildrenCached(fullpath util.FullPath) bool {
	i.RLock()
	defer i.RUnlock()
	inode, found := i.path2inode[fullpath]
	if !found {
		return false
	}
	path, found := i.inode2path[inode]
	if found {
		return path.isChildrenCached
	}
	return false
}

func (i *InodeToPath) HasInode(inode uint64) bool {
	if inode == 1 {
		return true
	}
	i.RLock()
	defer i.RUnlock()
	_, found := i.inode2path[inode]
	return found
}

func (i *InodeToPath) RemovePath(path util.FullPath) {
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if found {
		delete(i.path2inode, path)
		delete(i.inode2path, inode)
	}
}

func (i *InodeToPath) MovePath(sourcePath, targetPath util.FullPath) (replacedInode uint64) {
	i.Lock()
	defer i.Unlock()
	sourceInode, sourceFound := i.path2inode[sourcePath]
	targetInode, targetFound := i.path2inode[targetPath]
	if sourceFound {
		delete(i.path2inode, sourcePath)
		i.path2inode[targetPath] = sourceInode
	} else {
		// it is possible some source folder items has not been visited before
		// so no need to worry about their source inodes
		return
	}
	i.inode2path[sourceInode].FullPath = targetPath
	if targetFound {
		delete(i.inode2path, targetInode)
	} else {
		i.inode2path[sourceInode].nlookup++
	}
	return targetInode
}

func (i *InodeToPath) Forget(inode, nlookup uint64, onForgetDir func(dir util.FullPath)) {
	i.Lock()
	path, found := i.inode2path[inode]
	if found {
		path.nlookup -= nlookup
		if path.nlookup <= 0 {
			delete(i.path2inode, path.FullPath)
			delete(i.inode2path, inode)
		}
	}
	i.Unlock()
	if found {
		if path.isDirectory && onForgetDir != nil {
			onForgetDir(path.FullPath)
		}
	}
}
<commit_msg>mount: mark directory uncached if forgotten<commit_after>package mount

import (
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/hanwen/go-fuse/v2/fuse"
	"os"
	"sync"
)

type InodeToPath struct {
	sync.RWMutex
	nextInodeId uint64
	inode2path  map[uint64]*InodeEntry
	path2inode  map[util.FullPath]uint64
}
type InodeEntry struct {
	util.FullPath
	nlookup          uint64
	isDirectory      bool
	isChildrenCached bool
}

func NewInodeToPath() *InodeToPath {
	t := &InodeToPath{
		inode2path: make(map[uint64]*InodeEntry),
		path2inode: make(map[util.FullPath]uint64),
	}
	t.inode2path[1] = &InodeEntry{"/", 1, true, false}
	t.path2inode["/"] = 1
	return t
}

func (i *InodeToPath) Lookup(path util.FullPath, mode os.FileMode, isHardlink bool, possibleInode uint64, isLookup bool) uint64 {
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if !found {
		if possibleInode == 0 {
			inode = path.AsInode(mode)
		} else {
			inode = possibleInode
		}
		if !isHardlink {
			for _, found := i.inode2path[inode]; found; inode++ {
				_, found = i.inode2path[inode]
			}
		}
	}
	i.path2inode[path] = inode

	if _, found := i.inode2path[inode]; found {
		if isLookup {
			i.inode2path[inode].nlookup++
		}
	} else {
		if !isLookup {
			i.inode2path[inode] = &InodeEntry{path, 0, mode&os.ModeDir > 0, false}
		} else {
			i.inode2path[inode] = &InodeEntry{path, 1, mode&os.ModeDir > 0, false}
		}
	}

	return inode
}

func (i *InodeToPath) AllocateInode(path util.FullPath, mode os.FileMode) uint64 {
	if path == "/" {
		return 1
	}
	i.Lock()
	defer i.Unlock()
	inode := path.AsInode(mode)
	for _, found := i.inode2path[inode]; found; inode++ {
		_, found = i.inode2path[inode]
	}
	return inode
}

func (i *InodeToPath) GetInode(path util.FullPath) uint64 {
	if path == "/" {
		return 1
	}
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if !found {
		// glog.Fatalf("GetInode unknown inode for %s", path)
		// this could be the parent for mount point
	}
	return inode
}

func (i *InodeToPath) GetPath(inode uint64) (util.FullPath, fuse.Status) {
	i.RLock()
	defer i.RUnlock()
	path, found := i.inode2path[inode]
	if !found {
		return "", fuse.ENOENT
	}
	return path.FullPath, fuse.OK
}

func (i *InodeToPath) HasPath(path util.FullPath) bool {
	i.RLock()
	defer i.RUnlock()
	_, found := i.path2inode[path]
	return found
}

func (i *InodeToPath) MarkChildrenCached(fullpath util.FullPath) {
	i.RLock()
	defer i.RUnlock()
	inode, found := i.path2inode[fullpath]
	if !found {
		glog.Fatalf("MarkChildrenCached not found inode %v", fullpath)
	}
	path, found := i.inode2path[inode]
	path.isChildrenCached = true
}

func (i *InodeToPath) IsChildrenCached(fullpath util.FullPath) bool {
	i.RLock()
	defer i.RUnlock()
	inode, found := i.path2inode[fullpath]
	if !found {
		return false
	}
	path, found := i.inode2path[inode]
	if found {
		return path.isChildrenCached
	}
	return false
}

func (i *InodeToPath) HasInode(inode uint64) bool {
	if inode == 1 {
		return true
	}
	i.RLock()
	defer i.RUnlock()
	_, found := i.inode2path[inode]
	return found
}

func (i *InodeToPath) RemovePath(path util.FullPath) {
	i.Lock()
	defer i.Unlock()
	inode, found := i.path2inode[path]
	if found {
		delete(i.path2inode, path)
		delete(i.inode2path, inode)
	}
}

func (i *InodeToPath) MovePath(sourcePath, targetPath util.FullPath) (replacedInode uint64) {
	i.Lock()
	defer i.Unlock()
	sourceInode, sourceFound := i.path2inode[sourcePath]
	targetInode, targetFound := i.path2inode[targetPath]
	if sourceFound {
		delete(i.path2inode, sourcePath)
		i.path2inode[targetPath] = sourceInode
	} else {
		// it is possible some source folder items has not been visited before
		// so no need to worry about their source inodes
		return
	}
	i.inode2path[sourceInode].FullPath = targetPath
	if targetFound {
		delete(i.inode2path, targetInode)
	} else {
		i.inode2path[sourceInode].nlookup++
	}
	return targetInode
}

func (i *InodeToPath) Forget(inode, nlookup uint64, onForgetDir func(dir util.FullPath)) {
	i.Lock()
	path, found := i.inode2path[inode]
	if found {
		path.nlookup -= nlookup
		if path.nlookup <= 0 {
			delete(i.path2inode, path.FullPath)
			delete(i.inode2path, inode)
		}
	}
	i.Unlock()
	if found {
		if path.isDirectory && path.nlookup <= 0 && onForgetDir != nil {
			path.isChildrenCached = false
			onForgetDir(path.FullPath)
		}
	}
}
<|endoftext|>


<commit_before>package virtualbox

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"log"
	"time"
)

// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
//   communicator packer.Communicator
//   config *config
//   driver Driver
//   ui     packer.Ui
//   vmName string
//
// Produces:
//   <nothing>
type stepShutdown struct{}

func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
	comm := state["communicator"].(packer.Communicator)
	config := state["config"].(*config)
	driver := state["driver"].(Driver)
	ui := state["ui"].(packer.Ui)
	vmName := state["vmName"].(string)

	if config.ShutdownCommand != "" {
		ui.Say("Gracefully halting virtual machine...")
		log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
		cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
		if err := comm.Start(cmd); err != nil {
			ui.Error(fmt.Sprintf("Failed to send shutdown command: %s", err))
			return multistep.ActionHalt
		}

		// Wait for the command to run
		cmd.Wait()

		// Wait for the machine to actually shut down
		log.Printf("Waiting max %s for shutdown to complete", config.ShutdownTimeout)
		shutdownTimer := time.After(config.ShutdownTimeout)
		for {
			running, _ := driver.IsRunning(vmName)
			if !running {
				break
			}

			select {
			case <-shutdownTimer:
				ui.Error("Timeout while waiting for machine to shut down.")
				return multistep.ActionHalt
			default:
				time.Sleep(1 * time.Second)
			}
		}
	} else {
		if err := driver.Stop(vmName); err != nil {
			ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
			return multistep.ActionHalt
		}
	}

	log.Println("VM shut down.")
	return multistep.ActionContinue
}

func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
<commit_msg>builder/virtualbox: say when forcefully shutting down<commit_after>package virtualbox

import (
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"log"
	"time"
)

// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
//   communicator packer.Communicator
//   config *config
//   driver Driver
//   ui     packer.Ui
//   vmName string
//
// Produces:
//   <nothing>
type stepShutdown struct{}

func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
	comm := state["communicator"].(packer.Communicator)
	config := state["config"].(*config)
	driver := state["driver"].(Driver)
	ui := state["ui"].(packer.Ui)
	vmName := state["vmName"].(string)

	if config.ShutdownCommand != "" {
		ui.Say("Gracefully halting virtual machine...")
		log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
		cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
		if err := comm.Start(cmd); err != nil {
			ui.Error(fmt.Sprintf("Failed to send shutdown command: %s", err))
			return multistep.ActionHalt
		}

		// Wait for the command to run
		cmd.Wait()

		// Wait for the machine to actually shut down
		log.Printf("Waiting max %s for shutdown to complete", config.ShutdownTimeout)
		shutdownTimer := time.After(config.ShutdownTimeout)
		for {
			running, _ := driver.IsRunning(vmName)
			if !running {
				break
			}

			select {
			case <-shutdownTimer:
				ui.Error("Timeout while waiting for machine to shut down.")
				return multistep.ActionHalt
			default:
				time.Sleep(1 * time.Second)
			}
		}
	} else {
		ui.Say("Halting the virtual machine...")
		if err := driver.Stop(vmName); err != nil {
			ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
			return multistep.ActionHalt
		}
	}

	log.Println("VM shut down.")
	return multistep.ActionContinue
}

func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
<|endoftext|>


<commit_before>package vmware

import (
	"errors"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"io/ioutil"
	"log"
	"os"
	"time"
)

// This step waits for SSH to become available and establishes an SSH
// connection.
//
// Uses:
//   config *config
//   ui     packer.Ui
//   vmx_path string
//
// Produces:
//   <nothing>
type stepWaitForSSH struct{}

func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
	ui := state["ui"].(packer.Ui)
	vmxPath := state["vmx_path"].(string)

	ui.Say("Waiting for SSH to become available...")
	for {
		time.Sleep(5 * time.Second)

		log.Println("Lookup up IP information...")
		// First we wait for the IP to become available...
		ipLookup, err := s.dhcpLeaseLookup(vmxPath)
		if err != nil {
			log.Printf("Can't lookup via DHCP lease: %s", err)
		}

		ip, err := ipLookup.GuestIP()
		if err != nil {
			log.Printf("IP lookup failed: %s", err)
			continue
		}

		log.Printf("Detected IP: %s", ip)
	}

	return multistep.ActionContinue
}

func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {}

// Reads the network information for lookup via DHCP.
func (s *stepWaitForSSH) dhcpLeaseLookup(vmxPath string) (GuestIPFinder, error) {
	f, err := os.Open(vmxPath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	vmxBytes, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}

	vmxData := ParseVMX(string(vmxBytes))

	var ok bool
	macAddress := ""
	if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
		if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
			return nil, errors.New("couldn't find MAC address in VMX")
		}
	}

	return &DHCPLeaseGuestLookup{"vmnet8", macAddress}, nil
}
<commit_msg>builder/vmware: Connect to SSH<commit_after>package vmware

import (
	gossh "code.google.com/p/go.crypto/ssh"
	"errors"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/communicator/ssh"
	"github.com/mitchellh/packer/packer"
	"io/ioutil"
	"log"
	"net"
	"os"
	"time"
)

// This step waits for SSH to become available and establishes an SSH
// connection.
//
// Uses:
//   config *config
//   ui     packer.Ui
//   vmx_path string
//
// Produces:
//   communicator packer.Communicator
type stepWaitForSSH struct{}

func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
	config := state["config"].(*config)
	ui := state["ui"].(packer.Ui)
	vmxPath := state["vmx_path"].(string)

	ui.Say("Waiting for SSH to become available...")
	var comm packer.Communicator
	for {
		time.Sleep(5 * time.Second)

		// First we wait for the IP to become available...
		log.Println("Lookup up IP information...")
		ipLookup, err := s.dhcpLeaseLookup(vmxPath)
		if err != nil {
			log.Printf("Can't lookup via DHCP lease: %s", err)
		}

		ip, err := ipLookup.GuestIP()
		if err != nil {
			log.Printf("IP lookup failed: %s", err)
			continue
		}

		log.Printf("Detected IP: %s", ip)

		// Attempt to connect to SSH port
		nc, err := net.Dial("tcp", fmt.Sprintf("%s:22", ip))
		if err != nil {
			log.Printf("TCP connection to SSH ip/port failed: %s", err)
			continue
		}

		// Then we attempt to connect via SSH
		sshConfig := &gossh.ClientConfig{
			User: config.SSHUser,
			Auth: []gossh.ClientAuth{
				gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
			},
		}

		comm, err = ssh.New(nc, sshConfig)
		if err != nil {
			ui.Error(fmt.Sprintf("Error connecting via SSH: %s", err))
			return multistep.ActionHalt
		}

		ui.Say("Connected via SSH!")
		break
	}

	state["communicator"] = comm

	return multistep.ActionContinue
}

func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {}

// Reads the network information for lookup via DHCP.
func (s *stepWaitForSSH) dhcpLeaseLookup(vmxPath string) (GuestIPFinder, error) {
	f, err := os.Open(vmxPath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	vmxBytes, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}

	vmxData := ParseVMX(string(vmxBytes))

	var ok bool
	macAddress := ""
	if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
		if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
			return nil, errors.New("couldn't find MAC address in VMX")
		}
	}

	return &DHCPLeaseGuestLookup{"vmnet8", macAddress}, nil
}
<|endoftext|>


<commit_before>package gb

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

func testProject(t *testing.T) *Project {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	root := filepath.Join(cwd, "testdata")
	return &Project{
		rootdir: root,
	}
}

func testContext(t *testing.T) *Context {
	prj := testProject(t)
	tc, err := NewGcToolchain(runtime.GOROOT(), runtime.GOOS, runtime.GOARCH)
	if err != nil {
		t.Fatal(err)
	}
	ctx := prj.NewContext(tc)
	ctx.Force = true
	ctx.SkipInstall = true
	return ctx
}

func TestResolvePackage(t *testing.T) {
	ctx := testContext(t)
	_, err := ctx.ResolvePackage("a")
	if err != nil {
		t.Fatal(err)
	}
}

func TestPackageName(t *testing.T) {
	ctx := testContext(t)
	pkg, err := ctx.ResolvePackage("aprime")
	if err != nil {
		t.Fatal(err)
	}
	if got, want := "a", pkg.Name; got != want {
		t.Fatalf("Package.Name(): got %v, want %v", got, want)
	}
}
<commit_msg>fix tests<commit_after>package gb

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

func testProject(t *testing.T) *Project {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	root := filepath.Join(cwd, "testdata")
	return &Project{
		rootdir: root,
	}
}

func testContext(t *testing.T) *Context {
	prj := testProject(t)
	tc, err := NewGcToolchain(runtime.GOROOT())
	if err != nil {
		t.Fatal(err)
	}
	ctx := prj.NewContext(tc)
	ctx.Force = true
	ctx.SkipInstall = true
	return ctx
}

func TestResolvePackage(t *testing.T) {
	ctx := testContext(t)
	_, err := ctx.ResolvePackage("a")
	if err != nil {
		t.Fatal(err)
	}
}

func TestPackageName(t *testing.T) {
	ctx := testContext(t)
	pkg, err := ctx.ResolvePackage("aprime")
	if err != nil {
		t.Fatal(err)
	}
	if got, want := "a", pkg.Name; got != want {
		t.Fatalf("Package.Name(): got %v, want %v", got, want)
	}
}
<|endoftext|>


<commit_before>package gin

import (
	"bufio"
	"bytes"
	"fmt"
	"net"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestUnixSocket(t *testing.T) {
	buffer := new(bytes.Buffer)
	router := New()
	go func() {
		router.Use(LoggerWithWriter(buffer))
		router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
		router.RunUnix("/tmp/unix_unit_test")
	}()
	// have to wait for the goroutine to start and run the server
	// otherwise the main thread will complete
	time.Sleep(5 * time.Millisecond)

	c, err := net.Dial("unix", "/tmp/unix_unit_test")
	if err != nil {
		println(err)
		t.FailNow()
	}
	fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
	scanner := bufio.NewScanner(c)
	var response string
	for scanner.Scan() {
		response += scanner.Text()
	}
	assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
	assert.Contains(t, response, "it worked", "resp body should match")
}
<commit_msg>test standard router<commit_after>package gin

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestRun(t *testing.T) {
	buffer := new(bytes.Buffer)
	router := New()
	go func() {
		router.Use(LoggerWithWriter(buffer))
		router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
		router.Run(":5150")
	}()
	// have to wait for the goroutine to start and run the server
	// otherwise the main thread will complete
	time.Sleep(5 * time.Millisecond)

	resp, err := http.Get("http://localhost:5150/example")
	if err != nil {
		t.FailNow()
	}
	defer resp.Body.Close()
	body, ioerr := ioutil.ReadAll(resp.Body)
	if ioerr != nil {
		t.FailNow()
	}
	assert.Equal(t, "it worked", body, "resp body should match")
	assert.Equal(t, "200 OK", resp.Status, "should get a 200")
}

func TestUnixSocket(t *testing.T) {
	buffer := new(bytes.Buffer)
	router := New()
	go func() {
		router.Use(LoggerWithWriter(buffer))
		router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
		router.RunUnix("/tmp/unix_unit_test")
	}()
	// have to wait for the goroutine to start and run the server
	// otherwise the main thread will complete
	time.Sleep(5 * time.Millisecond)

	c, err := net.Dial("unix", "/tmp/unix_unit_test")
	if err != nil {
		println(err)
		t.FailNow()
	}
	fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
	scanner := bufio.NewScanner(c)
	var response string
	for scanner.Scan() {
		response += scanner.Text()
	}
	assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
	assert.Contains(t, response, "it worked", "resp body should match")
}
<|endoftext|>


<commit_before>package templates

import "strings"

func MainHelpTemplate() string {
	return decorate(mainHelpTemplate, false)
}

func MainUsageTemplate() string {
	return decorate(mainUsageTemplate, true)
}

func OptionsHelpTemplate() string {
	return decorate(optionsHelpTemplate, false)
}

func OptionsUsageTemplate() string {
	return decorate(optionsUsageTemplate, false)
}

func decorate(template string, trim bool) string {
	if trim && len(strings.Trim(template, " ")) > 0 {
		template = strings.Trim(template, "\n")
	}
	return template
}

const (
	vars = `{{$isRootCmd := isRootCmd .}}` +
		`{{$rootCmd := rootCmd .}}` +
		`{{$explicitlyExposedFlags := exposed .}}` +
		`{{$localNotPersistentFlags := flagsNotIntersected .LocalFlags .PersistentFlags}}`

	mainHelpTemplate = `{{.Long | trim}}
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

	mainUsageTemplate = vars + `{{ $cmd := . }}{{ if .HasRunnableSubCommands}}
Available Commands: {{range .Commands}}{{if and .Runnable (ne .Name "options")}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
{{end}}
{{ if or $localNotPersistentFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options:
{{ if $localNotPersistentFlags.HasFlags}}{{flagsUsages $localNotPersistentFlags}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{flagsUsages $explicitlyExposedFlags}}{{end}}
{{end}}{{ if not $isRootCmd}}Use "{{$rootCmd}} --help" for a list of all commands available in {{$rootCmd}}.
{{end}}{{ if .HasSubCommands }}Use "{{$rootCmd}} <command> --help" for more information about a given command.
{{end}}{{ if and .HasInheritedFlags (not $isRootCmd)}}Use "{{$rootCmd}} options" for a list of global command-line options (applies to all commands).
{{end}}`

	optionsHelpTemplate = ``

	optionsUsageTemplate = `{{ if .HasInheritedFlags}}The following options can be passed to any command:

{{flagsUsages .InheritedFlags}}{{end}}`
)
<commit_msg>Restores usage to cli templates<commit_after>package templates

import "strings"

func MainHelpTemplate() string {
	return decorate(mainHelpTemplate, false)
}

func MainUsageTemplate() string {
	return decorate(mainUsageTemplate, true) + "\n"
}

func OptionsHelpTemplate() string {
	return decorate(optionsHelpTemplate, false)
}

func OptionsUsageTemplate() string {
	return decorate(optionsUsageTemplate, false)
}

func decorate(template string, trim bool) string {
	if trim && len(strings.Trim(template, " ")) > 0 {
		template = strings.Trim(template, "\n")
	}
	return template
}

const (
	vars = `{{$isRootCmd := isRootCmd .}}` +
		`{{$rootCmd := rootCmd .}}` +
		`{{$explicitlyExposedFlags := exposed .}}` +
		`{{$localNotPersistentFlags := flagsNotIntersected .LocalFlags .PersistentFlags}}`

	mainHelpTemplate = `{{.Long | trim}}
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

	mainUsageTemplate = vars + `{{ $cmd := . }}{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}
Usage:
  {{.UseLine}}{{if .HasFlags}} [options]{{end}}{{ if .HasRunnableSubCommands}}
{{end}}{{end}}{{ if .HasRunnableSubCommands}}
Available Commands: {{range .Commands}}{{if and .Runnable (ne .Name "options")}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}
{{ if or $localNotPersistentFlags.HasFlags $explicitlyExposedFlags.HasFlags}}
Options:
{{ if $localNotPersistentFlags.HasFlags}}{{flagsUsages $localNotPersistentFlags}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{flagsUsages $explicitlyExposedFlags}}{{end}}{{end}}{{ if not $isRootCmd}}
Use "{{$rootCmd}} --help" for a list of all commands available in {{$rootCmd}}.{{end}}{{ if .HasSubCommands }}
Use "{{$rootCmd}} <command> --help" for more information about a given command.{{end}}{{ if and .HasInheritedFlags (not $isRootCmd)}}
Use "{{$rootCmd}} options" for a list of global command-line options (applies to all commands).{{end}}`

	optionsHelpTemplate = ``

	optionsUsageTemplate = `{{ if .HasInheritedFlags}}The following options can be passed to any command:

{{flagsUsages .InheritedFlags}}{{end}}`
)
<|endoftext|>


<commit_before>package compression

import (
	"bytes"
	"compress/bzip2"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/containers/image/v5/pkg/compression/internal"
	"github.com/containers/image/v5/pkg/compression/types"
	"github.com/containers/storage/pkg/chunked"
	"github.com/klauspost/pgzip"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/ulikunitz/xz"
)

// Algorithm is a compression algorithm that can be used for CompressStream.
type Algorithm = types.Algorithm

var (
	// Gzip compression.
	Gzip = internal.NewAlgorithm(types.GzipAlgorithmName, types.GzipAlgorithmName,
		[]byte{0x1F, 0x8B, 0x08}, GzipDecompressor, gzipCompressor)
	// Bzip2 compression.
	Bzip2 = internal.NewAlgorithm(types.Bzip2AlgorithmName, types.Bzip2AlgorithmName,
		[]byte{0x42, 0x5A, 0x68}, Bzip2Decompressor, bzip2Compressor)
	// Xz compression.
	Xz = internal.NewAlgorithm(types.XzAlgorithmName, types.XzAlgorithmName,
		[]byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, XzDecompressor, xzCompressor)
	// Zstd compression.
	Zstd = internal.NewAlgorithm(types.ZstdAlgorithmName, types.ZstdAlgorithmName,
		[]byte{0x28, 0xb5, 0x2f, 0xfd}, ZstdDecompressor, zstdCompressor)
	// Zstd:chunked compression.
	ZstdChunked = internal.NewAlgorithm(types.ZstdChunkedAlgorithmName, types.ZstdAlgorithmName, /* Note: InternalUnstableUndocumentedMIMEQuestionMark is not ZstdChunkedAlgorithmName */
		nil, ZstdDecompressor, chunked.ZstdCompressor)

	compressionAlgorithms = map[string]Algorithm{
		Gzip.Name():        Gzip,
		Bzip2.Name():       Bzip2,
		Xz.Name():          Xz,
		Zstd.Name():        Zstd,
		ZstdChunked.Name(): ZstdChunked,
	}
)

// AlgorithmByName returns the compressor by its name
func AlgorithmByName(name string) (Algorithm, error) {
	algorithm, ok := compressionAlgorithms[name]
	if ok {
		return algorithm, nil
	}
	return Algorithm{}, fmt.Errorf("cannot find compressor for %q", name)
}

// DecompressorFunc returns the decompressed stream, given a compressed stream.
// The caller must call Close() on the decompressed stream (even if the compressed input stream does not need closing!).
type DecompressorFunc = internal.DecompressorFunc

// GzipDecompressor is a DecompressorFunc for the gzip compression algorithm.
func GzipDecompressor(r io.Reader) (io.ReadCloser, error) {
	return pgzip.NewReader(r)
}

// Bzip2Decompressor is a DecompressorFunc for the bzip2 compression algorithm.
func Bzip2Decompressor(r io.Reader) (io.ReadCloser, error) {
	return ioutil.NopCloser(bzip2.NewReader(r)), nil
}

// XzDecompressor is a DecompressorFunc for the xz compression algorithm.
func XzDecompressor(r io.Reader) (io.ReadCloser, error) {
	r, err := xz.NewReader(r)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(r), nil
}

// gzipCompressor is a CompressorFunc for the gzip compression algorithm.
func gzipCompressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	if level != nil {
		return pgzip.NewWriterLevel(r, *level)
	}
	return pgzip.NewWriter(r), nil
}

// bzip2Compressor is a CompressorFunc for the bzip2 compression algorithm.
func bzip2Compressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	return nil, fmt.Errorf("bzip2 compression not supported")
}

// xzCompressor is a CompressorFunc for the xz compression algorithm.
func xzCompressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	return xz.NewWriter(r)
}

// CompressStream returns the compressor by its name
func CompressStream(dest io.Writer, algo Algorithm, level *int) (io.WriteCloser, error) {
	m := map[string]string{}
	return internal.AlgorithmCompressor(algo)(dest, m, level)
}

// CompressStreamWithMetadata returns the compressor by its name.  If the compression
// generates any metadata, it is written to the provided metadata map.
func CompressStreamWithMetadata(dest io.Writer, metadata map[string]string, algo Algorithm, level *int) (io.WriteCloser, error) {
	return internal.AlgorithmCompressor(algo)(dest, metadata, level)
}

// DetectCompressionFormat returns an Algorithm and DecompressorFunc if the input is recognized as a compressed format, an invalid
// value and nil otherwise.
// Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning.
func DetectCompressionFormat(input io.Reader) (Algorithm, DecompressorFunc, io.Reader, error) {
	buffer := [8]byte{}

	n, err := io.ReadAtLeast(input, buffer[:], len(buffer))
	if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
		// This is a “real” error. We could just ignore it this time, process the data we have, and hope that the source will report the same error again.
		// Instead, fail immediately with the original error cause instead of a possibly secondary/misleading error returned later.
		return Algorithm{}, nil, nil, err
	}

	var retAlgo Algorithm
	var decompressor DecompressorFunc
	for _, algo := range compressionAlgorithms {
		prefix := internal.AlgorithmPrefix(algo)
		if len(prefix) > 0 && bytes.HasPrefix(buffer[:n], prefix) {
			logrus.Debugf("Detected compression format %s", algo.Name())
			retAlgo = algo
			decompressor = internal.AlgorithmDecompressor(algo)
			break
		}
	}
	if decompressor == nil {
		logrus.Debugf("No compression detected")
	}

	return retAlgo, decompressor, io.MultiReader(bytes.NewReader(buffer[:n]), input), nil
}

// DetectCompression returns a DecompressorFunc if the input is recognized as a compressed format, nil otherwise.
// Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning.
func DetectCompression(input io.Reader) (DecompressorFunc, io.Reader, error) {
	_, d, r, e := DetectCompressionFormat(input)
	return d, r, e
}

// AutoDecompress takes a stream and returns an uncompressed version of the
// same stream.
// The caller must call Close() on the returned stream (even if the input does not need,
// or does not even support, closing!).
func AutoDecompress(stream io.Reader) (io.ReadCloser, bool, error) {
	decompressor, stream, err := DetectCompression(stream)
	if err != nil {
		return nil, false, errors.Wrapf(err, "detecting compression")
	}
	var res io.ReadCloser
	if decompressor != nil {
		res, err = decompressor(stream)
		if err != nil {
			return nil, false, errors.Wrapf(err, "initializing decompression")
		}
	} else {
		res = ioutil.NopCloser(stream)
	}
	return res, decompressor != nil, nil
}
<commit_msg>Reduce the dependency of pkg/compression on c/storage/pkg/chunked<commit_after>package compression

import (
	"bytes"
	"compress/bzip2"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/containers/image/v5/pkg/compression/internal"
	"github.com/containers/image/v5/pkg/compression/types"
	"github.com/containers/storage/pkg/chunked/compressor"
	"github.com/klauspost/pgzip"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/ulikunitz/xz"
)

// Algorithm is a compression algorithm that can be used for CompressStream.
type Algorithm = types.Algorithm

var (
	// Gzip compression.
	Gzip = internal.NewAlgorithm(types.GzipAlgorithmName, types.GzipAlgorithmName,
		[]byte{0x1F, 0x8B, 0x08}, GzipDecompressor, gzipCompressor)
	// Bzip2 compression.
	Bzip2 = internal.NewAlgorithm(types.Bzip2AlgorithmName, types.Bzip2AlgorithmName,
		[]byte{0x42, 0x5A, 0x68}, Bzip2Decompressor, bzip2Compressor)
	// Xz compression.
	Xz = internal.NewAlgorithm(types.XzAlgorithmName, types.XzAlgorithmName,
		[]byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, XzDecompressor, xzCompressor)
	// Zstd compression.
	Zstd = internal.NewAlgorithm(types.ZstdAlgorithmName, types.ZstdAlgorithmName,
		[]byte{0x28, 0xb5, 0x2f, 0xfd}, ZstdDecompressor, zstdCompressor)
	// Zstd:chunked compression.
	ZstdChunked = internal.NewAlgorithm(types.ZstdChunkedAlgorithmName, types.ZstdAlgorithmName, /* Note: InternalUnstableUndocumentedMIMEQuestionMark is not ZstdChunkedAlgorithmName */
		nil, ZstdDecompressor, compressor.ZstdCompressor)

	compressionAlgorithms = map[string]Algorithm{
		Gzip.Name():        Gzip,
		Bzip2.Name():       Bzip2,
		Xz.Name():          Xz,
		Zstd.Name():        Zstd,
		ZstdChunked.Name(): ZstdChunked,
	}
)

// AlgorithmByName returns the compressor by its name
func AlgorithmByName(name string) (Algorithm, error) {
	algorithm, ok := compressionAlgorithms[name]
	if ok {
		return algorithm, nil
	}
	return Algorithm{}, fmt.Errorf("cannot find compressor for %q", name)
}

// DecompressorFunc returns the decompressed stream, given a compressed stream.
// The caller must call Close() on the decompressed stream (even if the compressed input stream does not need closing!).
type DecompressorFunc = internal.DecompressorFunc

// GzipDecompressor is a DecompressorFunc for the gzip compression algorithm.
func GzipDecompressor(r io.Reader) (io.ReadCloser, error) {
	return pgzip.NewReader(r)
}

// Bzip2Decompressor is a DecompressorFunc for the bzip2 compression algorithm.
func Bzip2Decompressor(r io.Reader) (io.ReadCloser, error) {
	return ioutil.NopCloser(bzip2.NewReader(r)), nil
}

// XzDecompressor is a DecompressorFunc for the xz compression algorithm.
func XzDecompressor(r io.Reader) (io.ReadCloser, error) {
	r, err := xz.NewReader(r)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(r), nil
}

// gzipCompressor is a CompressorFunc for the gzip compression algorithm.
func gzipCompressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	if level != nil {
		return pgzip.NewWriterLevel(r, *level)
	}
	return pgzip.NewWriter(r), nil
}

// bzip2Compressor is a CompressorFunc for the bzip2 compression algorithm.
func bzip2Compressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	return nil, fmt.Errorf("bzip2 compression not supported")
}

// xzCompressor is a CompressorFunc for the xz compression algorithm.
func xzCompressor(r io.Writer, metadata map[string]string, level *int) (io.WriteCloser, error) {
	return xz.NewWriter(r)
}

// CompressStream returns the compressor by its name
func CompressStream(dest io.Writer, algo Algorithm, level *int) (io.WriteCloser, error) {
	m := map[string]string{}
	return internal.AlgorithmCompressor(algo)(dest, m, level)
}

// CompressStreamWithMetadata returns the compressor by its name.  If the compression
// generates any metadata, it is written to the provided metadata map.
func CompressStreamWithMetadata(dest io.Writer, metadata map[string]string, algo Algorithm, level *int) (io.WriteCloser, error) {
	return internal.AlgorithmCompressor(algo)(dest, metadata, level)
}

// DetectCompressionFormat returns an Algorithm and DecompressorFunc if the input is recognized as a compressed format, an invalid
// value and nil otherwise.
// Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning.
func DetectCompressionFormat(input io.Reader) (Algorithm, DecompressorFunc, io.Reader, error) {
	buffer := [8]byte{}

	n, err := io.ReadAtLeast(input, buffer[:], len(buffer))
	if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
		// This is a “real” error. We could just ignore it this time, process the data we have, and hope that the source will report the same error again.
		// Instead, fail immediately with the original error cause instead of a possibly secondary/misleading error returned later.
		return Algorithm{}, nil, nil, err
	}

	var retAlgo Algorithm
	var decompressor DecompressorFunc
	for _, algo := range compressionAlgorithms {
		prefix := internal.AlgorithmPrefix(algo)
		if len(prefix) > 0 && bytes.HasPrefix(buffer[:n], prefix) {
			logrus.Debugf("Detected compression format %s", algo.Name())
			retAlgo = algo
			decompressor = internal.AlgorithmDecompressor(algo)
			break
		}
	}
	if decompressor == nil {
		logrus.Debugf("No compression detected")
	}

	return retAlgo, decompressor, io.MultiReader(bytes.NewReader(buffer[:n]), input), nil
}

// DetectCompression returns a DecompressorFunc if the input is recognized as a compressed format, nil otherwise.
// Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning.
func DetectCompression(input io.Reader) (DecompressorFunc, io.Reader, error) {
	_, d, r, e := DetectCompressionFormat(input)
	return d, r, e
}

// AutoDecompress takes a stream and returns an uncompressed version of the
// same stream.
// The caller must call Close() on the returned stream (even if the input does not need,
// or does not even support, closing!).
func AutoDecompress(stream io.Reader) (io.ReadCloser, bool, error) {
	decompressor, stream, err := DetectCompression(stream)
	if err != nil {
		return nil, false, errors.Wrapf(err, "detecting compression")
	}
	var res io.ReadCloser
	if decompressor != nil {
		res, err = decompressor(stream)
		if err != nil {
			return nil, false, errors.Wrapf(err, "initializing decompression")
		}
	} else {
		res = ioutil.NopCloser(stream)
	}
	return res, decompressor != nil, nil
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 downloader

import (
	"bytes"
	"path/filepath"
	"reflect"
	"testing"

	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/getter"
	"helm.sh/helm/v3/pkg/repo/repotest"
)

func TestVersionEquals(t *testing.T) {
	tests := []struct {
		name, v1, v2 string
		expect       bool
	}{
		{name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true},
		{name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true},
		{name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true},
		{name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false},
		{name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false},
	}

	for _, tt := range tests {
		if versionEquals(tt.v1, tt.v2) != tt.expect {
			t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect)
		}
	}
}

func TestNormalizeURL(t *testing.T) {
	tests := []struct {
		name, base, path, expect string
	}{
		{name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
		{name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
	}

	for _, tt := range tests {
		got, err := normalizeURL(tt.base, tt.path)
		if err != nil {
			t.Errorf("%s: error %s", tt.name, err)
			continue
		} else if got != tt.expect {
			t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
		}
	}
}

func TestFindChartURL(t *testing.T) {
	var b bytes.Buffer
	m := &Manager{
		Out:              &b,
		RepositoryConfig: repoConfig,
		RepositoryCache:  repoCache,
	}
	repos, err := m.loadChartRepositories()
	if err != nil {
		t.Fatal(err)
	}

	name := "alpine"
	version := "0.1.0"
	repoURL := "http://example.com/charts"

	churl, username, password, err := m.findChartURL(name, version, repoURL, repos)
	if err != nil {
		t.Fatal(err)
	}
	if churl != "https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz" {
		t.Errorf("Unexpected URL %q", churl)
	}
	if username != "" {
		t.Errorf("Unexpected username %q", username)
	}
	if password != "" {
		t.Errorf("Unexpected password %q", password)
	}
}

func TestGetRepoNames(t *testing.T) {
	b := bytes.NewBuffer(nil)
	m := &Manager{
		Out:              b,
		RepositoryConfig: repoConfig,
		RepositoryCache:  repoCache,
	}
	tests := []struct {
		name   string
		req    []*chart.Dependency
		expect map[string]string
		err    bool
	}{
		{
			name: "no repo definition, but references a url",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "http://example.com/test"},
			},
			expect: map[string]string{"http://example.com/test": "http://example.com/test"},
		},
		{
			name: "no repo definition failure -- stable repo",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "stable"},
			},
			err: true,
		},
		{
			name: "no repo definition failure",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "http://example.com"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo from local path",
			req: []*chart.Dependency{
				{Name: "local-dep", Repository: "file://./testdata/signtest"},
			},
			expect: map[string]string{"local-dep": "file://./testdata/signtest"},
		},
		{
			name: "repo alias (alias:)",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "alias:testing"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo alias (@)",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "@testing"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo from local chart under charts path",
			req: []*chart.Dependency{
				{Name: "local-subchart", Repository: ""},
			},
			expect: map[string]string{},
		},
	}

	for _, tt := range tests {
		l, err := m.getRepoNames(tt.req)
		if err != nil {
			if tt.err {
				continue
			}
			t.Fatal(err)
		}

		if tt.err {
			t.Fatalf("Expected error in test %q", tt.name)
		}

		// m1 and m2 are the maps we want to compare
		eq := reflect.DeepEqual(l, tt.expect)
		if !eq {
			t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name)
		}
	}
}

// This function is the skeleton test code of failing tests for #6416 and bugs due to #5874.
// This function is used by below tests that ensures success of build operation
// with optional fields, alias, condition, tags, and even with ranged version.
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
	// Set up a fake repo
	srv, err := repotest.NewTempServer("testdata/*.tgz*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Stop()
	if err := srv.LinkIndices(); err != nil {
		t.Fatal(err)
	}
	dir := func(p ...string) string {
		return filepath.Join(append([]string{srv.Root()}, p...)...)
	}

	// Set main fields if not exist
	if dep.Name == "" {
		dep.Name = "local-subchart"
	}
	if dep.Version == "" {
		dep.Version = "0.1.0"
	}
	if dep.Repository == "" {
		dep.Repository = srv.URL()
	}

	// Save a chart
	c := &chart.Chart{
		Metadata: &chart.Metadata{
			Name:         chartName,
			Version:      "0.1.0",
			APIVersion:   "v1",
			Dependencies: []*chart.Dependency{&dep},
		},
	}
	if err := chartutil.SaveDir(c, dir()); err != nil {
		t.Fatal(err)
	}

	// Set-up a manager
	b := bytes.NewBuffer(nil)
	g := getter.Providers{getter.Provider{
		Schemes: []string{"http", "https"},
		New:     getter.NewHTTPGetter,
	}}
	m := &Manager{
		ChartPath:        dir(chartName),
		Out:              b,
		Getters:          g,
		RepositoryConfig: dir("repositories.yaml"),
		RepositoryCache:  dir(),
	}

	// First build will update dependencies and create Chart.lock file.
	err = m.Build()
	if err != nil {
		t.Fatal(err)
	}

	// Second build should be passed. See PR #6655.
	err = m.Build()
	if err != nil {
		t.Fatal(err)
	}
}

func TestBuild_WithoutOptionalFields(t *testing.T) {
	// Dependency has main fields only (name/version/repository)
	checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{})
}

func TestBuild_WithSemVerRange(t *testing.T) {
	// Dependency version is the form of SemVer range
	checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{
		Version: ">=0.1.0",
	})
}

func TestBuild_WithAlias(t *testing.T) {
	// Dependency has an alias
	checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{
		Alias: "local-subchart-alias",
	})
}

func TestBuild_WithCondition(t *testing.T) {
	// Dependency has a condition
	checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{
		Condition: "some.condition",
	})
}

func TestBuild_WithTags(t *testing.T) {
	// Dependency has several tags
	checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{
		Tags: []string{"tag1", "tag2"},
	})
}
<commit_msg>fix(pkg/downloader): add failing test for build with repo alias<commit_after>/*
Copyright The Helm 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 downloader

import (
	"bytes"
	"path/filepath"
	"reflect"
	"testing"

	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/getter"
	"helm.sh/helm/v3/pkg/repo/repotest"
)

func TestVersionEquals(t *testing.T) {
	tests := []struct {
		name, v1, v2 string
		expect       bool
	}{
		{name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true},
		{name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true},
		{name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true},
		{name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false},
		{name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false},
	}

	for _, tt := range tests {
		if versionEquals(tt.v1, tt.v2) != tt.expect {
			t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect)
		}
	}
}

func TestNormalizeURL(t *testing.T) {
	tests := []struct {
		name, base, path, expect string
	}{
		{name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
		{name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
	}

	for _, tt := range tests {
		got, err := normalizeURL(tt.base, tt.path)
		if err != nil {
			t.Errorf("%s: error %s", tt.name, err)
			continue
		} else if got != tt.expect {
			t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
		}
	}
}

func TestFindChartURL(t *testing.T) {
	var b bytes.Buffer
	m := &Manager{
		Out:              &b,
		RepositoryConfig: repoConfig,
		RepositoryCache:  repoCache,
	}
	repos, err := m.loadChartRepositories()
	if err != nil {
		t.Fatal(err)
	}

	name := "alpine"
	version := "0.1.0"
	repoURL := "http://example.com/charts"

	churl, username, password, err := m.findChartURL(name, version, repoURL, repos)
	if err != nil {
		t.Fatal(err)
	}
	if churl != "https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz" {
		t.Errorf("Unexpected URL %q", churl)
	}
	if username != "" {
		t.Errorf("Unexpected username %q", username)
	}
	if password != "" {
		t.Errorf("Unexpected password %q", password)
	}
}

func TestGetRepoNames(t *testing.T) {
	b := bytes.NewBuffer(nil)
	m := &Manager{
		Out:              b,
		RepositoryConfig: repoConfig,
		RepositoryCache:  repoCache,
	}
	tests := []struct {
		name   string
		req    []*chart.Dependency
		expect map[string]string
		err    bool
	}{
		{
			name: "no repo definition, but references a url",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "http://example.com/test"},
			},
			expect: map[string]string{"http://example.com/test": "http://example.com/test"},
		},
		{
			name: "no repo definition failure -- stable repo",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "stable"},
			},
			err: true,
		},
		{
			name: "no repo definition failure",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "http://example.com"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo from local path",
			req: []*chart.Dependency{
				{Name: "local-dep", Repository: "file://./testdata/signtest"},
			},
			expect: map[string]string{"local-dep": "file://./testdata/signtest"},
		},
		{
			name: "repo alias (alias:)",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "alias:testing"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo alias (@)",
			req: []*chart.Dependency{
				{Name: "oedipus-rex", Repository: "@testing"},
			},
			expect: map[string]string{"oedipus-rex": "testing"},
		},
		{
			name: "repo from local chart under charts path",
			req: []*chart.Dependency{
				{Name: "local-subchart", Repository: ""},
			},
			expect: map[string]string{},
		},
	}

	for _, tt := range tests {
		l, err := m.getRepoNames(tt.req)
		if err != nil {
			if tt.err {
				continue
			}
			t.Fatal(err)
		}

		if tt.err {
			t.Fatalf("Expected error in test %q", tt.name)
		}

		// m1 and m2 are the maps we want to compare
		eq := reflect.DeepEqual(l, tt.expect)
		if !eq {
			t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name)
		}
	}
}

// This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874.
//
// This function is used by below tests that ensures success of build operation
// with optional fields, alias, condition, tags, and even with ranged version.
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
	// Set up a fake repo
	srv, err := repotest.NewTempServer("testdata/*.tgz*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Stop()
	if err := srv.LinkIndices(); err != nil {
		t.Fatal(err)
	}
	dir := func(p ...string) string {
		return filepath.Join(append([]string{srv.Root()}, p...)...)
	}

	// Set main fields if not exist
	if dep.Name == "" {
		dep.Name = "local-subchart"
	}
	if dep.Version == "" {
		dep.Version = "0.1.0"
	}
	if dep.Repository == "" {
		dep.Repository = srv.URL()
	}

	// Save a chart
	c := &chart.Chart{
		Metadata: &chart.Metadata{
			Name:         chartName,
			Version:      "0.1.0",
			APIVersion:   "v1",
			Dependencies: []*chart.Dependency{&dep},
		},
	}
	if err := chartutil.SaveDir(c, dir()); err != nil {
		t.Fatal(err)
	}

	// Set-up a manager
	b := bytes.NewBuffer(nil)
	g := getter.Providers{getter.Provider{
		Schemes: []string{"http", "https"},
		New:     getter.NewHTTPGetter,
	}}
	m := &Manager{
		ChartPath:        dir(chartName),
		Out:              b,
		Getters:          g,
		RepositoryConfig: dir("repositories.yaml"),
		RepositoryCache:  dir(),
	}

	// First build will update dependencies and create Chart.lock file.
	err = m.Build()
	if err != nil {
		t.Fatal(err)
	}

	// Second build should be passed. See PR #6655.
	err = m.Build()
	if err != nil {
		t.Fatal(err)
	}
}

func TestBuild_WithoutOptionalFields(t *testing.T) {
	// Dependency has main fields only (name/version/repository)
	checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{})
}

func TestBuild_WithSemVerRange(t *testing.T) {
	// Dependency version is the form of SemVer range
	checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{
		Version: ">=0.1.0",
	})
}

func TestBuild_WithAlias(t *testing.T) {
	// Dependency has an alias
	checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{
		Alias: "local-subchart-alias",
	})
}

func TestBuild_WithCondition(t *testing.T) {
	// Dependency has a condition
	checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{
		Condition: "some.condition",
	})
}

func TestBuild_WithTags(t *testing.T) {
	// Dependency has several tags
	checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{
		Tags: []string{"tag1", "tag2"},
	})
}

// Failing test for #6871
func TestBuild_WithRepositoryAlias(t *testing.T) {
	// Dependency repository is aliased in Chart.yaml
	checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{
		Repository: "@test",
	})
}
<|endoftext|>


<commit_before>package rule

import (
	"io"
	"strings"
)

// DefaultRuleSet is the list of rules that are built into the inspector
const defaultRuleSet = `---
# Python 2.5+ is installed on all nodes
# This is required by ansible
- kind: Python2Version
  when: []
  supportedVersions:
   - Python 2.5
   - Python 2.6
   - Python 2.7

# Executables required by kubelet
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables-save
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables-restore

# Ports used by etcd are available
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 2379
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 6666
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 2380
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 6660

# Ports used by etcd are accessible
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 2379
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 6666
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 2380
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 6660
  timeout: 5s

# Ports used by K8s master are available
- kind: TCPPortAvailable
  when: ["master"]
  port: 6443
- kind: TCPPortAvailable
  when: ["master"]
  port: 8080

# Ports used by K8s master are accessible
# Port 8080 is not accessible from outside
- kind: TCPPortAccessible
  when: ["master"]
  port: 6443
  timeout: 5s

# Port used by Docker registry
- kind: TCPPortAvailable
  when: ["master"]
  port: 8443
- kind: TCPPortAccessible
  when: ["master"]
  port: 8443
  timeout: 5s

# Port used by Ingress
- kind: TCPPortAvailable
  when: ["ingress"]
  port: 80
- kind: TCPPortAccessible
  when: ["ingress"]
  port: 80
  timeout: 5s
- kind: TCPPortAvailable
  when: ["ingress"]
  port: 443
- kind: TCPPortAccessible
  when: ["ingress"]
  port: 443
  timeout: 5s

# TODO: Add kismatic package checks
- kind: PackageAvailable
  when: ["etcd", "ubuntu"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["master","ubuntu"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["worker","ubuntu"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["ingress","ubuntu"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1-1

- kind: PackageAvailable
  when: ["etcd", "centos"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["master","centos"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["worker","centos"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["ingress","centos"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1

- kind: PackageAvailable
  when: ["etcd", "rhel"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["master","rhel"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["worker","rhel"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["ingress","rhel"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1
`

// DefaultRules returns the list of rules that are built into the inspector
func DefaultRules() []Rule {
	rules, err := UnmarshalRulesYAML([]byte(defaultRuleSet))
	if err != nil {
		// The default rules should not contain errors
		// If they do, panic so that we catch them during tests
		panic(err)
	}
	return rules
}

// DumpDefaultRules writes the default rule set to a file
func DumpDefaultRules(writer io.Writer) error {
	_, err := io.Copy(writer, strings.NewReader(defaultRuleSet))
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Add gluster pkg checks to inspector<commit_after>package rule

import (
	"io"
	"strings"
)

// DefaultRuleSet is the list of rules that are built into the inspector
const defaultRuleSet = `---
# Python 2.5+ is installed on all nodes
# This is required by ansible
- kind: Python2Version
  when: []
  supportedVersions:
   - Python 2.5
   - Python 2.6
   - Python 2.7

# Executables required by kubelet
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables-save
- kind: ExecutableInPath
  when: ["master","worker"]
  executable: iptables-restore

# Ports used by etcd are available
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 2379
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 6666
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 2380
- kind: TCPPortAvailable
  when: ["etcd"]
  port: 6660

# Ports used by etcd are accessible
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 2379
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 6666
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 2380
  timeout: 5s
- kind: TCPPortAccessible
  when: ["etcd"]
  port: 6660
  timeout: 5s

# Ports used by K8s master are available
- kind: TCPPortAvailable
  when: ["master"]
  port: 6443
- kind: TCPPortAvailable
  when: ["master"]
  port: 8080

# Ports used by K8s master are accessible
# Port 8080 is not accessible from outside
- kind: TCPPortAccessible
  when: ["master"]
  port: 6443
  timeout: 5s

# Port used by Docker registry
- kind: TCPPortAvailable
  when: ["master"]
  port: 8443
- kind: TCPPortAccessible
  when: ["master"]
  port: 8443
  timeout: 5s

# Port used by Ingress
- kind: TCPPortAvailable
  when: ["ingress"]
  port: 80
- kind: TCPPortAccessible
  when: ["ingress"]
  port: 80
  timeout: 5s
- kind: TCPPortAvailable
  when: ["ingress"]
  port: 443
- kind: TCPPortAccessible
  when: ["ingress"]
  port: 443
  timeout: 5s

- kind: PackageAvailable
  when: ["etcd", "ubuntu"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["master","ubuntu"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["worker","ubuntu"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1-1
- kind: PackageAvailable
  when: ["ingress","ubuntu"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1-1

- kind: PackageAvailable
  when: ["etcd", "centos"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["master","centos"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["worker","centos"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["ingress","centos"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1

- kind: PackageAvailable
  when: ["etcd", "rhel"]
  packageName: kismatic-etcd
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["master","rhel"]
  packageName: kismatic-kubernetes-master
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["worker","rhel"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1
- kind: PackageAvailable
  when: ["ingress","rhel"]
  packageName: kismatic-kubernetes-node
  packageVersion: 1.5.1_1-1

  # Gluster packages
- kind: PackageAvailable
	when: ["storage", "centos"]
	packageName: glusterfs-server
	packageVersion: ""
- kind: PackageAvailable
	when: ["storage", "ubuntu"]
	packageName: glusterfs-server
	packageVersion: ""
- kind: PackageAvailable
	when: ["storage", "ubuntu"]
	packageName: nfs-common
	packageVersion: ""
`

// DefaultRules returns the list of rules that are built into the inspector
func DefaultRules() []Rule {
	rules, err := UnmarshalRulesYAML([]byte(defaultRuleSet))
	if err != nil {
		// The default rules should not contain errors
		// If they do, panic so that we catch them during tests
		panic(err)
	}
	return rules
}

// DumpDefaultRules writes the default rule set to a file
func DumpDefaultRules(writer io.Writer) error {
	_, err := io.Copy(writer, strings.NewReader(defaultRuleSet))
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 components

import (
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/loader"
)

// DockerOptionsBuilder adds options for docker to the model
type DockerOptionsBuilder struct {
	*OptionsContext
}

var _ loader.OptionsBuilder = &DockerOptionsBuilder{}

// BuildOptions is responsible for filling in the default setting for docker daemon
func (b *DockerOptionsBuilder) BuildOptions(o interface{}) error {
	clusterSpec := o.(*kops.ClusterSpec)

	if clusterSpec.Docker == nil {
		clusterSpec.Docker = &kops.DockerConfig{}
	}

	docker := clusterSpec.Docker

	// Container runtime is not Docker, should not install
	if clusterSpec.ContainerRuntime != "docker" {
		docker.SkipInstall = true
		return nil
	}

	// Set the Docker version for known Kubernetes versions
	if fi.StringValue(clusterSpec.Docker.Version) == "" {
		if b.IsKubernetesGTE("1.18") {
			docker.Version = fi.String("19.03.11")
		} else if b.IsKubernetesGTE("1.17") {
			docker.Version = fi.String("19.03.4")
		} else if b.IsKubernetesGTE("1.16") {
			docker.Version = fi.String("18.09.9")
		} else if b.IsKubernetesGTE("1.12") {
			docker.Version = fi.String("18.06.3")
		} else {
			docker.Version = fi.String("17.03.2")
		}
	}

	if len(clusterSpec.Docker.LogOpt) == 0 && clusterSpec.Docker.LogDriver == nil {
		// Use built-in docker logging, if not configured otherwise (by the user)
		logDriver := "json-file"
		clusterSpec.Docker.LogDriver = &logDriver
		clusterSpec.Docker.LogOpt = append(clusterSpec.Docker.LogOpt, "max-size=10m")
		clusterSpec.Docker.LogOpt = append(clusterSpec.Docker.LogOpt, "max-file=5")
	}

	docker.LogLevel = fi.String("warn")
	docker.IPTables = fi.Bool(false)
	docker.IPMasq = fi.Bool(false)

	// Note the alternative syntax... with a comma nodeup will try each of the filesystems in turn
	// TODO(justinsb): The ContainerOS image now has docker configured to use overlay2 out-of-the-box
	// and it is an error to specify the flag twice.
	docker.Storage = fi.String("overlay2,overlay,aufs")

	return nil
}
<commit_msg>Use Docker 19.03.11 for Kubernetes 1.17+<commit_after>/*
Copyright 2019 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 components

import (
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/loader"
)

// DockerOptionsBuilder adds options for docker to the model
type DockerOptionsBuilder struct {
	*OptionsContext
}

var _ loader.OptionsBuilder = &DockerOptionsBuilder{}

// BuildOptions is responsible for filling in the default setting for docker daemon
func (b *DockerOptionsBuilder) BuildOptions(o interface{}) error {
	clusterSpec := o.(*kops.ClusterSpec)

	if clusterSpec.Docker == nil {
		clusterSpec.Docker = &kops.DockerConfig{}
	}

	docker := clusterSpec.Docker

	// Container runtime is not Docker, should not install
	if clusterSpec.ContainerRuntime != "docker" {
		docker.SkipInstall = true
		return nil
	}

	// Set the Docker version for known Kubernetes versions
	if fi.StringValue(clusterSpec.Docker.Version) == "" {
		if b.IsKubernetesGTE("1.17") {
			docker.Version = fi.String("19.03.11")
		} else if b.IsKubernetesGTE("1.16") {
			docker.Version = fi.String("18.09.9")
		} else if b.IsKubernetesGTE("1.12") {
			docker.Version = fi.String("18.06.3")
		} else {
			docker.Version = fi.String("17.03.2")
		}
	}

	if len(clusterSpec.Docker.LogOpt) == 0 && clusterSpec.Docker.LogDriver == nil {
		// Use built-in docker logging, if not configured otherwise (by the user)
		logDriver := "json-file"
		clusterSpec.Docker.LogDriver = &logDriver
		clusterSpec.Docker.LogOpt = append(clusterSpec.Docker.LogOpt, "max-size=10m")
		clusterSpec.Docker.LogOpt = append(clusterSpec.Docker.LogOpt, "max-file=5")
	}

	docker.LogLevel = fi.String("warn")
	docker.IPTables = fi.Bool(false)
	docker.IPMasq = fi.Bool(false)

	// Note the alternative syntax... with a comma nodeup will try each of the filesystems in turn
	// TODO(justinsb): The ContainerOS image now has docker configured to use overlay2 out-of-the-box
	// and it is an error to specify the flag twice.
	docker.Storage = fi.String("overlay2,overlay,aufs")

	return nil
}
<|endoftext|>


<commit_before>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package local

import (
	"context"
	"github.com/lastbackend/dynamic/pkg/log"
	"github.com/lastbackend/lastbackend/pkg/distribution/types"
	"github.com/lastbackend/lastbackend/pkg/runtime/csi"
	"github.com/spf13/viper"
	"os"
	"path/filepath"
)

type Storage struct {
	csi.CSI
	root string
}

type StorageOpts struct {
	root string
}

func (s *Storage) Create (ctx context.Context, name string, manifest *types.VolumeManifest) (*types.VolumeState, error) {

	var (
		status = new(types.VolumeState)
		path = filepath.Join(s.root, manifest.Path)
	)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, 0755)
		if err != nil {
			return status, err
		}
	}

	status.Path = path
	status.Ready = true

	return status, nil
}

func (s *Storage) Remove (ctx context.Context, name string, manifest *types.VolumeManifest) error {
	return os.Remove(filepath.Join(s.root, manifest.Path))
}

func Get() (*Storage, error) {

	log.Debug("Initialize local storage interface")
	var s = new(Storage)

	if viper.GetString("node.csi.local.root") != "" {
		s.root = viper.GetString("node.csi.local.root")
		log.Debugf("Initialize local storage interface root: %s", s.root)
	}

	if _, err := os.Stat(s.root); os.IsNotExist(err) {
		err = os.MkdirAll(s.root, 0755)
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}
<commit_msg>update deps clean up private<commit_after>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package local

import (
	"context"
	"github.com/lastbackend/lastbackend/pkg/log"
	"github.com/lastbackend/lastbackend/pkg/distribution/types"
	"github.com/lastbackend/lastbackend/pkg/runtime/csi"
	"github.com/spf13/viper"
	"os"
	"path/filepath"
)

type Storage struct {
	csi.CSI
	root string
}

type StorageOpts struct {
	root string
}

func (s *Storage) Create (ctx context.Context, name string, manifest *types.VolumeManifest) (*types.VolumeState, error) {

	var (
		status = new(types.VolumeState)
		path = filepath.Join(s.root, manifest.Path)
	)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, 0755)
		if err != nil {
			return status, err
		}
	}

	status.Path = path
	status.Ready = true

	return status, nil
}

func (s *Storage) Remove (ctx context.Context, name string, manifest *types.VolumeManifest) error {
	return os.Remove(filepath.Join(s.root, manifest.Path))
}

func Get() (*Storage, error) {

	log.Debug("Initialize local storage interface")
	var s = new(Storage)

	if viper.GetString("node.csi.local.root") != "" {
		s.root = viper.GetString("node.csi.local.root")
		log.Debugf("Initialize local storage interface root: %s", s.root)
	}

	if _, err := os.Stat(s.root); os.IsNotExist(err) {
		err = os.MkdirAll(s.root, 0755)
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 kubernetes

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"sync"
	"sync/atomic"
	"time"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/watch"
)

// Client is for tests
var Client = GetClientset
var DynamicClient = GetDynamicClient

// LogAggregator aggregates the logs for all the deployed pods.
type LogAggregator struct {
	output      io.Writer
	podSelector PodSelector
	namespaces  []string
	colorPicker ColorPicker

	muted             int32
	sinceTime         time.Time
	cancel            context.CancelFunc
	trackedContainers trackedContainers
}

// NewLogAggregator creates a new LogAggregator for a given output.
func NewLogAggregator(out io.Writer, baseImageNames []string, podSelector PodSelector, namespaces []string) *LogAggregator {
	return &LogAggregator{
		output:      out,
		podSelector: podSelector,
		namespaces:  namespaces,
		colorPicker: NewColorPicker(baseImageNames),
		trackedContainers: trackedContainers{
			ids: map[string]bool{},
		},
	}
}

func (a *LogAggregator) SetSince(t time.Time) {
	a.sinceTime = t
}

// Start starts a logger that listens to pods and tail their logs
// if they are matched by the `podSelector`.
func (a *LogAggregator) Start(ctx context.Context) error {
	cancelCtx, cancel := context.WithCancel(ctx)
	a.cancel = cancel

	aggregate := make(chan watch.Event)
	stopWatchers, err := AggregatePodWatcher(a.namespaces, aggregate)
	if err != nil {
		stopWatchers()
		return errors.Wrap(err, "initializing aggregate pod watcher")
	}

	go func() {
		defer stopWatchers()

		for {
			select {
			case <-cancelCtx.Done():
				return
			case evt, ok := <-aggregate:
				if !ok {
					return
				}

				pod, ok := evt.Object.(*v1.Pod)
				if !ok {
					continue
				}

				if !a.podSelector.Select(pod) {
					continue
				}

				for _, c := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
					if c.ContainerID == "" {
						if c.State.Waiting != nil && c.State.Waiting.Message != "" {
							color.Red.Fprintln(a.output, c.State.Waiting.Message)
						}
						continue
					}

					if !a.trackedContainers.add(c.ContainerID) {
						go a.streamContainerLogs(cancelCtx, pod, c)
					}
				}
			}
		}
	}()

	return nil
}

// Stop stops the logger.
func (a *LogAggregator) Stop() {
	if a.cancel != nil {
		a.cancel()
	}
}

func sinceSeconds(d time.Duration) int64 {
	since := int64((d + 999*time.Millisecond).Truncate(1 * time.Second).Seconds())
	if since != 0 {
		return since
	}

	// 0 means all the logs. So we ask for the logs since 1s.
	return 1
}

func (a *LogAggregator) streamContainerLogs(ctx context.Context, pod *v1.Pod, container v1.ContainerStatus) {
	logrus.Infof("Stream logs from pod: %s container: %s", pod.Name, container.Name)

	// In theory, it's more precise to use --since-time='' but there can be a time
	// difference between the user's machine and the server.
	// So we use --since=Xs and round up to the nearest second to not lose any log.
	sinceSeconds := fmt.Sprintf("--since=%ds", sinceSeconds(time.Since(a.sinceTime)))

	tr, tw := io.Pipe()
	cmd := exec.CommandContext(ctx, "kubectl", "logs", sinceSeconds, "-f", pod.Name, "-c", container.Name, "--namespace", pod.Namespace)
	cmd.Stdout = tw
	go func() {
		util.RunCmd(cmd)
		tw.Close()
	}()

	headerColor := a.colorPicker.Pick(pod)
	prefix := prefix(pod, container)
	if err := a.streamRequest(ctx, headerColor, prefix, tr); err != nil {
		logrus.Errorf("streaming request %s", err)
	}
}

func (a *LogAggregator) printLogLine(headerColor color.Color, prefix, text string) {
	if !a.IsMuted() {
		headerColor.Fprintf(a.output, "%s ", prefix)
		fmt.Fprint(a.output, text)
	}
}

func prefix(pod *v1.Pod, container v1.ContainerStatus) string {
	if pod.Name != container.Name {
		return fmt.Sprintf("[%s %s]", pod.Name, container.Name)
	}
	return fmt.Sprintf("[%s]", container.Name)
}

func (a *LogAggregator) streamRequest(ctx context.Context, headerColor color.Color, prefix string, rc io.Reader) error {
	r := bufio.NewReader(rc)
	for {
		select {
		case <-ctx.Done():
			logrus.Infof("%s interrupted", prefix)
			return nil
		default:
			// Read up to newline
			line, err := r.ReadString('\n')
			if err == io.EOF {
				a.printLogLine(headerColor, prefix, "<Exited>\n")
				return nil
			}
			if err != nil {
				return errors.Wrap(err, "reading bytes from log stream")
			}

			a.printLogLine(headerColor, prefix, line)
		}
	}
}

// Mute mutes the logs.
func (a *LogAggregator) Mute() {
	atomic.StoreInt32(&a.muted, 1)
}

// Unmute unmutes the logs.
func (a *LogAggregator) Unmute() {
	atomic.StoreInt32(&a.muted, 0)
}

// IsMuted says if the logs are to be muted.
func (a *LogAggregator) IsMuted() bool {
	return atomic.LoadInt32(&a.muted) == 1
}

type trackedContainers struct {
	sync.Mutex
	ids map[string]bool
}

// add adds a containerID to be tracked. Return true if the container
// was already tracked.
func (t *trackedContainers) add(id string) bool {
	t.Lock()
	alreadyTracked := t.ids[id]
	t.ids[id] = true
	t.Unlock()

	return alreadyTracked
}

// PodSelector is used to choose which pods to log.
type PodSelector interface {
	Select(pod *v1.Pod) bool
}

// ImageList implements PodSelector based on a list of images names.
type ImageList struct {
	sync.RWMutex
	names map[string]bool
}

// NewImageList creates a new ImageList.
func NewImageList() *ImageList {
	return &ImageList{
		names: make(map[string]bool),
	}
}

// Add adds an image to the list.
func (l *ImageList) Add(image string) {
	l.Lock()
	l.names[image] = true
	l.Unlock()
}

// Select returns true if one of the pod's images is in the list.
func (l *ImageList) Select(pod *v1.Pod) bool {
	l.RLock()
	defer l.RUnlock()

	for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {
		if l.names[container.Image] {
			return true
		}
	}

	return false
}
<commit_msg>Better message when a container is terminated (#2514)<commit_after>/*
Copyright 2019 The Skaffold 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 kubernetes

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"sync"
	"sync/atomic"
	"time"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/watch"
)

// Client is for tests
var Client = GetClientset
var DynamicClient = GetDynamicClient

// LogAggregator aggregates the logs for all the deployed pods.
type LogAggregator struct {
	output      io.Writer
	podSelector PodSelector
	namespaces  []string
	colorPicker ColorPicker

	muted             int32
	sinceTime         time.Time
	cancel            context.CancelFunc
	trackedContainers trackedContainers
}

// NewLogAggregator creates a new LogAggregator for a given output.
func NewLogAggregator(out io.Writer, baseImageNames []string, podSelector PodSelector, namespaces []string) *LogAggregator {
	return &LogAggregator{
		output:      out,
		podSelector: podSelector,
		namespaces:  namespaces,
		colorPicker: NewColorPicker(baseImageNames),
		trackedContainers: trackedContainers{
			ids: map[string]bool{},
		},
	}
}

func (a *LogAggregator) SetSince(t time.Time) {
	a.sinceTime = t
}

// Start starts a logger that listens to pods and tail their logs
// if they are matched by the `podSelector`.
func (a *LogAggregator) Start(ctx context.Context) error {
	cancelCtx, cancel := context.WithCancel(ctx)
	a.cancel = cancel

	aggregate := make(chan watch.Event)
	stopWatchers, err := AggregatePodWatcher(a.namespaces, aggregate)
	if err != nil {
		stopWatchers()
		return errors.Wrap(err, "initializing aggregate pod watcher")
	}

	go func() {
		defer stopWatchers()

		for {
			select {
			case <-cancelCtx.Done():
				return
			case evt, ok := <-aggregate:
				if !ok {
					return
				}

				pod, ok := evt.Object.(*v1.Pod)
				if !ok {
					continue
				}

				if !a.podSelector.Select(pod) {
					continue
				}

				for _, c := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
					if c.ContainerID == "" {
						if c.State.Waiting != nil && c.State.Waiting.Message != "" {
							color.Red.Fprintln(a.output, c.State.Waiting.Message)
						}
						continue
					}

					if !a.trackedContainers.add(c.ContainerID) {
						go a.streamContainerLogs(cancelCtx, pod, c)
					}
				}
			}
		}
	}()

	return nil
}

// Stop stops the logger.
func (a *LogAggregator) Stop() {
	if a.cancel != nil {
		a.cancel()
	}
}

func sinceSeconds(d time.Duration) int64 {
	since := int64((d + 999*time.Millisecond).Truncate(1 * time.Second).Seconds())
	if since != 0 {
		return since
	}

	// 0 means all the logs. So we ask for the logs since 1s.
	return 1
}

func (a *LogAggregator) streamContainerLogs(ctx context.Context, pod *v1.Pod, container v1.ContainerStatus) {
	logrus.Infof("Stream logs from pod: %s container: %s", pod.Name, container.Name)

	// In theory, it's more precise to use --since-time='' but there can be a time
	// difference between the user's machine and the server.
	// So we use --since=Xs and round up to the nearest second to not lose any log.
	sinceSeconds := fmt.Sprintf("--since=%ds", sinceSeconds(time.Since(a.sinceTime)))

	tr, tw := io.Pipe()
	cmd := exec.CommandContext(ctx, "kubectl", "logs", sinceSeconds, "-f", pod.Name, "-c", container.Name, "--namespace", pod.Namespace)
	cmd.Stdout = tw
	go func() {
		util.RunCmd(cmd)
		tw.Close()
	}()

	headerColor := a.colorPicker.Pick(pod)
	prefix := prefix(pod, container)
	if err := a.streamRequest(ctx, headerColor, prefix, tr); err != nil {
		logrus.Errorf("streaming request %s", err)
	}
}

func (a *LogAggregator) printLogLine(headerColor color.Color, prefix, text string) {
	if !a.IsMuted() {
		headerColor.Fprintf(a.output, "%s ", prefix)
		fmt.Fprint(a.output, text)
	}
}

func prefix(pod *v1.Pod, container v1.ContainerStatus) string {
	if pod.Name != container.Name {
		return fmt.Sprintf("[%s %s]", pod.Name, container.Name)
	}
	return fmt.Sprintf("[%s]", container.Name)
}

func (a *LogAggregator) streamRequest(ctx context.Context, headerColor color.Color, prefix string, rc io.Reader) error {
	r := bufio.NewReader(rc)
	for {
		select {
		case <-ctx.Done():
			logrus.Infof("%s interrupted", prefix)
			return nil
		default:
			// Read up to newline
			line, err := r.ReadString('\n')
			if err == io.EOF {
				a.printLogLine(headerColor, prefix, "<Container was Terminated>\n")
				return nil
			}
			if err != nil {
				return errors.Wrap(err, "reading bytes from log stream")
			}

			a.printLogLine(headerColor, prefix, line)
		}
	}
}

// Mute mutes the logs.
func (a *LogAggregator) Mute() {
	atomic.StoreInt32(&a.muted, 1)
}

// Unmute unmutes the logs.
func (a *LogAggregator) Unmute() {
	atomic.StoreInt32(&a.muted, 0)
}

// IsMuted says if the logs are to be muted.
func (a *LogAggregator) IsMuted() bool {
	return atomic.LoadInt32(&a.muted) == 1
}

type trackedContainers struct {
	sync.Mutex
	ids map[string]bool
}

// add adds a containerID to be tracked. Return true if the container
// was already tracked.
func (t *trackedContainers) add(id string) bool {
	t.Lock()
	alreadyTracked := t.ids[id]
	t.ids[id] = true
	t.Unlock()

	return alreadyTracked
}

// PodSelector is used to choose which pods to log.
type PodSelector interface {
	Select(pod *v1.Pod) bool
}

// ImageList implements PodSelector based on a list of images names.
type ImageList struct {
	sync.RWMutex
	names map[string]bool
}

// NewImageList creates a new ImageList.
func NewImageList() *ImageList {
	return &ImageList{
		names: make(map[string]bool),
	}
}

// Add adds an image to the list.
func (l *ImageList) Add(image string) {
	l.Lock()
	l.names[image] = true
	l.Unlock()
}

// Select returns true if one of the pod's images is in the list.
func (l *ImageList) Select(pod *v1.Pod) bool {
	l.RLock()
	defer l.RUnlock()

	for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {
		if l.names[container.Image] {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package notifier

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/eternnoir/gmrn/apis"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"time"
)

func InitGitLabNotifier(url, token string, projects []string, pollingInterval, notifyInterval time.Duration, notifyCommand string) *GitLabNotifier {
	gitlab := GitLabNotifier{}
	gitlab.Url = url
	gitlab.Token = token
	gitlab.Projects = projects
	gitlab.PollingInterval = pollingInterval
	gitlab.NotifyInterval = notifyInterval
	gitlab.NotifyCommand = notifyCommand
	gitlab.Api = apis.InitGitlabApi(url, token)
	gitlab.MRLastNotifyTime = make(map[string]time.Time)
	log.Infof("Init GitLabNotifier. Url:%s , Toke:%s, %d projects.", url, token, len(projects))
	return &gitlab
}

type GitLabNotifier struct {
	Url              string
	Token            string
	Projects         []string
	PollingInterval  time.Duration
	NotifyInterval   time.Duration
	Api              *apis.GitLabApi
	NotifyCommand    string
	MRLastNotifyTime map[string]time.Time
}

func (notifier *GitLabNotifier) Run() {

	notifier.checkProjects()
	//  loops forever to polling merge request.
	for {
		err := notifier.notifyForMergeRequest()
		if err != nil {
			log.Error(err)
			break
		}
		time.Sleep(notifier.PollingInterval)
	}
}

func (notifier *GitLabNotifier) notifyForMergeRequest() error {
	allMrs, err := notifier.getAllProjectsMr()
	if err != nil {
		log.Error(err)
		return err
	}
	log.Infof("Get %d Merge Requests", len(allMrs))
	for _, mr := range allMrs {
		go notifier.triggerNitifyCommand(mr)
	}
	return nil
}

func (notifier *GitLabNotifier) triggerNitifyCommand(mr *apis.MergeRequest) {
	if mr.WorkInProgress {
		log.Debugf("%s Merger Reques is WorkInProgress. Do not need to notify.", mr.Title)
		return
	}
	uumrid := strconv.Itoa(int(mr.ProjectId)) + ":" + strconv.Itoa(int(mr.Id))
	if val, ok := notifier.MRLastNotifyTime[uumrid]; ok {
		if time.Now().Before(val.Add(notifier.NotifyInterval)) {
			// Do not need to run notify command.
			log.Debugf("Do not need to run notify command for %s", mr.Title)
			return
		}
	}
	log.Infof("Trigger command for %s", mr.Title)
	notifier.runNotifyCommand(mr)
	notifier.MRLastNotifyTime[uumrid] = time.Now()

}

func (notifier *GitLabNotifier) runNotifyCommand(mr *apis.MergeRequest) {
	log.Infof("Start Trigger Command %s", notifier.NotifyCommand)
	binary, err := exec.LookPath(notifier.NotifyCommand)
	if err != nil {
		log.Error(err)
		return
	}
	cmd := exec.Command(binary)
	env := os.Environ()
	env = append(env, fmt.Sprintf("MR_TITLE=%s", mr.Title))
	cmd.Env = env
	cmdOut, _ := cmd.StdoutPipe()

	startErr := cmd.Start()
	if startErr != nil {
		log.Error(startErr)
		return
	}

	// read stdout and stderr
	stdOutput, _ := ioutil.ReadAll(cmdOut)
	log.Debug(string(stdOutput[:]))

	err = cmd.Wait()
	if err != nil {
		log.Error(err)
		return
	}
	log.Infof("Command %s Done.", notifier.NotifyCommand)
}

func (notifier *GitLabNotifier) getAllProjectsMr() ([]*apis.MergeRequest, error) {
	var mrs []*apis.MergeRequest
	for _, projectId := range notifier.Projects {
		resultmrs, err := notifier.Api.GetMergeRequests(projectId, "opened")
		if err != nil {
			log.Error(err)
			return nil, err
		}
		mrs = append(mrs, resultmrs...)
	}
	return mrs, nil
}

// checkProjects is check notifier's projects is exist or not.
// If projects is empty, It will set all project to project list.
func (notifier *GitLabNotifier) checkProjects() error {
	if len(notifier.Projects) < 1 {
		log.Infof("Notifier' project is empty. Load all projects from gitlab.")
		err := notifier.setAllProjectId()
		if err != nil {
			return err
		}
		log.Infof("%d projects loaded.", len(notifier.Projects))
	}
	return nil
}

func (notifier *GitLabNotifier) setAllProjectId() error {
	projects, err := notifier.Api.GetProjects()
	if err != nil {
		log.Error(err)
		return err
	}
	for _, project := range projects {
		notifier.Projects = append(notifier.Projects, project.PathWithNamespace)
	}
	return nil
}
<commit_msg>fix trigger command mutitimes bug.<commit_after>package notifier

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/eternnoir/gmrn/apis"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"time"
)

func InitGitLabNotifier(url, token string, projects []string, pollingInterval, notifyInterval time.Duration, notifyCommand string) *GitLabNotifier {
	gitlab := GitLabNotifier{}
	gitlab.Url = url
	gitlab.Token = token
	gitlab.Projects = projects
	gitlab.PollingInterval = pollingInterval
	gitlab.NotifyInterval = notifyInterval
	gitlab.NotifyCommand = notifyCommand
	gitlab.Api = apis.InitGitlabApi(url, token)
	gitlab.MRLastNotifyTime = make(map[string]time.Time)
	log.Infof("Init GitLabNotifier. Url:%s , Toke:%s, %d projects.", url, token, len(projects))
	return &gitlab
}

type GitLabNotifier struct {
	Url              string
	Token            string
	Projects         []string
	PollingInterval  time.Duration
	NotifyInterval   time.Duration
	Api              *apis.GitLabApi
	NotifyCommand    string
	MRLastNotifyTime map[string]time.Time
}

func (notifier *GitLabNotifier) Run() {

	notifier.checkProjects()
	//  loops forever to polling merge request.
	for {
		err := notifier.notifyForMergeRequest()
		if err != nil {
			log.Error(err)
			break
		}
		time.Sleep(notifier.PollingInterval)
	}
}

func (notifier *GitLabNotifier) notifyForMergeRequest() error {
	allMrs, err := notifier.getAllProjectsMr()
	if err != nil {
		log.Error(err)
		return err
	}
	log.Infof("Get %d Merge Requests", len(allMrs))
	for _, mr := range allMrs {
		go notifier.triggerNitifyCommand(mr)
	}
	return nil
}

func (notifier *GitLabNotifier) triggerNitifyCommand(mr *apis.MergeRequest) {
	if mr.WorkInProgress {
		log.Debugf("%s Merger Reques is WorkInProgress. Do not need to notify.", mr.Title)
		return
	}
	uumrid := strconv.Itoa(int(mr.ProjectId)) + ":" + strconv.Itoa(int(mr.Id))
	if val, ok := notifier.MRLastNotifyTime[uumrid]; ok {
		if time.Now().Before(val.Add(notifier.NotifyInterval)) {
			// Do not need to run notify command.
			log.Debugf("Do not need to run notify command for %s", mr.Title)
			return
		}
	}
	log.Infof("Trigger command for %s", mr.Title)
	notifier.MRLastNotifyTime[uumrid] = time.Now()
	notifier.runNotifyCommand(mr)

}

func (notifier *GitLabNotifier) runNotifyCommand(mr *apis.MergeRequest) {
	log.Infof("Start Trigger Command %s", notifier.NotifyCommand)
	binary, err := exec.LookPath(notifier.NotifyCommand)
	if err != nil {
		log.Error(err)
		return
	}
	cmd := exec.Command(binary)
	env := os.Environ()
	env = append(env, fmt.Sprintf("MR_TITLE=%s", mr.Title))
	cmd.Env = env
	cmdOut, _ := cmd.StdoutPipe()

	startErr := cmd.Start()
	if startErr != nil {
		log.Error(startErr)
		return
	}

	// read stdout and stderr
	stdOutput, _ := ioutil.ReadAll(cmdOut)
	log.Debug(string(stdOutput[:]))

	err = cmd.Wait()
	if err != nil {
		log.Error(err)
		return
	}
	log.Infof("Command %s Done.", notifier.NotifyCommand)
}

func (notifier *GitLabNotifier) getAllProjectsMr() ([]*apis.MergeRequest, error) {
	var mrs []*apis.MergeRequest
	for _, projectId := range notifier.Projects {
		resultmrs, err := notifier.Api.GetMergeRequests(projectId, "opened")
		if err != nil {
			log.Error(err)
			return nil, err
		}
		mrs = append(mrs, resultmrs...)
	}
	return mrs, nil
}

// checkProjects is check notifier's projects is exist or not.
// If projects is empty, It will set all project to project list.
func (notifier *GitLabNotifier) checkProjects() error {
	if len(notifier.Projects) < 1 {
		log.Infof("Notifier' project is empty. Load all projects from gitlab.")
		err := notifier.setAllProjectId()
		if err != nil {
			return err
		}
		log.Infof("%d projects loaded.", len(notifier.Projects))
	}
	return nil
}

func (notifier *GitLabNotifier) setAllProjectId() error {
	projects, err := notifier.Api.GetProjects()
	if err != nil {
		log.Error(err)
		return err
	}
	for _, project := range projects {
		notifier.Projects = append(notifier.Projects, project.PathWithNamespace)
	}
	return nil
}
<|endoftext|>