<commit_before>package readline

import (
	"context"
	"io"
	"strings"
	"unicode"

	"github.com/atotto/clipboard"
)

func keyFuncEnter(ctx context.Context, this *Buffer) Result { // Ctrl-M
	return ENTER
}

func keyFuncIntr(ctx context.Context, this *Buffer) Result { // Ctrl-C
	this.Buffer = this.Buffer[:0]
	this.Cursor = 0
	this.ViewStart = 0
	return INTR
}

func keyFuncHead(ctx context.Context, this *Buffer) Result { // Ctrl-A
	this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, 1)
	return CONTINUE
}

func keyFuncBackward(ctx context.Context, this *Buffer) Result { // Ctrl-B
	if this.Cursor <= 0 {
		return CONTINUE
	}
	this.Cursor--
	if this.Cursor < this.ViewStart {
		this.ViewStart--
		this.Repaint(this.Cursor, 1)
	} else {
		this.backspace(GetCharWidth(this.Buffer[this.Cursor]))
	}
	return CONTINUE
}

func keyFuncTail(ctx context.Context, this *Buffer) Result { // Ctrl-E
	allength := this.GetWidthBetween(this.ViewStart, len(this.Buffer))
	if allength < this.ViewWidth() {
		for ; this.Cursor < len(this.Buffer); this.Cursor++ {
			this.putRune(this.Buffer[this.Cursor])
		}
	} else {
		io.WriteString(this.Out, "\a")
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.ViewStart = len(this.Buffer) - 1
		w := GetCharWidth(this.Buffer[this.ViewStart])
		for {
			if this.ViewStart <= 0 {
				break
			}
			w_ := w + GetCharWidth(this.Buffer[this.ViewStart-1])
			if w_ >= this.ViewWidth() {
				break
			}
			w = w_
			this.ViewStart--
		}
		for this.Cursor = this.ViewStart; this.Cursor < len(this.Buffer); this.Cursor++ {
			this.putRune(this.Buffer[this.Cursor])
		}
	}
	return CONTINUE
}

func keyFuncForward(ctx context.Context, this *Buffer) Result { // Ctrl-F
	if this.Cursor >= len(this.Buffer) {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
	if w < this.ViewWidth() {
		// No Scroll
		this.putRune(this.Buffer[this.Cursor])
	} else {
		// Right Scroll
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		if GetCharWidth(this.Buffer[this.Cursor]) > GetCharWidth(this.Buffer[this.ViewStart]) {
			this.ViewStart++
		}
		this.ViewStart++
		for i := this.ViewStart; i <= this.Cursor; i++ {
			this.putRune(this.Buffer[i])
		}
		this.Eraseline()
	}
	this.Cursor++
	return CONTINUE
}

func keyFuncBackSpace(ctx context.Context, this *Buffer) Result { // Backspace
	if this.Cursor > 0 {
		this.Cursor--
		delw := this.Delete(this.Cursor, 1)
		if this.Cursor >= this.ViewStart {
			this.backspace(delw)
		} else {
			this.ViewStart = this.Cursor
		}
		this.Repaint(this.Cursor, delw)
	}
	return CONTINUE
}

func keyFuncDelete(ctx context.Context, this *Buffer) Result { // Del
	delw := this.Delete(this.Cursor, 1)
	this.Repaint(this.Cursor, delw)
	return CONTINUE
}

func keyFuncDeleteOrAbort(ctx context.Context, this *Buffer) Result { // Ctrl-D
	if len(this.Buffer) > 0 {
		return keyFuncDelete(ctx, this)
	} else {
		return ABORT
	}
}

func keyFuncInsertSelf(ctx context.Context, this *Buffer, keys string) Result {
	if len(keys) == 2 && keys[0] == '\x1B' { // for AltGr-shift
		keys = keys[1:]
	}
	this.InsertString(this.Cursor, keys)

	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	w1 := GetStringWidth(keys)
	if w+w1 >= this.ViewWidth() {
		// scroll left
		this.backspace(w)
		this.Cursor += len([]rune(keys))
		this.ResetViewStart()
		for _, ch := range this.Buffer[this.ViewStart:this.Cursor] {
			this.putRune(ch)
		}
		this.Eraseline()
	} else {
		this.Repaint(this.Cursor, -w1)
		this.Cursor += len([]rune(keys))
	}
	return CONTINUE
}

func keyFuncClearAfter(ctx context.Context, this *Buffer) Result {
	clipboard.WriteAll(this.SubString(this.Cursor, len(this.Buffer)))

	this.Eraseline()
	this.Buffer = this.Buffer[:this.Cursor]
	return CONTINUE
}

func keyFuncClear(ctx context.Context, this *Buffer) Result {
	width := this.GetWidthBetween(this.ViewStart, this.Cursor)
	this.backspace(width)
	this.Eraseline()
	this.Buffer = this.Buffer[:0]
	this.Cursor = 0
	this.ViewStart = 0
	return CONTINUE
}

func keyFuncWordRubout(ctx context.Context, this *Buffer) Result {
	org_cursor := this.Cursor
	for this.Cursor > 0 && unicode.IsSpace(this.Buffer[this.Cursor-1]) {
		this.Cursor--
	}
	i := this.CurrentWordTop()
	clipboard.WriteAll(this.SubString(i, org_cursor))
	keta := this.Delete(i, org_cursor-i)
	if i >= this.ViewStart {
		this.backspace(keta)
	} else {
		this.backspace(this.GetWidthBetween(this.ViewStart, org_cursor))
	}
	this.Cursor = i
	this.Repaint(i, keta)
	return CONTINUE
}

func keyFuncClearBefore(ctx context.Context, this *Buffer) Result {
	keta := this.GetWidthBetween(this.ViewStart, this.Cursor)
	clipboard.WriteAll(this.SubString(0, this.Cursor))
	this.Delete(0, this.Cursor)
	this.backspace(keta)
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, keta)
	return CONTINUE
}

func keyFuncCLS(ctx context.Context, this *Buffer) Result {
	io.WriteString(this.Out, "\x1B[1;1H\x1B[2J")
	this.RepaintAll()
	return CONTINUE
}

func keyFuncRepaintOnNewline(ctx context.Context, this *Buffer) Result {
	this.Out.WriteByte('\n')
	this.RepaintAll()
	return CONTINUE
}

func keyFuncQuotedInsert(ctx context.Context, this *Buffer) Result {
	io.WriteString(this.Out, ansiCursorOn)
	defer io.WriteString(this.Out, ansiCursorOff)

	this.Out.Flush()
	if key, err := getKey(this.TTY); err == nil {
		return keyFuncInsertSelf(ctx, this, key)
	} else {
		return CONTINUE
	}
}

func keyFuncPaste(ctx context.Context, this *Buffer) Result {
	text, err := clipboard.ReadAll()
	if err != nil {
		return CONTINUE
	}
	this.InsertAndRepaint(text)
	return CONTINUE
}

func keyFuncPasteQuote(ctx context.Context, this *Buffer) Result {
	text, err := clipboard.ReadAll()
	if err != nil {
		return CONTINUE
	}
	if strings.IndexRune(text, ' ') >= 0 &&
		!strings.HasPrefix(text, `"`) {
		text = `"` + strings.Replace(text, `"`, `""`, -1) + `"`
	}
	this.InsertAndRepaint(text)
	return CONTINUE
}

func maxInt(a, b int) int {
	if a < b {
		return b
	} else {
		return a
	}
}

func keyFuncSwapChar(ctx context.Context, this *Buffer) Result {
	if len(this.Buffer) == this.Cursor {
		if this.Cursor < 2 {
			return CONTINUE
		}
		this.Buffer[this.Cursor-2], this.Buffer[this.Cursor-1] = this.Buffer[this.Cursor-1], this.Buffer[this.Cursor-2]

		redrawStart := maxInt(this.Cursor-2, this.ViewStart)
		this.backspace(this.GetWidthBetween(redrawStart, this.Cursor))
		for _, ch := range this.Buffer[redrawStart:this.Cursor] {
			this.putRune(ch)
		}
	} else {
		if this.Cursor < 1 {
			return CONTINUE
		}

		w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
		this.Buffer[this.Cursor-1], this.Buffer[this.Cursor] = this.Buffer[this.Cursor], this.Buffer[this.Cursor-1]
		if w >= this.ViewWidth() {
			// cursor move right and scroll
			w_1 := w - GetCharWidth(this.Buffer[this.Cursor])
			this.backspace(w_1)
			this.ViewStart++
			for i := this.ViewStart; i <= this.Cursor; i++ {
				this.putRune(this.Buffer[i])
			}
		} else {
			// no necessary to scroll
			redrawStart := maxInt(this.Cursor-1, this.ViewStart)
			this.backspace(this.GetWidthBetween(redrawStart, this.Cursor))
			for i := redrawStart; i <= this.Cursor; i++ {
				this.putRune(this.Buffer[i])
			}
		}
		this.Cursor++
	}
	return CONTINUE
}

