<commit_before>package kube

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

	"github.com/pkg/errors"

	log "github.com/sirupsen/logrus"

	squashkube "github.com/solo-io/squash/pkg/platforms/kubernetes"
)

type DebuggerInfo struct {
	CmdlineGen func(int) []string
}

var debuggers map[string]*DebuggerInfo

func init() {
	debuggers["dlv"] = &DebuggerInfo{CmdlineGen: func(pid int) []string {
		return []string{"attach", fmt.Sprintf("%d", pid)}
	}}

	debuggers["gdb"] = &DebuggerInfo{CmdlineGen: func(pid int) []string {
		return []string{"-p", fmt.Sprintf("%d", pid)}
	}}
}

func Debug() error {
	cfg := GetConfig()

	dbgInfo := debuggers[cfg.Debugger]
	if dbgInfo == nil {
		return errors.New("unknown debugger")
	}

	containerProcess := squashkube.NewContainerProcess()
	info, err := containerProcess.GetContainerInfoKube(nil, &cfg.Attachment)
	if err != nil {
		return err
	}

	pid := info.Pids[0]

	// exec into dlv
	log.WithField("pid", pid).Info("attaching with " + cfg.Debugger)
	fullpath, err := exec.LookPath(cfg.Debugger)
	if err != nil {
		return err
	}
	err = syscall.Exec(fullpath, append([]string{fullpath}, dbgInfo.CmdlineGen(pid)...), nil)
	log.WithField("err", err).Info("exec failed!")

	return errors.New("can't start " + cfg.Debugger)
}
<commit_msg>create map...<commit_after>package kube

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

	"github.com/pkg/errors"

	log "github.com/sirupsen/logrus"

	squashkube "github.com/solo-io/squash/pkg/platforms/kubernetes"
)

type DebuggerInfo struct {
	CmdlineGen func(int) []string
}

var debuggers map[string]*DebuggerInfo

func init() {
	debuggers := make(map[string]*DebuggerInfo)
	debuggers["dlv"] = &DebuggerInfo{CmdlineGen: func(pid int) []string {
		return []string{"attach", fmt.Sprintf("%d", pid)}
	}}

	debuggers["gdb"] = &DebuggerInfo{CmdlineGen: func(pid int) []string {
		return []string{"-p", fmt.Sprintf("%d", pid)}
	}}
}

func Debug() error {
	cfg := GetConfig()

	dbgInfo := debuggers[cfg.Debugger]
	if dbgInfo == nil {
		return errors.New("unknown debugger")
	}

	containerProcess := squashkube.NewContainerProcess()
	info, err := containerProcess.GetContainerInfoKube(nil, &cfg.Attachment)
	if err != nil {
		return err
	}

	pid := info.Pids[0]

	// exec into dlv
	log.WithField("pid", pid).Info("attaching with " + cfg.Debugger)
	fullpath, err := exec.LookPath(cfg.Debugger)
	if err != nil {
		return err
	}
	err = syscall.Exec(fullpath, append([]string{fullpath}, dbgInfo.CmdlineGen(pid)...), nil)
	log.WithField("err", err).Info("exec failed!")

	return errors.New("can't start " + cfg.Debugger)
}
<|endoftext|>


<commit_before>package config

import (
	"context"
	"io/ioutil"
	"time"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"

	"github.com/openshift/installer/pkg/rhcos"
)

// ParseConfig parses a yaml string and returns, if successful, a Cluster.
func ParseConfig(data []byte) (*Cluster, error) {
	cluster := defaultCluster

	if err := yaml.Unmarshal(data, &cluster); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal to config.Cluster")
	}

	// Deprecated: remove after openshift/release is ported to pullSecret
	if cluster.PullSecretPath != "" {
		if cluster.PullSecret != "" {
			return nil, errors.New("pullSecretPath is deprecated; just set pullSecret")
		}

		data, err := ioutil.ReadFile(cluster.PullSecretPath)
		if err != nil {
			return nil, errors.Wrap(err, "failed to read PullSecretPath")
		}
		cluster.PullSecret = string(data)
		cluster.PullSecretPath = ""
	}

	if cluster.Platform == PlatformAWS && cluster.EC2AMIOverride == "" {
		ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
		defer cancel()

		ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, cluster.AWS.Region)
		if err != nil {
			return nil, errors.Wrap(err, "failed to determine default AMI")
		}
		cluster.EC2AMIOverride = ami
	}

	return &cluster, nil
}

// ParseConfigFile parses a yaml file and returns, if successful, a Cluster.
func ParseConfigFile(path string) (*Cluster, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, errors.Wrap(err, "failed to read file")
	}

	return ParseConfig(data)
}

// ParseInternal parses a yaml string and returns, if successful, an internal.
func ParseInternal(data []byte) (*Internal, error) {
	internal := &Internal{}

	if err := yaml.Unmarshal(data, internal); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal to config.Internal")
	}

	return internal, nil
}

// ParseInternalFile parses a yaml file and returns, if successful, an internal.
func ParseInternalFile(path string) (*Internal, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, errors.Wrap(err, "failed to read file")
	}

	return ParseInternal(data)
}
<commit_msg>pkg/types/config: Drop ParseConfig and other Parse* methods<commit_after><|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// 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 updateengine

import (
	"fmt"
)

// The possible update statuses returned from the update engine
const (
	UpdateStatusIdle                = "UPDATE_STATUS_IDLE"
	UpdateStatusCheckingForUpdate   = "UPDATE_STATUS_CHECKING_FOR_UPDATE"
	UpdateStatusUpdateAvailable     = "UPDATE_STATUS_UPDATE_AVAILABLE"
	UpdateStatusDownloading         = "UPDATE_STATUS_DOWNLOADING"
	UpdateStatusVerifying           = "UPDATE_STATUS_VERIFYING"
	UpdateStatusFinalizing          = "UPDATE_STATUS_FINALIZING"
	UpdateStatusUpdatedNeedReboot   = "UPDATE_STATUS_UPDATED_NEED_REBOOT"
	UpdateStatusReportingErrorEvent = "UPDATE_STATUS_REPORTING_ERROR_EVENT"
)

type Status struct {
	LastCheckedTime  int64
	Progress         float64
	CurrentOperation string
	NewVersion       string
	NewSize          int64
}

func NewStatus(body []interface{}) (s Status) {
	s.LastCheckedTime = body[0].(int64)
	s.Progress = body[1].(float64)
	s.CurrentOperation = body[2].(string)
	s.NewVersion = body[3].(string)
	s.NewSize = body[4].(int64)

	return
}

func (s *Status) String() string {
	return fmt.Sprintf("LastCheckedTime=%v Progress=%v CurrentOperation=%q NewVersion=%v NewSize=%v",
		s.LastCheckedTime,
		s.Progress,
		s.CurrentOperation,
		s.NewVersion,
		s.NewSize,
	)
}
<commit_msg>pkg/updateengine: add external enum reference in comment<commit_after>// Copyright 2015 CoreOS, Inc.
//
// 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 updateengine

import (
	"fmt"
)

// The possible update statuses returned from the update engine
//
// These correspond to current operation values exposed over DBus and defined by `update_engine`:
// https://github.com/coreos/update_engine/blob/v0.4.3/src/update_engine/update_attempter.h#L34-L43
const (
	UpdateStatusIdle                = "UPDATE_STATUS_IDLE"
	UpdateStatusCheckingForUpdate   = "UPDATE_STATUS_CHECKING_FOR_UPDATE"
	UpdateStatusUpdateAvailable     = "UPDATE_STATUS_UPDATE_AVAILABLE"
	UpdateStatusDownloading         = "UPDATE_STATUS_DOWNLOADING"
	UpdateStatusVerifying           = "UPDATE_STATUS_VERIFYING"
	UpdateStatusFinalizing          = "UPDATE_STATUS_FINALIZING"
	UpdateStatusUpdatedNeedReboot   = "UPDATE_STATUS_UPDATED_NEED_REBOOT"
	UpdateStatusReportingErrorEvent = "UPDATE_STATUS_REPORTING_ERROR_EVENT"
)

type Status struct {
	LastCheckedTime  int64
	Progress         float64
	CurrentOperation string
	NewVersion       string
	NewSize          int64
}

func NewStatus(body []interface{}) (s Status) {
	s.LastCheckedTime = body[0].(int64)
	s.Progress = body[1].(float64)
	s.CurrentOperation = body[2].(string)
	s.NewVersion = body[3].(string)
	s.NewSize = body[4].(int64)

	return
}

func (s *Status) String() string {
	return fmt.Sprintf("LastCheckedTime=%v Progress=%v CurrentOperation=%q NewVersion=%v NewSize=%v",
		s.LastCheckedTime,
		s.Progress,
		s.CurrentOperation,
		s.NewVersion,
		s.NewSize,
	)
}
<|endoftext|>


<commit_before>package vfs

import (
	"encoding/json"
	"os"
	"path"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
)

type couchdbIndexer struct {
	db couchdb.Database
}

// NewCouchdbIndexer creates an Indexer instance based on couchdb to store
// files and directories metadata and index them.
func NewCouchdbIndexer(db couchdb.Database) Indexer {
	return &couchdbIndexer{
		db: db,
	}
}

func (c *couchdbIndexer) InitIndex() error {
	err := couchdb.CreateNamedDocWithDB(c.db, &DirDoc{
		DocName:  "",
		Type:     consts.DirType,
		DocID:    consts.RootDirID,
		Fullpath: "/",
		DirID:    "",
	})
	if err != nil {
		return err
	}

	err = couchdb.CreateNamedDocWithDB(c.db, &DirDoc{
		DocName:  path.Base(TrashDirName),
		Type:     consts.DirType,
		DocID:    consts.TrashDirID,
		Fullpath: TrashDirName,
		DirID:    consts.RootDirID,
	})
	if err != nil && !couchdb.IsConflictError(err) {
		return err
	}
	return nil
}

func (c *couchdbIndexer) DiskUsage() (int64, error) {
	var doc couchdb.ViewResponse
	err := couchdb.ExecView(c.db, consts.DiskUsageView, &couchdb.ViewRequest{
		Reduce: true,
	}, &doc)
	if err != nil {
		return 0, err
	}
	if len(doc.Rows) == 0 {
		return 0, nil
	}
	// Reduce of _count should give us a number value
	f64, ok := doc.Rows[0].Value.(float64)
	if !ok {
		return 0, ErrWrongCouchdbState
	}
	return int64(f64), nil
}

func (c *couchdbIndexer) CreateFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.CreateDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateNamedFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.CreateNamedDoc(c.db, doc)
}

func (c *couchdbIndexer) UpdateFileDoc(olddoc, newdoc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := olddoc.Path(c); err != nil {
		return err
	}
	if _, err := newdoc.Path(c); err != nil {
		return err
	}
	newdoc.SetID(olddoc.ID())
	newdoc.SetRev(olddoc.Rev())
	return couchdb.UpdateDoc(c.db, newdoc)
}

func (c *couchdbIndexer) UpdateFileDocs(docs []*FileDoc) error {
	if len(docs) == 0 {
		return nil
	}
	// Ensure that fullpath is filled because it's used in realtime/@events
	couchdocs := make([]interface{}, len(docs))
	for i, doc := range docs {
		if _, err := doc.Path(c); err != nil {
			return err
		}
		couchdocs[i] = doc
	}
	return couchdb.BulkUpdateDocs(c.db, consts.Files, couchdocs)
}

