<commit_before>package renter

import (
	"io"
	"os"

	"github.com/NebulousLabs/Sia/crypto"

	"github.com/NebulousLabs/errors"
)

// managedDistributeChunkToWorkers will take a chunk with fully prepared
// physical data and distribute it to the worker pool.
func (r *Renter) managedDistributeChunkToWorkers(uc *unfinishedChunk) {
	// Give the chunk to each worker, marking the number of workers that have
	// received the chunk. The workers cannot be interacted with while the
	// renter is holding a lock, so we need to build a list of workers while
	// under lock and then launch work jobs after that.
	id := r.mu.RLock()
	uc.workersRemaining += len(r.workerPool)
	r.heapWG.Add(len(r.workerPool))
	workers := make([]*worker, 0, len(r.workerPool))
	for _, worker := range r.workerPool {
		workers = append(workers, worker)
	}
	r.mu.RUnlock(id)
	for _, worker := range workers {
		worker.managedQueueChunkRepair(uc)
	}

	// Perform cleanup for any pieces that will never be used by a worker.
	r.managedReleaseIdleChunkPieces(uc)
}

// managedDownloadLogicalChunkData will fetch the logical chunk data by sending a
// download to the renter's downloader, and then using the data that gets
// returned.
func (r *Renter) managedDownloadLogicalChunkData(chunk *unfinishedChunk) error {
	// Create the download, queue the download, and then wait for the download
	// to finish.
	//
	// TODO / NOTE: Once we migrate to the uploader and downloader having a
	// shared memory pool, this part will need to signal to the download group
	// that the memory has already been allocated - upload memory always takes
	// more than download memory, and if we need to allocate two times in a row
	// from the same memory pool while other processes are asynchronously doing
	// the same, we risk deadlock.
	buf := NewDownloadBufferWriter(chunk.length, chunk.offset)
	// TODO: Should convert the inputs of newSectionDownload to use an int64 for
	// the offset.
	d := r.newSectionDownload(chunk.renterFile, buf, uint64(chunk.offset), chunk.length)
	select {
	case r.newDownloads <- d:
	case <-r.tg.StopChan():
		return errors.New("repair download queing interrupted by stop call")
	}
	select {
	case <-d.downloadFinished:
	case <-r.tg.StopChan():
		return errors.New("repair download interrupted by stop call")
	}
	if d.Err() != nil {
		buf.data = nil
		return d.Err()
	}

	chunk.logicalChunkData = buf.Bytes()
	return nil

}

// managedFetchAndRepairChunk will fetch the logical data for a chunk, create
// the physical pieces for the chunk, and then distribute them. The returned
// bool indicates whether the chunk was successfully distributed to workers.
func (r *Renter) managedFetchAndRepairChunk(chunk *unfinishedChunk) bool {
	// Fetch the logical data for the chunk.
	err := r.managedFetchLogicalChunkData(chunk)
	if err != nil {
		// Logical data is not available, nothing to do.
		r.log.Debugln("Fetching logical data of a chunk failed:", err)
		return false
	}

	// Create the physical pieces for the data. Immediately release the logical
	// data.
	chunk.physicalChunkData, err = chunk.renterFile.erasureCode.Encode(chunk.logicalChunkData)
	memoryFreed := uint64(len(chunk.logicalChunkData))
	chunk.logicalChunkData = nil
	r.managedMemoryAvailableAdd(memoryFreed)
	chunk.memoryReleased += memoryFreed
	memoryFreed = 0
	if err != nil {
		// Logical data is not available, nothing to do.
		r.log.Debugln("Fetching physical data of a chunk failed:", err)
		return false
	}

	// Sanity check - we should have at least as many physical data pieces as we
	// do elements in our piece usage.
	if len(chunk.physicalChunkData) < len(chunk.pieceUsage) {
		r.log.Critical("not enough physical pieces to match the upload settings of the file")
		return false
	}
	// Loop through the pieces and encrypt any that our needed, while dropping
	// any pieces that are not needed.
	for i := 0; i < len(chunk.pieceUsage); i++ {
		if chunk.pieceUsage[i] {
			memoryFreed += uint64(len(chunk.physicalChunkData[i]) + crypto.TwofishOverhead)
			chunk.physicalChunkData[i] = nil
		} else {
			// Encrypt the piece.
			key := deriveKey(chunk.renterFile.masterKey, chunk.index, uint64(i))
			chunk.physicalChunkData[i] = key.EncryptBytes(chunk.physicalChunkData[i])
		}
	}
	// Return the released memory.
	r.managedMemoryAvailableAdd(memoryFreed)
	chunk.memoryReleased += memoryFreed

	// Distribute the chunk to the workers.
	r.managedDistributeChunkToWorkers(chunk)
	return true
}

// managedFetchLogicalChunkData will get the raw data for a chunk, pulling it from disk if
// possible but otherwise queueing a download.
//
// chunk.data should be passed as 'nil' to the download, to keep memory usage as
// light as possible.
func (r *Renter) managedFetchLogicalChunkData(chunk *unfinishedChunk) error {
	// Only download this file if more than 25% of the redundancy is missing.
	minMissingPiecesToDownload := (chunk.piecesNeeded - chunk.minimumPieces) / 4
	download := chunk.piecesCompleted+minMissingPiecesToDownload < chunk.piecesNeeded

	// Download the chunk if it's not on disk.
	if chunk.localPath == "" && download {
		return r.managedDownloadLogicalChunkData(chunk)
	} else if chunk.localPath == "" {
		return errors.New("file not available locally")
	}

	// Try to read the data from disk. If that fails at any point, prefer to
	// download the chunk.
	//
	// TODO: Might want to remove the file from the renter tracking if the disk
	// loading fails. Should do this after we swap the file format, the tracking
	// data for the file should reside in the file metadata and not in a
	// separate struct.
	osFile, err := os.Open(chunk.localPath)
	if err != nil && download {
		return r.managedDownloadLogicalChunkData(chunk)
	} else if err != nil {
		return errors.Extend(err, errors.New("failed to open file locally"))
	}
	defer osFile.Close()
	// TODO: Once we have enabled support for small chunks, we should stop
	// needing to ignore the EOF errors, because the chunk size should always
	// match the tail end of the file. Until then, we ignore io.EOF.
	chunk.logicalChunkData = make([]byte, chunk.length)
	_, err = osFile.ReadAt(chunk.logicalChunkData, chunk.offset)
	if err != nil && err != io.EOF && download {
		chunk.logicalChunkData = nil
		return r.managedDownloadLogicalChunkData(chunk)
	} else if err != nil && err != io.EOF {
		chunk.logicalChunkData = nil
		return errors.Extend(err, errors.New("failed to read file locally"))
	}

	// Data successfully read from disk.
	return nil
}

// releaseIdleChunkPieces will drop any chunk pieces that are no longer going to
// be used by workers because the number of remaining pieces is greater than the
// number of remaining workers. The memory will be returned to the renter.
func (r *Renter) managedReleaseIdleChunkPieces(uc *unfinishedChunk) {
	uc.mu.Lock()
	piecesAvailable := 0
	memoryReleased := 0
	for i := 0; i < len(uc.pieceUsage); i++ {
		// Skip the piece if it's not available.
		if uc.pieceUsage[i] {
			continue
		}

		// If we have all the available pieces we need, release this piece.
		// Otherwise, mark that there's another piece available. This algorithm
		// will prefer releasing later pieces, which improves computational
		// complexity for erasure coding.
		if piecesAvailable >= uc.workersRemaining {
			uc.pieceUsage[i] = false
			memoryReleased += len(uc.physicalChunkData[i])
			uc.physicalChunkData[i] = nil
		} else {
			piecesAvailable++
		}
	}
	uc.memoryReleased += uint64(memoryReleased)
	// Sanity check - if there are no workers remaining, we should have released
	// all of the memory by now.
	if uc.workersRemaining == 0 && uc.memoryReleased != uc.memoryNeeded {
		r.log.Critical("No workers remaining, but not all memory released:", uc.workersRemaining, uc.memoryReleased, uc.memoryNeeded)
	}
	uc.mu.Unlock()
	if memoryReleased > 0 {
		r.managedMemoryAvailableAdd(uint64(memoryReleased))
	}
}
<commit_msg>fix grammar in comment<commit_after>package renter

import (
	"io"
	"os"

	"github.com/NebulousLabs/Sia/crypto"

	"github.com/NebulousLabs/errors"
)

// managedDistributeChunkToWorkers will take a chunk with fully prepared
// physical data and distribute it to the worker pool.
func (r *Renter) managedDistributeChunkToWorkers(uc *unfinishedChunk) {
	// Give the chunk to each worker, marking the number of workers that have
	// received the chunk. The workers cannot be interacted with while the
	// renter is holding a lock, so we need to build a list of workers while
	// under lock and then launch work jobs after that.
	id := r.mu.RLock()
	uc.workersRemaining += len(r.workerPool)
	r.heapWG.Add(len(r.workerPool))
	workers := make([]*worker, 0, len(r.workerPool))
	for _, worker := range r.workerPool {
		workers = append(workers, worker)
	}
	r.mu.RUnlock(id)
	for _, worker := range workers {
		worker.managedQueueChunkRepair(uc)
	}

	// Perform cleanup for any pieces that will never be used by a worker.
	r.managedReleaseIdleChunkPieces(uc)
}