func keyFuncBackwardWord(ctx context.Context, this *Buffer) Result {
	newPos := this.Cursor
	for newPos > 0 && this.Buffer[newPos-1] == ' ' {
		newPos--
	}
	for newPos > 0 && this.Buffer[newPos-1] != ' ' {
		newPos--
	}
	if newPos >= this.ViewStart {
		w := this.GetWidthBetween(newPos, this.Cursor)
		this.backspace(w)
		this.Cursor = newPos
	} else {
		w := this.GetWidthBetween(this.ViewStart, this.Cursor)
		this.backspace(w)
		this.Cursor = newPos
		this.ViewStart = newPos
		this.Repaint(newPos, 0)
	}
	return CONTINUE
}

func keyFuncForwardWord(ctx context.Context, this *Buffer) Result {
	newPos := this.Cursor
	for newPos < len(this.Buffer) && this.Buffer[newPos] != ' ' {
		newPos++
	}
	for newPos < len(this.Buffer) && this.Buffer[newPos] == ' ' {
		newPos++
	}
	w := this.GetWidthBetween(this.ViewStart, newPos)
	if w < this.ViewWidth() {
		for this.Cursor < newPos {
			this.putRune(this.Buffer[this.Cursor])
			this.Cursor++
		}
	} else {
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.Cursor = newPos
		for w >= this.ViewWidth() {
			w -= GetCharWidth(this.Buffer[this.ViewStart])
			this.ViewStart++
		}
		for _, ch := range this.Buffer[this.ViewStart:this.Cursor] {
			this.putRune(ch)
		}
		this.Eraseline()
	}
	return CONTINUE
}
<commit_msg>readline: use for-range loop more (2)<commit_after>package readline

import (
	"context"
	"io"
	"strings"
	"unicode"

	"github.com/atotto/clipboard"
)

func keyFuncEnter(ctx context.Context, this *Buffer) Result { // Ctrl-M
	return ENTER
}

func keyFuncIntr(ctx context.Context, this *Buffer) Result { // Ctrl-C
	this.Buffer = this.Buffer[:0]
	this.Cursor = 0
	this.ViewStart = 0
	return INTR
}

func keyFuncHead(ctx context.Context, this *Buffer) Result { // Ctrl-A
	this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, 1)
	return CONTINUE
}

func keyFuncBackward(ctx context.Context, this *Buffer) Result { // Ctrl-B
	if this.Cursor <= 0 {
		return CONTINUE
	}
	this.Cursor--
	if this.Cursor < this.ViewStart {
		this.ViewStart--
		this.Repaint(this.Cursor, 1)
	} else {
		this.backspace(GetCharWidth(this.Buffer[this.Cursor]))
	}
	return CONTINUE
}

func keyFuncTail(ctx context.Context, this *Buffer) Result { // Ctrl-E
	allength := this.GetWidthBetween(this.ViewStart, len(this.Buffer))
	if allength < this.ViewWidth() {
		for ; this.Cursor < len(this.Buffer); this.Cursor++ {
			this.putRune(this.Buffer[this.Cursor])
		}
	} else {
		io.WriteString(this.Out, "\a")
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.ViewStart = len(this.Buffer) - 1
		w := GetCharWidth(this.Buffer[this.ViewStart])
		for {
			if this.ViewStart <= 0 {
				break
			}
			w_ := w + GetCharWidth(this.Buffer[this.ViewStart-1])
			if w_ >= this.ViewWidth() {
				break
			}
			w = w_
			this.ViewStart--
		}
		for this.Cursor = this.ViewStart; this.Cursor < len(this.Buffer); this.Cursor++ {
			this.putRune(this.Buffer[this.Cursor])
		}
	}
	return CONTINUE
}

func keyFuncForward(ctx context.Context, this *Buffer) Result { // Ctrl-F
	if this.Cursor >= len(this.Buffer) {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
	if w < this.ViewWidth() {
		// No Scroll
		this.putRune(this.Buffer[this.Cursor])
	} else {
		// Right Scroll
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		if GetCharWidth(this.Buffer[this.Cursor]) > GetCharWidth(this.Buffer[this.ViewStart]) {
			this.ViewStart++
		}
		this.ViewStart++
		for _, ch := range this.Buffer[this.ViewStart : this.Cursor+1] {
			this.putRune(ch)
		}
		this.Eraseline()
	}
	this.Cursor++
	return CONTINUE
}

func keyFuncBackSpace(ctx context.Context, this *Buffer) Result { // Backspace
	if this.Cursor > 0 {
		this.Cursor--
		delw := this.Delete(this.Cursor, 1)
		if this.Cursor >= this.ViewStart {
			this.backspace(delw)
		} else {
			this.ViewStart = this.Cursor
		}
		this.Repaint(this.Cursor, delw)
	}
	return CONTINUE
}

func keyFuncDelete(ctx context.Context, this *Buffer) Result { // Del
	delw := this.Delete(this.Cursor, 1)
	this.Repaint(this.Cursor, delw)
	return CONTINUE
}

func keyFuncDeleteOrAbort(ctx context.Context, this *Buffer) Result { // Ctrl-D
	if len(this.Buffer) > 0 {
		return keyFuncDelete(ctx, this)
	} else {
		return ABORT
	}
}

func keyFuncInsertSelf(ctx context.Context, this *Buffer, keys string) Result {
	if len(keys) == 2 && keys[0] == '\x1B' { // for AltGr-shift
		keys = keys[1:]
	}
	this.InsertString(this.Cursor, keys)

	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	w1 := GetStringWidth(keys)
	if w+w1 >= this.ViewWidth() {
		// scroll left
		this.backspace(w)
		this.Cursor += len([]rune(keys))
		this.ResetViewStart()
		for _, ch := range this.Buffer[this.ViewStart:this.Cursor] {
			this.putRune(ch)
		}
		this.Eraseline()
	} else {
		this.Repaint(this.Cursor, -w1)
		this.Cursor += len([]rune(keys))
	}
	return CONTINUE
}

func keyFuncClearAfter(ctx context.Context, this *Buffer) Result {
	clipboard.WriteAll(this.SubString(this.Cursor, len(this.Buffer)))

	this.Eraseline()
	this.Buffer = this.Buffer[:this.Cursor]
	return CONTINUE
}

func keyFuncClear(ctx context.Context, this *Buffer) Result {
	width := this.GetWidthBetween(this.ViewStart, this.Cursor)
	this.backspace(width)
	this.Eraseline()
	this.Buffer = this.Buffer[:0]
	this.Cursor = 0
	this.ViewStart = 0
	return CONTINUE
}

func keyFuncWordRubout(ctx context.Context, this *Buffer) Result {
	org_cursor := this.Cursor
	for this.Cursor > 0 && unicode.IsSpace(this.Buffer[this.Cursor-1]) {
		this.Cursor--
	}
	i := this.CurrentWordTop()
	clipboard.WriteAll(this.SubString(i, org_cursor))
	keta := this.Delete(i, org_cursor-i)
	if i >= this.ViewStart {
		this.backspace(keta)
	} else {
		this.backspace(this.GetWidthBetween(this.ViewStart, org_cursor))
	}
	this.Cursor = i
	this.Repaint(i, keta)
	return CONTINUE
}

func keyFuncClearBefore(ctx context.Context, this *Buffer) Result {
	keta := this.GetWidthBetween(this.ViewStart, this.Cursor)
	clipboard.WriteAll(this.SubString(0, this.Cursor))
	this.Delete(0, this.Cursor)
	this.backspace(keta)
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, keta)
	return CONTINUE
}

func keyFuncCLS(ctx context.Context, this *Buffer) Result {
	io.WriteString(this.Out, "\x1B[1;1H\x1B[2J")
	this.RepaintAll()
	return CONTINUE
}

func keyFuncRepaintOnNewline(ctx context.Context, this *Buffer) Result {
	this.Out.WriteByte('\n')
	this.RepaintAll()
	return CONTINUE
}

func keyFuncQuotedInsert(ctx context.Context, this *Buffer) Result {
	io.WriteString(this.Out, ansiCursorOn)
	defer io.WriteString(this.Out, ansiCursorOff)

	this.Out.Flush()
	if key, err := getKey(this.TTY); err == nil {
		return keyFuncInsertSelf(ctx, this, key)
	} else {
		return CONTINUE
	}
}

func keyFuncPaste(ctx context.Context, this *Buffer) Result {
	text, err := clipboard.ReadAll()
	if err != nil {
		return CONTINUE
	}
	this.InsertAndRepaint(text)
	return CONTINUE
}

func keyFuncPasteQuote(ctx context.Context, this *Buffer) Result {
	text, err := clipboard.ReadAll()
	if err != nil {
		return CONTINUE
	}
	if strings.IndexRune(text, ' ') >= 0 &&
		!strings.HasPrefix(text, `"`) {
		text = `"` + strings.Replace(text, `"`, `""`, -1) + `"`
	}
	this.InsertAndRepaint(text)
	return CONTINUE
}

func maxInt(a, b int) int {
	if a < b {
		return b
	} else {
		return a
	}
}