func (c *couchdbIndexer) DeleteFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.DeleteDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateDirDoc(doc *DirDoc) error {
	return couchdb.CreateDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateNamedDirDoc(doc *DirDoc) error {
	return couchdb.CreateNamedDoc(c.db, doc)
}

func (c *couchdbIndexer) UpdateDirDoc(olddoc, newdoc *DirDoc) error {
	newdoc.SetID(olddoc.ID())
	newdoc.SetRev(olddoc.Rev())
	if newdoc.Fullpath != olddoc.Fullpath {
		if err := c.moveDir(olddoc.Fullpath, newdoc.Fullpath); err != nil {
			return err
		}
	}
	return couchdb.UpdateDoc(c.db, newdoc)
}

func (c *couchdbIndexer) DeleteDirDoc(doc *DirDoc) error {
	return couchdb.DeleteDoc(c.db, doc)
}

func (c *couchdbIndexer) moveDir(oldpath, newpath string) error {
	var children []*DirDoc
	sel := mango.StartWith("path", oldpath+"/")
	req := &couchdb.FindRequest{
		UseIndex: "dir-by-path",
		Selector: sel,
	}
	err := couchdb.FindDocs(c.db, consts.Files, req, &children)
	if err != nil || len(children) == 0 {
		return err
	}

	couchdocs := make([]interface{}, len(children))
	for i, child := range children {
		child.Fullpath = path.Join(newpath, child.Fullpath[len(oldpath)+1:])
		couchdocs[i] = child
	}
	return couchdb.BulkUpdateDocs(c.db, consts.Files, couchdocs)
}

func (c *couchdbIndexer) DirByID(fileID string) (*DirDoc, error) {
	doc := &DirDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, doc)
	if couchdb.IsNotFoundError(err) {
		err = os.ErrNotExist
	}
	if err != nil {
		if fileID == consts.RootDirID {
			panic("Root directory is not in database")
		}
		if fileID == consts.TrashDirID {
			panic("Trash directory is not in database")
		}
		return nil, err
	}
	if doc.Type != consts.DirType {
		return nil, os.ErrNotExist
	}
	return doc, err
}

func (c *couchdbIndexer) DirByPath(name string) (*DirDoc, error) {
	if !path.IsAbs(name) {
		return nil, ErrNonAbsolutePath
	}
	var docs []*DirDoc
	sel := mango.Equal("path", path.Clean(name))
	req := &couchdb.FindRequest{
		UseIndex: "dir-by-path",
		Selector: sel,
		Limit:    1,
	}
	err := couchdb.FindDocs(c.db, consts.Files, req, &docs)
	if err != nil {
		return nil, err
	}
	if len(docs) == 0 {
		if name == "/" {
			panic("Root directory is not in database")
		}
		return nil, os.ErrNotExist
	}
	return docs[0], nil
}

func (c *couchdbIndexer) FileByID(fileID string) (*FileDoc, error) {
	doc := &FileDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, doc)
	if couchdb.IsNotFoundError(err) {
		return nil, os.ErrNotExist
	}
	if err != nil {
		return nil, err
	}
	if doc.Type != consts.FileType {
		return nil, os.ErrNotExist
	}
	return doc, nil
}

func (c *couchdbIndexer) FileByPath(name string) (*FileDoc, error) {
	if !path.IsAbs(name) {
		return nil, ErrNonAbsolutePath
	}
	parent, err := c.DirByPath(path.Dir(name))
	if err != nil {
		return nil, err
	}

	// consts.FilesByParentView keys are [parentID, type, name]
	var res couchdb.ViewResponse
	err = couchdb.ExecView(c.db, consts.FilesByParentView, &couchdb.ViewRequest{
		Key:         []string{parent.DocID, consts.FileType, path.Base(name)},
		IncludeDocs: true,
	}, &res)
	if err != nil {
		return nil, err
	}

	if len(res.Rows) == 0 {
		return nil, os.ErrNotExist
	}

	var fdoc FileDoc
	err = json.Unmarshal(*res.Rows[0].Doc, &fdoc)
	return &fdoc, err
}

func (c *couchdbIndexer) FilePath(doc *FileDoc) (string, error) {
	var parentPath string
	if doc.DirID == consts.RootDirID {
		parentPath = "/"
	} else if doc.DirID == consts.TrashDirID {
		parentPath = TrashDirName
	} else {
		parent, err := c.DirByID(doc.DirID)
		if err != nil {
			return "", ErrParentDoesNotExist
		}
		parentPath = parent.Fullpath
	}
	return path.Join(parentPath, doc.DocName), nil
}

func (c *couchdbIndexer) DirOrFileByID(fileID string) (*DirDoc, *FileDoc, error) {
	dirOrFile := &DirOrFileDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, dirOrFile)
	if err != nil {
		return nil, nil, err
	}
	dirDoc, fileDoc := dirOrFile.Refine()
	return dirDoc, fileDoc, nil
}

func (c *couchdbIndexer) DirOrFileByPath(name string) (*DirDoc, *FileDoc, error) {
	dirDoc, err := c.DirByPath(name)
	if err != nil && !os.IsNotExist(err) {
		return nil, nil, err
	}
	if err == nil {
		return dirDoc, nil, nil
	}
	fileDoc, err := c.FileByPath(name)
	if err != nil && !os.IsNotExist(err) {
		return nil, nil, err
	}
	if err == nil {
		return nil, fileDoc, nil
	}
	return nil, nil, err
}

func (c *couchdbIndexer) DirIterator(doc *DirDoc, opts *IteratorOptions) DirIterator {
	return NewIterator(c.db, doc, opts)
}

func (c *couchdbIndexer) DirBatch(doc *DirDoc, cursor couchdb.Cursor) ([]DirOrFileDoc, error) {
	// consts.FilesByParentView keys are [parentID, type, name]
	req := couchdb.ViewRequest{
		StartKey:    []string{doc.DocID, ""},
		EndKey:      []string{doc.DocID, couchdb.MaxString},
		IncludeDocs: true,
	}
	var res couchdb.ViewResponse
	cursor.ApplyTo(&req)
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &req, &res)
	if err != nil {
		return nil, err
	}
	cursor.UpdateFrom(&res)

	docs := make([]DirOrFileDoc, len(res.Rows))
	for i, row := range res.Rows {
		var doc DirOrFileDoc
		err := json.Unmarshal(*row.Doc, &doc)
		if err != nil {
			return nil, err
		}
		docs[i] = doc
	}

	return docs, nil
}

func (c *couchdbIndexer) DirLength(doc *DirDoc) (int, error) {
	req := couchdb.ViewRequest{
		StartKey:   []string{doc.DocID, ""},
		EndKey:     []string{doc.DocID, couchdb.MaxString},
		Reduce:     true,
		GroupLevel: 1,
	}
	var res couchdb.ViewResponse
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &req, &res)
	if err != nil {
		return 0, err
	}

	if len(res.Rows) == 0 {
		return 0, nil
	}

	// Reduce of _count should give us a number value
	f64, ok := res.Rows[0].Value.(float64)
	if !ok {
		return 0, ErrWrongCouchdbState
	}
	return int(f64), nil
}

func (c *couchdbIndexer) DirChildExists(dirID, name string) (bool, error) {
	var res couchdb.ViewResponse

	// consts.FilesByParentView keys are [parentID, type, name]
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &couchdb.ViewRequest{
		Keys: []interface{}{
			[]string{dirID, consts.FileType, name},
			[]string{dirID, consts.DirType, name},
		},
		Reduce: true,
		Group:  true,
	}, &res)
	if err != nil {
		return false, err
	}

	if len(res.Rows) == 0 {
		return false, nil
	}

	// Reduce of _count should give us a number value
	f64, ok := res.Rows[0].Value.(float64)
	if !ok {
		return false, ErrWrongCouchdbState
	}
	return int(f64) > 0, nil
}
<commit_msg>Fix created_at/updated_at for root dir and trash dir<commit_after>package vfs

import (
	"encoding/json"
	"os"
	"path"
	"time"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
)

type couchdbIndexer struct {
	db couchdb.Database
}

// NewCouchdbIndexer creates an Indexer instance based on couchdb to store
// files and directories metadata and index them.
func NewCouchdbIndexer(db couchdb.Database) Indexer {
	return &couchdbIndexer{
		db: db,
	}
}

func (c *couchdbIndexer) InitIndex() error {
	createDate := time.Now()
	err := couchdb.CreateNamedDocWithDB(c.db, &DirDoc{
		DocName:   "",
		Type:      consts.DirType,
		DocID:     consts.RootDirID,
		Fullpath:  "/",
		DirID:     "",
		CreatedAt: createDate,
		UpdatedAt: createDate,
	})
	if err != nil {
		return err
	}

	err = couchdb.CreateNamedDocWithDB(c.db, &DirDoc{
		DocName:   path.Base(TrashDirName),
		Type:      consts.DirType,
		DocID:     consts.TrashDirID,
		Fullpath:  TrashDirName,
		DirID:     consts.RootDirID,
		CreatedAt: createDate,
		UpdatedAt: createDate,
	})
	if err != nil && !couchdb.IsConflictError(err) {
		return err
	}
	return nil
}

func (c *couchdbIndexer) DiskUsage() (int64, error) {
	var doc couchdb.ViewResponse
	err := couchdb.ExecView(c.db, consts.DiskUsageView, &couchdb.ViewRequest{
		Reduce: true,
	}, &doc)
	if err != nil {
		return 0, err
	}
	if len(doc.Rows) == 0 {
		return 0, nil
	}
	// Reduce of _count should give us a number value
	f64, ok := doc.Rows[0].Value.(float64)
	if !ok {
		return 0, ErrWrongCouchdbState
	}
	return int64(f64), nil
}

func (c *couchdbIndexer) CreateFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.CreateDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateNamedFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.CreateNamedDoc(c.db, doc)
}

func (c *couchdbIndexer) UpdateFileDoc(olddoc, newdoc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := olddoc.Path(c); err != nil {
		return err
	}
	if _, err := newdoc.Path(c); err != nil {
		return err
	}
	newdoc.SetID(olddoc.ID())
	newdoc.SetRev(olddoc.Rev())
	return couchdb.UpdateDoc(c.db, newdoc)
}

func (c *couchdbIndexer) UpdateFileDocs(docs []*FileDoc) error {
	if len(docs) == 0 {
		return nil
	}
	// Ensure that fullpath is filled because it's used in realtime/@events
	couchdocs := make([]interface{}, len(docs))
	for i, doc := range docs {
		if _, err := doc.Path(c); err != nil {
			return err
		}
		couchdocs[i] = doc
	}
	return couchdb.BulkUpdateDocs(c.db, consts.Files, couchdocs)
}

func (c *couchdbIndexer) DeleteFileDoc(doc *FileDoc) error {
	// Ensure that fullpath is filled because it's used in realtime/@events
	if _, err := doc.Path(c); err != nil {
		return err
	}
	return couchdb.DeleteDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateDirDoc(doc *DirDoc) error {
	return couchdb.CreateDoc(c.db, doc)
}

func (c *couchdbIndexer) CreateNamedDirDoc(doc *DirDoc) error {
	return couchdb.CreateNamedDoc(c.db, doc)
}

func (c *couchdbIndexer) UpdateDirDoc(olddoc, newdoc *DirDoc) error {
	newdoc.SetID(olddoc.ID())
	newdoc.SetRev(olddoc.Rev())
	if newdoc.Fullpath != olddoc.Fullpath {
		if err := c.moveDir(olddoc.Fullpath, newdoc.Fullpath); err != nil {
			return err
		}
	}
	return couchdb.UpdateDoc(c.db, newdoc)
}

func (c *couchdbIndexer) DeleteDirDoc(doc *DirDoc) error {
	return couchdb.DeleteDoc(c.db, doc)
}

func (c *couchdbIndexer) moveDir(oldpath, newpath string) error {
	var children []*DirDoc
	sel := mango.StartWith("path", oldpath+"/")
	req := &couchdb.FindRequest{
		UseIndex: "dir-by-path",
		Selector: sel,
	}
	err := couchdb.FindDocs(c.db, consts.Files, req, &children)
	if err != nil || len(children) == 0 {
		return err
	}

	couchdocs := make([]interface{}, len(children))
	for i, child := range children {
		child.Fullpath = path.Join(newpath, child.Fullpath[len(oldpath)+1:])
		couchdocs[i] = child
	}
	return couchdb.BulkUpdateDocs(c.db, consts.Files, couchdocs)
}

func (c *couchdbIndexer) DirByID(fileID string) (*DirDoc, error) {
	doc := &DirDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, doc)
	if couchdb.IsNotFoundError(err) {
		err = os.ErrNotExist
	}
	if err != nil {
		if fileID == consts.RootDirID {
			panic("Root directory is not in database")
		}
		if fileID == consts.TrashDirID {
			panic("Trash directory is not in database")
		}
		return nil, err
	}
	if doc.Type != consts.DirType {
		return nil, os.ErrNotExist
	}
	return doc, err
}