// managedDownloadLogicalChunkData will fetch the logical chunk data by sending a
// download to the renter's downloader, and then using the data that gets
// returned.
func (r *Renter) managedDownloadLogicalChunkData(chunk *unfinishedChunk) error {
	// Create the download, queue the download, and then wait for the download
	// to finish.
	//
	// TODO / NOTE: Once we migrate to the uploader and downloader having a
	// shared memory pool, this part will need to signal to the download group
	// that the memory has already been allocated - upload memory always takes
	// more than download memory, and if we need to allocate two times in a row
	// from the same memory pool while other processes are asynchronously doing
	// the same, we risk deadlock.
	buf := NewDownloadBufferWriter(chunk.length, chunk.offset)
	// TODO: Should convert the inputs of newSectionDownload to use an int64 for
	// the offset.
	d := r.newSectionDownload(chunk.renterFile, buf, uint64(chunk.offset), chunk.length)
	select {
	case r.newDownloads <- d:
	case <-r.tg.StopChan():
		return errors.New("repair download queing interrupted by stop call")
	}
	select {
	case <-d.downloadFinished:
	case <-r.tg.StopChan():
		return errors.New("repair download interrupted by stop call")
	}
	if d.Err() != nil {
		buf.data = nil
		return d.Err()
	}

	chunk.logicalChunkData = buf.Bytes()
	return nil

}

// managedFetchAndRepairChunk will fetch the logical data for a chunk, create
// the physical pieces for the chunk, and then distribute them. The returned
// bool indicates whether the chunk was successfully distributed to workers.
func (r *Renter) managedFetchAndRepairChunk(chunk *unfinishedChunk) bool {
	// Fetch the logical data for the chunk.
	err := r.managedFetchLogicalChunkData(chunk)
	if err != nil {
		// Logical data is not available, nothing to do.
		r.log.Debugln("Fetching logical data of a chunk failed:", err)
		return false
	}

	// Create the physical pieces for the data. Immediately release the logical
	// data.
	chunk.physicalChunkData, err = chunk.renterFile.erasureCode.Encode(chunk.logicalChunkData)
	memoryFreed := uint64(len(chunk.logicalChunkData))
	chunk.logicalChunkData = nil
	r.managedMemoryAvailableAdd(memoryFreed)
	chunk.memoryReleased += memoryFreed
	memoryFreed = 0
	if err != nil {
		// Logical data is not available, nothing to do.
		r.log.Debugln("Fetching physical data of a chunk failed:", err)
		return false
	}

	// Sanity check - we should have at least as many physical data pieces as we
	// do elements in our piece usage.
	if len(chunk.physicalChunkData) < len(chunk.pieceUsage) {
		r.log.Critical("not enough physical pieces to match the upload settings of the file")
		return false
	}
	// Loop through the pieces and encrypt any that are needed, while dropping
	// any pieces that are not needed.
	for i := 0; i < len(chunk.pieceUsage); i++ {
		if chunk.pieceUsage[i] {
			memoryFreed += uint64(len(chunk.physicalChunkData[i]) + crypto.TwofishOverhead)
			chunk.physicalChunkData[i] = nil
		} else {
			// Encrypt the piece.
			key := deriveKey(chunk.renterFile.masterKey, chunk.index, uint64(i))
			chunk.physicalChunkData[i] = key.EncryptBytes(chunk.physicalChunkData[i])
		}
	}
	// Return the released memory.
	r.managedMemoryAvailableAdd(memoryFreed)
	chunk.memoryReleased += memoryFreed

	// Distribute the chunk to the workers.
	r.managedDistributeChunkToWorkers(chunk)
	return true
}

// managedFetchLogicalChunkData will get the raw data for a chunk, pulling it from disk if
// possible but otherwise queueing a download.
//
// chunk.data should be passed as 'nil' to the download, to keep memory usage as
// light as possible.
func (r *Renter) managedFetchLogicalChunkData(chunk *unfinishedChunk) error {
	// Only download this file if more than 25% of the redundancy is missing.
	minMissingPiecesToDownload := (chunk.piecesNeeded - chunk.minimumPieces) / 4
	download := chunk.piecesCompleted+minMissingPiecesToDownload < chunk.piecesNeeded

	// Download the chunk if it's not on disk.
	if chunk.localPath == "" && download {
		return r.managedDownloadLogicalChunkData(chunk)
	} else if chunk.localPath == "" {
		return errors.New("file not available locally")
	}

	// Try to read the data from disk. If that fails at any point, prefer to
	// download the chunk.
	//
	// TODO: Might want to remove the file from the renter tracking if the disk
	// loading fails. Should do this after we swap the file format, the tracking
	// data for the file should reside in the file metadata and not in a
	// separate struct.
	osFile, err := os.Open(chunk.localPath)
	if err != nil && download {
		return r.managedDownloadLogicalChunkData(chunk)
	} else if err != nil {
		return errors.Extend(err, errors.New("failed to open file locally"))
	}
	defer osFile.Close()
	// TODO: Once we have enabled support for small chunks, we should stop
	// needing to ignore the EOF errors, because the chunk size should always
	// match the tail end of the file. Until then, we ignore io.EOF.
	chunk.logicalChunkData = make([]byte, chunk.length)
	_, err = osFile.ReadAt(chunk.logicalChunkData, chunk.offset)
	if err != nil && err != io.EOF && download {
		chunk.logicalChunkData = nil
		return r.managedDownloadLogicalChunkData(chunk)
	} else if err != nil && err != io.EOF {
		chunk.logicalChunkData = nil
		return errors.Extend(err, errors.New("failed to read file locally"))
	}

	// Data successfully read from disk.
	return nil
}

// releaseIdleChunkPieces will drop any chunk pieces that are no longer going to
// be used by workers because the number of remaining pieces is greater than the
// number of remaining workers. The memory will be returned to the renter.
func (r *Renter) managedReleaseIdleChunkPieces(uc *unfinishedChunk) {
	uc.mu.Lock()
	piecesAvailable := 0
	memoryReleased := 0
	for i := 0; i < len(uc.pieceUsage); i++ {
		// Skip the piece if it's not available.
		if uc.pieceUsage[i] {
			continue
		}

		// If we have all the available pieces we need, release this piece.
		// Otherwise, mark that there's another piece available. This algorithm
		// will prefer releasing later pieces, which improves computational
		// complexity for erasure coding.
		if piecesAvailable >= uc.workersRemaining {
			uc.pieceUsage[i] = false
			memoryReleased += len(uc.physicalChunkData[i])
			uc.physicalChunkData[i] = nil
		} else {
			piecesAvailable++
		}
	}
	uc.memoryReleased += uint64(memoryReleased)
	// Sanity check - if there are no workers remaining, we should have released
	// all of the memory by now.
	if uc.workersRemaining == 0 && uc.memoryReleased != uc.memoryNeeded {
		r.log.Critical("No workers remaining, but not all memory released:", uc.workersRemaining, uc.memoryReleased, uc.memoryNeeded)
	}
	uc.mu.Unlock()
	if memoryReleased > 0 {
		r.managedMemoryAvailableAdd(uint64(memoryReleased))
	}
}
<|endoftext|>


<commit_before>package drive

import (
    "fmt"
    "os"
    "path/filepath"
    "github.com/gyuho/goraph/graph"
    "google.golang.org/api/drive/v3"
)

func (self *Drive) prepareSyncFiles(localPath string, root *drive.File) (*syncFiles, error) {
    localCh := make(chan struct{files []*localFile; err error})
    remoteCh := make(chan struct{files []*remoteFile; err error})

    go func() {
        files, err := prepareLocalFiles(localPath)
        localCh <- struct{files []*localFile; err error}{files, err}
    }()

    go func() {
        files, err := self.prepareRemoteFiles(root)
        remoteCh <- struct{files []*remoteFile; err error}{files, err}
    }()

    local := <-localCh
    if local.err != nil {
        return nil, local.err
    }

    remote := <-remoteCh
    if remote.err != nil {
        return nil, remote.err
    }

    return &syncFiles{
        root: &remoteFile{file: root},
        local: local.files,
        remote: remote.files,
    }, nil
}

func prepareLocalFiles(root string) ([]*localFile, error) {
    var files []*localFile

    // Get absolute root path
    absRootPath, err := filepath.Abs(root)
    if err != nil {
        return nil, err
    }

    err = filepath.Walk(absRootPath, func(absPath string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // Skip root directory
        if absPath == absRootPath {
            return nil
        }

        relPath, err := filepath.Rel(absRootPath, absPath)
        if err != nil {
            return err
        }

        files = append(files, &localFile{
            absPath: absPath,
            relPath: relPath,
            info: info,
        })

        return nil
    })

    if err != nil {
        return nil, fmt.Errorf("Failed to prepare local files: %s", err)
    }

    return files, err
}

func (self *Drive) prepareRemoteFiles(rootDir *drive.File) ([]*remoteFile, error) {
    // Find all files which has rootDir as root
    query := fmt.Sprintf("appProperties has {key='syncRootId' and value='%s'}", rootDir.Id)
    fileList, err := self.service.Files.List().Q(query).Fields("files(id,name,parents,md5Checksum,mimeType)").Do()
    if err != nil {
        return nil, fmt.Errorf("Failed listing files: %s", err)
    }

    if err := checkFiles(fileList.Files); err != nil {
        return nil, err
    }

    relPaths, err := prepareRemoteRelPaths(rootDir.Id, fileList.Files)
    if err != nil {
        return nil, err
    }

    var remoteFiles []*remoteFile
    for _, f := range fileList.Files {
        relPath, ok := relPaths[f.Id]
        if !ok {
            return nil, fmt.Errorf("File %s does not have a valid parent, aborting...", f.Id)
        }
        remoteFiles = append(remoteFiles, &remoteFile{
            relPath: relPath,
            file: f,
        })
    }

    return remoteFiles, nil
}

func prepareRemoteRelPaths(rootId string, files []*drive.File) (map[string]string, error) {
    names := map[string]string{}
    idGraph := graph.NewDefaultGraph()

    for _, f := range files {
        // Store directory name for quick lookup
        names[f.Id] = f.Name

        // Store path between parent and child folder
        idGraph.AddVertex(f.Id)
        idGraph.AddVertex(f.Parents[0])
        idGraph.AddEdge(f.Parents[0], f.Id, 0)
    }

    paths := map[string]string{}

    for _, f := range files {
        // Find path from root to directory
        pathIds, _, err := graph.Dijkstra(idGraph, rootId, f.Id)
        if err != nil {
            return nil, err
        }

        // Convert path ids to path names
        var pathNames []string
        for _, id := range pathIds {
            pathNames = append(pathNames, names[id])
        }

        // Store relative file path from root to directory
        paths[f.Id] = filepath.Join(pathNames...)
    }

    return paths, nil
}