func keyFuncSwapChar(ctx context.Context, this *Buffer) Result {
	if len(this.Buffer) == this.Cursor {
		if this.Cursor < 2 {
			return CONTINUE
		}
		this.Buffer[this.Cursor-2], this.Buffer[this.Cursor-1] = this.Buffer[this.Cursor-1], this.Buffer[this.Cursor-2]

		redrawStart := maxInt(this.Cursor-2, this.ViewStart)
		this.backspace(this.GetWidthBetween(redrawStart, this.Cursor))
		for _, ch := range this.Buffer[redrawStart:this.Cursor] {
			this.putRune(ch)
		}
	} else {
		if this.Cursor < 1 {
			return CONTINUE
		}

		w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
		this.Buffer[this.Cursor-1], this.Buffer[this.Cursor] = this.Buffer[this.Cursor], this.Buffer[this.Cursor-1]
		if w >= this.ViewWidth() {
			// cursor move right and scroll
			w_1 := w - GetCharWidth(this.Buffer[this.Cursor])
			this.backspace(w_1)
			this.ViewStart++
			for _, ch := range this.Buffer[this.ViewStart : this.Cursor+1] {
				this.putRune(ch)
			}
		} else {
			// no necessary to scroll
			redrawStart := maxInt(this.Cursor-1, this.ViewStart)
			this.backspace(this.GetWidthBetween(redrawStart, this.Cursor))
			for _, ch := range this.Buffer[redrawStart : this.Cursor+1] {
				this.putRune(ch)
			}
		}
		this.Cursor++
	}
	return CONTINUE
}

func keyFuncBackwardWord(ctx context.Context, this *Buffer) Result {
	newPos := this.Cursor
	for newPos > 0 && this.Buffer[newPos-1] == ' ' {
		newPos--
	}
	for newPos > 0 && this.Buffer[newPos-1] != ' ' {
		newPos--
	}
	if newPos >= this.ViewStart {
		w := this.GetWidthBetween(newPos, this.Cursor)
		this.backspace(w)
		this.Cursor = newPos
	} else {
		w := this.GetWidthBetween(this.ViewStart, this.Cursor)
		this.backspace(w)
		this.Cursor = newPos
		this.ViewStart = newPos
		this.Repaint(newPos, 0)
	}
	return CONTINUE
}

func keyFuncForwardWord(ctx context.Context, this *Buffer) Result {
	newPos := this.Cursor
	for newPos < len(this.Buffer) && this.Buffer[newPos] != ' ' {
		newPos++
	}
	for newPos < len(this.Buffer) && this.Buffer[newPos] == ' ' {
		newPos++
	}
	w := this.GetWidthBetween(this.ViewStart, newPos)
	if w < this.ViewWidth() {
		for this.Cursor < newPos {
			this.putRune(this.Buffer[this.Cursor])
			this.Cursor++
		}
	} else {
		this.backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.Cursor = newPos
		for w >= this.ViewWidth() {
			w -= GetCharWidth(this.Buffer[this.ViewStart])
			this.ViewStart++
		}
		for _, ch := range this.Buffer[this.ViewStart:this.Cursor] {
			this.putRune(ch)
		}
		this.Eraseline()
	}
	return CONTINUE
}
<|endoftext|>


<commit_before>//
// Copyright 2021, Sander van Harmelen
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// RepositoryFilesService handles communication with the repository files
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type RepositoryFilesService struct {
	client *Client
}

// File represents a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type File struct {
	FileName     string `json:"file_name"`
	FilePath     string `json:"file_path"`
	Size         int    `json:"size"`
	Encoding     string `json:"encoding"`
	Content      string `json:"content"`
	Ref          string `json:"ref"`
	BlobID       string `json:"blob_id"`
	CommitID     string `json:"commit_id"`
	SHA256       string `json:"content_sha256"`
	LastCommitID string `json:"last_commit_id"`
}

func (r File) String() string {
	return Stringify(r)
}

// GetFileOptions represents the available GetFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFile allows you to receive information about a file in repository like
// name, size, content. Note that file content is Base64 encoded.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(File)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// GetFileMetaDataOptions represents the available GetFileMetaData() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileMetaDataOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileMetaData allows you to receive meta information about a file in
// repository like name, size.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName string, opt *GetFileMetaDataOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodHead, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return nil, resp, err
	}

	f := &File{
		BlobID:       resp.Header.Get("X-Gitlab-Blob-Id"),
		CommitID:     resp.Header.Get("X-Gitlab-Last-Commit-Id"),
		Encoding:     resp.Header.Get("X-Gitlab-Encoding"),
		FileName:     resp.Header.Get("X-Gitlab-File-Name"),
		FilePath:     resp.Header.Get("X-Gitlab-File-Path"),
		Ref:          resp.Header.Get("X-Gitlab-Ref"),
		SHA256:       resp.Header.Get("X-Gitlab-Content-Sha256"),
		LastCommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"),
	}

	if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
		f.Size, err = strconv.Atoi(sizeString)
		if err != nil {
			return nil, resp, err
		}
	}

	return f, resp, err
}

// FileBlameRange represents one item of blame information.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileBlameRange struct {
	Commit struct {
		ID             string     `json:"id"`
		ParentIDs      []string   `json:"parent_ids"`
		Message        string     `json:"message"`
		AuthoredDate   *time.Time `json:"authored_date"`
		AuthorName     string     `json:"author_name"`
		AuthorEmail    string     `json:"author_email"`
		CommittedDate  *time.Time `json:"committed_date"`
		CommitterName  string     `json:"committer_name"`
		CommitterEmail string     `json:"committer_email"`
	} `json:"commit"`
	Lines []string `json:"lines"`
}

func (b FileBlameRange) String() string {
	return Stringify(b)
}

// GetFileBlameOptions represents the available GetFileBlame() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
type GetFileBlameOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileBlame allows you to receive blame information. Each blame range
// contains lines and corresponding commit info.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
func (s *RepositoryFilesService) GetFileBlame(pid interface{}, file string, opt *GetFileBlameOptions, options ...RequestOptionFunc) ([]*FileBlameRange, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/blame",
		pathEscape(project),
		url.PathEscape(file),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var br []*FileBlameRange
	resp, err := s.client.Do(req, &br)
	if err != nil {
		return nil, resp, err
	}

	return br, resp, err
}

// GetRawFileOptions represents the available GetRawFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
type GetRawFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetRawFile allows you to receive the raw file in repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...RequestOptionFunc) ([]byte, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/raw",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var f bytes.Buffer
	resp, err := s.client.Do(req, &f)
	if err != nil {
		return nil, resp, err
	}

	return f.Bytes(), resp, err
}

// FileInfo represents file details of a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileInfo struct {
	FilePath string `json:"file_path"`
	Branch   string `json:"branch"`
}

func (r FileInfo) String() string {
	return Stringify(r)
}

// CreateFileOptions represents the available CreateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
type CreateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// CreateFile creates a new file in a repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// UpdateFileOptions represents the available UpdateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
type UpdateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// UpdateFile updates an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// DeleteFileOptions represents the available DeleteFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
type DeleteFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// DeleteFile deletes an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodDelete, u, opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>FIX(repository_files): Fix Filename Escape<commit_after>//
// Copyright 2021, Sander van Harmelen
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// RepositoryFilesService handles communication with the repository files
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type RepositoryFilesService struct {
	client *Client
}

// File represents a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type File struct {
	FileName     string `json:"file_name"`
	FilePath     string `json:"file_path"`
	Size         int    `json:"size"`
	Encoding     string `json:"encoding"`
	Content      string `json:"content"`
	Ref          string `json:"ref"`
	BlobID       string `json:"blob_id"`
	CommitID     string `json:"commit_id"`
	SHA256       string `json:"content_sha256"`
	LastCommitID string `json:"last_commit_id"`
}

func (r File) String() string {
	return Stringify(r)
}

// GetFileOptions represents the available GetFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFile allows you to receive information about a file in repository like
// name, size, content. Note that file content is Base64 encoded.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(File)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// GetFileMetaDataOptions represents the available GetFileMetaData() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileMetaDataOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileMetaData allows you to receive meta information about a file in
// repository like name, size.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName string, opt *GetFileMetaDataOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		pathEscape(url.PathEscape(fileName)),
	)

	req, err := s.client.NewRequest(http.MethodHead, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return nil, resp, err
	}

	f := &File{
		BlobID:       resp.Header.Get("X-Gitlab-Blob-Id"),
		CommitID:     resp.Header.Get("X-Gitlab-Last-Commit-Id"),
		Encoding:     resp.Header.Get("X-Gitlab-Encoding"),
		FileName:     resp.Header.Get("X-Gitlab-File-Name"),
		FilePath:     resp.Header.Get("X-Gitlab-File-Path"),
		Ref:          resp.Header.Get("X-Gitlab-Ref"),
		SHA256:       resp.Header.Get("X-Gitlab-Content-Sha256"),
		LastCommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"),
	}

	if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
		f.Size, err = strconv.Atoi(sizeString)
		if err != nil {
			return nil, resp, err
		}
	}

	return f, resp, err
}