func (c *couchdbIndexer) DirByPath(name string) (*DirDoc, error) {
	if !path.IsAbs(name) {
		return nil, ErrNonAbsolutePath
	}
	var docs []*DirDoc
	sel := mango.Equal("path", path.Clean(name))
	req := &couchdb.FindRequest{
		UseIndex: "dir-by-path",
		Selector: sel,
		Limit:    1,
	}
	err := couchdb.FindDocs(c.db, consts.Files, req, &docs)
	if err != nil {
		return nil, err
	}
	if len(docs) == 0 {
		if name == "/" {
			panic("Root directory is not in database")
		}
		return nil, os.ErrNotExist
	}
	return docs[0], nil
}

func (c *couchdbIndexer) FileByID(fileID string) (*FileDoc, error) {
	doc := &FileDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, doc)
	if couchdb.IsNotFoundError(err) {
		return nil, os.ErrNotExist
	}
	if err != nil {
		return nil, err
	}
	if doc.Type != consts.FileType {
		return nil, os.ErrNotExist
	}
	return doc, nil
}

func (c *couchdbIndexer) FileByPath(name string) (*FileDoc, error) {
	if !path.IsAbs(name) {
		return nil, ErrNonAbsolutePath
	}
	parent, err := c.DirByPath(path.Dir(name))
	if err != nil {
		return nil, err
	}

	// consts.FilesByParentView keys are [parentID, type, name]
	var res couchdb.ViewResponse
	err = couchdb.ExecView(c.db, consts.FilesByParentView, &couchdb.ViewRequest{
		Key:         []string{parent.DocID, consts.FileType, path.Base(name)},
		IncludeDocs: true,
	}, &res)
	if err != nil {
		return nil, err
	}

	if len(res.Rows) == 0 {
		return nil, os.ErrNotExist
	}

	var fdoc FileDoc
	err = json.Unmarshal(*res.Rows[0].Doc, &fdoc)
	return &fdoc, err
}

func (c *couchdbIndexer) FilePath(doc *FileDoc) (string, error) {
	var parentPath string
	if doc.DirID == consts.RootDirID {
		parentPath = "/"
	} else if doc.DirID == consts.TrashDirID {
		parentPath = TrashDirName
	} else {
		parent, err := c.DirByID(doc.DirID)
		if err != nil {
			return "", ErrParentDoesNotExist
		}
		parentPath = parent.Fullpath
	}
	return path.Join(parentPath, doc.DocName), nil
}

func (c *couchdbIndexer) DirOrFileByID(fileID string) (*DirDoc, *FileDoc, error) {
	dirOrFile := &DirOrFileDoc{}
	err := couchdb.GetDoc(c.db, consts.Files, fileID, dirOrFile)
	if err != nil {
		return nil, nil, err
	}
	dirDoc, fileDoc := dirOrFile.Refine()
	return dirDoc, fileDoc, nil
}

func (c *couchdbIndexer) DirOrFileByPath(name string) (*DirDoc, *FileDoc, error) {
	dirDoc, err := c.DirByPath(name)
	if err != nil && !os.IsNotExist(err) {
		return nil, nil, err
	}
	if err == nil {
		return dirDoc, nil, nil
	}
	fileDoc, err := c.FileByPath(name)
	if err != nil && !os.IsNotExist(err) {
		return nil, nil, err
	}
	if err == nil {
		return nil, fileDoc, nil
	}
	return nil, nil, err
}

func (c *couchdbIndexer) DirIterator(doc *DirDoc, opts *IteratorOptions) DirIterator {
	return NewIterator(c.db, doc, opts)
}

func (c *couchdbIndexer) DirBatch(doc *DirDoc, cursor couchdb.Cursor) ([]DirOrFileDoc, error) {
	// consts.FilesByParentView keys are [parentID, type, name]
	req := couchdb.ViewRequest{
		StartKey:    []string{doc.DocID, ""},
		EndKey:      []string{doc.DocID, couchdb.MaxString},
		IncludeDocs: true,
	}
	var res couchdb.ViewResponse
	cursor.ApplyTo(&req)
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &req, &res)
	if err != nil {
		return nil, err
	}
	cursor.UpdateFrom(&res)

	docs := make([]DirOrFileDoc, len(res.Rows))
	for i, row := range res.Rows {
		var doc DirOrFileDoc
		err := json.Unmarshal(*row.Doc, &doc)
		if err != nil {
			return nil, err
		}
		docs[i] = doc
	}

	return docs, nil
}

func (c *couchdbIndexer) DirLength(doc *DirDoc) (int, error) {
	req := couchdb.ViewRequest{
		StartKey:   []string{doc.DocID, ""},
		EndKey:     []string{doc.DocID, couchdb.MaxString},
		Reduce:     true,
		GroupLevel: 1,
	}
	var res couchdb.ViewResponse
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &req, &res)
	if err != nil {
		return 0, err
	}

	if len(res.Rows) == 0 {
		return 0, nil
	}

	// Reduce of _count should give us a number value
	f64, ok := res.Rows[0].Value.(float64)
	if !ok {
		return 0, ErrWrongCouchdbState
	}
	return int(f64), nil
}

func (c *couchdbIndexer) DirChildExists(dirID, name string) (bool, error) {
	var res couchdb.ViewResponse

	// consts.FilesByParentView keys are [parentID, type, name]
	err := couchdb.ExecView(c.db, consts.FilesByParentView, &couchdb.ViewRequest{
		Keys: []interface{}{
			[]string{dirID, consts.FileType, name},
			[]string{dirID, consts.DirType, name},
		},
		Reduce: true,
		Group:  true,
	}, &res)
	if err != nil {
		return false, err
	}

	if len(res.Rows) == 0 {
		return false, nil
	}

	// Reduce of _count should give us a number value
	f64, ok := res.Rows[0].Value.(float64)
	if !ok {
		return false, ErrWrongCouchdbState
	}
	return int(f64) > 0, nil
}
<|endoftext|>


<commit_before>// +build linux

/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package volume

import (
	"path/filepath"
	"syscall"

	"os"

	v1 "k8s.io/api/core/v1"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/klog/v2"
	"k8s.io/kubernetes/pkg/features"
)

const (
	rwMask   = os.FileMode(0660)
	roMask   = os.FileMode(0440)
	execMask = os.FileMode(0110)
)

// SetVolumeOwnership modifies the given volume to be owned by
// fsGroup, and sets SetGid so that newly created files are owned by
// fsGroup. If fsGroup is nil nothing is done.
func SetVolumeOwnership(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy) error {
	if fsGroup == nil {
		return nil
	}

	fsGroupPolicyEnabled := utilfeature.DefaultFeatureGate.Enabled(features.ConfigurableFSGroupPolicy)

	klog.Warningf("Setting volume ownership for %s and fsGroup set. If the volume has a lot of files then setting volume ownership could be slow, see https://github.com/kubernetes/kubernetes/issues/69699", mounter.GetPath())

	// This code exists for legacy purposes, so as old behaviour is entirely preserved when feature gate is disabled
	// TODO: remove this when ConfigurableFSGroupPolicy turns GA.
	if !fsGroupPolicyEnabled {
		return legacyOwnershipChange(mounter, fsGroup)
	}

	if skipPermissionChange(mounter, fsGroup, fsGroupChangePolicy) {
		klog.V(3).Infof("skipping permission and ownership change for volume %s", mounter.GetPath())
		return nil
	}

	return walkDeep(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		return changeFilePermission(path, fsGroup, mounter.GetAttributes().ReadOnly, info)
	})

}

func legacyOwnershipChange(mounter Mounter, fsGroup *int64) error {
	return filepath.Walk(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		return changeFilePermission(path, fsGroup, mounter.GetAttributes().ReadOnly, info)
	})
}

func changeFilePermission(filename string, fsGroup *int64, readonly bool, info os.FileInfo) error {
	// chown and chmod pass through to the underlying file for symlinks.
	// Symlinks have a mode of 777 but this really doesn't mean anything.
	// The permissions of the underlying file are what matter.
	// However, if one reads the mode of a symlink then chmods the symlink
	// with that mode, it changes the mode of the underlying file, overridden
	// the defaultMode and permissions initialized by the volume plugin, which
	// is not what we want; thus, we skip chown/chmod for symlinks.
	if info.Mode()&os.ModeSymlink != 0 {
		return nil
	}

	stat, ok := info.Sys().(*syscall.Stat_t)
	if !ok {
		return nil
	}

	if stat == nil {
		klog.Errorf("Got nil stat_t for path %v while setting ownership of volume", filename)
		return nil
	}

	err := os.Chown(filename, int(stat.Uid), int(*fsGroup))
	if err != nil {
		klog.Errorf("Chown failed on %v: %v", filename, err)
	}

	mask := rwMask
	if readonly {
		mask = roMask
	}

	if info.IsDir() {
		mask |= os.ModeSetgid
		mask |= execMask
	}

	err = os.Chmod(filename, info.Mode()|mask)
	if err != nil {
		klog.Errorf("Chmod failed on %v: %v", filename, err)
	}

	return nil
}

func skipPermissionChange(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy) bool {
	dir := mounter.GetPath()

	if fsGroupChangePolicy == nil || *fsGroupChangePolicy != v1.FSGroupChangeOnRootMismatch {
		klog.V(4).Infof("perform recursive ownership change for %s", dir)
		return false
	}
	return !requiresPermissionChange(mounter.GetPath(), fsGroup, mounter.GetAttributes().ReadOnly)
}

func requiresPermissionChange(rootDir string, fsGroup *int64, readonly bool) bool {
	fsInfo, err := os.Stat(rootDir)
	if err != nil {
		klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed: %v", rootDir, err)
		return true
	}
	stat, ok := fsInfo.Sys().(*syscall.Stat_t)
	if !ok || stat == nil {
		klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed", rootDir)
		return true
	}

	if int(stat.Gid) != int(*fsGroup) {
		klog.V(4).Infof("expected group ownership of volume %s did not match with: %d", rootDir, stat.Gid)
		return true
	}
	unixPerms := rwMask

	if readonly {
		unixPerms = roMask
	}

	// if rootDir is not a directory then we should apply permission change anyways
	if !fsInfo.IsDir() {
		return true
	}
	unixPerms |= execMask
	filePerm := fsInfo.Mode().Perm()

	// We need to check if actual permissions of root directory is a superset of permissions required by unixPerms.
	// This is done by checking if permission bits expected in unixPerms is set in actual permissions of the directory.
	// We use bitwise AND operation to check set bits. For example:
	//     unixPerms: 770, filePerms: 775 : 770&775 = 770 (perms on directory is a superset)
	//     unixPerms: 770, filePerms: 770 : 770&770 = 770 (perms on directory is a superset)
	//     unixPerms: 770, filePerms: 750 : 770&750 = 750 (perms on directory is NOT a superset)
	// We also need to check if setgid bits are set in permissions of the directory.
	if (unixPerms&filePerm != unixPerms) || (fsInfo.Mode()&os.ModeSetgid == 0) {
		klog.V(4).Infof("performing recursive ownership change on %s because of mismatching mode", rootDir)
		return true
	}
	return false
}