func checkFiles(files []*drive.File) error {
    uniq := map[string]string{}

    for _, f := range files {
        // Ensure all files have exactly one parent
        if len(f.Parents) != 1 {
            return fmt.Errorf("File %s does not have exacly one parent, aborting...", f.Id)
        }

        // Ensure that there are no duplicate files
        uniqKey := f.Name + f.Parents[0]
        if dupeId, isDupe := uniq[uniqKey]; isDupe {
            return fmt.Errorf("Found name collision between %s and %s, aborting", f.Id, dupeId)
        }
        uniq[uniqKey] = f.Id
    }

    return nil
}

type localFile struct {
    absPath string
    relPath string
    info os.FileInfo
}

type remoteFile struct {
    relPath string
    file *drive.File
}

type changedFile struct {
    local *localFile
    remote *remoteFile
}

type syncFiles struct {
    root *remoteFile
    local []*localFile
    remote []*remoteFile
}

func (self *syncFiles) filterMissingRemoteDirs() []*localFile {
    var files []*localFile

    for _, f := range self.local {
        if f.info.IsDir() && !self.existsRemote(f) {
            files = append(files, f)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalDirs() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingRemoteFiles() []*localFile {
    var files []*localFile

    for _, f := range self.local {
        if !f.info.IsDir() && !self.existsRemote(f) {
            files = append(files, f)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
    var files []*changedFile

    for _, lf := range self.local {
        // Skip directories
        if lf.info.IsDir() {
            continue
        }

        // Skip files that don't exist on drive
        rf, found := self.findRemoteByPath(lf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
    var files []*changedFile

    for _, rf := range self.remote {
        // Skip directories
        if isDir(rf.file) {
            continue
        }

        // Skip local files that don't exist
        lf, found := self.findLocalByPath(rf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousRemoteFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousLocalFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) existsRemote(lf *localFile) bool {
    _, found := self.findRemoteByPath(lf.relPath)
    return found
}

func (self *syncFiles) existsLocal(rf *remoteFile) bool {
    _, found := self.findLocalByPath(rf.relPath)
    return found
}

func (self *syncFiles) findRemoteByPath(relPath string) (*remoteFile, bool) {
    if relPath == "." {
        return self.root, true
    }

    for _, rf := range self.remote {
        if relPath == rf.relPath {
            return rf, true
        }
    }

    return nil, false
}

func (self *syncFiles) findLocalByPath(relPath string) (*localFile, bool) {
    for _, lf := range self.local {
        if relPath == lf.relPath {
            return lf, true
        }
    }

    return nil, false
}

type byLocalPathLength []*localFile

func (self byLocalPathLength) Len() int {
    return len(self)
}

func (self byLocalPathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byLocalPathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type byRemotePathLength []*remoteFile

func (self byRemotePathLength) Len() int {
    return len(self)
}

func (self byRemotePathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byRemotePathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}
<commit_msg>Use same short var name as elsewhere<commit_after>package drive

import (
    "fmt"
    "os"
    "path/filepath"
    "github.com/gyuho/goraph/graph"
    "google.golang.org/api/drive/v3"
)

func (self *Drive) prepareSyncFiles(localPath string, root *drive.File) (*syncFiles, error) {
    localCh := make(chan struct{files []*localFile; err error})
    remoteCh := make(chan struct{files []*remoteFile; err error})

    go func() {
        files, err := prepareLocalFiles(localPath)
        localCh <- struct{files []*localFile; err error}{files, err}
    }()

    go func() {
        files, err := self.prepareRemoteFiles(root)
        remoteCh <- struct{files []*remoteFile; err error}{files, err}
    }()

    local := <-localCh
    if local.err != nil {
        return nil, local.err
    }

    remote := <-remoteCh
    if remote.err != nil {
        return nil, remote.err
    }

    return &syncFiles{
        root: &remoteFile{file: root},
        local: local.files,
        remote: remote.files,
    }, nil
}

func prepareLocalFiles(root string) ([]*localFile, error) {
    var files []*localFile

    // Get absolute root path
    absRootPath, err := filepath.Abs(root)
    if err != nil {
        return nil, err
    }

    err = filepath.Walk(absRootPath, func(absPath string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // Skip root directory
        if absPath == absRootPath {
            return nil
        }

        relPath, err := filepath.Rel(absRootPath, absPath)
        if err != nil {
            return err
        }

        files = append(files, &localFile{
            absPath: absPath,
            relPath: relPath,
            info: info,
        })

        return nil
    })

    if err != nil {
        return nil, fmt.Errorf("Failed to prepare local files: %s", err)
    }

    return files, err
}

func (self *Drive) prepareRemoteFiles(rootDir *drive.File) ([]*remoteFile, error) {
    // Find all files which has rootDir as root
    query := fmt.Sprintf("appProperties has {key='syncRootId' and value='%s'}", rootDir.Id)
    fileList, err := self.service.Files.List().Q(query).Fields("files(id,name,parents,md5Checksum,mimeType)").Do()
    if err != nil {
        return nil, fmt.Errorf("Failed listing files: %s", err)
    }

    if err := checkFiles(fileList.Files); err != nil {
        return nil, err
    }

    relPaths, err := prepareRemoteRelPaths(rootDir.Id, fileList.Files)
    if err != nil {
        return nil, err
    }

    var remoteFiles []*remoteFile
    for _, f := range fileList.Files {
        relPath, ok := relPaths[f.Id]
        if !ok {
            return nil, fmt.Errorf("File %s does not have a valid parent, aborting...", f.Id)
        }
        remoteFiles = append(remoteFiles, &remoteFile{
            relPath: relPath,
            file: f,
        })
    }

    return remoteFiles, nil
}

func prepareRemoteRelPaths(rootId string, files []*drive.File) (map[string]string, error) {
    names := map[string]string{}
    idGraph := graph.NewDefaultGraph()

    for _, f := range files {
        // Store directory name for quick lookup
        names[f.Id] = f.Name

        // Store path between parent and child folder
        idGraph.AddVertex(f.Id)
        idGraph.AddVertex(f.Parents[0])
        idGraph.AddEdge(f.Parents[0], f.Id, 0)
    }

    paths := map[string]string{}

    for _, f := range files {
        // Find path from root to directory
        pathIds, _, err := graph.Dijkstra(idGraph, rootId, f.Id)
        if err != nil {
            return nil, err
        }

        // Convert path ids to path names
        var pathNames []string
        for _, id := range pathIds {
            pathNames = append(pathNames, names[id])
        }

        // Store relative file path from root to directory
        paths[f.Id] = filepath.Join(pathNames...)
    }

    return paths, nil
}

func checkFiles(files []*drive.File) error {
    uniq := map[string]string{}

    for _, f := range files {
        // Ensure all files have exactly one parent
        if len(f.Parents) != 1 {
            return fmt.Errorf("File %s does not have exacly one parent, aborting...", f.Id)
        }

        // Ensure that there are no duplicate files
        uniqKey := f.Name + f.Parents[0]
        if dupeId, isDupe := uniq[uniqKey]; isDupe {
            return fmt.Errorf("Found name collision between %s and %s, aborting", f.Id, dupeId)
        }
        uniq[uniqKey] = f.Id
    }

    return nil
}

type localFile struct {
    absPath string
    relPath string
    info os.FileInfo
}

type remoteFile struct {
    relPath string
    file *drive.File
}

type changedFile struct {
    local *localFile
    remote *remoteFile
}

type syncFiles struct {
    root *remoteFile
    local []*localFile
    remote []*remoteFile
}

func (self *syncFiles) filterMissingRemoteDirs() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalDirs() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingRemoteFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
    var files []*changedFile

    for _, lf := range self.local {
        // Skip directories
        if lf.info.IsDir() {
            continue
        }

        // Skip files that don't exist on drive
        rf, found := self.findRemoteByPath(lf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
    var files []*changedFile

    for _, rf := range self.remote {
        // Skip directories
        if isDir(rf.file) {
            continue
        }

        // Skip local files that don't exist
        lf, found := self.findLocalByPath(rf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousRemoteFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousLocalFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) existsRemote(lf *localFile) bool {
    _, found := self.findRemoteByPath(lf.relPath)
    return found
}

func (self *syncFiles) existsLocal(rf *remoteFile) bool {
    _, found := self.findLocalByPath(rf.relPath)
    return found
}

func (self *syncFiles) findRemoteByPath(relPath string) (*remoteFile, bool) {
    if relPath == "." {
        return self.root, true
    }

    for _, rf := range self.remote {
        if relPath == rf.relPath {
            return rf, true
        }
    }

    return nil, false
}

func (self *syncFiles) findLocalByPath(relPath string) (*localFile, bool) {
    for _, lf := range self.local {
        if relPath == lf.relPath {
            return lf, true
        }
    }

    return nil, false
}

type byLocalPathLength []*localFile

func (self byLocalPathLength) Len() int {
    return len(self)
}

func (self byLocalPathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byLocalPathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type byRemotePathLength []*remoteFile

func (self byRemotePathLength) Len() int {
    return len(self)
}

func (self byRemotePathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byRemotePathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"io"
	"log"
	"fmt"
	"flag"
	"strings"
	"net/http"
	"os/exec"
	"text/template"
	"html"
)

type TemplateData struct {
	Name string
	Link string
	Content string
}

const (
	TemplateName = ".tmpl"
	InterpreterName = ".interpreters"
)

var siteRoot *string = flag.String("r", ".", "Path to files")

var rootName *string = flag.String("n", "debug",
"Name given to template when / is requested")

var nameFormat *string = flag.String("f", "%s - debug",
"String used by fmt to get name to give to template, one string is " +
"given for parsing, the name of the file less it's suffix.")

var serverPort *string = flag.String("p", "80", "Port to listen on")

var maxBytes *int = flag.Int("m", 2 * 1024 * 1024,
"Max file size that will be given to templates. Also the chunk size " +
"that is read in before writing to the stream")

/*
 * Split s on last occurence of pattern, so returns (most, suffix).
 * If no matches of pattern were found then returns (s, "").
 */
func splitSuffix(s string, pattern string) (string, string) {
	l := strings.LastIndex(s, pattern)
	if l > 0 {
		return s[:l], s[l+1:]
	} else {
		return s, ""
	}
}

func findFile(path string, name string) string {
	for {
		path, _ = splitSuffix(path, "/")
		if path == "" {
			return os.DevNull
		}
		p := path + "/" + name
		_, err := os.Stat(p)
		if err == nil {
			return p
		}
	}
}

func dirIndex(file *os.File) string {
	names, err := file.Readdirnames(0)
	if err != nil {
		return ""
	}
	file.Seek(0, 0)

	dir := file.Name()
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}

	for _, name := range names {
		if strings.HasPrefix(name, "index") {
			return name
		}
	}

	return ""
}

func readLine(file *os.File, bytes []byte) (string, error) {
	n, err := file.Read(bytes)
	if err != nil {
		return "", err
	}

	s := string(bytes[:n])
	l := strings.IndexByte(s, '\n') + 1

	if l > 0 {
		file.Seek(int64(l - n), 1)
		return s[:l-1], nil
	} else {
		return "", nil
	}
}

func findInterpreter(path string) (bool, []string) {
	intPath := findFile(path, InterpreterName)
	if intPath == "" {
		return false, []string{}
	}

	file, err := os.Open(intPath)
	if err != nil {
		log.Print(err)
		return false, []string{}
	}

	bytes := make([]byte, 256)

	for {
		line, err := readLine(file, bytes)
		if err != nil {
			return false, []string{}
		} else if len(line) == 0 || line[0] == '#' {
			continue
		}

		suffix := strings.SplitN(line, " ", 2)
		if len(suffix) > 0 && strings.HasSuffix(path, suffix[0]) {
			parts := strings.Split(line, " ")
			if len(parts) < 2 {
				log.Print("Error in interpreter file. ", path)
				continue
			} else {
				return strings.HasPrefix(parts[1], "y"),
					parts[2:]
			}
		}
	}
}

func runInterpreter(interpreter []string,
		values map[string][]string, file *os.File) ([]byte, error) {
	dir, base := splitSuffix(file.Name(), "/")
	cmd := exec.Command(interpreter[0])
	cmd.Args = append(interpreter, base)
	cmd.Dir = dir

	l := len(cmd.Env) + len(values) + 1
	env := make([]string, l)
	copy(env, cmd.Env)

	i := len(cmd.Env) + 1
	for name, value := range values {
		env[i] = name + "=" + value[0]
		i++
	}

	cmd.Env = env
	return cmd.Output()
}

func processFile(w http.ResponseWriter, req *http.Request,
		data *TemplateData, file *os.File, fi os.FileInfo) {
	var err error
	var bytes []byte = make([]byte, *maxBytes)
	var n int

	useTemplate, interpreter := findInterpreter(file.Name())

	if len(interpreter) > 0 {
		bytes, err = runInterpreter(interpreter,
				req.URL.Query(), file)

	} else {
		n, err = file.Read(bytes)
	}

	if err != nil {
		log.Print(err)
		io.WriteString(w, "ERROR")
		return
	}

	if useTemplate {
		tmplPath := findFile(file.Name(), TemplateName)
		tmpl, err := template.ParseFiles(tmplPath)
		if err == nil {
			data.Content = string(bytes)
			tmpl.Execute(w, data)
			return
		}
	}

	/* No template/error opening template */
	if len(interpreter) > 0 {
		req.ContentLength = int64(len(bytes))
		w.Write(bytes)
	} else {
		req.ContentLength = fi.Size()
		for {
			_, err = w.Write(bytes[:n])
			if err != nil {
				break
			}
			n, err = file.Read(bytes)
			if err != nil {
				break
			}
		}
	}

}

func handler(w http.ResponseWriter, req *http.Request) {
	var file *os.File
	var err error
	var name string

	log.Print(req.RemoteAddr, " request: ", req.URL.String())

	path := "." + html.EscapeString(req.URL.Path)

	if strings.Contains(path, "/.") {
		log.Print("requested dot file")
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "404: " + html.EscapeString(req.URL.Path))
		return
	}

	file, err = os.Open(path)
	if err != nil {
		log.Print("404 ", err)
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "404: " + html.EscapeString(req.URL.Path))
		return
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		log.Print(err)
		return
	}

	data := new(TemplateData)
	data.Link = req.URL.Path

	if strings.HasPrefix(fi.Name(), "index") {
		path, _ = splitSuffix(path, "/")
		_, name = splitSuffix(path, "/")
		path += "/"
	} else {
		name, _ = splitSuffix(fi.Name(), ".")
	}

	if path == "./" {
		data.Name = *rootName
	} else {
		data.Name = fmt.Sprintf(*nameFormat, name)
	}

	if fi.IsDir() {
		index := dirIndex(file)

		if index == "" {
			w.WriteHeader(http.StatusNotFound)
			io.WriteString(w, "404: " +
				html.EscapeString(req.URL.Path))
			return
		} else if !strings.HasSuffix(path, "/") {
			url := req.URL.Scheme + req.URL.Path +
				"/" + req.URL.RawQuery
			http.Redirect(w, req, url,
				http.StatusMovedPermanently)
		} else {
			path += index
			file, err = os.Open(path)
			if err != nil {
				log.Print(err)
				return
			}
			defer file.Close()
			/* Fall through to process file */
		}
	}

	processFile(w, req, data, file, fi)
}

func main() {
	flag.Parse()

	os.Chdir(*siteRoot)

	http.HandleFunc("/", handler)
	err := http.ListenAndServe(":" + *serverPort, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Added support for tls connections<commit_after>package main

import (
	"os"
	"io"
	"log"
	"fmt"
	"flag"
	"strings"
	"net/http"
	"os/exec"
	"text/template"
	"html"
	"sync"
)

type TemplateData struct {
	Name string
	Link string
	Content string
}

const (
	TemplateName = ".tmpl"
	InterpreterName = ".interpreters"
)

var siteRoot *string = flag.String("r", ".", "Path to files")

var rootName *string = flag.String("n", "debug",
"Name given to template when / is requested")

var nameFormat *string = flag.String("f", "%s - debug",
"String used by fmt to get name to give to template, one string is " +
"given for parsing, the name of the file less it's suffix.")

var serverPort *string = flag.String("p", "80", "Port to listen on")

var serverPortTLS *string = flag.String("t", "0", "Port to listen on for TLS connections. Set to 0 to disable TLS.")

var certFilePath *string = flag.String("c", "/dev/null", "Public TLS certificate.")

var keyFilePath *string = flag.String("k", "/dev/null", "TLS key file.")

var maxBytes *int = flag.Int("m", 2 * 1024 * 1024,
"Max file size that will be given to templates. Also the chunk size " +
"that is read in before writing to the stream")

/*
 * Split s on last occurence of pattern, so returns (most, suffix).
 * If no matches of pattern were found then returns (s, "").
 */
func splitSuffix(s string, pattern string) (string, string) {
	l := strings.LastIndex(s, pattern)
	if l > 0 {
		return s[:l], s[l+1:]
	} else {
		return s, ""
	}
}

func findFile(path string, name string) string {
	for {
		path, _ = splitSuffix(path, "/")
		if path == "" {
			return os.DevNull
		}
		p := path + "/" + name
		_, err := os.Stat(p)
		if err == nil {
			return p
		}
	}
}

func dirIndex(file *os.File) string {
	names, err := file.Readdirnames(0)
	if err != nil {
		return ""
	}
	file.Seek(0, 0)

	dir := file.Name()
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}

	for _, name := range names {
		if strings.HasPrefix(name, "index") {
			return name
		}
	}

	return ""
}

func readLine(file *os.File, bytes []byte) (string, error) {
	n, err := file.Read(bytes)
	if err != nil {
		return "", err
	}

	s := string(bytes[:n])
	l := strings.IndexByte(s, '\n') + 1

	if l > 0 {
		file.Seek(int64(l - n), 1)
		return s[:l-1], nil
	} else {
		return "", nil
	}
}

func findInterpreter(path string) (bool, []string) {
	intPath := findFile(path, InterpreterName)
	if intPath == "" {
		return false, []string{}
	}

	file, err := os.Open(intPath)
	if err != nil {
		log.Print(err)
		return false, []string{}
	}

	bytes := make([]byte, 256)

	for {
		line, err := readLine(file, bytes)
		if err != nil {
			return false, []string{}
		} else if len(line) == 0 || line[0] == '#' {
			continue
		}

		suffix := strings.SplitN(line, " ", 2)
		if len(suffix) > 0 && strings.HasSuffix(path, suffix[0]) {
			parts := strings.Split(line, " ")
			if len(parts) < 2 {
				log.Print("Error in interpreter file. ", path)
				continue
			} else {
				return strings.HasPrefix(parts[1], "y"),
					parts[2:]
			}
		}
	}
}

func runInterpreter(interpreter []string,
		values map[string][]string, file *os.File) ([]byte, error) {
	dir, base := splitSuffix(file.Name(), "/")
	cmd := exec.Command(interpreter[0])
	cmd.Args = append(interpreter, base)
	cmd.Dir = dir

	l := len(cmd.Env) + len(values) + 1
	env := make([]string, l)
	copy(env, cmd.Env)

	i := len(cmd.Env) + 1
	for name, value := range values {
		env[i] = name + "=" + value[0]
		i++
	}

	cmd.Env = env
	return cmd.Output()
}

func processFile(w http.ResponseWriter, req *http.Request,
		data *TemplateData, file *os.File, fi os.FileInfo) {
	var err error
	var bytes []byte = make([]byte, *maxBytes)
	var n int

	useTemplate, interpreter := findInterpreter(file.Name())

	if len(interpreter) > 0 {
		bytes, err = runInterpreter(interpreter,
				req.URL.Query(), file)

	} else {
		n, err = file.Read(bytes)
	}

	if err != nil {
		log.Print(err)
		io.WriteString(w, "ERROR")
		return
	}

	if useTemplate {
		tmplPath := findFile(file.Name(), TemplateName)
		tmpl, err := template.ParseFiles(tmplPath)
		if err == nil {
			data.Content = string(bytes)
			tmpl.Execute(w, data)
			return
		}
	}

	/* No template/error opening template */
	if len(interpreter) > 0 {
		req.ContentLength = int64(len(bytes))
		w.Write(bytes)
	} else {
		req.ContentLength = fi.Size()
		for {
			_, err = w.Write(bytes[:n])
			if err != nil {
				break
			}
			n, err = file.Read(bytes)
			if err != nil {
				break
			}
		}
	}

}

func handler(w http.ResponseWriter, req *http.Request) {
	var file *os.File
	var err error
	var name string

	log.Print(req.RemoteAddr, " request: ", req.URL.String())

	path := "." + html.EscapeString(req.URL.Path)

	if strings.Contains(path, "/.") {
		log.Print("requested dot file")
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "404: " + html.EscapeString(req.URL.Path))
		return
	}

	file, err = os.Open(path)
	if err != nil {
		log.Print("404 ", err)
		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "404: " + html.EscapeString(req.URL.Path))
		return
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		log.Print(err)
		return
	}

	data := new(TemplateData)
	data.Link = req.URL.Path

	if strings.HasPrefix(fi.Name(), "index") {
		path, _ = splitSuffix(path, "/")
		_, name = splitSuffix(path, "/")
		path += "/"
	} else {
		name, _ = splitSuffix(fi.Name(), ".")
	}

	if path == "./" {
		data.Name = *rootName
	} else {
		data.Name = fmt.Sprintf(*nameFormat, name)
	}

	if fi.IsDir() {
		index := dirIndex(file)

		if index == "" {
			w.WriteHeader(http.StatusNotFound)
			io.WriteString(w, "404: " +
				html.EscapeString(req.URL.Path))
			return
		} else if !strings.HasSuffix(path, "/") {
			url := req.URL.Scheme + req.URL.Path +
				"/" + req.URL.RawQuery
			http.Redirect(w, req, url,
				http.StatusMovedPermanently)
		} else {
			path += index
			file, err = os.Open(path)
			if err != nil {
				log.Print(err)
				return
			}
			defer file.Close()
			/* Fall through to process file */
		}
	}

	processFile(w, req, data, file, fi)
}

func main() {
	var wg sync.WaitGroup

	flag.Parse()

	os.Chdir(*siteRoot)

	http.HandleFunc("/", handler)

	wg.Add(2)

	go func() {
		defer wg.Done()

		err := http.ListenAndServe(":" + *serverPort, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	}()

	go func() {
		defer wg.Done()

		if *serverPortTLS == "0" {
			return
		}

		err := http.ListenAndServeTLS(":" + *serverPortTLS,
			*certFilePath, *keyFilePath, nil)
		if err != nil {
			log.Fatal("ListenAndServeTLS: ", err)
		}
	}()

	wg.Wait()
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Package psbt is an implementation of Partially Signed Bitcoin
// Transactions (PSBT). The format is defined in BIP 174:
// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
package psbt

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
	"errors"

	"io"
	"sort"

	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
)

// psbtMagicLength is the length of the magic bytes used to signal the start of
// a serialized PSBT packet.
const psbtMagicLength = 5

var (
	// psbtMagic is the separator
	psbtMagic = [psbtMagicLength]byte{0x70,
		0x73, 0x62, 0x74, 0xff, // = "psbt" + 0xff sep
	}
)

// MaxPsbtValueLength is the size of the largest transaction serialization
// that could be passed in a NonWitnessUtxo field. This is definitely
//less than 4M.
const MaxPsbtValueLength = 4000000

var (

	// ErrInvalidPsbtFormat is a generic error for any situation in which a
	// provided Psbt serialization does not conform to the rules of BIP174.
	ErrInvalidPsbtFormat = errors.New("Invalid PSBT serialization format")

	// ErrDuplicateKey indicates that a passed Psbt serialization is invalid
	// due to having the same key repeated in the same key-value pair.
	ErrDuplicateKey = errors.New("Invalid Psbt due to duplicate key")

	// ErrInvalidKeydata indicates that a key-value pair in the PSBT
	// serialization contains data in the key which is not valid.
	ErrInvalidKeydata = errors.New("Invalid key data")

	// ErrInvalidMagicBytes indicates that a passed Psbt serialization is invalid
	// due to having incorrect magic bytes.
	ErrInvalidMagicBytes = errors.New("Invalid Psbt due to incorrect magic bytes")

	// ErrInvalidRawTxSigned indicates that the raw serialized transaction in the
	// global section of the passed Psbt serialization is invalid because it
	// contains scriptSigs/witnesses (i.e. is fully or partially signed), which
	// is not allowed by BIP174.
	ErrInvalidRawTxSigned = errors.New("Invalid Psbt, raw transaction must " +
		"be unsigned.")

	// ErrInvalidPrevOutNonWitnessTransaction indicates that the transaction
	// hash (i.e. SHA256^2) of the fully serialized previous transaction
	// provided in the NonWitnessUtxo key-value field doesn't match the prevout
	// hash in the UnsignedTx field in the PSBT itself.
	ErrInvalidPrevOutNonWitnessTransaction = errors.New("Prevout hash does " +
		"not match the provided non-witness utxo serialization")

	// ErrInvalidSignatureForInput indicates that the signature the user is
	// trying to append to the PSBT is invalid, either because it does
	// not correspond to the previous transaction hash, or redeem script,
	// or witness script.
	// NOTE this does not include ECDSA signature checking.
	ErrInvalidSignatureForInput = errors.New("Signature does not correspond " +
		"to this input")

	// ErrInputAlreadyFinalized indicates that the PSBT passed to a Finalizer
	// already contains the finalized scriptSig or witness.
	ErrInputAlreadyFinalized = errors.New("Cannot finalize PSBT, finalized " +
		"scriptSig or scriptWitnes already exists")

	// ErrIncompletePSBT indicates that the Extractor object
	// was unable to successfully extract the passed Psbt struct because
	// it is not complete
	ErrIncompletePSBT = errors.New("PSBT cannot be extracted as it is " +
		"incomplete")

	// ErrNotFinalizable indicates that the PSBT struct does not have
	// sufficient data (e.g. signatures) for finalization
	ErrNotFinalizable = errors.New("PSBT is not finalizable")

	// ErrInvalidSigHashFlags indicates that a signature added to the PSBT
	// uses Sighash flags that are not in accordance with the requirement
	// according to the entry in PsbtInSighashType, or otherwise not the
	// default value (SIGHASH_ALL)
	ErrInvalidSigHashFlags = errors.New("Invalid Sighash Flags")

	// ErrUnsupportedScriptType indicates that the redeem script or
	// scriptwitness given is not supported by this codebase, or is otherwise
	// not valid.
	ErrUnsupportedScriptType = errors.New("Unsupported script type")
)

// Unknown is a struct encapsulating a key-value pair for which the key type is
// unknown by this package; these fields are allowed in both the 'Global' and
// the 'Input' section of a PSBT.
type Unknown struct {
	Key   []byte
	Value []byte
}

}

// Psbt is a set of 1 + N + M key-value pair lists, 1 global,
// defining the unsigned transaction structure with N inputs and M outputs.
// These key-value pairs can contain scripts, signatures,
// key derivations and other transaction-defining data.
type Psbt struct {
	UnsignedTx *wire.MsgTx // Deserialization of unsigned tx
	Inputs     []PInput
	Outputs    []POutput
	Unknowns   []Unknown // Data of unknown type at global scope
}

// validateUnsignedTx returns true if the transaction is unsigned.
// Note that more basic sanity requirements,
// such as the presence of inputs and outputs, is implicitly
// checked in the call to MsgTx.Deserialize()
func validateUnsignedTX(tx *wire.MsgTx) bool {
	for _, tin := range tx.TxIn {
		if len(tin.SignatureScript) != 0 || len(tin.Witness) != 0 {
			return false
		}
	}
	return true
}

// NewPsbtFromUnsignedTx creates a new Psbt struct, without
// any signatures (i.e. only the global section is non-empty).
func NewPsbtFromUnsignedTx(tx *wire.MsgTx) (*Psbt, error) {

	if !validateUnsignedTX(tx) {
		return nil, ErrInvalidRawTxSigned
	}

	inSlice := make([]PInput, len(tx.TxIn))
	outSlice := make([]POutput, len(tx.TxOut))
	unknownSlice := make([]Unknown, 0)

	retPsbt := Psbt{
		UnsignedTx: tx,
		Inputs:     inSlice,
		Outputs:    outSlice,
		Unknowns:   unknownSlice,
	}

	return &retPsbt, nil
}

// NewPsbt returns a new instance of a Psbt struct created
// by reading from a byte slice. If the format is invalid, an error
// is returned. If the argument b64 is true, the passed byte slice
// is decoded from base64 encoding before processing.
// NOTE To create a Psbt from one's own data, rather than reading
// in a serialization from a counterparty, one should use a psbt.Creator.
func NewPsbt(psbtBytes []byte, b64 bool) (*Psbt, error) {
	var err error
	if b64 {
		decoded := make([]byte, base64.StdEncoding.DecodedLen(len(psbtBytes)))
		_, err = base64.StdEncoding.Decode(decoded, psbtBytes)
		if err != nil {
			return nil, err
		}
		psbtBytes = decoded
	}
	r := bytes.NewReader(psbtBytes)
	// The Psbt struct does not store the fixed magic bytes,
	// but they must be present or the serialization must
	// be explicitly rejected.
	var magic [5]byte
	if _, err = io.ReadFull(r, magic[:]); err != nil {
		return nil, err
	}
	if magic != psbtMagic {
		return nil, ErrInvalidMagicBytes
	}

	// Next we parse the GLOBAL section.
	// There is currently only 1 known key type, UnsignedTx.
	// We insist this exists first; unknowns are allowed, but
	// only after.
	keyint, keydata, err := getKey(r)
	if err != nil {
		return nil, err
	}
	if uint8(keyint) != PsbtGlobalUnsignedTx || keydata != nil {
		return nil, ErrInvalidPsbtFormat
	}
	value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength,
		"PSBT value")
	if err != nil {
		return nil, err
	}

	// Attempt to deserialize the unsigned transaction.
	msgTx := wire.NewMsgTx(2)
	err = msgTx.Deserialize(bytes.NewReader(value))
	if err != nil {
		return nil, err
	}
	if !validateUnsignedTX(msgTx) {
		return nil, ErrInvalidRawTxSigned
	}

	// parse any unknowns that may be present, break at separator
	unknownSlice := make([]Unknown, 0)
	for {
		keyint, keydata, err := getKey(r)
		if err != nil {
			return nil, ErrInvalidPsbtFormat
		}
		if keyint == -1 {
			break
		}
		value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength,
			"PSBT value")
		if err != nil {
			return nil, err
		}
		keyintanddata := []byte{byte(keyint)}
		keyintanddata = append(keyintanddata, keydata...)
		newUnknown := Unknown{
			Key:   keyintanddata,
			Value: value,
		}
		unknownSlice = append(unknownSlice, newUnknown)
	}

	// Next we parse the INPUT section
	inSlice := make([]PInput, len(msgTx.TxIn))

	for i := range msgTx.TxIn {
		input := PInput{}
		err = input.deserialize(r)
		if err != nil {
			return nil, err
		}
		inSlice[i] = input
	}

	//Next we parse the OUTPUT section
	outSlice := make([]POutput, len(msgTx.TxOut))

	for i := range msgTx.TxOut {
		output := POutput{}
		err = output.deserialize(r)
		if err != nil {
			return nil, err
		}
		outSlice[i] = output
	}

	// Populate the new Psbt object
	newPsbt := Psbt{
		UnsignedTx: msgTx,
		Inputs:     inSlice,
		Outputs:    outSlice,
		Unknowns:   unknownSlice,
	}
	// Extended sanity checking is applied here
	// to make sure the externally-passed Psbt follows
	// all the rules.
	if err = newPsbt.SanityCheck(); err != nil {
		return nil, err
	}

	return &newPsbt, nil
}

// Serialize creates a binary serialization of the referenced
// Psbt struct with lexicographical ordering (by key) of the subsections
func (p *Psbt) Serialize() ([]byte, error) {

	serPsbt := []byte{}
	serPsbt = append(serPsbt, psbtMagic[:]...)

	// Create serialization of unsignedtx
	serializedTx := bytes.NewBuffer(make([]byte, 0,
		p.UnsignedTx.SerializeSize()))
	if err := p.UnsignedTx.Serialize(serializedTx); err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	err := serializeKVPairWithType(&buf, PsbtGlobalUnsignedTx,
		nil, serializedTx.Bytes())
	if err != nil {
		return nil, err
	}
	serPsbt = append(serPsbt, buf.Bytes()...)
	serPsbt = append(serPsbt, 0x00)

	for _, pInput := range p.Inputs {
		var buf bytes.Buffer
		err := pInput.serialize(&buf)
		if err != nil {
			return nil, err
		}
		serPsbt = append(serPsbt, buf.Bytes()...)
		serPsbt = append(serPsbt, 0x00)
	}

	for _, pOutput := range p.Outputs {
		var buf bytes.Buffer
		err := pOutput.serialize(&buf)
		if err != nil {
			return nil, err
		}
		serPsbt = append(serPsbt, buf.Bytes()...)
		serPsbt = append(serPsbt, 0x00)
	}

	return serPsbt, nil
}

// B64Encode returns the base64 encoding of the serialization of
// the current PSBT, or an error if the encoding fails.
func (p *Psbt) B64Encode() (string, error) {
	raw, err := p.Serialize()
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(raw), nil
}

// IsComplete returns true only if all of the inputs are
// finalized; this is particularly important in that it decides
// whether the final extraction to a network serialized signed
// transaction will be possible.
func (p *Psbt) IsComplete() bool {
	for i := 0; i < len(p.UnsignedTx.TxIn); i++ {
		if !isFinalized(p, i) {
			return false
		}
	}
	return true
}

// SanityCheck checks conditions on a PSBT to ensure that it obeys the
// rules of BIP174, and returns true if so, false if not.
func (p *Psbt) SanityCheck() error {

	if !validateUnsignedTX(p.UnsignedTx) {
		return ErrInvalidRawTxSigned
	}

	for _, tin := range p.Inputs {
		if !tin.IsSane() {
			return ErrInvalidPsbtFormat
		}
	}

	return nil
}
<commit_msg>psbt: rename psbt.Psbt to psbt.Packet, rename existing constructors<commit_after>// Copyright (c) 2018 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Package psbt is an implementation of Partially Signed Bitcoin
// Transactions (PSBT). The format is defined in BIP 174:
// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
package psbt

import (
	"bytes"
	"encoding/base64"
	"errors"

	"io"

	"github.com/btcsuite/btcd/wire"
)

// psbtMagicLength is the length of the magic bytes used to signal the start of
// a serialized PSBT packet.
const psbtMagicLength = 5

var (
	// psbtMagic is the separator
	psbtMagic = [psbtMagicLength]byte{0x70,
		0x73, 0x62, 0x74, 0xff, // = "psbt" + 0xff sep
	}
)

// MaxPsbtValueLength is the size of the largest transaction serialization
// that could be passed in a NonWitnessUtxo field. This is definitely
//less than 4M.
const MaxPsbtValueLength = 4000000

var (

	// ErrInvalidPsbtFormat is a generic error for any situation in which a
	// provided Psbt serialization does not conform to the rules of BIP174.
	ErrInvalidPsbtFormat = errors.New("Invalid PSBT serialization format")

	// ErrDuplicateKey indicates that a passed Psbt serialization is invalid
	// due to having the same key repeated in the same key-value pair.
	ErrDuplicateKey = errors.New("Invalid Psbt due to duplicate key")

	// ErrInvalidKeydata indicates that a key-value pair in the PSBT
	// serialization contains data in the key which is not valid.
	ErrInvalidKeydata = errors.New("Invalid key data")

	// ErrInvalidMagicBytes indicates that a passed Psbt serialization is invalid
	// due to having incorrect magic bytes.
	ErrInvalidMagicBytes = errors.New("Invalid Psbt due to incorrect magic bytes")

	// ErrInvalidRawTxSigned indicates that the raw serialized transaction in the
	// global section of the passed Psbt serialization is invalid because it
	// contains scriptSigs/witnesses (i.e. is fully or partially signed), which
	// is not allowed by BIP174.
	ErrInvalidRawTxSigned = errors.New("Invalid Psbt, raw transaction must " +
		"be unsigned.")

	// ErrInvalidPrevOutNonWitnessTransaction indicates that the transaction
	// hash (i.e. SHA256^2) of the fully serialized previous transaction
	// provided in the NonWitnessUtxo key-value field doesn't match the prevout
	// hash in the UnsignedTx field in the PSBT itself.
	ErrInvalidPrevOutNonWitnessTransaction = errors.New("Prevout hash does " +
		"not match the provided non-witness utxo serialization")

	// ErrInvalidSignatureForInput indicates that the signature the user is
	// trying to append to the PSBT is invalid, either because it does
	// not correspond to the previous transaction hash, or redeem script,
	// or witness script.
	// NOTE this does not include ECDSA signature checking.
	ErrInvalidSignatureForInput = errors.New("Signature does not correspond " +
		"to this input")

	// ErrInputAlreadyFinalized indicates that the PSBT passed to a Finalizer
	// already contains the finalized scriptSig or witness.
	ErrInputAlreadyFinalized = errors.New("Cannot finalize PSBT, finalized " +
		"scriptSig or scriptWitnes already exists")

	// ErrIncompletePSBT indicates that the Extractor object
	// was unable to successfully extract the passed Psbt struct because
	// it is not complete
	ErrIncompletePSBT = errors.New("PSBT cannot be extracted as it is " +
		"incomplete")

	// ErrNotFinalizable indicates that the PSBT struct does not have
	// sufficient data (e.g. signatures) for finalization
	ErrNotFinalizable = errors.New("PSBT is not finalizable")

	// ErrInvalidSigHashFlags indicates that a signature added to the PSBT
	// uses Sighash flags that are not in accordance with the requirement
	// according to the entry in PsbtInSighashType, or otherwise not the
	// default value (SIGHASH_ALL)
	ErrInvalidSigHashFlags = errors.New("Invalid Sighash Flags")

	// ErrUnsupportedScriptType indicates that the redeem script or
	// scriptwitness given is not supported by this codebase, or is otherwise
	// not valid.
	ErrUnsupportedScriptType = errors.New("Unsupported script type")
)

// Unknown is a struct encapsulating a key-value pair for which the key type is
// unknown by this package; these fields are allowed in both the 'Global' and
// the 'Input' section of a PSBT.
type Unknown struct {
	Key   []byte
	Value []byte
}

// Packet is the actual psbt repreesntation. It is a is a set of 1 + N + M
// key-value pair lists, 1 global, defining the unsigned transaction structure
// with N inputs and M outputs.  These key-value pairs can contain scripts,
// signatures, key derivations and other transaction-defining data.
type Packet struct {
	// UnsignedTx is the decoded unsigned transaction for this PSBT.
	UnsignedTx *wire.MsgTx // Deserialization of unsigned tx

	// Inputs contains all the information needed to properly sign this
	// target input within the above transaction.
	Inputs []PInput

	// Outputs contains all information required to spend any outputs
	// produced by this PSBT.
	Outputs []POutput

	// Unknowns are the set of custom types (global only) within this PSBT.
	Unknowns []Unknown
}

// validateUnsignedTx returns true if the transaction is unsigned.  Note that
// more basic sanity requirements, such as the presence of inputs and outputs,
// is implicitly checked in the call to MsgTx.Deserialize().
func validateUnsignedTX(tx *wire.MsgTx) bool {
	for _, tin := range tx.TxIn {
		if len(tin.SignatureScript) != 0 || len(tin.Witness) != 0 {
			return false
		}
	}

	return true
}

// NewFromUnsignedTx creates a new Psbt struct, without any signatures (i.e.
// only the global section is non-empty) using the passed unsigned transaction.
func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) {

	if !validateUnsignedTX(tx) {
		return nil, ErrInvalidRawTxSigned
	}

	inSlice := make([]PInput, len(tx.TxIn))
	outSlice := make([]POutput, len(tx.TxOut))
	unknownSlice := make([]Unknown, 0)

	retPsbt := Packet{
		UnsignedTx: tx,
		Inputs:     inSlice,
		Outputs:    outSlice,
		Unknowns:   unknownSlice,
	}

	return &retPsbt, nil
}

// NewFromRawBytes returns a new instance of a Packet struct created by reading
// from a byte slice. If the format is invalid, an error is returned. If the
// argument b64 is true, the passed byte slice is decoded from base64 encoding
// before processing.
//
// NOTE: To create a Packet from one's own data, rather than reading in a
// serialization from a counterparty, one should use a psbt.New.
func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {

	// If the PSBT is encoded in bas64, then we'll create a new wrapper
	// reader that'll allow us to incrementally decode the contents of the
	// io.Reader.
	if b64 {
		based64EncodedReader := r
		r = base64.NewDecoder(base64.StdEncoding, based64EncodedReader)
	}

	// The Packet struct does not store the fixed magic bytes, but they
	// must be present or the serialization must be explicitly rejected.
	var magic [5]byte
	if _, err := io.ReadFull(r, magic[:]); err != nil {
		return nil, err
	}
	if magic != psbtMagic {
		return nil, ErrInvalidMagicBytes
	}

	// Next we parse the GLOBAL section.  There is currently only 1 known
	// key type, UnsignedTx.  We insist this exists first; unknowns are
	// allowed, but only after.
	keyint, keydata, err := getKey(r)
	if err != nil {
		return nil, err
	}
	if GlobalType(keyint) != UnsignedTxType || keydata != nil {
		return nil, ErrInvalidPsbtFormat
	}

	// Now that we've verified the global type is present, we'll decode it
	// into a proper unsigned transaction, and validate it.
	value, err := wire.ReadVarBytes(
		r, 0, MaxPsbtValueLength, "PSBT value",
	)
	if err != nil {
		return nil, err
	}
	msgTx := wire.NewMsgTx(2)
	err = msgTx.Deserialize(bytes.NewReader(value))
	if err != nil {
		return nil, err
	}
	if !validateUnsignedTX(msgTx) {
		return nil, ErrInvalidRawTxSigned
	}

	// Next we parse any unknowns that may be present, making sure that we
	// break at the separator.
	var unknownSlice []Unknown
	for {
		keyint, keydata, err := getKey(r)
		if err != nil {
			return nil, ErrInvalidPsbtFormat
		}
		if keyint == -1 {
			break
		}

		value, err := wire.ReadVarBytes(
			r, 0, MaxPsbtValueLength, "PSBT value",
		)
		if err != nil {
			return nil, err
		}

		keyintanddata := []byte{byte(keyint)}
		keyintanddata = append(keyintanddata, keydata...)

		newUnknown := Unknown{
			Key:   keyintanddata,
			Value: value,
		}
		unknownSlice = append(unknownSlice, newUnknown)
	}

	// Next we parse the INPUT section.
	inSlice := make([]PInput, len(msgTx.TxIn))
	for i := range msgTx.TxIn {
		input := PInput{}
		err = input.deserialize(r)
		if err != nil {
			return nil, err
		}

		inSlice[i] = input
	}

	// Next we parse the OUTPUT section.
	outSlice := make([]POutput, len(msgTx.TxOut))
	for i := range msgTx.TxOut {
		output := POutput{}
		err = output.deserialize(r)
		if err != nil {
			return nil, err
		}

		outSlice[i] = output
	}

	// Populate the new Packet object
	newPsbt := Packet{
		UnsignedTx: msgTx,
		Inputs:     inSlice,
		Outputs:    outSlice,
		Unknowns:   unknownSlice,
	}

	// Extended sanity checking is applied here to make sure the
	// externally-passed Packet follows all the rules.
	if err = newPsbt.SanityCheck(); err != nil {
		return nil, err
	}

	return &newPsbt, nil
}

// Serialize creates a binary serialization of the referenced Packet struct
// with lexicographical ordering (by key) of the subsections.
func (p *Packet) Serialize(w io.Writer) error {

	// First we write out the precise set of magic bytes that identify a
	// valid PSBT transaction.
	if _, err := w.Write(psbtMagic[:]); err != nil {
		return err
	}

	// Next we prep to write out the unsigned transaction by first
	// serializing it into an intermediate buffer.
	serializedTx := bytes.NewBuffer(
		make([]byte, 0, p.UnsignedTx.SerializeSize()),
	)
	if err := p.UnsignedTx.Serialize(serializedTx); err != nil {
		return err
	}

	// Now that we have the serialized transaction, we'll write it out to
	// the proper global type.
	err := serializeKVPairWithType(
		w, uint8(UnsignedTxType), nil, serializedTx.Bytes(),
	)
	if err != nil {
		return err
	}

	// With that our global section is done, so we'll write out the
	// separator.
	separator := []byte{0x00}
	if _, err := w.Write(separator); err != nil {
		return err
	}

	for _, pInput := range p.Inputs {
		err := pInput.serialize(w)
		if err != nil {
			return err
		}

		if _, err := w.Write(separator); err != nil {
			return err
		}
	}

	for _, pOutput := range p.Outputs {
		err := pOutput.serialize(w)
		if err != nil {
			return err
		}

		if _, err := w.Write(separator); err != nil {
			return err
		}
	}

	return nil
}

// B64Encode returns the base64 encoding of the serialization of
// the current PSBT, or an error if the encoding fails.
func (p *Packet) B64Encode() (string, error) {
	var b bytes.Buffer
	if err := p.Serialize(&b); err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(b.Bytes()), nil
}

// IsComplete returns true only if all of the inputs are
// finalized; this is particularly important in that it decides
// whether the final extraction to a network serialized signed
// transaction will be possible.
func (p *Packet) IsComplete() bool {
	for i := 0; i < len(p.UnsignedTx.TxIn); i++ {
		if !isFinalized(p, i) {
			return false
		}
	}
	return true
}

// SanityCheck checks conditions on a PSBT to ensure that it obeys the
// rules of BIP174, and returns true if so, false if not.
func (p *Packet) SanityCheck() error {

	if !validateUnsignedTX(p.UnsignedTx) {
		return ErrInvalidRawTxSigned
	}

	for _, tin := range p.Inputs {
		if !tin.IsSane() {
			return ErrInvalidPsbtFormat
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 CNI 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/go-iptables/iptables"
	shellwords "github.com/mattn/go-shellwords"
)

type chain struct {
	table       string
	name        string
	entryChains []string // the chains to add the entry rule

	entryRules [][]string // the rules that "point" to this chain
	rules      [][]string // the rules this chain contains
}

// setup idempotently creates the chain. It will not error if the chain exists.
func (c *chain) setup(ipt *iptables.IPTables) error {
	// create the chain
	exists, err := chainExists(ipt, c.table, c.name)
	if err != nil {
		return err
	}
	if !exists {
		if err := ipt.NewChain(c.table, c.name); err != nil {
			return err
		}
	}

	// Add the rules to the chain
	for i := len(c.rules) - 1; i >= 0; i-- {
		if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil {
			return err
		}
	}

	// Add the entry rules to the entry chains
	for _, entryChain := range c.entryChains {
		for i := len(c.entryRules) - 1; i >= 0; i-- {
			r := []string{}
			r = append(r, c.entryRules[i]...)
			r = append(r, "-j", c.name)
			if err := prependUnique(ipt, c.table, entryChain, r); err != nil {
				return err
			}
		}
	}

	return nil
}

// teardown idempotently deletes a chain. It will not error if the chain doesn't exist.
// It will first delete all references to this chain in the entryChains.
func (c *chain) teardown(ipt *iptables.IPTables) error {
	// flush the chain
	// This will succeed *and create the chain* if it does not exist.
	// If the chain doesn't exist, the next checks will fail.
	if err := ipt.ClearChain(c.table, c.name); err != nil {
		return err
	}

	for _, entryChain := range c.entryChains {
		entryChainRules, err := ipt.List(c.table, entryChain)
		if err != nil {
			// Swallow error here - probably the chain doesn't exist.
			// If we miss something the deletion will fail
			continue
		}

		for _, entryChainRule := range entryChainRules[1:] {
			if strings.HasSuffix(entryChainRule, "-j "+c.name) {
				chainParts, err := shellwords.Parse(entryChainRule)
				if err != nil {
					return fmt.Errorf("error parsing iptables rule: %s: %v", entryChainRule, err)
				}
				chainParts = chainParts[2:] // List results always include an -A CHAINNAME

				if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
					return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
				}
			}
		}
	}

	if err := ipt.DeleteChain(c.table, c.name); err != nil {
		return err
	}
	return nil
}

// prependUnique will prepend a rule to a chain, if it does not already exist
func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error {
	exists, err := ipt.Exists(table, chain, rule...)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}

	return ipt.Insert(table, chain, 1, rule...)
}

func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
	chains, err := ipt.ListChains(tableName)
	if err != nil {
		return false, err
	}

	for _, ch := range chains {
		if ch == chainName {
			return true, nil
		}
	}
	return false, nil
}
<commit_msg>portmap: don't panic if listing a chain returns fewer lines than expected<commit_after>// Copyright 2017 CNI 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/go-iptables/iptables"
	shellwords "github.com/mattn/go-shellwords"
)

type chain struct {
	table       string
	name        string
	entryChains []string // the chains to add the entry rule

	entryRules [][]string // the rules that "point" to this chain
	rules      [][]string // the rules this chain contains
}

// setup idempotently creates the chain. It will not error if the chain exists.
func (c *chain) setup(ipt *iptables.IPTables) error {
	// create the chain
	exists, err := chainExists(ipt, c.table, c.name)
	if err != nil {
		return err
	}
	if !exists {
		if err := ipt.NewChain(c.table, c.name); err != nil {
			return err
		}
	}

	// Add the rules to the chain
	for i := len(c.rules) - 1; i >= 0; i-- {
		if err := prependUnique(ipt, c.table, c.name, c.rules[i]); err != nil {
			return err
		}
	}

	// Add the entry rules to the entry chains
	for _, entryChain := range c.entryChains {
		for i := len(c.entryRules) - 1; i >= 0; i-- {
			r := []string{}
			r = append(r, c.entryRules[i]...)
			r = append(r, "-j", c.name)
			if err := prependUnique(ipt, c.table, entryChain, r); err != nil {
				return err
			}
		}
	}

	return nil
}

// teardown idempotently deletes a chain. It will not error if the chain doesn't exist.
// It will first delete all references to this chain in the entryChains.
func (c *chain) teardown(ipt *iptables.IPTables) error {
	// flush the chain
	// This will succeed *and create the chain* if it does not exist.
	// If the chain doesn't exist, the next checks will fail.
	if err := ipt.ClearChain(c.table, c.name); err != nil {
		return err
	}

	for _, entryChain := range c.entryChains {
		entryChainRules, err := ipt.List(c.table, entryChain)
		if err != nil || len(entryChainRules) < 1 {
			// Swallow error here - probably the chain doesn't exist.
			// If we miss something the deletion will fail
			continue
		}

		for _, entryChainRule := range entryChainRules[1:] {
			if strings.HasSuffix(entryChainRule, "-j "+c.name) {
				chainParts, err := shellwords.Parse(entryChainRule)
				if err != nil {
					return fmt.Errorf("error parsing iptables rule: %s: %v", entryChainRule, err)
				}
				chainParts = chainParts[2:] // List results always include an -A CHAINNAME

				if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
					return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
				}
			}
		}
	}

	if err := ipt.DeleteChain(c.table, c.name); err != nil {
		return err
	}
	return nil
}

// prependUnique will prepend a rule to a chain, if it does not already exist
func prependUnique(ipt *iptables.IPTables, table, chain string, rule []string) error {
	exists, err := ipt.Exists(table, chain, rule...)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}

	return ipt.Insert(table, chain, 1, rule...)
}

func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
	chains, err := ipt.ListChains(tableName)
	if err != nil {
		return false, err
	}

	for _, ch := range chains {
		if ch == chainName {
			return true, nil
		}
	}
	return false, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"os"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/service/acm"
	"github.com/hashicorp/terraform/helper/resource"
)

const ACMCertificateRe = `^arn:[^:]+:acm:[^:]+:[^:]+:certificate/.+$`

func TestAccAWSAcmCertificateDataSource_singleIssued(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	var arnRe *regexp.Regexp
	var domain string

	if os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN") != "" {
		arnRe = regexp.MustCompile(fmt.Sprintf("^%s$", os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN")))
	} else {
		arnRe = regexp.MustCompile(ACMCertificateRe)
	}

	if os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN") != "" {
		domain = os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN")
	} else {
		domain = fmt.Sprintf("tf-acc-single-issued.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))
	}

	resourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_multipleIssued(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	var arnRe *regexp.Regexp
	var domain string

	if os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARN") != "" {
		arnRe = regexp.MustCompile(fmt.Sprintf("^%s$", os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARN")))
	} else {
		arnRe = regexp.MustCompile(ACMCertificateRe)
	}

	if os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAIN") != "" {
		domain = os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAIN")
	} else {
		domain = fmt.Sprintf("tf-acc-multiple-issued.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))
	}

	resourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_noMatchReturnsError(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	domain := fmt.Sprintf("tf-acc-nonexistent.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_Rsa4096(t *testing.T) {
	resourceName := "aws_acm_certificate.test"
	dataSourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProvidersWithTLS,
		CheckDestroy: testAccCheckAcmCertificateDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsAcmCertificateDataSourceConfigRsa4096(),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"),
				),
			},
		},
	})
}

func testAccCheckAwsAcmCertificateDataSourceConfig(domain string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
}
`, domain)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, status string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	statuses = ["%s"]
}
`, domain, status)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, certType string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	types = ["%s"]
}
`, domain, certType)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	most_recent = %v
}
`, domain, mostRecent)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, status string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	statuses = ["%s"]
	most_recent = %v
}
`, domain, status, mostRecent)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, certType string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	types = ["%s"]
	most_recent = %v
}
`, domain, certType, mostRecent)
}