// FileBlameRange represents one item of blame information.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileBlameRange struct {
	Commit struct {
		ID             string     `json:"id"`
		ParentIDs      []string   `json:"parent_ids"`
		Message        string     `json:"message"`
		AuthoredDate   *time.Time `json:"authored_date"`
		AuthorName     string     `json:"author_name"`
		AuthorEmail    string     `json:"author_email"`
		CommittedDate  *time.Time `json:"committed_date"`
		CommitterName  string     `json:"committer_name"`
		CommitterEmail string     `json:"committer_email"`
	} `json:"commit"`
	Lines []string `json:"lines"`
}

func (b FileBlameRange) String() string {
	return Stringify(b)
}

// GetFileBlameOptions represents the available GetFileBlame() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
type GetFileBlameOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileBlame allows you to receive blame information. Each blame range
// contains lines and corresponding commit info.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
func (s *RepositoryFilesService) GetFileBlame(pid interface{}, file string, opt *GetFileBlameOptions, options ...RequestOptionFunc) ([]*FileBlameRange, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/blame",
		pathEscape(project),
		url.PathEscape(file),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var br []*FileBlameRange
	resp, err := s.client.Do(req, &br)
	if err != nil {
		return nil, resp, err
	}

	return br, resp, err
}

// GetRawFileOptions represents the available GetRawFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
type GetRawFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetRawFile allows you to receive the raw file in repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...RequestOptionFunc) ([]byte, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/raw",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var f bytes.Buffer
	resp, err := s.client.Do(req, &f)
	if err != nil {
		return nil, resp, err
	}

	return f.Bytes(), resp, err
}

// FileInfo represents file details of a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileInfo struct {
	FilePath string `json:"file_path"`
	Branch   string `json:"branch"`
}

func (r FileInfo) String() string {
	return Stringify(r)
}

// CreateFileOptions represents the available CreateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
type CreateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// CreateFile creates a new file in a repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// UpdateFileOptions represents the available UpdateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
type UpdateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// UpdateFile updates an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// DeleteFileOptions represents the available DeleteFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
type DeleteFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	StartBranch   *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// DeleteFile deletes an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodDelete, u, opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>package local

import (
	"reflect"
	"testing"

	"github.com/hashicorp/terraform/terraform"
)

func TestCountHook_impl(t *testing.T) {
	var _ terraform.Hook = new(CountHook)
}