// readDirNames reads the directory named by dirname and returns
// a list of directory entries.
// We are not using filepath.readDirNames because we do not want to sort files found in a directory before changing
// permissions for performance reasons.
func readDirNames(dirname string) ([]string, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return nil, err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	return names, nil
}

// walkDeep can be used to traverse directories and has two minor differences
// from filepath.Walk:
//   - List of files/dirs is not sorted for performance reasons
//   - callback walkFunc is invoked on root directory after visiting children dirs and files
func walkDeep(root string, walkFunc filepath.WalkFunc) error {
	info, err := os.Lstat(root)
	if err != nil {
		return walkFunc(root, nil, err)
	}
	return walk(root, info, walkFunc)
}

func walk(path string, info os.FileInfo, walkFunc filepath.WalkFunc) error {
	if !info.IsDir() {
		return walkFunc(path, info, nil)
	}
	names, err := readDirNames(path)
	if err != nil {
		return err
	}
	for _, name := range names {
		filename := filepath.Join(path, name)
		fileInfo, err := os.Lstat(filename)
		if err != nil {
			if err := walkFunc(filename, fileInfo, err); err != nil {
				return err
			}
		} else {
			err = walk(filename, fileInfo, walkFunc)
			if err != nil {
				return err
			}
		}
	}
	return walkFunc(path, info, nil)
}
<commit_msg>cleanup: print warning message after timeout<commit_after>// +build linux

/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package volume

import (
	"path/filepath"
	"syscall"

	"os"
	"time"

	v1 "k8s.io/api/core/v1"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/klog/v2"
	"k8s.io/kubernetes/pkg/features"
)

const (
	rwMask   = os.FileMode(0660)
	roMask   = os.FileMode(0440)
	execMask = os.FileMode(0110)
)

// SetVolumeOwnership modifies the given volume to be owned by
// fsGroup, and sets SetGid so that newly created files are owned by
// fsGroup. If fsGroup is nil nothing is done.
func SetVolumeOwnership(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy) error {
	if fsGroup == nil {
		return nil
	}

	fsGroupPolicyEnabled := utilfeature.DefaultFeatureGate.Enabled(features.ConfigurableFSGroupPolicy)

	timer := time.AfterFunc(30*time.Second, func() {
		klog.Warningf("Setting volume ownership for %s and fsGroup set. If the volume has a lot of files then setting volume ownership could be slow, see https://github.com/kubernetes/kubernetes/issues/69699", mounter.GetPath())
	})
	defer timer.Stop()

	// This code exists for legacy purposes, so as old behaviour is entirely preserved when feature gate is disabled
	// TODO: remove this when ConfigurableFSGroupPolicy turns GA.
	if !fsGroupPolicyEnabled {
		return legacyOwnershipChange(mounter, fsGroup)
	}

	if skipPermissionChange(mounter, fsGroup, fsGroupChangePolicy) {
		klog.V(3).Infof("skipping permission and ownership change for volume %s", mounter.GetPath())
		return nil
	}

	return walkDeep(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		return changeFilePermission(path, fsGroup, mounter.GetAttributes().ReadOnly, info)
	})

}

func legacyOwnershipChange(mounter Mounter, fsGroup *int64) error {
	return filepath.Walk(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		return changeFilePermission(path, fsGroup, mounter.GetAttributes().ReadOnly, info)
	})
}

func changeFilePermission(filename string, fsGroup *int64, readonly bool, info os.FileInfo) error {
	// chown and chmod pass through to the underlying file for symlinks.
	// Symlinks have a mode of 777 but this really doesn't mean anything.
	// The permissions of the underlying file are what matter.
	// However, if one reads the mode of a symlink then chmods the symlink
	// with that mode, it changes the mode of the underlying file, overridden
	// the defaultMode and permissions initialized by the volume plugin, which
	// is not what we want; thus, we skip chown/chmod for symlinks.
	if info.Mode()&os.ModeSymlink != 0 {
		return nil
	}

	stat, ok := info.Sys().(*syscall.Stat_t)
	if !ok {
		return nil
	}

	if stat == nil {
		klog.Errorf("Got nil stat_t for path %v while setting ownership of volume", filename)
		return nil
	}

	err := os.Chown(filename, int(stat.Uid), int(*fsGroup))
	if err != nil {
		klog.Errorf("Chown failed on %v: %v", filename, err)
	}

	mask := rwMask
	if readonly {
		mask = roMask
	}

	if info.IsDir() {
		mask |= os.ModeSetgid
		mask |= execMask
	}

	err = os.Chmod(filename, info.Mode()|mask)
	if err != nil {
		klog.Errorf("Chmod failed on %v: %v", filename, err)
	}

	return nil
}

func skipPermissionChange(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy) bool {
	dir := mounter.GetPath()

	if fsGroupChangePolicy == nil || *fsGroupChangePolicy != v1.FSGroupChangeOnRootMismatch {
		klog.V(4).Infof("perform recursive ownership change for %s", dir)
		return false
	}
	return !requiresPermissionChange(mounter.GetPath(), fsGroup, mounter.GetAttributes().ReadOnly)
}

func requiresPermissionChange(rootDir string, fsGroup *int64, readonly bool) bool {
	fsInfo, err := os.Stat(rootDir)
	if err != nil {
		klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed: %v", rootDir, err)
		return true
	}
	stat, ok := fsInfo.Sys().(*syscall.Stat_t)
	if !ok || stat == nil {
		klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed", rootDir)
		return true
	}

	if int(stat.Gid) != int(*fsGroup) {
		klog.V(4).Infof("expected group ownership of volume %s did not match with: %d", rootDir, stat.Gid)
		return true
	}
	unixPerms := rwMask

	if readonly {
		unixPerms = roMask
	}

	// if rootDir is not a directory then we should apply permission change anyways
	if !fsInfo.IsDir() {
		return true
	}
	unixPerms |= execMask
	filePerm := fsInfo.Mode().Perm()

	// We need to check if actual permissions of root directory is a superset of permissions required by unixPerms.
	// This is done by checking if permission bits expected in unixPerms is set in actual permissions of the directory.
	// We use bitwise AND operation to check set bits. For example:
	//     unixPerms: 770, filePerms: 775 : 770&775 = 770 (perms on directory is a superset)
	//     unixPerms: 770, filePerms: 770 : 770&770 = 770 (perms on directory is a superset)
	//     unixPerms: 770, filePerms: 750 : 770&750 = 750 (perms on directory is NOT a superset)
	// We also need to check if setgid bits are set in permissions of the directory.
	if (unixPerms&filePerm != unixPerms) || (fsInfo.Mode()&os.ModeSetgid == 0) {
		klog.V(4).Infof("performing recursive ownership change on %s because of mismatching mode", rootDir)
		return true
	}
	return false
}

// readDirNames reads the directory named by dirname and returns
// a list of directory entries.
// We are not using filepath.readDirNames because we do not want to sort files found in a directory before changing
// permissions for performance reasons.
func readDirNames(dirname string) ([]string, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return nil, err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	return names, nil
}

// walkDeep can be used to traverse directories and has two minor differences
// from filepath.Walk:
//   - List of files/dirs is not sorted for performance reasons
//   - callback walkFunc is invoked on root directory after visiting children dirs and files
func walkDeep(root string, walkFunc filepath.WalkFunc) error {
	info, err := os.Lstat(root)
	if err != nil {
		return walkFunc(root, nil, err)
	}
	return walk(root, info, walkFunc)
}

func walk(path string, info os.FileInfo, walkFunc filepath.WalkFunc) error {
	if !info.IsDir() {
		return walkFunc(path, info, nil)
	}
	names, err := readDirNames(path)
	if err != nil {
		return err
	}
	for _, name := range names {
		filename := filepath.Join(path, name)
		fileInfo, err := os.Lstat(filename)
		if err != nil {
			if err := walkFunc(filename, fileInfo, err); err != nil {
				return err
			}
		} else {
			err = walk(filename, fileInfo, walkFunc)
			if err != nil {
				return err
			}
		}
	}
	return walkFunc(path, info, nil)
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://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 webrtcclient

import (
	"fmt"
	"sync"

	"github.com/pion/webrtc/v3"
)

type Signaling struct {
	SendCh chan interface{}
	RecvCh chan map[string]interface{}
	// The ICE servers to use in the webRTC connection.
	ICEServers []webrtc.ICEServer
	// The servers that were created client side and need to be sent to the device.
	// This is typically a subset of Servers. Ignored if empty.
	ClientICEServers []webrtc.ICEServer
}

type Controller struct {
	signaling      *Signaling
	peerConnection *webrtc.PeerConnection
	observer       Observer
}

func (c Controller) connect(onComplete func(error)) {
	// Provide sufficient buffer in case several events are triggered when the peer
	// connection fails or closes.
	closeCh := make(chan bool, 10)
	c.peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
		switch s {
		case webrtc.PeerConnectionStateConnected:
			// Connected successfully
			onComplete(nil)
		case webrtc.PeerConnectionStateFailed:
			onComplete(fmt.Errorf("Peer Connection failed"))
			closeCh <- true
		case webrtc.PeerConnectionStateClosed:
			onComplete(fmt.Errorf("Peer Connection closed unexpectedly"))
			closeCh <- true
		default:
			// These are temporary states, ignore.
		}
	})

	c.peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
		if candidate == nil {
			return
		}
		c.sendICECandidate(candidate.ToJSON())
	})

	// Handle signaling messages in a new go routine so this one can return when
	// the connection is established.
	go func() {
		err := c.recvLoop(closeCh)
		if err != nil {
			c.observer.OnError(err)
		}
	}()

	// Request offer from the device to start the signaling process.
	c.signaling.SendCh <- NewRequestOfferMsg(c.signaling.ClientICEServers)
}

func (c Controller) recvLoop(closeCh chan bool) error {
	for {
		select {
		case _ = <-closeCh:
			return nil
		case msg, open := <-c.signaling.RecvCh:
			if !open {
				return fmt.Errorf("Signaling channel closed")
			}
			switch msg["type"] {
			case RequestOfferMsgType:
				panic("Device requested an offer. This violates the signaling protocol")
			case OfferMsgType:
				var offer webrtc.SessionDescription
				err := Reshape(msg, &offer)
				if err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Errorf("Failed to reshape json: ", err))
				}
				err = c.onOffer(offer)
				if err != nil {
					return fmt.Errorf("Error handling offer: %v", err)
				}
			case ICECandidateMsgType:
				var candidate webrtc.ICECandidateInit
				err := Reshape(msg, &candidate)
				if err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Errorf("Failed to reshape json: ", err))
				}
				err = c.onICECandidate(candidate)
				if err != nil {
					return fmt.Errorf("Error handling ICE candidate: %v", err)
				}
			case AnswerMsgType:
				var answer webrtc.SessionDescription
				err := Reshape(msg, &answer)
				if err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Errorf("Failed to reshape json: ", err))
				}
				err = c.onAnswer(answer)
				if err != nil {
					return fmt.Errorf("Error handling answer: %v", err)
				}
			default:
				if errMsg, errPresent := msg["error"]; errPresent {
					return fmt.Errorf("Received error from signaling server: %v", errMsg)
				} else {
					return fmt.Errorf("Unknown message type: %v", msg["type"])
				}
			}
		}
	}
}