func testAccAwsAcmCertificateDataSourceConfigRsa4096() string {
	return fmt.Sprintf(`
resource "tls_private_key" "test" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "tls_self_signed_cert" "test" {
  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]

  key_algorithm         = "RSA"
  private_key_pem       = "${tls_private_key.test.private_key_pem}"
  validity_period_hours = 12

  subject {
    common_name  = "example.com"
    organization = "ACME Examples, Inc"
  }
}

resource "aws_acm_certificate" "test" {
  certificate_body = "${tls_self_signed_cert.test.cert_pem}"
  private_key      = "${tls_private_key.test.private_key_pem}"
}

data "aws_acm_certificate" "test" {
  domain = "${aws_acm_certificate.test.domain_name}"
}
`)
}<commit_msg>Format<commit_after>package aws

import (
	"fmt"
	"os"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/service/acm"
	"github.com/hashicorp/terraform/helper/resource"
)

const ACMCertificateRe = `^arn:[^:]+:acm:[^:]+:[^:]+:certificate/.+$`

func TestAccAWSAcmCertificateDataSource_singleIssued(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	var arnRe *regexp.Regexp
	var domain string

	if os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN") != "" {
		arnRe = regexp.MustCompile(fmt.Sprintf("^%s$", os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN")))
	} else {
		arnRe = regexp.MustCompile(ACMCertificateRe)
	}

	if os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN") != "" {
		domain = os.Getenv("ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN")
	} else {
		domain = fmt.Sprintf("tf-acc-single-issued.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))
	}

	resourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_multipleIssued(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	var arnRe *regexp.Regexp
	var domain string

	if os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARN") != "" {
		arnRe = regexp.MustCompile(fmt.Sprintf("^%s$", os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARN")))
	} else {
		arnRe = regexp.MustCompile(ACMCertificateRe)
	}

	if os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAIN") != "" {
		domain = os.Getenv("ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAIN")
	} else {
		domain = fmt.Sprintf("tf-acc-multiple-issued.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))
	}

	resourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				ExpectError: regexp.MustCompile(`Multiple certificates for domain`),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
			{
				Config: testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr(resourceName, "arn", arnRe),
				),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_noMatchReturnsError(t *testing.T) {
	if os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") == "" {
		t.Skip("Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set")
	}

	domain := fmt.Sprintf("tf-acc-nonexistent.%s", os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN"))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfig(domain),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, acm.CertificateStatusIssued),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, acm.CertificateTypeAmazonIssued),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, acm.CertificateStatusIssued, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
			{
				Config:      testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, acm.CertificateTypeAmazonIssued, true),
				ExpectError: regexp.MustCompile(`No certificate for domain`),
			},
		},
	})
}