func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"lorem": &terraform.InstanceDiff{DestroyDeposed: true},
	}

	n := &terraform.InstanceInfo{} // TODO

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 1

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo":   &terraform.InstanceDiff{Destroy: true},
		"bar":   &terraform.InstanceDiff{Destroy: true},
		"lorem": &terraform.InstanceDiff{Destroy: true},
		"ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	n := &terraform.InstanceInfo{} // TODO

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 4

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_AddOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
		"bar": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
		"lorem": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 3
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"bar": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 3
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_Mixed(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Destroy: true,
		},
		"bar": &terraform.InstanceDiff{},
		"lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 1
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 2

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_NoChange(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo":   &terraform.InstanceDiff{},
		"bar":   &terraform.InstanceDiff{},
		"lorem": &terraform.InstanceDiff{},
		"ipsum": &terraform.InstanceDiff{},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_DataSource(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"data.foo": &terraform.InstanceDiff{
			Destroy: true,
		},
		"data.bar": &terraform.InstanceDiff{},
		"data.lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"data.ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	for k, d := range resources {
		n := &terraform.InstanceInfo{Id: k}
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}
<commit_msg>core: Test to ensure PostDiff is ignoring stubs<commit_after>package local

import (
	"reflect"
	"testing"

	"github.com/hashicorp/terraform/terraform"
)

func TestCountHook_impl(t *testing.T) {
	var _ terraform.Hook = new(CountHook)
}

func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"lorem": &terraform.InstanceDiff{DestroyDeposed: true},
	}

	n := &terraform.InstanceInfo{} // TODO

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 1

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo":   &terraform.InstanceDiff{Destroy: true},
		"bar":   &terraform.InstanceDiff{Destroy: true},
		"lorem": &terraform.InstanceDiff{Destroy: true},
		"ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	n := &terraform.InstanceInfo{} // TODO

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 4

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_AddOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
		"bar": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
		"lorem": &terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 3
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"bar": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 3
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_Mixed(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo": &terraform.InstanceDiff{
			Destroy: true,
		},
		"bar": &terraform.InstanceDiff{},
		"lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 1
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 2

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_NoChange(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"foo":   &terraform.InstanceDiff{},
		"bar":   &terraform.InstanceDiff{},
		"lorem": &terraform.InstanceDiff{},
		"ipsum": &terraform.InstanceDiff{},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_DataSource(t *testing.T) {
	h := new(CountHook)

	resources := map[string]*terraform.InstanceDiff{
		"data.foo": &terraform.InstanceDiff{
			Destroy: true,
		},
		"data.bar": &terraform.InstanceDiff{},
		"data.lorem": &terraform.InstanceDiff{
			Destroy: false,
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo": &terraform.ResourceAttrDiff{},
			},
		},
		"data.ipsum": &terraform.InstanceDiff{Destroy: true},
	}

	for k, d := range resources {
		n := &terraform.InstanceInfo{Id: k}
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 0
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}

func TestCountHookPostDiff_IgnoreStub(t *testing.T) {
	h := new(CountHook)

	resources := []*terraform.InstanceDiff{
		&terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo.0": &terraform.ResourceAttrDiff{},
				"foo.1": &terraform.ResourceAttrDiff{},
				"foo.2": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
			Stub: true,
		},
		&terraform.InstanceDiff{
			Attributes: map[string]*terraform.ResourceAttrDiff{
				"foo.0": &terraform.ResourceAttrDiff{},
				"foo.1": &terraform.ResourceAttrDiff{},
				"foo.2": &terraform.ResourceAttrDiff{RequiresNew: true},
			},
		},
	}

	n := &terraform.InstanceInfo{}

	for _, d := range resources {
		h.PostDiff(n, d)
	}

	expected := new(CountHook)
	expected.ToAdd = 1
	expected.ToChange = 0
	expected.ToRemoveAndAdd = 0
	expected.ToRemove = 0

	if !reflect.DeepEqual(expected, h) {
		t.Fatalf("Expected %#v, got %#v instead.",
			expected, h)
	}
}
<|endoftext|>


<commit_before>package shell

import (
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/golang/protobuf/proto"

	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/util"
)

func init() {
	Commands = append(Commands, &commandFsMetaSave{})
}

type commandFsMetaSave struct {
}

func (c *commandFsMetaSave) Name() string {
	return "fs.meta.save"
}

func (c *commandFsMetaSave) Help() string {
	return `save all directory and file meta data to a local file for metadata backup.

	fs.meta.save /               # save from the root
	fs.meta.save -v -o t.meta /  # save from the root, output to t.meta file.
	fs.meta.save /path/to/save   # save from the directory /path/to/save
	fs.meta.save .               # save from current directory
	fs.meta.save                 # save from current directory

	The meta data will be saved into a local <filer_host>-<port>-<time>.meta file.
	These meta data can be later loaded by fs.meta.load command

`
}

func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {

	fsMetaSaveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	verbose := fsMetaSaveCommand.Bool("v", false, "print out each processed files")
	outputFileName := fsMetaSaveCommand.String("o", "", "output the meta data to this file")
	isObfuscate := fsMetaSaveCommand.Bool("obfuscate", false, "obfuscate the file names")
	// chunksFileName := fsMetaSaveCommand.String("chunks", "", "output all the chunks to this file")
	if err = fsMetaSaveCommand.Parse(args); err != nil {
		return nil
	}

	path, parseErr := commandEnv.parseUrl(findInputDirectory(fsMetaSaveCommand.Args()))
	if parseErr != nil {
		return parseErr
	}

	fileName := *outputFileName
	if fileName == "" {
		t := time.Now()
		fileName = fmt.Sprintf("%s-%d-%4d%02d%02d-%02d%02d%02d.meta",
			commandEnv.option.FilerHost, commandEnv.option.FilerPort, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
	}

	dst, openErr := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if openErr != nil {
		return fmt.Errorf("failed to create file %s: %v", fileName, openErr)
	}
	defer dst.Close()

	var cipherKey util.CipherKey
	if *isObfuscate {
		cipherKey = util.GenCipherKey()
	}

	err = doTraverseBfsAndSaving(commandEnv, writer, path, *verbose, func(outputChan chan interface{}) {
		sizeBuf := make([]byte, 4)
		for item := range outputChan {
			b := item.([]byte)
			util.Uint32toBytes(sizeBuf, uint32(len(b)))
			dst.Write(sizeBuf)
			dst.Write(b)
		}
	}, func(entry *filer_pb.FullEntry, outputChan chan interface{}) (err error) {
		if !entry.Entry.IsDirectory {
			ext := filepath.Ext(entry.Entry.Name)
			if encrypted, encErr := util.Encrypt([]byte(entry.Entry.Name), cipherKey); encErr == nil {
				entry.Entry.Name = util.Base64Encode(encrypted)[:len(entry.Entry.Name)] + ext
				entry.Entry.Name = strings.ReplaceAll(entry.Entry.Name, "/", "x")
			}
		}
		bytes, err := proto.Marshal(entry)
		if err != nil {
			fmt.Fprintf(writer, "marshall error: %v\n", err)
			return
		}

		outputChan <- bytes
		return nil
	})

	if err == nil {
		fmt.Fprintf(writer, "meta data for http://%s:%d%s is saved to %s\n", commandEnv.option.FilerHost, commandEnv.option.FilerPort, path, fileName)
	}

	return err

}

func doTraverseBfsAndSaving(filerClient filer_pb.FilerClient, writer io.Writer, path string, verbose bool, saveFn func(outputChan chan interface{}), genFn func(entry *filer_pb.FullEntry, outputChan chan interface{}) error) error {

	var wg sync.WaitGroup
	wg.Add(1)
	outputChan := make(chan interface{}, 1024)
	go func() {
		saveFn(outputChan)
		wg.Done()
	}()

	var dirCount, fileCount uint64

	err := filer_pb.TraverseBfs(filerClient, util.FullPath(path), func(parentPath util.FullPath, entry *filer_pb.Entry) {

		protoMessage := &filer_pb.FullEntry{
			Dir:   string(parentPath),
			Entry: entry,
		}

		if err := genFn(protoMessage, outputChan); err != nil {
			fmt.Fprintf(writer, "marshall error: %v\n", err)
			return
		}

		if entry.IsDirectory {
			atomic.AddUint64(&dirCount, 1)
		} else {
			atomic.AddUint64(&fileCount, 1)
		}

		if verbose {
			println(parentPath.Child(entry.Name))
		}

	})

	close(outputChan)

	wg.Wait()

	if err == nil && writer != nil {
		fmt.Fprintf(writer, "total %d directories, %d files\n", dirCount, fileCount)
	}
	return err
}
<commit_msg>shell: fs.meta.save skip saving system logs<commit_after>package shell

import (
	"flag"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/golang/protobuf/proto"

	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/util"
)

func init() {
	Commands = append(Commands, &commandFsMetaSave{})
}

type commandFsMetaSave struct {
}

func (c *commandFsMetaSave) Name() string {
	return "fs.meta.save"
}

func (c *commandFsMetaSave) Help() string {
	return `save all directory and file meta data to a local file for metadata backup.

	fs.meta.save /               # save from the root
	fs.meta.save -v -o t.meta /  # save from the root, output to t.meta file.
	fs.meta.save /path/to/save   # save from the directory /path/to/save
	fs.meta.save .               # save from current directory
	fs.meta.save                 # save from current directory

	The meta data will be saved into a local <filer_host>-<port>-<time>.meta file.
	These meta data can be later loaded by fs.meta.load command

`
}

func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {

	fsMetaSaveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	verbose := fsMetaSaveCommand.Bool("v", false, "print out each processed files")
	outputFileName := fsMetaSaveCommand.String("o", "", "output the meta data to this file")
	isObfuscate := fsMetaSaveCommand.Bool("obfuscate", false, "obfuscate the file names")
	// chunksFileName := fsMetaSaveCommand.String("chunks", "", "output all the chunks to this file")
	if err = fsMetaSaveCommand.Parse(args); err != nil {
		return nil
	}

	path, parseErr := commandEnv.parseUrl(findInputDirectory(fsMetaSaveCommand.Args()))
	if parseErr != nil {
		return parseErr
	}

	fileName := *outputFileName
	if fileName == "" {
		t := time.Now()
		fileName = fmt.Sprintf("%s-%d-%4d%02d%02d-%02d%02d%02d.meta",
			commandEnv.option.FilerHost, commandEnv.option.FilerPort, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
	}

	dst, openErr := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if openErr != nil {
		return fmt.Errorf("failed to create file %s: %v", fileName, openErr)
	}
	defer dst.Close()

	var cipherKey util.CipherKey
	if *isObfuscate {
		cipherKey = util.GenCipherKey()
	}

	err = doTraverseBfsAndSaving(commandEnv, writer, path, *verbose, func(outputChan chan interface{}) {
		sizeBuf := make([]byte, 4)
		for item := range outputChan {
			b := item.([]byte)
			util.Uint32toBytes(sizeBuf, uint32(len(b)))
			dst.Write(sizeBuf)
			dst.Write(b)
		}
	}, func(entry *filer_pb.FullEntry, outputChan chan interface{}) (err error) {
		if !entry.Entry.IsDirectory {
			ext := filepath.Ext(entry.Entry.Name)
			if encrypted, encErr := util.Encrypt([]byte(entry.Entry.Name), cipherKey); encErr == nil {
				entry.Entry.Name = util.Base64Encode(encrypted)[:len(entry.Entry.Name)] + ext
				entry.Entry.Name = strings.ReplaceAll(entry.Entry.Name, "/", "x")
			}
		}
		bytes, err := proto.Marshal(entry)
		if err != nil {
			fmt.Fprintf(writer, "marshall error: %v\n", err)
			return
		}

		outputChan <- bytes
		return nil
	})

	if err == nil {
		fmt.Fprintf(writer, "meta data for http://%s:%d%s is saved to %s\n", commandEnv.option.FilerHost, commandEnv.option.FilerPort, path, fileName)
	}

	return err

}

func doTraverseBfsAndSaving(filerClient filer_pb.FilerClient, writer io.Writer, path string, verbose bool, saveFn func(outputChan chan interface{}), genFn func(entry *filer_pb.FullEntry, outputChan chan interface{}) error) error {

	var wg sync.WaitGroup
	wg.Add(1)
	outputChan := make(chan interface{}, 1024)
	go func() {
		saveFn(outputChan)
		wg.Done()
	}()

	var dirCount, fileCount uint64

	err := filer_pb.TraverseBfs(filerClient, util.FullPath(path), func(parentPath util.FullPath, entry *filer_pb.Entry) {

		if strings.HasPrefix(string(parentPath), filer.SystemLogDir) {
			return
		}

		protoMessage := &filer_pb.FullEntry{
			Dir:   string(parentPath),
			Entry: entry,
		}

		if err := genFn(protoMessage, outputChan); err != nil {
			fmt.Fprintf(writer, "marshall error: %v\n", err)
			return
		}

		if entry.IsDirectory {
			atomic.AddUint64(&dirCount, 1)
		} else {
			atomic.AddUint64(&fileCount, 1)
		}

		if verbose {
			println(parentPath.Child(entry.Name))
		}

	})

	close(outputChan)

	wg.Wait()

	if err == nil && writer != nil {
		fmt.Fprintf(writer, "total %d directories, %d files\n", dirCount, fileCount)
	}
	return err
}
<|endoftext|>


<commit_before>package shell

import (
	"context"
	"flag"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/pb/remote_pb"
	"github.com/chrislusf/seaweedfs/weed/remote_storage"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"io"
	"os"
	"strings"
	"time"
)

func init() {
	Commands = append(Commands, &commandRemoteMount{})
}

type commandRemoteMount struct {
}

func (c *commandRemoteMount) Name() string {
	return "remote.mount"
}

func (c *commandRemoteMount) Help() string {
	return `mount remote storage and pull its metadata

	# assume a remote storage is configured to name "cloud1"
	remote.configure -name=cloud1 -type=s3 -access_key=xxx -secret_key=yyy

	# mount and pull one bucket
	remote.mount -dir=/xxx -remote=cloud1/bucket
	# mount and pull one directory in the bucket
	remote.mount -dir=/xxx -remote=cloud1/bucket/dir1

	# after mount, start a separate process to write updates to remote storage
	weed filer.remote.sync -filer=<filerHost>:<filerPort> -dir=/xxx

`
}

func (c *commandRemoteMount) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {

	remoteMountCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)

	dir := remoteMountCommand.String("dir", "", "a directory in filer")
	nonEmpty := remoteMountCommand.Bool("nonempty", false, "allows the mounting over a non-empty directory")
	remote := remoteMountCommand.String("remote", "", "a directory in remote storage, ex. <storageName>/<bucket>/path/to/dir")

	if err = remoteMountCommand.Parse(args); err != nil {
		return nil
	}

	if *dir == "" {
		_, err = listExistingRemoteStorageMounts(commandEnv, writer)
		return err
	}

	// find configuration for remote storage
	remoteConf, err := filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote_storage.ParseLocationName(*remote))
	if err != nil {
		return fmt.Errorf("find configuration for %s: %v", *remote, err)
	}

	remoteStorageLocation, err := remote_storage.ParseRemoteLocation(remoteConf.Type, *remote)
	if err != nil {
		return err
	}

	// sync metadata from remote
	if err = c.syncMetadata(commandEnv, writer, *dir, *nonEmpty, remoteConf, remoteStorageLocation); err != nil {
		return fmt.Errorf("pull metadata: %v", err)
	}

	// store a mount configuration in filer
	if err = c.saveMountMapping(commandEnv, writer, *dir, remoteStorageLocation); err != nil {
		return fmt.Errorf("save mount mapping: %v", err)
	}

	return nil
}

func listExistingRemoteStorageMounts(commandEnv *CommandEnv, writer io.Writer) (mappings *remote_pb.RemoteStorageMapping, err error) {

	// read current mapping
	mappings, err = filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress)
	if err != nil {
		return mappings, err
	}

	jsonPrintln(writer, mappings)

	return

}