func (c Controller) onOffer(offer webrtc.SessionDescription) error {
	err := c.peerConnection.SetRemoteDescription(offer)
	if err != nil {
		return fmt.Errorf("Failed to set remote description: %v", err)
	}
	answer, err := c.peerConnection.CreateAnswer(nil)
	if err != nil {
		return fmt.Errorf("Failed to create answer: %v", err)
	}
	fmt.Println("Answer created -===-=-=-=--=-=-==-=")
	err = c.peerConnection.SetLocalDescription(answer)
	if err != nil {
		return fmt.Errorf("Failed to set local description: %v", err)
	}
	c.signaling.SendCh <- answer
	return nil
}

func (c Controller) onAnswer(answer webrtc.SessionDescription) error {
	err := c.peerConnection.SetRemoteDescription(answer)
	if err != nil {
		return fmt.Errorf("Failed to set remote description: %v", err)
	}
	return nil
}

func (c Controller) onICECandidate(candidate webrtc.ICECandidateInit) error {
	err := c.peerConnection.AddICECandidate(candidate)
	if err != nil {
		return fmt.Errorf("Failed to add ICE candidate: %v", err)
	}
	return nil
}

func (c Controller) sendICECandidate(candidate webrtc.ICECandidateInit) {
	c.signaling.SendCh <- NewICECandidateMsg(candidate)
}

type Observer interface {
	OnADBDataChannel(*webrtc.DataChannel)
	OnError(error)
}

type Connection struct {
	controller Controller
}

// Connects to a device. Blocks until the connection is established successfully
// or fails. If the returned error is not nil the Connection should be
// ignored.
func NewDeviceConnection(signaling *Signaling, observer Observer) (*Connection, error) {
	cfg := webrtc.Configuration{
		SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
		ICEServers:   signaling.ICEServers,
	}
	pc, err := webrtc.NewPeerConnection(cfg)
	if err != nil {
		return nil, fmt.Errorf("Failed to create peer connection: %v", err)
	}
	adbChannel, err := pc.CreateDataChannel("adb-channel", nil /*options*/)
	if err != nil {
		return nil, fmt.Errorf("Failed to create adb data channel: %v", err)
	}
	pc.OnNegotiationNeeded(func() {
		// TODO(jemoreira): This needs to be handled when unnecessary tracks and
		// channels are removed from the peer connection.
		fmt.Println("Negotiation needed")
	})
	pc.OnTrack(func(tr *webrtc.TrackRemote, rtpRec *webrtc.RTPReceiver) {
		// TODO(jemoreira): Remove from the peer connection to save bandwidth.
		fmt.Printf("New track: %s\n", tr.ID())
	})

	observer.OnADBDataChannel(adbChannel)

	ret := &Connection{
		controller: Controller{
			signaling:      signaling,
			peerConnection: pc,
			observer:       observer,
		},
	}

	ch := make(chan error)
	// The channel will be read from only once, so make sure the other side only
	// writes once too.
	var once sync.Once
	ret.controller.connect(func(err error) {
		once.Do(func() {
			ch <- err
		})
	})
	err = <-ch
	return ret, err
}

func (dc Connection) Close() {
	dc.controller.peerConnection.Close()
}
<commit_msg>Address review comments<commit_after>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://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 webrtcclient

import (
	"fmt"
	"sync"

	"github.com/pion/webrtc/v3"
)

type Signaling struct {
	SendCh chan interface{}
	RecvCh chan map[string]interface{}
	// The ICE servers to use in the webRTC connection.
	ICEServers []webrtc.ICEServer
	// The servers that were created client side and need to be sent to the device.
	// This is typically a subset of Servers. Ignored if empty.
	ClientICEServers []webrtc.ICEServer
}

type Controller struct {
	signaling      *Signaling
	peerConnection *webrtc.PeerConnection
	observer       Observer
}

func (c *Controller) connect(onComplete func(error)) {
	// Provide sufficient buffer in case several events are triggered when the peer
	// connection fails or closes.
	closeCh := make(chan bool, 10)
	c.peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
		switch s {
		case webrtc.PeerConnectionStateConnected:
			// Connected successfully
			onComplete(nil)
		case webrtc.PeerConnectionStateFailed:
			onComplete(fmt.Errorf("Peer Connection failed"))
			closeCh <- true
		case webrtc.PeerConnectionStateClosed:
			onComplete(fmt.Errorf("Peer Connection closed unexpectedly"))
			closeCh <- true
		default:
			// These are temporary states, ignore.
		}
	})

	c.peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
		if candidate == nil {
			return
		}
		c.sendICECandidate(candidate.ToJSON())
	})

	// Handle signaling messages in a new go routine so this one can return when
	// the connection is established.
	go func() {
		err := c.recvLoop(closeCh)
		if err != nil {
			c.observer.OnError(err)
		}
	}()

	// Request offer from the device to start the signaling process.
	c.signaling.SendCh <- NewRequestOfferMsg(c.signaling.ClientICEServers)
}

func (c *Controller) recvLoop(closeCh chan bool) error {
	for {
		select {
		case _ = <-closeCh:
			return nil
		case msg, open := <-c.signaling.RecvCh:
			if !open {
				return fmt.Errorf("Signaling channel closed")
			}
			switch msg["type"] {
			case RequestOfferMsgType:
				panic("Device requested an offer. This violates the signaling protocol")
			case OfferMsgType:
				var offer webrtc.SessionDescription
				if err := Reshape(msg, &offer); err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Sprintf("Failed to reshape json: %v", err))
				}
				if err := c.onOffer(offer); err != nil {
					return fmt.Errorf("Error handling offer: %w", err)
				}
			case ICECandidateMsgType:
				var candidate webrtc.ICECandidateInit
				if err := Reshape(msg, &candidate); err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Sprintf("Failed to reshape json: %v", err))
				}
				if err := c.onICECandidate(candidate); err != nil {
					return fmt.Errorf("Error handling ICE candidate: %w", err)
				}
			case AnswerMsgType:
				var answer webrtc.SessionDescription
				if err := Reshape(msg, &answer); err != nil {
					// msg was obtained from a JSON string so Reshape shouldn't fail
					panic(fmt.Sprintf("Failed to reshape json: %v", err))
				}
				if err := c.onAnswer(answer); err != nil {
					return fmt.Errorf("Error handling answer: %w", err)
				}
			default:
				if errMsg, errPresent := msg["error"]; errPresent {
					return fmt.Errorf("Received error from signaling server: %v", errMsg)
				} else {
					return fmt.Errorf("Unknown message type: %v", msg["type"])
				}
			}
		}
	}
}

func (c *Controller) onOffer(offer webrtc.SessionDescription) error {
	if err := c.peerConnection.SetRemoteDescription(offer); err != nil {
		return fmt.Errorf("Failed to set remote description: %w", err)
	}
	answer, err := c.peerConnection.CreateAnswer(nil)
	if err != nil {
		return fmt.Errorf("Failed to create answer: %w", err)
	}
	if err = c.peerConnection.SetLocalDescription(answer); err != nil {
		return fmt.Errorf("Failed to set local description: %w", err)
	}
	c.signaling.SendCh <- answer
	return nil
}

func (c *Controller) onAnswer(answer webrtc.SessionDescription) error {
	err := c.peerConnection.SetRemoteDescription(answer)
	if err != nil {
		return fmt.Errorf("Failed to set remote description: %w", err)
	}
	return nil
}

func (c *Controller) onICECandidate(candidate webrtc.ICECandidateInit) error {
	err := c.peerConnection.AddICECandidate(candidate)
	if err != nil {
		return fmt.Errorf("Failed to add ICE candidate: %w", err)
	}
	return nil
}

func (c *Controller) sendICECandidate(candidate webrtc.ICECandidateInit) {
	c.signaling.SendCh <- NewICECandidateMsg(candidate)
}

type Observer interface {
	OnADBDataChannel(*webrtc.DataChannel)
	OnError(error)
}

type Connection struct {
	controller Controller
}

// Connects to a device. Blocks until the connection is established successfully
// or fails. If the returned error is not nil the Connection should be
// ignored.
func NewDeviceConnection(signaling *Signaling, observer Observer) (*Connection, error) {
	cfg := webrtc.Configuration{
		SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
		ICEServers:   signaling.ICEServers,
	}
	pc, err := webrtc.NewPeerConnection(cfg)
	if err != nil {
		return nil, fmt.Errorf("Failed to create peer connection: %w", err)
	}
	adbChannel, err := pc.CreateDataChannel("adb-channel", nil /*options*/)
	if err != nil {
		return nil, fmt.Errorf("Failed to create adb data channel: %w", err)
	}
	pc.OnNegotiationNeeded(func() {
		// TODO(jemoreira): This needs to be handled when unnecessary tracks and
		// channels are removed from the peer connection.
		fmt.Println("Negotiation needed")
	})
	pc.OnTrack(func(tr *webrtc.TrackRemote, rtpRec *webrtc.RTPReceiver) {
		// TODO(jemoreira): Remove from the peer connection to save bandwidth.
		fmt.Printf("New track: %s\n", tr.ID())
	})

	observer.OnADBDataChannel(adbChannel)

	ret := &Connection{
		controller: Controller{
			signaling:      signaling,
			peerConnection: pc,
			observer:       observer,
		},
	}

	ch := make(chan error)
	// The channel will be read from only once, so make sure the other side only
	// writes once too.
	var once sync.Once
	ret.controller.connect(func(err error) {
		once.Do(func() {
			ch <- err
		})
	})
	err = <-ch
	return ret, err
}

func (dc Connection) Close() {
	dc.controller.peerConnection.Close()
}
<|endoftext|>


<commit_before>// Copyright 2016 CoreOS, Inc.
//
// 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 aws

import (
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/iam"
)

type EC2ImageType string

const (
	EC2ImageTypeHVM EC2ImageType = "hvm"
	EC2ImageTypePV  EC2ImageType = "paravirtual"
)

type EC2ImageFormat string

const (
	EC2ImageFormatRaw  EC2ImageFormat = ec2.DiskImageFormatRaw
	EC2ImageFormatVmdk                = ec2.DiskImageFormatVmdk
)

// TODO, these can be derived at runtime
var akis = map[string]string{
	"us-east-1":      "aki-919dcaf8",
	"us-east-2":      "aki-da055ebf",
	"us-west-1":      "aki-880531cd",
	"us-west-2":      "aki-fc8f11cc",
	"eu-west-1":      "aki-52a34525",
	"eu-west-2":      "aki-8b6369ef",
	"eu-central-1":   "aki-184c7a05",
	"ap-south-1":     "aki-a7305ac8",
	"ap-southeast-1": "aki-503e7402",
	"ap-southeast-2": "aki-c362fff9",
	"ap-northeast-1": "aki-176bf516",
	"ap-northeast-2": "aki-01a66b6f",
	"sa-east-1":      "aki-5553f448",
	"ca-central-1":   "aki-320ebd56",

	"us-gov-west-1": "aki-1de98d3e",
}

func (e *EC2ImageFormat) Set(s string) error {
	switch s {
	case string(EC2ImageFormatVmdk):
		*e = EC2ImageFormatVmdk
	case string(EC2ImageFormatRaw):
		*e = EC2ImageFormatRaw
	default:
		return fmt.Errorf("invalid ec2 image format: must be raw or vmdk")
	}
	return nil
}

func (e *EC2ImageFormat) String() string {
	return string(*e)
}

func (e *EC2ImageFormat) Type() string {
	return "ec2ImageFormat"
}

var vmImportRole = "vmimport"

type Snapshot struct {
	SnapshotID string
}