func TestAccAWSAcmCertificateDataSource_Rsa4096(t *testing.T) {
	resourceName := "aws_acm_certificate.test"
	dataSourceName := "data.aws_acm_certificate.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProvidersWithTLS,
		CheckDestroy: testAccCheckAcmCertificateDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsAcmCertificateDataSourceConfigRsa4096(),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"),
				),
			},
		},
	})
}

func testAccCheckAwsAcmCertificateDataSourceConfig(domain string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
}
`, domain)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithStatus(domain, status string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	statuses = ["%s"]
}
`, domain, status)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithTypes(domain, certType string) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	types = ["%s"]
}
`, domain, certType)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecent(domain string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	most_recent = %v
}
`, domain, mostRecent)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndStatus(domain, status string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	statuses = ["%s"]
	most_recent = %v
}
`, domain, status, mostRecent)
}

func testAccCheckAwsAcmCertificateDataSourceConfigWithMostRecentAndTypes(domain, certType string, mostRecent bool) string {
	return fmt.Sprintf(`
data "aws_acm_certificate" "test" {
	domain = "%s"
	types = ["%s"]
	most_recent = %v
}
`, domain, certType, mostRecent)
}

func testAccAwsAcmCertificateDataSourceConfigRsa4096() string {
	return fmt.Sprintf(`
resource "tls_private_key" "test" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "tls_self_signed_cert" "test" {
  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]

  key_algorithm         = "RSA"
  private_key_pem       = "${tls_private_key.test.private_key_pem}"
  validity_period_hours = 12

  subject {
    common_name  = "example.com"
    organization = "ACME Examples, Inc"
  }
}

resource "aws_acm_certificate" "test" {
  certificate_body = "${tls_self_signed_cert.test.cert_pem}"
  private_key      = "${tls_private_key.test.private_key_pem}"
}

data "aws_acm_certificate" "test" {
  domain = "${aws_acm_certificate.test.domain_name}"
}
`)
}
<|endoftext|>