func jsonPrintln(writer io.Writer, message proto.Message) error {
	if message == nil {
		return nil
	}
	m := jsonpb.Marshaler{
		EmitDefaults: false,
		Indent:       "  ",
	}

	err := m.Marshal(writer, message)
	fmt.Fprintln(writer)
	return err
}

func (c *commandRemoteMount) findRemoteStorageConfiguration(commandEnv *CommandEnv, writer io.Writer, remote *remote_pb.RemoteStorageLocation) (conf *remote_pb.RemoteConf, err error) {

	return filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote.Name)

}

func (c *commandRemoteMount) syncMetadata(commandEnv *CommandEnv, writer io.Writer, dir string, nonEmpty bool, remoteConf *remote_pb.RemoteConf, remote *remote_pb.RemoteStorageLocation) error {

	// find existing directory, and ensure the directory is empty
	err := commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		parent, name := util.FullPath(dir).DirAndName()
		_, lookupErr := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
			Directory: parent,
			Name:      name,
		})
		if lookupErr != nil {
			if !strings.Contains(lookupErr.Error(), filer_pb.ErrNotFound.Error()) {
				_, createErr := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
					Directory: parent,
					Entry: &filer_pb.Entry{
						Name:        name,
						IsDirectory: true,
						Attributes: &filer_pb.FuseAttributes{
							Mtime:    time.Now().Unix(),
							Crtime:   time.Now().Unix(),
							FileMode: uint32(0644 | os.ModeDir),
						},
					},
				})
				return createErr
			}
		}

		mountToDirIsEmpty := true
		listErr := filer_pb.SeaweedList(client, dir, "", func(entry *filer_pb.Entry, isLast bool) error {
			mountToDirIsEmpty = false
			return nil
		}, "", false, 1)

		if listErr != nil {
			return fmt.Errorf("list %s: %v", dir, listErr)
		}

		if !mountToDirIsEmpty {
			if !nonEmpty {
				return fmt.Errorf("dir %s is not empty", dir)
			}
		}

		return nil
	})
	if err != nil {
		return err
	}

	// pull metadata from remote
	if err = pullMetadata(commandEnv, writer, util.FullPath(dir), remote, util.FullPath(dir), remoteConf); err != nil {
		return fmt.Errorf("cache metadata: %v", err)
	}

	return nil
}

func (c *commandRemoteMount) saveMountMapping(commandEnv *CommandEnv, writer io.Writer, dir string, remoteStorageLocation *remote_pb.RemoteStorageLocation) (err error) {

	// read current mapping
	var oldContent, newContent []byte
	err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		oldContent, err = filer.ReadInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE)
		return err
	})
	if err != nil {
		if err != filer_pb.ErrNotFound {
			return fmt.Errorf("read existing mapping: %v", err)
		}
	}

	// add new mapping
	newContent, err = filer.AddRemoteStorageMapping(oldContent, dir, remoteStorageLocation)
	if err != nil {
		return fmt.Errorf("add mapping %s~%s: %v", dir, remoteStorageLocation, err)
	}

	// save back
	err = commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		return filer.SaveInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE, newContent)
	})
	if err != nil {
		return fmt.Errorf("save mapping: %v", err)
	}

	return nil
}

// if an entry has synchronized metadata but has not synchronized content
//    entry.Attributes.FileSize == entry.RemoteEntry.RemoteSize
//    entry.Attributes.Mtime    == entry.RemoteEntry.RemoteMtime
//    entry.RemoteEntry.LastLocalSyncTsNs == 0
// if an entry has synchronized metadata but has synchronized content before
//    entry.Attributes.FileSize == entry.RemoteEntry.RemoteSize
//    entry.Attributes.Mtime    == entry.RemoteEntry.RemoteMtime
//    entry.RemoteEntry.LastLocalSyncTsNs > 0
// if an entry has synchronized metadata but has new updates
//    entry.Attributes.Mtime * 1,000,000,000    > entry.RemoteEntry.LastLocalSyncTsNs
func doSaveRemoteEntry(client filer_pb.SeaweedFilerClient, localDir string, existingEntry *filer_pb.Entry, remoteEntry *filer_pb.RemoteEntry) error {
	existingEntry.RemoteEntry = remoteEntry
	existingEntry.Attributes.FileSize = uint64(remoteEntry.RemoteSize)
	existingEntry.Attributes.Mtime = remoteEntry.RemoteMtime
	_, updateErr := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
		Directory: localDir,
		Entry:     existingEntry,
	})
	if updateErr != nil {
		return updateErr
	}
	return nil
}
<commit_msg>refactor<commit_after>package shell

import (
	"context"
	"flag"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/pb/remote_pb"
	"github.com/chrislusf/seaweedfs/weed/remote_storage"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"io"
	"os"
	"strings"
	"time"
)

func init() {
	Commands = append(Commands, &commandRemoteMount{})
}

type commandRemoteMount struct {
}

func (c *commandRemoteMount) Name() string {
	return "remote.mount"
}

func (c *commandRemoteMount) Help() string {
	return `mount remote storage and pull its metadata

	# assume a remote storage is configured to name "cloud1"
	remote.configure -name=cloud1 -type=s3 -access_key=xxx -secret_key=yyy

	# mount and pull one bucket
	remote.mount -dir=/xxx -remote=cloud1/bucket
	# mount and pull one directory in the bucket
	remote.mount -dir=/xxx -remote=cloud1/bucket/dir1

	# after mount, start a separate process to write updates to remote storage
	weed filer.remote.sync -filer=<filerHost>:<filerPort> -dir=/xxx

`
}

func (c *commandRemoteMount) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {

	remoteMountCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)

	dir := remoteMountCommand.String("dir", "", "a directory in filer")
	nonEmpty := remoteMountCommand.Bool("nonempty", false, "allows the mounting over a non-empty directory")
	remote := remoteMountCommand.String("remote", "", "a directory in remote storage, ex. <storageName>/<bucket>/path/to/dir")

	if err = remoteMountCommand.Parse(args); err != nil {
		return nil
	}

	if *dir == "" {
		_, err = listExistingRemoteStorageMounts(commandEnv, writer)
		return err
	}

	// find configuration for remote storage
	remoteConf, err := filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote_storage.ParseLocationName(*remote))
	if err != nil {
		return fmt.Errorf("find configuration for %s: %v", *remote, err)
	}

	remoteStorageLocation, err := remote_storage.ParseRemoteLocation(remoteConf.Type, *remote)
	if err != nil {
		return err
	}

	// sync metadata from remote
	if err = c.syncMetadata(commandEnv, writer, *dir, *nonEmpty, remoteConf, remoteStorageLocation); err != nil {
		return fmt.Errorf("pull metadata: %v", err)
	}

	// store a mount configuration in filer
	if err = c.saveMountMapping(commandEnv, *dir, remoteStorageLocation); err != nil {
		return fmt.Errorf("save mount mapping: %v", err)
	}

	return nil
}

func listExistingRemoteStorageMounts(commandEnv *CommandEnv, writer io.Writer) (mappings *remote_pb.RemoteStorageMapping, err error) {

	// read current mapping
	mappings, err = filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress)
	if err != nil {
		return mappings, err
	}

	jsonPrintln(writer, mappings)

	return

}

func jsonPrintln(writer io.Writer, message proto.Message) error {
	if message == nil {
		return nil
	}
	m := jsonpb.Marshaler{
		EmitDefaults: false,
		Indent:       "  ",
	}

	err := m.Marshal(writer, message)
	fmt.Fprintln(writer)
	return err
}

func (c *commandRemoteMount) findRemoteStorageConfiguration(commandEnv *CommandEnv, writer io.Writer, remote *remote_pb.RemoteStorageLocation) (conf *remote_pb.RemoteConf, err error) {

	return filer.ReadRemoteStorageConf(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress, remote.Name)

}

func (c *commandRemoteMount) syncMetadata(commandEnv *CommandEnv, writer io.Writer, dir string, nonEmpty bool, remoteConf *remote_pb.RemoteConf, remote *remote_pb.RemoteStorageLocation) error {

	// find existing directory, and ensure the directory is empty
	err := commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		parent, name := util.FullPath(dir).DirAndName()
		_, lookupErr := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
			Directory: parent,
			Name:      name,
		})
		if lookupErr != nil {
			if !strings.Contains(lookupErr.Error(), filer_pb.ErrNotFound.Error()) {
				_, createErr := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
					Directory: parent,
					Entry: &filer_pb.Entry{
						Name:        name,
						IsDirectory: true,
						Attributes: &filer_pb.FuseAttributes{
							Mtime:    time.Now().Unix(),
							Crtime:   time.Now().Unix(),
							FileMode: uint32(0644 | os.ModeDir),
						},
					},
				})
				return createErr
			}
		}

		mountToDirIsEmpty := true
		listErr := filer_pb.SeaweedList(client, dir, "", func(entry *filer_pb.Entry, isLast bool) error {
			mountToDirIsEmpty = false
			return nil
		}, "", false, 1)

		if listErr != nil {
			return fmt.Errorf("list %s: %v", dir, listErr)
		}

		if !mountToDirIsEmpty {
			if !nonEmpty {
				return fmt.Errorf("dir %s is not empty", dir)
			}
		}

		return nil
	})
	if err != nil {
		return err
	}

	// pull metadata from remote
	if err = pullMetadata(commandEnv, writer, util.FullPath(dir), remote, util.FullPath(dir), remoteConf); err != nil {
		return fmt.Errorf("cache metadata: %v", err)
	}

	return nil
}