// CreateSnapshot creates an AWS Snapshot
func (a *API) CreateSnapshot(description, sourceURL string, format EC2ImageFormat) (*Snapshot, error) {
	if format == "" {
		format = EC2ImageFormatVmdk
	}
	s3url, err := url.Parse(sourceURL)
	if err != nil {
		return nil, err
	}
	if s3url.Scheme != "s3" {
		return nil, fmt.Errorf("source must have a 's3://' scheme, not: '%v://'", s3url.Scheme)
	}
	s3key := strings.TrimPrefix(s3url.Path, "/")

	importRes, err := a.ec2.ImportSnapshot(&ec2.ImportSnapshotInput{
		RoleName:    aws.String(vmImportRole),
		Description: aws.String(description),
		DiskContainer: &ec2.SnapshotDiskContainer{
			// TODO(euank): allow s3 source / local file -> s3 source
			UserBucket: &ec2.UserBucket{
				S3Bucket: aws.String(s3url.Host),
				S3Key:    aws.String(s3key),
			},
			Format: aws.String(string(format)),
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to create import snapshot task: %v", err)
	}

	plog.Infof("created snapshot import task %v", *importRes.ImportTaskId)

	snapshotDone := func(snapshotTaskID string) (bool, string, error) {
		taskRes, err := a.ec2.DescribeImportSnapshotTasks(&ec2.DescribeImportSnapshotTasksInput{
			ImportTaskIds: []*string{aws.String(snapshotTaskID)},
		})
		if err != nil {
			return false, "", err
		}

		details := taskRes.ImportSnapshotTasks[0].SnapshotTaskDetail

		// I dream of AWS specifying this as an enum shape, not string
		switch *details.Status {
		case "completed":
			return true, *details.SnapshotId, nil
		case "pending", "active":
			plog.Debugf("waiting for import task: %v (%v): %v", *details.Status, *details.Progress, *details.StatusMessage)
			return false, "", nil
		case "cancelled", "cancelling":
			return false, "", fmt.Errorf("import task cancelled")
		case "deleted", "deleting":
			errMsg := "unknown error occured importing snapshot"
			if details.StatusMessage != nil {
				errMsg = *details.StatusMessage
			}
			return false, "", fmt.Errorf("could not import snapshot: %v", errMsg)
		default:
			return false, "", fmt.Errorf("unexpected status: %v", *details.Status)
		}
	}

	// TODO(euank): write a waiter for import snapshot
	for {
		done, snapshotID, err := snapshotDone(*importRes.ImportTaskId)
		if err != nil {
			return nil, err
		}
		if done {
			return &Snapshot{
				SnapshotID: snapshotID,
			}, nil
		}
		time.Sleep(20 * time.Second)
	}
}

func (a *API) CreateImportRole(bucket string) error {
	iamc := iam.New(a.session)
	_, err := iamc.GetRole(&iam.GetRoleInput{
		RoleName: &vmImportRole,
	})
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "NoSuchEntity" {
			// Role does not exist, let's try to create it
			_, err := iamc.CreateRole(&iam.CreateRoleInput{
				RoleName: &vmImportRole,
				AssumeRolePolicyDocument: aws.String(`{
					"Version": "2012-10-17",
					"Statement": [{
						"Effect": "Allow",
						"Condition": {
							"StringEquals": {
								"sts:ExternalId": "vmimport"
							}
						},
						"Action": "sts:AssumeRole",
						"Principal": {
							"Service": "vmie.amazonaws.com"
						}
					}]
				}`),
			})
			if err != nil {
				return fmt.Errorf("coull not create vmimport role: %v", err)
			}
		}
	}

	// by convention, name our policies after the bucket so we can identify
	// whether a regional bucket is covered by a policy without parsing the
	// policy-doc json
	policyName := bucket
	_, err = iamc.GetRolePolicy(&iam.GetRolePolicyInput{
		RoleName:   &vmImportRole,
		PolicyName: &policyName,
	})
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "NoSuchEntity" {
			// Policy does not exist, let's try to create it
			_, err := iamc.PutRolePolicy(&iam.PutRolePolicyInput{
				RoleName:   &vmImportRole,
				PolicyName: &policyName,
				PolicyDocument: aws.String((`{
	"Version": "2012-10-17",
	"Statement": [{
		"Effect": "Allow",
		"Action": [
			"s3:ListBucket",
			"s3:GetBucketLocation",
			"s3:GetObject"
		],
		"Resource": [
			"arn:aws:s3:::` + bucket + `",
			"arn:aws:s3:::` + bucket + `/*"
		]
	},
	{
		"Effect": "Allow",
		"Action": [
			"ec2:ModifySnapshotAttribute",
			"ec2:CopySnapshot",
			"ec2:RegisterImage",
			"ec2:Describe*"
		],
		"Resource": "*"
	}]
}`)),
			})
			if err != nil {
				return fmt.Errorf("could not create role policy: %v", err)
			}
		} else {
			return err
		}
	}

	return nil
}

func (a *API) CreateHVMImage(snapshotID string, name string, description string) (string, error) {
	res, err := a.ec2.RegisterImage(registerImageParams(snapshotID, name+"-hvm", description, "xvd", EC2ImageTypeHVM))
	if err != nil {
		return "", fmt.Errorf("error creating hvm AMI: %v", err)
	}
	return *res.ImageId, nil
}

func (a *API) CreatePVImage(snapshotID string, name string, description string) (string, error) {
	params := registerImageParams(snapshotID, name, description, "sd", EC2ImageTypePV)
	params.KernelId = aws.String(akis[a.opts.Region])
	res, err := a.ec2.RegisterImage(params)
	if err != nil {
		return "", fmt.Errorf("error creating paravirtual AMI: %v", err)
	}
	return *res.ImageId, nil
}

const diskSize = 8 // GB

func registerImageParams(snapshotID, name, description string, diskBaseName string, imageType EC2ImageType) *ec2.RegisterImageInput {
	return &ec2.RegisterImageInput{
		Name:               aws.String(name),
		Description:        aws.String(description),
		Architecture:       aws.String("x86_64"),
		VirtualizationType: aws.String(string(imageType)),
		RootDeviceName:     aws.String(fmt.Sprintf("/dev/%sa", diskBaseName)),
		BlockDeviceMappings: []*ec2.BlockDeviceMapping{
			&ec2.BlockDeviceMapping{
				DeviceName: aws.String(fmt.Sprintf("/dev/%sa", diskBaseName)),
				Ebs: &ec2.EbsBlockDevice{
					SnapshotId:          aws.String(snapshotID),
					DeleteOnTermination: aws.Bool(true),
					VolumeSize:          aws.Int64(diskSize),
				},
			},
			&ec2.BlockDeviceMapping{
				DeviceName:  aws.String(fmt.Sprintf("/dev/%sb", diskBaseName)),
				VirtualName: aws.String("ephemeral0"),
			},
		},
	}
}
<commit_msg>platform/api/aws: Enable ENA networking<commit_after>// Copyright 2016 CoreOS, Inc.
//
// 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 aws

import (
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/iam"
)

type EC2ImageType string

const (
	EC2ImageTypeHVM EC2ImageType = "hvm"
	EC2ImageTypePV  EC2ImageType = "paravirtual"
)

type EC2ImageFormat string

const (
	EC2ImageFormatRaw  EC2ImageFormat = ec2.DiskImageFormatRaw
	EC2ImageFormatVmdk                = ec2.DiskImageFormatVmdk
)

// TODO, these can be derived at runtime
var akis = map[string]string{
	"us-east-1":      "aki-919dcaf8",
	"us-east-2":      "aki-da055ebf",
	"us-west-1":      "aki-880531cd",
	"us-west-2":      "aki-fc8f11cc",
	"eu-west-1":      "aki-52a34525",
	"eu-west-2":      "aki-8b6369ef",
	"eu-central-1":   "aki-184c7a05",
	"ap-south-1":     "aki-a7305ac8",
	"ap-southeast-1": "aki-503e7402",
	"ap-southeast-2": "aki-c362fff9",
	"ap-northeast-1": "aki-176bf516",
	"ap-northeast-2": "aki-01a66b6f",
	"sa-east-1":      "aki-5553f448",
	"ca-central-1":   "aki-320ebd56",

	"us-gov-west-1": "aki-1de98d3e",
}

func (e *EC2ImageFormat) Set(s string) error {
	switch s {
	case string(EC2ImageFormatVmdk):
		*e = EC2ImageFormatVmdk
	case string(EC2ImageFormatRaw):
		*e = EC2ImageFormatRaw
	default:
		return fmt.Errorf("invalid ec2 image format: must be raw or vmdk")
	}
	return nil
}

func (e *EC2ImageFormat) String() string {
	return string(*e)
}

func (e *EC2ImageFormat) Type() string {
	return "ec2ImageFormat"
}

var vmImportRole = "vmimport"

type Snapshot struct {
	SnapshotID string
}

// CreateSnapshot creates an AWS Snapshot
func (a *API) CreateSnapshot(description, sourceURL string, format EC2ImageFormat) (*Snapshot, error) {
	if format == "" {
		format = EC2ImageFormatVmdk
	}
	s3url, err := url.Parse(sourceURL)
	if err != nil {
		return nil, err
	}
	if s3url.Scheme != "s3" {
		return nil, fmt.Errorf("source must have a 's3://' scheme, not: '%v://'", s3url.Scheme)
	}
	s3key := strings.TrimPrefix(s3url.Path, "/")

	importRes, err := a.ec2.ImportSnapshot(&ec2.ImportSnapshotInput{
		RoleName:    aws.String(vmImportRole),
		Description: aws.String(description),
		DiskContainer: &ec2.SnapshotDiskContainer{
			// TODO(euank): allow s3 source / local file -> s3 source
			UserBucket: &ec2.UserBucket{
				S3Bucket: aws.String(s3url.Host),
				S3Key:    aws.String(s3key),
			},
			Format: aws.String(string(format)),
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to create import snapshot task: %v", err)
	}

	plog.Infof("created snapshot import task %v", *importRes.ImportTaskId)

	snapshotDone := func(snapshotTaskID string) (bool, string, error) {
		taskRes, err := a.ec2.DescribeImportSnapshotTasks(&ec2.DescribeImportSnapshotTasksInput{
			ImportTaskIds: []*string{aws.String(snapshotTaskID)},
		})
		if err != nil {
			return false, "", err
		}

		details := taskRes.ImportSnapshotTasks[0].SnapshotTaskDetail

		// I dream of AWS specifying this as an enum shape, not string
		switch *details.Status {
		case "completed":
			return true, *details.SnapshotId, nil
		case "pending", "active":
			plog.Debugf("waiting for import task: %v (%v): %v", *details.Status, *details.Progress, *details.StatusMessage)
			return false, "", nil
		case "cancelled", "cancelling":
			return false, "", fmt.Errorf("import task cancelled")
		case "deleted", "deleting":
			errMsg := "unknown error occured importing snapshot"
			if details.StatusMessage != nil {
				errMsg = *details.StatusMessage
			}
			return false, "", fmt.Errorf("could not import snapshot: %v", errMsg)
		default:
			return false, "", fmt.Errorf("unexpected status: %v", *details.Status)
		}
	}

	// TODO(euank): write a waiter for import snapshot
	for {
		done, snapshotID, err := snapshotDone(*importRes.ImportTaskId)
		if err != nil {
			return nil, err
		}
		if done {
			return &Snapshot{
				SnapshotID: snapshotID,
			}, nil
		}
		time.Sleep(20 * time.Second)
	}
}

func (a *API) CreateImportRole(bucket string) error {
	iamc := iam.New(a.session)
	_, err := iamc.GetRole(&iam.GetRoleInput{
		RoleName: &vmImportRole,
	})
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "NoSuchEntity" {
			// Role does not exist, let's try to create it
			_, err := iamc.CreateRole(&iam.CreateRoleInput{
				RoleName: &vmImportRole,
				AssumeRolePolicyDocument: aws.String(`{
					"Version": "2012-10-17",
					"Statement": [{
						"Effect": "Allow",
						"Condition": {
							"StringEquals": {
								"sts:ExternalId": "vmimport"
							}
						},
						"Action": "sts:AssumeRole",
						"Principal": {
							"Service": "vmie.amazonaws.com"
						}
					}]
				}`),
			})
			if err != nil {
				return fmt.Errorf("coull not create vmimport role: %v", err)
			}
		}
	}

	// by convention, name our policies after the bucket so we can identify
	// whether a regional bucket is covered by a policy without parsing the
	// policy-doc json
	policyName := bucket
	_, err = iamc.GetRolePolicy(&iam.GetRolePolicyInput{
		RoleName:   &vmImportRole,
		PolicyName: &policyName,
	})
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "NoSuchEntity" {
			// Policy does not exist, let's try to create it
			_, err := iamc.PutRolePolicy(&iam.PutRolePolicyInput{
				RoleName:   &vmImportRole,
				PolicyName: &policyName,
				PolicyDocument: aws.String((`{
	"Version": "2012-10-17",
	"Statement": [{
		"Effect": "Allow",
		"Action": [
			"s3:ListBucket",
			"s3:GetBucketLocation",
			"s3:GetObject"
		],
		"Resource": [
			"arn:aws:s3:::` + bucket + `",
			"arn:aws:s3:::` + bucket + `/*"
		]
	},
	{
		"Effect": "Allow",
		"Action": [
			"ec2:ModifySnapshotAttribute",
			"ec2:CopySnapshot",
			"ec2:RegisterImage",
			"ec2:Describe*"
		],
		"Resource": "*"
	}]
}`)),
			})
			if err != nil {
				return fmt.Errorf("could not create role policy: %v", err)
			}
		} else {
			return err
		}
	}

	return nil
}