func (c *commandRemoteMount) saveMountMapping(filerClient filer_pb.FilerClient, dir string, remoteStorageLocation *remote_pb.RemoteStorageLocation) (err error) {

	// read current mapping
	var oldContent, newContent []byte
	err = filerClient.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		oldContent, err = filer.ReadInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE)
		return err
	})
	if err != nil {
		if err != filer_pb.ErrNotFound {
			return fmt.Errorf("read existing mapping: %v", err)
		}
	}

	// add new mapping
	newContent, err = filer.AddRemoteStorageMapping(oldContent, dir, remoteStorageLocation)
	if err != nil {
		return fmt.Errorf("add mapping %s~%s: %v", dir, remoteStorageLocation, err)
	}

	// save back
	err = filerClient.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		return filer.SaveInsideFiler(client, filer.DirectoryEtcRemote, filer.REMOTE_STORAGE_MOUNT_FILE, newContent)
	})
	if err != nil {
		return fmt.Errorf("save mapping: %v", err)
	}

	return nil
}

// if an entry has synchronized metadata but has not synchronized content
//    entry.Attributes.FileSize == entry.RemoteEntry.RemoteSize
//    entry.Attributes.Mtime    == entry.RemoteEntry.RemoteMtime
//    entry.RemoteEntry.LastLocalSyncTsNs == 0
// if an entry has synchronized metadata but has synchronized content before
//    entry.Attributes.FileSize == entry.RemoteEntry.RemoteSize
//    entry.Attributes.Mtime    == entry.RemoteEntry.RemoteMtime
//    entry.RemoteEntry.LastLocalSyncTsNs > 0
// if an entry has synchronized metadata but has new updates
//    entry.Attributes.Mtime * 1,000,000,000    > entry.RemoteEntry.LastLocalSyncTsNs
func doSaveRemoteEntry(client filer_pb.SeaweedFilerClient, localDir string, existingEntry *filer_pb.Entry, remoteEntry *filer_pb.RemoteEntry) error {
	existingEntry.RemoteEntry = remoteEntry
	existingEntry.Attributes.FileSize = uint64(remoteEntry.RemoteSize)
	existingEntry.Attributes.Mtime = remoteEntry.RemoteMtime
	_, updateErr := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
		Directory: localDir,
		Entry:     existingEntry,
	})
	if updateErr != nil {
		return updateErr
	}
	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/vault/helper/pgpkeys"
)

func resourceAwsIamUserLoginProfile() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamUserLoginProfileCreate,
		Read:   schema.Noop,
		Update: schema.Noop,
		Delete: schema.RemoveFromState,

		Schema: map[string]*schema.Schema{
			"user": {
				Type:     schema.TypeString,
				Required: true,
			},
			"pgp_key": {
				Type:     schema.TypeString,
				Required: true,
			},
			"password_reset_required": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"password_length": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      20,
				ValidateFunc: validateAwsIamLoginProfilePasswordLength,
			},

			"key_fingerprint": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encrypted_password": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func validateAwsIamLoginProfilePasswordLength(v interface{}, _ string) (_ []string, es []error) {
	length := v.(int)
	if length < 4 {
		es = append(es, errors.New("minimum password_length is 4 characters"))
	}
	if length > 128 {
		es = append(es, errors.New("maximum password_length is 128 characters"))
	}
	return
}

// generatePassword generates a random password of a given length using
// characters that are likely to satisfy any possible AWS password policy
// (given sufficient length).
func generatePassword(length int) string {
	charsets := []string{
		"abcdefghijklmnopqrstuvwxyz",
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
		"012346789",
		"!@#$%^&*()_+-=[]{}|'",
	}

	// Use all character sets
	random := rand.New(rand.Source(time.Now().UTC().UnixNano()))
	components := make(map[int]byte, length)
	for i := 0; i < length; i++ {
		charset := charsets[i%len(charsets)]
		components[i] = charset[random.Intn(len(charset))]
	}

	// Randomise the ordering so we don't end up with a predictable
	// lower case, upper case, numeric, symbol pattern
	result := make([]byte, length)
	i := 0
	for _, b := range components {
		result[i] = b
		i = i + 1
	}

	return string(result)
}

func encryptPassword(password string, pgpKey string) (string, string, error) {
	const keybasePrefix = "keybase:"

	encryptionKey := pgpKey
	if strings.HasPrefix(pgpKey, keybasePrefix) {
		publicKeys, err := pgpkeys.FetchKeybasePubkeys([]string{pgpKey})
		if err != nil {
			return "", "", errwrap.Wrapf(
				fmt.Sprintf("Error retrieving Public Key for %s: {{err}}", pgpKey), err)
		}
		encryptionKey = publicKeys[pgpKey]
	}

	fingerprints, encrypted, err := pgpkeys.EncryptShares([][]byte{[]byte(password)}, []string{encryptionKey})
	if err != nil {
		return "", "", errwrap.Wrapf(
			fmt.Sprintf("Error encrypting password for %s: {{err}}", pgpKey), err)
	}

	return fingerprints[0], base64.StdEncoding.EncodeToString(encrypted[0]), nil
}

func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	username := d.Get("user").(string)
	passwordResetRequired := d.Get("password_reset_required").(bool)
	passwordLength := d.Get("password_length").(int)

	var pgpKey string
	if pgpKeyInterface, ok := d.GetOk("pgp_key"); ok {
		pgpKey = pgpKeyInterface.(string)
	}

	initialPassword := generatePassword(passwordLength)
	fingerprint, encrypted, err := encryptPassword(initialPassword, pgpKey)
	if err != nil {
		return err
	}

	request := &iam.CreateLoginProfileInput{
		UserName:              aws.String(username),
		Password:              aws.String(initialPassword),
		PasswordResetRequired: aws.Bool(passwordResetRequired),
	}

	log.Println("[DEBUG] Create IAM User Login Profile request:", request)
	createResp, err := iamconn.CreateLoginProfile(request)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Error creating IAM User Login Profile for %q: {{err}}", username), err)
	}

	d.SetId(*createResp.LoginProfile.UserName)
	d.Set("key_fingerprint", fingerprint)
	d.Set("encrypted_password", encrypted)
	return nil
}
<commit_msg>provider/aws: Don't fail if login profile exists<commit_after>package aws

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/vault/helper/pgpkeys"
)

func resourceAwsIamUserLoginProfile() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamUserLoginProfileCreate,
		Read:   schema.Noop,
		Update: schema.Noop,
		Delete: schema.RemoveFromState,

		Schema: map[string]*schema.Schema{
			"user": {
				Type:     schema.TypeString,
				Required: true,
			},
			"pgp_key": {
				Type:     schema.TypeString,
				Required: true,
			},
			"password_reset_required": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"password_length": {
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      20,
				ValidateFunc: validateAwsIamLoginProfilePasswordLength,
			},

			"key_fingerprint": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encrypted_password": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func validateAwsIamLoginProfilePasswordLength(v interface{}, _ string) (_ []string, es []error) {
	length := v.(int)
	if length < 4 {
		es = append(es, errors.New("minimum password_length is 4 characters"))
	}
	if length > 128 {
		es = append(es, errors.New("maximum password_length is 128 characters"))
	}
	return
}

// generatePassword generates a random password of a given length using
// characters that are likely to satisfy any possible AWS password policy
// (given sufficient length).
func generatePassword(length int) string {
	charsets := []string{
		"abcdefghijklmnopqrstuvwxyz",
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
		"012346789",
		"!@#$%^&*()_+-=[]{}|'",
	}

	// Use all character sets
	random := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
	components := make(map[int]byte, length)
	for i := 0; i < length; i++ {
		charset := charsets[i%len(charsets)]
		components[i] = charset[random.Intn(len(charset))]
	}

	// Randomise the ordering so we don't end up with a predictable
	// lower case, upper case, numeric, symbol pattern
	result := make([]byte, length)
	i := 0
	for _, b := range components {
		result[i] = b
		i = i + 1
	}

	return string(result)
}

func encryptPassword(password string, pgpKey string) (string, string, error) {
	const keybasePrefix = "keybase:"

	encryptionKey := pgpKey
	if strings.HasPrefix(pgpKey, keybasePrefix) {
		publicKeys, err := pgpkeys.FetchKeybasePubkeys([]string{pgpKey})
		if err != nil {
			return "", "", errwrap.Wrapf(
				fmt.Sprintf("Error retrieving Public Key for %s: {{err}}", pgpKey), err)
		}
		encryptionKey = publicKeys[pgpKey]
	}

	fingerprints, encrypted, err := pgpkeys.EncryptShares([][]byte{[]byte(password)}, []string{encryptionKey})
	if err != nil {
		return "", "", errwrap.Wrapf(
			fmt.Sprintf("Error encrypting password for %s: {{err}}", pgpKey), err)
	}

	return fingerprints[0], base64.StdEncoding.EncodeToString(encrypted[0]), nil
}

func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	username := d.Get("user").(string)
	passwordResetRequired := d.Get("password_reset_required").(bool)
	passwordLength := d.Get("password_length").(int)

	var pgpKey string
	if pgpKeyInterface, ok := d.GetOk("pgp_key"); ok {
		pgpKey = pgpKeyInterface.(string)
	}

	initialPassword := generatePassword(passwordLength)
	fingerprint, encrypted, err := encryptPassword(initialPassword, pgpKey)
	if err != nil {
		return err
	}

	request := &iam.CreateLoginProfileInput{
		UserName:              aws.String(username),
		Password:              aws.String(initialPassword),
		PasswordResetRequired: aws.Bool(passwordResetRequired),
	}

	log.Println("[DEBUG] Create IAM User Login Profile request:", request)
	createResp, err := iamconn.CreateLoginProfile(request)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "EntityAlreadyExists" {
			// If there is already a login profile, bring it under management (to prevent
			// resource creation diffs) - we will never modify it, but obviously cannot
			// set the password.
			d.SetId(*createResp.LoginProfile.UserName)
			return nil
		}
		return errwrap.Wrapf(fmt.Sprintf("Error creating IAM User Login Profile for %q: {{err}}", username), err)
	}

	d.SetId(*createResp.LoginProfile.UserName)
	d.Set("key_fingerprint", fingerprint)
	d.Set("encrypted_password", encrypted)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package dependency_test

import (
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/dependency"
)

type ReportSuite struct {
	engineFixture
}

var _ = gc.Suite(&ReportSuite{})

func (s *ReportSuite) TestReportStarted(c *gc.C) {
	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state":     "started",
		"error":     nil,
		"manifolds": map[string]interface{}{},
	})
}

func (s *ReportSuite) TestReportStopped(c *gc.C) {
	s.engine.Kill()
	s.engine.Wait()
	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state":     "stopped",
		"error":     nil,
		"manifolds": map[string]interface{}{},
	})
}

func (s *ReportSuite) TestReportStopping(c *gc.C) {
	mh1 := newErrorIgnoringManifoldHarness()
	err := s.engine.Install("task", mh1.Manifold())
	c.Assert(err, jc.ErrorIsNil)
	defer func() {
		s.engine.Kill()
		mh1.InjectError(c, nil)
		err := s.engine.Wait()
		c.Check(err, jc.ErrorIsNil)
	}()
	mh1.AssertOneStart(c)

	// It may take a short time for the main loop to notice
	// the change and stop the "task" worker.
	s.engine.Kill()
	var isTaskStopping = func(report map[string]interface{}) bool {
		manifolds := report["manifolds"].(map[string]interface{})
		task := manifolds["task"].(map[string]interface{})
		switch taskState := task["state"]; taskState {
		case "started":
			return false
		case "stopping":
			return true
		default:
			c.Fatalf("unexpected task state: %v", taskState)
		}
		panic("unreachable")
	}

	var report map[string]interface{}
	for i := 0; i < 3; i++ {
		report = s.engine.Report()
		if isTaskStopping(report) {
			break
		}
		time.Sleep(coretesting.ShortWait)
	}
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state": "stopping",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":  "stopping",
				"error":  nil,
				"inputs": ([]string)(nil),
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
		},
	})
}

func (s *ReportSuite) TestReportInputs(c *gc.C) {
	mh1 := newManifoldHarness()
	manifold := mh1.Manifold()
	err := s.engine.Install("task", manifold)
	c.Assert(err, jc.ErrorIsNil)
	mh1.AssertStart(c)

	mh2 := newManifoldHarness("task")
	err = s.engine.Install("another task", mh2.Manifold())
	c.Assert(err, jc.ErrorIsNil)
	mh2.AssertStart(c)

	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state": "started",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":  "started",
				"error":  nil,
				"inputs": ([]string)(nil),
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
			"another task": map[string]interface{}{
				"state":  "started",
				"error":  nil,
				"inputs": []string{"task"},
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
		},
	})
}

func (s *ReportSuite) TestReportError(c *gc.C) {
	mh1 := newManifoldHarness("missing")
	manifold := mh1.Manifold()
	err := s.engine.Install("task", manifold)
	c.Assert(err, jc.ErrorIsNil)
	mh1.AssertNoStart(c)

	s.engine.Kill()
	err = s.engine.Wait()
	c.Check(err, jc.ErrorIsNil)

	report := s.engine.Report()
	c.Check(report, jc.DeepEquals, map[string]interface{}{
		"state": "stopped",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":  "stopped",
				"error":  dependency.ErrMissing,
				"inputs": []string{"missing"},
				"report": (map[string]interface{})(nil),
			},
		},
	})
}
<commit_msg>tests now match report output<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package dependency_test

import (
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/dependency"
)

type ReportSuite struct {
	engineFixture
}

var _ = gc.Suite(&ReportSuite{})

func (s *ReportSuite) TestReportStarted(c *gc.C) {
	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state":     "started",
		"error":     nil,
		"manifolds": map[string]interface{}{},
	})
}

func (s *ReportSuite) TestReportStopped(c *gc.C) {
	s.engine.Kill()
	s.engine.Wait()
	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state":     "stopped",
		"error":     nil,
		"manifolds": map[string]interface{}{},
	})
}

func (s *ReportSuite) TestReportStopping(c *gc.C) {
	mh1 := newErrorIgnoringManifoldHarness()
	err := s.engine.Install("task", mh1.Manifold())
	c.Assert(err, jc.ErrorIsNil)
	defer func() {
		s.engine.Kill()
		mh1.InjectError(c, nil)
		err := s.engine.Wait()
		c.Check(err, jc.ErrorIsNil)
	}()
	mh1.AssertOneStart(c)

	// It may take a short time for the main loop to notice
	// the change and stop the "task" worker.
	s.engine.Kill()
	var isTaskStopping = func(report map[string]interface{}) bool {
		manifolds := report["manifolds"].(map[string]interface{})
		task := manifolds["task"].(map[string]interface{})
		switch taskState := task["state"]; taskState {
		case "started":
			return false
		case "stopping":
			return true
		default:
			c.Fatalf("unexpected task state: %v", taskState)
		}
		panic("unreachable")
	}

	var report map[string]interface{}
	for i := 0; i < 3; i++ {
		report = s.engine.Report()
		if isTaskStopping(report) {
			break
		}
		time.Sleep(coretesting.ShortWait)
	}
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state": "stopping",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":    "stopping",
				"error":    nil,
				"inputs":   ([]string)(nil),
				"accesses": []map[string]interface{}{},
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
		},
	})
}

func (s *ReportSuite) TestReportInputs(c *gc.C) {
	mh1 := newManifoldHarness()
	manifold := mh1.Manifold()
	err := s.engine.Install("task", manifold)
	c.Assert(err, jc.ErrorIsNil)
	mh1.AssertStart(c)

	mh2 := newManifoldHarness("task")
	err = s.engine.Install("another task", mh2.Manifold())
	c.Assert(err, jc.ErrorIsNil)
	mh2.AssertStart(c)

	report := s.engine.Report()
	c.Assert(report, jc.DeepEquals, map[string]interface{}{
		"state": "started",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":    "started",
				"error":    nil,
				"inputs":   ([]string)(nil),
				"accesses": []map[string]interface{}{},
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
			"another task": map[string]interface{}{
				"state":  "started",
				"error":  nil,
				"inputs": []string{"task"},
				"accesses": []map[string]interface{}{{
					"name":  "task",
					"type":  "<nil>",
					"error": nil,
				}},
				"report": map[string]interface{}{
					"key1": "hello there",
				},
			},
		},
	})
}

func (s *ReportSuite) TestReportError(c *gc.C) {
	mh1 := newManifoldHarness("missing")
	manifold := mh1.Manifold()
	err := s.engine.Install("task", manifold)
	c.Assert(err, jc.ErrorIsNil)
	mh1.AssertNoStart(c)

	s.engine.Kill()
	err = s.engine.Wait()
	c.Check(err, jc.ErrorIsNil)

	report := s.engine.Report()
	c.Check(report, jc.DeepEquals, map[string]interface{}{
		"state": "stopped",
		"error": nil,
		"manifolds": map[string]interface{}{
			"task": map[string]interface{}{
				"state":  "stopped",
				"error":  dependency.ErrMissing,
				"inputs": []string{"missing"},
				"accesses": []map[string]interface{}{{
					"name":  "missing",
					"type":  "<nil>",
					"error": dependency.ErrMissing,
				}},
				"report": (map[string]interface{})(nil),
			},
		},
	})
}
<|endoftext|>