func (a *API) CreateHVMImage(snapshotID string, name string, description string) (string, error) {
	params := registerImageParams(snapshotID, name+"-hvm", description, "xvd", EC2ImageTypeHVM)
	params.EnaSupport = aws.Bool(true)
	res, err := a.ec2.RegisterImage(params)
	if err != nil {
		return "", fmt.Errorf("error creating hvm AMI: %v", err)
	}
	return *res.ImageId, nil
}

func (a *API) CreatePVImage(snapshotID string, name string, description string) (string, error) {
	params := registerImageParams(snapshotID, name, description, "sd", EC2ImageTypePV)
	params.KernelId = aws.String(akis[a.opts.Region])
	res, err := a.ec2.RegisterImage(params)
	if err != nil {
		return "", fmt.Errorf("error creating paravirtual AMI: %v", err)
	}
	return *res.ImageId, nil
}

const diskSize = 8 // GB

func registerImageParams(snapshotID, name, description string, diskBaseName string, imageType EC2ImageType) *ec2.RegisterImageInput {
	return &ec2.RegisterImageInput{
		Name:               aws.String(name),
		Description:        aws.String(description),
		Architecture:       aws.String("x86_64"),
		VirtualizationType: aws.String(string(imageType)),
		RootDeviceName:     aws.String(fmt.Sprintf("/dev/%sa", diskBaseName)),
		BlockDeviceMappings: []*ec2.BlockDeviceMapping{
			&ec2.BlockDeviceMapping{
				DeviceName: aws.String(fmt.Sprintf("/dev/%sa", diskBaseName)),
				Ebs: &ec2.EbsBlockDevice{
					SnapshotId:          aws.String(snapshotID),
					DeleteOnTermination: aws.Bool(true),
					VolumeSize:          aws.Int64(diskSize),
				},
			},
			&ec2.BlockDeviceMapping{
				DeviceName:  aws.String(fmt.Sprintf("/dev/%sb", diskBaseName)),
				VirtualName: aws.String("ephemeral0"),
			},
		},
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"testing"
)

func runTemplateFunctionTest(t *testing.T,
	funcName, arg, expected string) {

	result := funcMap[funcName].(func(string) string)(arg)

	if result != expected {
		t.Error("Error: \"" + result + "\" != \"" + expected + "\"")
	}
}

func TestTemplateFunctions(t *testing.T) {
	runTemplateFunctionTest(t, "VarName", "C++11", "Cxx11")
	runTemplateFunctionTest(t, "VarName", "one-half", "one_half")

	runTemplateFunctionTest(t, "VarNameUC", "C++11", "CXX11")
	runTemplateFunctionTest(t, "VarNameUC", "cross-country", "CROSS_COUNTRY")

	runTemplateFunctionTest(t, "LibName", "libc++11", "libc++11")
	runTemplateFunctionTest(t, "LibName", "dash-dot.", "dash-dot.")
}
<commit_msg>Break a line exceeding 80 chars into two<commit_after>// Copyright (C) 2017 Damon Revoe. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.

package main

import (
	"testing"
)

func runTemplateFunctionTest(t *testing.T,
	funcName, arg, expected string) {

	result := funcMap[funcName].(func(string) string)(arg)

	if result != expected {
		t.Error("Error: \"" + result + "\" != \"" + expected + "\"")
	}
}

func TestTemplateFunctions(t *testing.T) {
	runTemplateFunctionTest(t, "VarName", "C++11", "Cxx11")
	runTemplateFunctionTest(t, "VarName", "one-half", "one_half")

	runTemplateFunctionTest(t, "VarNameUC", "C++11", "CXX11")
	runTemplateFunctionTest(t, "VarNameUC",
		"cross-country", "CROSS_COUNTRY")

	runTemplateFunctionTest(t, "LibName", "libc++11", "libc++11")
	runTemplateFunctionTest(t, "LibName", "dash-dot.", "dash-dot.")
}
<|endoftext|>


<commit_before>package test

import (
	"time"

	. "github.com/onsi/ginkgo"
)

var _ = Describe("Issues", func() {

	const (
		nsParent = "parent"
		nsChild = "child"
		nsSub1 = "sub1"
		nsSub2 = "sub2"
		nsSub1Sub1 = "sub1-sub1"
		nsSub2Sub1 = "sub2-sub1"
		nsSubSub2 = "sub-sub2"
		nsSubChild = "sub-child"
		nsSubSubChild = "sub-sub-child"
	)

	BeforeEach(func() {
		cleanupNamespaces(nsParent, nsChild, nsSub1, nsSub2, nsSub1Sub1, nsSub2Sub1,
			nsSubSub2, nsSubChild, nsSubSubChild)
	})

	AfterEach(func() {
		cleanupNamespaces(nsParent, nsChild, nsSub1, nsSub2, nsSub1Sub1, nsSub2Sub1,
			nsSubSub2, nsSubChild, nsSubSubChild)
	})

	It("Should remove obsolete conditions CannotPropagateObject and CannotUpdateObject - issue #328", func() {
		// Setting up hierarchy with rolebinding that HNC doesn't have permission to copy.
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		// cluster-admin is the highest-powered ClusterRole and HNC is missing some of
		// its permissions, so it cannot propagate it.
		mustRun("kubectl create rolebinding cluster-admin-rb -n", nsParent,
			"--clusterrole='cluster-admin' --serviceaccount="+nsParent+":default")
		// Tree should show CannotPropagateObject in nsParent and CannotUpdateObject in nsChild
		runShouldContainMultiple([]string{"1) CannotPropagateObject", "2) CannotUpdateObject"}, 1, "kubectl hns tree", nsParent)
		// Remove the child and verify that the condition is gone
		mustRun("kubectl hns set", nsChild, "--root")
		// There should no longer be any conditions in parent and child
		runShouldContain("No conditions", 1, "kubectl hns describe", nsParent)
		runShouldContain("No conditions", 1, "kubectl hns describe", nsChild)
	})

	It("Should set SubnamespaceAnchorMissing condition if the anchor is missing - issue #501", func() {
		// Setting up a 3-level tree with 'parent' as the root
		mustRun("kubectl create ns", nsParent)
		// create a subnamespace without anchor by creating a full namespace with SubnamespaceOf annotation
		mustRun("kubectl create ns", nsSub1)
		mustRun("kubectl hns set", nsSub1, "--parent", nsParent)
		mustRun("kubectl annotate ns", nsSub1, "hnc.x-k8s.io/subnamespaceOf=" + nsParent)
		// If the subnamespace doesn't allow cascadingDelete and the anchor is missing in the parent namespace, it should have 'SubnamespaceAnchorMissing' condition while its descendants shoudn't have any conditions."
		// Expected: 'sub1' namespace is not deleted and should have 'SubnamespaceAnchorMissing' condition; no other conditions."
		runShouldContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
	})

	It("Should unset SubnamespaceAnchorMissing condition if the anchor is re-added - issue #501", func(){
		// set up
		mustRun("kubectl create ns", nsParent)
		// create a subnamespace without anchor by creating a full namespace with SubnamespaceOf annotation
		mustRun("kubectl create ns", nsSub1)
		mustRun("kubectl hns set", nsSub1, "--parent", nsParent)
		mustRun("kubectl annotate ns", nsSub1, "hnc.x-k8s.io/subnamespaceOf=" + nsParent)
		runShouldContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
		// If the anchor is re-added, it should unset the 'SubnamespaceAnchorMissing' condition in the subnamespace.
		// Operation: recreate the 'sub1' subns in 'parent' - kubectl hns create sub1 -n parent
		// Expected: no conditions.
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		runShouldNotContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
	})

	It("Should cascading delete immediate subnamespaces if the anchor is deleted and the subnamespace allows cascadingDelete - issue #501", func() {
		// set up
		mustRun("kubectl create ns", nsParent)
		// Creating the a branch of subnamespace
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		mustRun("kubectl hns create", nsSub1Sub1, "-n", nsSub1)
		mustRun("kubectl hns create", nsSub2Sub1, "-n", nsSub1)
		// If the subnamespace allows cascadingDelete and the anchor is deleted, it should cascading delete all immediate subnamespaces.
		// Operation: 1) allow cascadingDelete in 'ochid1' - kubectl hns set sub1 --allowCascadingDelete=true
		// 2) delete 'sub1' subns in 'parent' - kubectl delete subns sub1 -n parent
		// Expected: 'sub1', 'sub1-sub1', 'sub2-sub1' should all be gone
		mustRun("kubectl hns set", nsSub1, "--allowCascadingDelete=true")
		mustRun("kubectl delete subns", nsSub1, "-n", nsParent)
		runShouldNotContainMultiple([]string {nsSub1, nsSub1Sub1, nsSub2Sub1}, 1, "kubectl hns tree", nsParent)
	})

	It("Should cascading delete all subnamespaces if the parent is deleted and allows cascadingDelete - issue #501", func() {
		// Setting up a 3-level tree with 'parent' as the root
		mustRun("kubectl create ns", nsParent)
		// Creating the 1st branch of subnamespace
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		mustRun("kubectl hns create", nsSub1Sub1, "-n", nsSub1)
		mustRun("kubectl hns create", nsSub2Sub1, "-n", nsSub1)
		// Creating the 2nd branch of subnamespaces
		mustRun("kubectl hns create", nsSub2, "-n", nsParent)
		mustRun("kubectl hns create", nsSubSub2, "-n", nsSub2)
		// Creating the 3rd branch of a mix of full and subnamespaces
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		mustRun("kubectl hns create", nsSubChild, "-n", nsChild)
		// If the parent namespace allows cascadingDelete and it's deleted, all its subnamespaces should be cascading deleted.
		// Operation: 1) allow cascadingDelete in 'parent' - kubectl hns set parent --allowCascadingDelete=true
		// 2) delete 'parent' namespace - kubectl delete ns parent
		// Expected: only 'fullchild' and 'sub-fullchild' should be left and they should have CRIT_ conditions related to missing 'parent'
		mustRun("kubectl hns set", nsParent, "--allowCascadingDelete=true")
		mustRun("kubectl delete ns", nsParent)
		mustNotRun("kubectl hns tree", nsParent)
		mustNotRun("kubectl hns tree", nsSub1)
		mustNotRun("kubectl hns tree", nsSub2)
		runShouldContain("CritParentMissing: missing parent", 1, "kubectl hns tree", nsChild)
		runShouldContain("CritAncestor", 1, "kubectl hns describe", nsSubChild)
	})

	It("Should clear CannotUpdate conditions in descendants when a hierarchy changes - issue #605", func() {
		// Setting up hierarchy with rolebinding that HNC doesn't have permission to copy
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl create ns", nsSubChild)
		mustRun("kubectl create ns", nsSubSubChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		mustRun("kubectl hns set", nsSubChild, "--parent", nsChild)
		mustRun("kubectl hns set", nsSubSubChild, "--parent", nsSubChild)
		// cluster-admin is the highest-powered ClusterRole and HNC is missing some of its permissions, so it cannot propagate it.
		mustRun("kubectl create rolebinding cluster-admin-rb -n", nsParent, "--clusterrole='cluster-admin' --serviceaccount="+nsParent+":default")
		// We put 30s sleep here because - before fixing issue #605, we should see the object gets reconciled after around 8s,
		// triggered by controller-runtime, with this sleep time. After fixing this issue, the obsolete condition should be cleared
		// immediately
		time.Sleep(30 * time.Second)
		// Tree should show CannotPropagateObject in parent and CannotUpdateObject in child and subchild
		runShouldContain("CannotPropagateObject", 1, "kubectl hns describe", nsParent)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsChild)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubChild)
		// Remove the grandchild to avoid removing CannotPropagate condition in parent or CannotUpdate condition in child.
		// Verify that the conditions in grandchild and greatgrandchild are gone.
		mustRun("kubectl hns set", nsSubChild, "--root")
		// We should see sub-child has conditions, and should see sub-sub-child has no conditions immediately after the fix.
		runShouldNotContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubChild)
		runShouldNotContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubSubChild)
		// There should still be CannotPropagate condition in parent and CannotUpdate condition in child
		runShouldContain("CannotPropagateObject", 1, "kubectl hns describe", nsParent)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsChild)
	})
})
<commit_msg>Add test for CritParentMissing condition<commit_after>package test

import (
	"time"

	. "github.com/onsi/ginkgo"
)

var _ = Describe("Issues", func() {

	const (
		nsParent = "parent"
		nsChild = "child"
		nsSub1 = "sub1"
		nsSub2 = "sub2"
		nsSub1Sub1 = "sub1-sub1"
		nsSub2Sub1 = "sub2-sub1"
		nsSubSub2 = "sub-sub2"
		nsSubChild = "sub-child"
		nsSubSubChild = "sub-sub-child"
	)

	BeforeEach(func() {
		cleanupNamespaces(nsParent, nsChild, nsSub1, nsSub2, nsSub1Sub1, nsSub2Sub1,
			nsSubSub2, nsSubChild, nsSubSubChild)
	})

	AfterEach(func() {
		cleanupNamespaces(nsParent, nsChild, nsSub1, nsSub2, nsSub1Sub1, nsSub2Sub1,
			nsSubSub2, nsSubChild, nsSubSubChild)
	})

	It("Should remove obsolete conditions CannotPropagateObject and CannotUpdateObject - issue #328", func() {
		// Setting up hierarchy with rolebinding that HNC doesn't have permission to copy.
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		// cluster-admin is the highest-powered ClusterRole and HNC is missing some of
		// its permissions, so it cannot propagate it.
		mustRun("kubectl create rolebinding cluster-admin-rb -n", nsParent,
			"--clusterrole='cluster-admin' --serviceaccount="+nsParent+":default")
		// Tree should show CannotPropagateObject in nsParent and CannotUpdateObject in nsChild
		runShouldContainMultiple([]string{"1) CannotPropagateObject", "2) CannotUpdateObject"}, 1, "kubectl hns tree", nsParent)
		// Remove the child and verify that the condition is gone
		mustRun("kubectl hns set", nsChild, "--root")
		// There should no longer be any conditions in parent and child
		runShouldContain("No conditions", 1, "kubectl hns describe", nsParent)
		runShouldContain("No conditions", 1, "kubectl hns describe", nsChild)
	})

	It("Should set SubnamespaceAnchorMissing condition if the anchor is missing - issue #501", func() {
		// Setting up a 3-level tree with 'parent' as the root
		mustRun("kubectl create ns", nsParent)
		// create a subnamespace without anchor by creating a full namespace with SubnamespaceOf annotation
		mustRun("kubectl create ns", nsSub1)
		mustRun("kubectl hns set", nsSub1, "--parent", nsParent)
		mustRun("kubectl annotate ns", nsSub1, "hnc.x-k8s.io/subnamespaceOf=" + nsParent)
		// If the subnamespace doesn't allow cascadingDelete and the anchor is missing in the parent namespace, it should have 'SubnamespaceAnchorMissing' condition while its descendants shoudn't have any conditions."
		// Expected: 'sub1' namespace is not deleted and should have 'SubnamespaceAnchorMissing' condition; no other conditions."
		runShouldContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
	})

	It("Should unset SubnamespaceAnchorMissing condition if the anchor is re-added - issue #501", func(){
		// set up
		mustRun("kubectl create ns", nsParent)
		// create a subnamespace without anchor by creating a full namespace with SubnamespaceOf annotation
		mustRun("kubectl create ns", nsSub1)
		mustRun("kubectl hns set", nsSub1, "--parent", nsParent)
		mustRun("kubectl annotate ns", nsSub1, "hnc.x-k8s.io/subnamespaceOf=" + nsParent)
		runShouldContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
		// If the anchor is re-added, it should unset the 'SubnamespaceAnchorMissing' condition in the subnamespace.
		// Operation: recreate the 'sub1' subns in 'parent' - kubectl hns create sub1 -n parent
		// Expected: no conditions.
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		runShouldNotContain("SubnamespaceAnchorMissing", 1, "kubectl get hierarchyconfigurations.hnc.x-k8s.io -n", nsSub1, "-o yaml")
	})

	It("Should cascading delete immediate subnamespaces if the anchor is deleted and the subnamespace allows cascadingDelete - issue #501", func() {
		// set up
		mustRun("kubectl create ns", nsParent)
		// Creating the a branch of subnamespace
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		mustRun("kubectl hns create", nsSub1Sub1, "-n", nsSub1)
		mustRun("kubectl hns create", nsSub2Sub1, "-n", nsSub1)
		// If the subnamespace allows cascadingDelete and the anchor is deleted, it should cascading delete all immediate subnamespaces.
		// Operation: 1) allow cascadingDelete in 'ochid1' - kubectl hns set sub1 --allowCascadingDelete=true
		// 2) delete 'sub1' subns in 'parent' - kubectl delete subns sub1 -n parent
		// Expected: 'sub1', 'sub1-sub1', 'sub2-sub1' should all be gone
		mustRun("kubectl hns set", nsSub1, "--allowCascadingDelete=true")
		mustRun("kubectl delete subns", nsSub1, "-n", nsParent)
		runShouldNotContainMultiple([]string {nsSub1, nsSub1Sub1, nsSub2Sub1}, 1, "kubectl hns tree", nsParent)
	})

	It("Should cascading delete all subnamespaces if the parent is deleted and allows cascadingDelete - issue #501", func() {
		// Setting up a 3-level tree with 'parent' as the root
		mustRun("kubectl create ns", nsParent)
		// Creating the 1st branch of subnamespace
		mustRun("kubectl hns create", nsSub1, "-n", nsParent)
		mustRun("kubectl hns create", nsSub1Sub1, "-n", nsSub1)
		mustRun("kubectl hns create", nsSub2Sub1, "-n", nsSub1)
		// Creating the 2nd branch of subnamespaces
		mustRun("kubectl hns create", nsSub2, "-n", nsParent)
		mustRun("kubectl hns create", nsSubSub2, "-n", nsSub2)
		// Creating the 3rd branch of a mix of full and subnamespaces
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		mustRun("kubectl hns create", nsSubChild, "-n", nsChild)
		// If the parent namespace allows cascadingDelete and it's deleted, all its subnamespaces should be cascading deleted.
		// Operation: 1) allow cascadingDelete in 'parent' - kubectl hns set parent --allowCascadingDelete=true
		// 2) delete 'parent' namespace - kubectl delete ns parent
		// Expected: only 'fullchild' and 'sub-fullchild' should be left and they should have CRIT_ conditions related to missing 'parent'
		mustRun("kubectl hns set", nsParent, "--allowCascadingDelete=true")
		mustRun("kubectl delete ns", nsParent)
		mustNotRun("kubectl hns tree", nsParent)
		mustNotRun("kubectl hns tree", nsSub1)
		mustNotRun("kubectl hns tree", nsSub2)
		runShouldContain("CritParentMissing: missing parent", 1, "kubectl hns tree", nsChild)
		runShouldContain("CritAncestor", 1, "kubectl hns describe", nsSubChild)
	})

	It("Should clear CannotUpdate conditions in descendants when a hierarchy changes - issue #605", func() {
		// Setting up hierarchy with rolebinding that HNC doesn't have permission to copy
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl create ns", nsSubChild)
		mustRun("kubectl create ns", nsSubSubChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		mustRun("kubectl hns set", nsSubChild, "--parent", nsChild)
		mustRun("kubectl hns set", nsSubSubChild, "--parent", nsSubChild)
		// cluster-admin is the highest-powered ClusterRole and HNC is missing some of its permissions, so it cannot propagate it.
		mustRun("kubectl create rolebinding cluster-admin-rb -n", nsParent, "--clusterrole='cluster-admin' --serviceaccount="+nsParent+":default")
		// We put 30s sleep here because - before fixing issue #605, we should see the object gets reconciled after around 8s,
		// triggered by controller-runtime, with this sleep time. After fixing this issue, the obsolete condition should be cleared
		// immediately
		time.Sleep(30 * time.Second)
		// Tree should show CannotPropagateObject in parent and CannotUpdateObject in child and subchild
		runShouldContain("CannotPropagateObject", 1, "kubectl hns describe", nsParent)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsChild)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubChild)
		// Remove the grandchild to avoid removing CannotPropagate condition in parent or CannotUpdate condition in child.
		// Verify that the conditions in grandchild and greatgrandchild are gone.
		mustRun("kubectl hns set", nsSubChild, "--root")
		// We should see sub-child has conditions, and should see sub-sub-child has no conditions immediately after the fix.
		runShouldNotContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubChild)
		runShouldNotContain("CannotUpdateObject", 1, "kubectl hns describe", nsSubSubChild)
		// There should still be CannotPropagate condition in parent and CannotUpdate condition in child
		runShouldContain("CannotPropagateObject", 1, "kubectl hns describe", nsParent)
		runShouldContain("CannotUpdateObject", 1, "kubectl hns describe", nsChild)
	})

	It("Should have CritParentMissing condition when parent namespace is deleted - issue #716", func(){
		// Setting up a 2-level tree with 'a' as the root and 'b' as a child of 'a'"
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		// Test: Remove parent namespace 'a'
		// Expected: b should have 'CritParentMissing' condition
		mustRun("kubectl delete ns", nsParent)
		runShouldContain("CritParentMissing", 1, "kubectl hns describe", nsChild)
	})

	It("Should delete namespace with CritParentMissing condition - issue #716", func(){
		// Setting up a 2-level tree with 'a' as the root and 'b' as a child of 'a'"
		mustRun("kubectl create ns", nsParent)
		mustRun("kubectl create ns", nsChild)
		mustRun("kubectl hns set", nsChild, "--parent", nsParent)
		// create and verify CritParentMissing condition
		mustRun("kubectl delete ns", nsParent)
		runShouldContain("CritParentMissing", 1, "kubectl hns describe", nsChild)
		// test: delete namespace
		mustRun("kubectl delete ns", nsChild)
	})
})
<|endoftext|>