<commit_before>package http

import (
	"fmt"
	"io"
	"log"
	gohttp "net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/wanelo/image-server/core"
)

var ImageDownloads map[string][]chan error

func init() {
	ImageDownloads = make(map[string][]chan error)
}

func FetchOriginal(ic *core.ImageConfiguration) error {
	c := make(chan error)
	go uniqueFetchOriginal(c, ic)
	return <-c
}

// Even if simultaneous calls request the same image, only the first one will download
// the image, and will then notify all requesters. The channel returns an error object
func uniqueFetchOriginal(c chan error, ic *core.ImageConfiguration) {
	key := ic.RemoteImageURL()

	_, present := ImageDownloads[key]

	if present {
		ImageDownloads[key] = append(ImageDownloads[key], c)
	} else {
		ImageDownloads[key] = []chan error{c}

		err := downloadAndSaveOriginal(ic)
		for _, cc := range ImageDownloads[key] {
			cc <- err
		}
		delete(ImageDownloads, key)
	}
}

func downloadAndSaveOriginal(ic *core.ImageConfiguration) error {
	path := ic.LocalOriginalImagePath()
	if _, err := os.Stat(path); os.IsNotExist(err) {
		start := time.Now()

		remoteURL := ic.RemoteImageURL()
		resp, err := gohttp.Get(remoteURL)

		if err != nil || resp.StatusCode != 200 {
			log.Printf("Unable to download image: %s, status code: %d", remoteURL, resp.StatusCode)
			log.Println(err)
			go func() {
				ic.ServerConfiguration.Events.OriginalDownloadUnavailable <- ic
			}()
			return fmt.Errorf("unable to download image: %s, status code: %d", remoteURL, resp.StatusCode)
		}
		log.Printf("Downloaded from %s with code %d", remoteURL, resp.StatusCode)
		defer resp.Body.Close()

		dir := filepath.Dir(path)
		os.MkdirAll(dir, 0700)

		out, err := os.Create(path)
		defer out.Close()
		if err != nil {
			log.Printf("Unable to create file: %s", path)
			log.Println(err)
			return fmt.Errorf("unable to create file: %s", path)
		}

		io.Copy(out, resp.Body)
		log.Printf("Took %s to download image: %s", time.Since(start), path)

		go func() {
			ic.ServerConfiguration.Events.OriginalDownloaded <- ic
		}()
	}
	return nil
}
<commit_msg>Clean up error messages<commit_after>package http

import (
	"fmt"
	"io"
	"log"
	gohttp "net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/wanelo/image-server/core"
)

var ImageDownloads map[string][]chan error

func init() {
	ImageDownloads = make(map[string][]chan error)
}

func FetchOriginal(ic *core.ImageConfiguration) error {
	c := make(chan error)
	go uniqueFetchOriginal(c, ic)
	return <-c
}

// Even if simultaneous calls request the same image, only the first one will download
// the image, and will then notify all requesters. The channel returns an error object
func uniqueFetchOriginal(c chan error, ic *core.ImageConfiguration) {
	key := ic.RemoteImageURL()

	_, present := ImageDownloads[key]

	if present {
		ImageDownloads[key] = append(ImageDownloads[key], c)
	} else {
		ImageDownloads[key] = []chan error{c}

		err := downloadAndSaveOriginal(ic)
		for _, cc := range ImageDownloads[key] {
			cc <- err
		}
		delete(ImageDownloads, key)
	}
}

func downloadAndSaveOriginal(ic *core.ImageConfiguration) error {
	path := ic.LocalOriginalImagePath()
	if _, err := os.Stat(path); os.IsNotExist(err) {
		start := time.Now()

		remoteURL := ic.RemoteImageURL()
		resp, err := gohttp.Get(remoteURL)

		if err != nil || resp.StatusCode != 200 {
			log.Printf("Unable to download image: %s, status code: %d", remoteURL, resp.StatusCode)
			log.Println(err)
			go func() {
				ic.ServerConfiguration.Events.OriginalDownloadUnavailable <- ic
			}()
			return fmt.Errorf("Unable to download image: %s, status code: %d", remoteURL, resp.StatusCode)
		}
		log.Printf("Downloaded from %s with code %d", remoteURL, resp.StatusCode)
		defer resp.Body.Close()

		dir := filepath.Dir(path)
		os.MkdirAll(dir, 0700)

		out, err := os.Create(path)
		defer out.Close()
		if err != nil {
			log.Printf("Unable to create file: %s", path)
			log.Println(err)
			return fmt.Errorf("Unable to create file: %s", path)
		}

		io.Copy(out, resp.Body)
		log.Printf("Took %s to download image: %s", time.Since(start), path)

		go func() {
			ic.ServerConfiguration.Events.OriginalDownloaded <- ic
		}()
	}
	return nil
}
<|endoftext|>


<commit_before>/*
 * Network packet analysis framework.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Provides an API for compiling and manipulating BPF filters. A filter can be
// either compiled from tcpdump-like expressions, or created from basic BPF
// instructions. Filters can then be either applied directly to packet sources
// (see the capture package) or directly run against binary data.
package filter

// #include <stdlib.h>
// #include "bpf_filter.h"
import "C"

import "fmt"
import "strings"
import "unsafe"

type Filter struct {
	program C.struct_bpf_program
}

type Code uint16

const (
	LD Code = 0x00
	LDX     = 0x01
	ST      = 0x02
	STX     = 0x03
	ALU     = 0x04
	JMP     = 0x05
	RET     = 0x06
	MISC    = 0x07
)

type Size uint16

const (
	Word Size = 0x00
	Half      = 0x08
	Byte      = 0x10
)

type Mode uint16

const (
	IMM Mode = 0x00
	ABS      = 0x20
	IND      = 0x40
	MEM      = 0x60
	LEN      = 0x80
	MSH      = 0xa0
)

type Src uint16

const (
	Const Src = 0x00
	Index     = 0x08
	Acc       = 0x10
)

// Try to match the given buffer against the filter.
func (f *Filter) Match(raw_pkt []byte) bool {
	buf  := (*C.char)(unsafe.Pointer(&raw_pkt[0]))
	blen := C.uint(len(raw_pkt))

	if C.bpf_filter(f.program.bf_insns, buf, blen, blen) > 0 {
		return true
	}

	return false
}

// Validate the filter. The constraints are that each jump be forward and to a
// valid code. The code must terminate with either an accept or reject.
func (f *Filter) Validate() bool {
	if C.bpf_validate(f.program.bf_insns, C.int(f.program.bf_len)) > 0 {
		return true
	}

	return false
}

// Deallocate the filter.
func (f *Filter) Cleanup() {
	f.program.bf_len = 0

	if f.program.bf_insns != nil {
		C.free(unsafe.Pointer(f.program.bf_insns))
		f.program.bf_insns = nil
	}
}

// Return the number of instructions in the filter.
func (f *Filter) Len() int {
	prog := (*C.struct_bpf_program)(f.Program())
	flen := C.bpf_get_len(prog)
	return int(flen)
}

// Return the compiled BPF program.
func (f *Filter) Program() unsafe.Pointer {
	return unsafe.Pointer(&f.program)
}

func (f *Filter) String() string {
	var insns []string

	prog := (*C.struct_bpf_program)(f.Program())
	flen := C.bpf_get_len(prog)

	for i := C.int(0); i < flen; i++ {
		insn := C.bpf_get_insn(prog, i)

		str := fmt.Sprintf(
			"{ 0x%.2x, %3d, %3d, 0x%.8x },",
			insn.code, insn.jt, insn.jf, insn.k,
		)

		insns = append(insns, str)
	}

	return strings.Join(insns, "\n")
}

func (f *Filter) append_insn(code Code, jt, jf uint8, k uint32) {
	prog := (*C.struct_bpf_program)(f.Program())
	C.bpf_append_insn(
		prog, C.ushort(code), C.uchar(jt), C.uchar(jf), C.uint(k),
	)
}
<commit_msg>filter: minor doc fix<commit_after>/*
 * Network packet analysis framework.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// Provides an API for compiling and manipulating BPF filters. A filter can be
// either compiled from tcpdump-like expressions, or created from basic BPF
// instructions. Filters can then be either applied to packet sources (see the
// capture package) or directly run against binary data.
package filter

// #include <stdlib.h>
// #include "bpf_filter.h"
import "C"

import "fmt"
import "strings"
import "unsafe"

type Filter struct {
	program C.struct_bpf_program
}

type Code uint16

const (
	LD Code = 0x00
	LDX     = 0x01
	ST      = 0x02
	STX     = 0x03
	ALU     = 0x04
	JMP     = 0x05
	RET     = 0x06
	MISC    = 0x07
)

type Size uint16

const (
	Word Size = 0x00
	Half      = 0x08
	Byte      = 0x10
)

type Mode uint16

const (
	IMM Mode = 0x00
	ABS      = 0x20
	IND      = 0x40
	MEM      = 0x60
	LEN      = 0x80
	MSH      = 0xa0
)

type Src uint16

const (
	Const Src = 0x00
	Index     = 0x08
	Acc       = 0x10
)

// Try to match the given buffer against the filter.
func (f *Filter) Match(raw_pkt []byte) bool {
	buf  := (*C.char)(unsafe.Pointer(&raw_pkt[0]))
	blen := C.uint(len(raw_pkt))

	if C.bpf_filter(f.program.bf_insns, buf, blen, blen) > 0 {
		return true
	}

	return false
}

// Validate the filter. The constraints are that each jump be forward and to a
// valid code. The code must terminate with either an accept or reject.
func (f *Filter) Validate() bool {
	if C.bpf_validate(f.program.bf_insns, C.int(f.program.bf_len)) > 0 {
		return true
	}

	return false
}

// Deallocate the filter.
func (f *Filter) Cleanup() {
	f.program.bf_len = 0

	if f.program.bf_insns != nil {
		C.free(unsafe.Pointer(f.program.bf_insns))
		f.program.bf_insns = nil
	}
}

// Return the number of instructions in the filter.
func (f *Filter) Len() int {
	prog := (*C.struct_bpf_program)(f.Program())
	flen := C.bpf_get_len(prog)
	return int(flen)
}

// Return the compiled BPF program.
func (f *Filter) Program() unsafe.Pointer {
	return unsafe.Pointer(&f.program)
}

func (f *Filter) String() string {
	var insns []string

	prog := (*C.struct_bpf_program)(f.Program())
	flen := C.bpf_get_len(prog)

	for i := C.int(0); i < flen; i++ {
		insn := C.bpf_get_insn(prog, i)

		str := fmt.Sprintf(
			"{ 0x%.2x, %3d, %3d, 0x%.8x },",
			insn.code, insn.jt, insn.jf, insn.k,
		)

		insns = append(insns, str)
	}

	return strings.Join(insns, "\n")
}

func (f *Filter) append_insn(code Code, jt, jf uint8, k uint32) {
	prog := (*C.struct_bpf_program)(f.Program())
	C.bpf_append_insn(
		prog, C.ushort(code), C.uchar(jt), C.uchar(jf), C.uint(k),
	)
}
<|endoftext|>


<commit_before>package recording

import (
	"context"
	"errors"
	"log"
	"os"
	"os/exec"
	"time"
)

var (
	ErrMismatchWrite = errors.New("recording: did not write the same number of bytes that were read")
)

// Recording ...
type Recording struct {
	ctx      context.Context
	url      string
	fname    string
	cancel   context.CancelFunc
	started  time.Time
	restarts int

	Debug bool
	Err   error
}

// New creates a new Recording of the given URL to the given filename for output.
func New(url, fname string) (*Recording, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Hour)

	r := &Recording{
		ctx:     ctx,
		url:     url,
		fname:   fname,
		cancel:  cancel,
		started: time.Now(),
	}

	return r, nil
}

// Cancel stops the recording.
func (r *Recording) Cancel() {
	r.cancel()
}

// Done returns the done channel of the recording.
func (r *Recording) Done() <-chan struct{} {
	return r.ctx.Done()
}

// OutputFilename gets the output filename originally passed into New.
func (r *Recording) OutputFilename() string {
	return r.fname
}

// StartTime gets start time
func (r *Recording) StartTime() time.Time {
	return r.started
}

// Start blockingly starts the recording and returns the error if one is encountered while streaming.
// This should be stopped in another goroutine.
func (r *Recording) Start() error {
	sr, err := exec.LookPath("streamripper")
	if err != nil {
		return err
	}

	cmd := exec.Command(sr, r.url, "-A", "-a", r.fname)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout

	log.Printf("%s: %v", cmd.Path, cmd.Args)

	err = cmd.Start()
	if err != nil {
		return err
	}

	go func() {
		defer r.Cancel()
		err := cmd.Wait()
		if err != nil {
			log.Println(err)
		}
	}()

	defer r.cancel()

	for {
		time.Sleep(250 * time.Millisecond)

		select {
		case <-r.ctx.Done():
			return cmd.Process.Signal(os.Interrupt)
		default:
		}
	}
}
<commit_msg>recording: automatically kill recordings after 4 hours<commit_after>package recording

import (
	"context"
	"errors"
	"log"
	"os"
	"os/exec"
	"time"
)

var (
	ErrMismatchWrite = errors.New("recording: did not write the same number of bytes that were read")
)

// Recording ...
type Recording struct {
	ctx      context.Context
	url      string
	fname    string
	cancel   context.CancelFunc
	started  time.Time
	restarts int

	Debug bool
	Err   error
}

// New creates a new Recording of the given URL to the given filename for output.
func New(url, fname string) (*Recording, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Hour)

	r := &Recording{
		ctx:     ctx,
		url:     url,
		fname:   fname,
		cancel:  cancel,
		started: time.Now(),
	}

	return r, nil
}

// Cancel stops the recording.
func (r *Recording) Cancel() {
	r.cancel()
}

// Done returns the done channel of the recording.
func (r *Recording) Done() <-chan struct{} {
	return r.ctx.Done()
}

// OutputFilename gets the output filename originally passed into New.
func (r *Recording) OutputFilename() string {
	return r.fname
}

// StartTime gets start time
func (r *Recording) StartTime() time.Time {
	return r.started
}

// Start blockingly starts the recording and returns the error if one is encountered while streaming.
// This should be stopped in another goroutine.
func (r *Recording) Start() error {
	sr, err := exec.LookPath("streamripper")
	if err != nil {
		return err
	}

	cmd := exec.CommandContext(r.ctx, sr, r.url, "-A", "-a", r.fname)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout

	log.Printf("%s: %v", cmd.Path, cmd.Args)

	err = cmd.Start()
	if err != nil {
		return err
	}

	// Automatically kill recordings after four hours
	go func() {
		t := time.NewTicker(4 * time.Hour)
		defer t.Stop()

		for {
			select {
			case <-r.ctx.Done():
			case <-t.C:
				log.Printf("Automatically killing recording after 4 hours...")
				r.Cancel()
			}
		}
	}()

	go func() {
		defer r.Cancel()
		err := cmd.Wait()
		if err != nil {
			log.Println(err)
		}
	}()

	defer r.cancel()

	for {
		time.Sleep(250 * time.Millisecond)

		select {
		case <-r.ctx.Done():
			return cmd.Process.Signal(os.Interrupt)
		default:
		}
	}
}
<|endoftext|>


<commit_before>package osinfo

import "strings"

func Gather() (i *OSInfo) {
	out, err := CmdOut("wmic", "os", "get", "Caption,Version,OSArchitecture", "/value")
	for err != nil {
		return
	}
	info := strings.Split(out, " ")
	i = &OSInfo{Kernel: info[0], Release: info[1], Platform: info[2]}
	return

}
<commit_msg>update<commit_after>package osinfo

import "strings"

func Gather() (i *OSInfo) {
	out, err := CmdOut("wmic", "os", "get", "Caption,OSArchitecture,Version", "/value")
	for err != nil {
		return
	}
	info := strings.Split(out, "\n")
	i = &OSInfo{Kernel: strings.Split(info[0], "=")[1],
		Release:  strings.Split(info[2], "=")[1],
		Platform: strings.Split(info[1], "=")[1]}
	return

}
<|endoftext|>


<commit_before>// Derivative work from:
//	- https://golang.org/src/cmd/gofmt/gofmt.go
//	- https://github.com/fatih/hclfmt

package fmtcmd

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/hashicorp/hcl/hcl/printer"
)

var (
	ErrWriteStdin = errors.New("cannot use write option with standard input")
)

type Options struct {
	List  bool // list files whose formatting differs
	Write bool // write result to (source) file instead of stdout
	Diff  bool // display diffs of formatting changes
}

func isValidFile(f os.FileInfo, extensions []string) bool {
	if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
		for _, ext := range extensions {
			if strings.HasSuffix(f.Name(), "."+ext) {
				return true
			}
		}
	}

	return false
}

// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error {
	if in == nil {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}
		defer f.Close()
		in = f
	}

	src, err := ioutil.ReadAll(in)
	if err != nil {
		return err
	}

	res, err := printer.Format(src)
	if err != nil {
		return err
	}

	if !bytes.Equal(src, res) {
		// formatting has changed
		if opts.List {
			fmt.Fprintln(out, filename)
		}
		if opts.Write {
			err = ioutil.WriteFile(filename, res, 0644)
			if err != nil {
				return err
			}
		}
		if opts.Diff {
			data, err := diff(src, res)
			if err != nil {
				return fmt.Errorf("computing diff: %s", err)
			}
			fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename)
			out.Write(data)
		}
	}

	if !opts.List && !opts.Write && !opts.Diff {
		_, err = out.Write(res)
	}

	return err
}

func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error {
	visitFile := func(path string, f os.FileInfo, err error) error {
		if err == nil && isValidFile(f, extensions) {
			err = processFile(path, nil, stdout, false, opts)
		}
		return err
	}

	return filepath.Walk(path, visitFile)
}

func Run(
	paths, extensions []string,
	stdin io.Reader,
	stdout io.Writer,
	opts Options,
) error {
	if len(paths) == 0 {
		if opts.Write {
			return ErrWriteStdin
		}
		if err := processFile("<standard input>", stdin, stdout, true, opts); err != nil {
			return err
		}
		return nil
	}

	for _, path := range paths {
		switch dir, err := os.Stat(path); {
		case err != nil:
			return err
		case dir.IsDir():
			if err := walkDir(path, extensions, stdout, opts); err != nil {
				return err
			}
		default:
			if err := processFile(path, nil, stdout, false, opts); err != nil {
				return err
			}
		}
	}

	return nil
}

func diff(b1, b2 []byte) (data []byte, err error) {
	f1, err := ioutil.TempFile("", "")
	if err != nil {
		return
	}
	defer os.Remove(f1.Name())
	defer f1.Close()

	f2, err := ioutil.TempFile("", "")
	if err != nil {
		return
	}
	defer os.Remove(f2.Name())
	defer f2.Close()

	f1.Write(b1)
	f2.Write(b2)

	data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
	if len(data) > 0 {
		// diff exits with a non-zero status when the files don't match.
		// Ignore that failure as long as we get output.
		err = nil
	}
	return
}
<commit_msg>hcl/fmtcmd: include filename in error<commit_after>// Derivative work from:
//	- https://golang.org/src/cmd/gofmt/gofmt.go
//	- https://github.com/fatih/hclfmt

package fmtcmd

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/hashicorp/hcl/hcl/printer"
)

var (
	ErrWriteStdin = errors.New("cannot use write option with standard input")
)

type Options struct {
	List  bool // list files whose formatting differs
	Write bool // write result to (source) file instead of stdout
	Diff  bool // display diffs of formatting changes
}

func isValidFile(f os.FileInfo, extensions []string) bool {
	if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
		for _, ext := range extensions {
			if strings.HasSuffix(f.Name(), "."+ext) {
				return true
			}
		}
	}

	return false
}

// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error {
	if in == nil {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}
		defer f.Close()
		in = f
	}

	src, err := ioutil.ReadAll(in)
	if err != nil {
		return err
	}

	res, err := printer.Format(src)
	if err != nil {
		return fmt.Errorf("In %s: %s", filename, err)
	}

	if !bytes.Equal(src, res) {
		// formatting has changed
		if opts.List {
			fmt.Fprintln(out, filename)
		}
		if opts.Write {
			err = ioutil.WriteFile(filename, res, 0644)
			if err != nil {
				return err
			}
		}
		if opts.Diff {
			data, err := diff(src, res)
			if err != nil {
				return fmt.Errorf("computing diff: %s", err)
			}
			fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename)
			out.Write(data)
		}
	}

	if !opts.List && !opts.Write && !opts.Diff {
		_, err = out.Write(res)
	}

	return err
}

func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error {
	visitFile := func(path string, f os.FileInfo, err error) error {
		if err == nil && isValidFile(f, extensions) {
			err = processFile(path, nil, stdout, false, opts)
		}
		return err
	}

	return filepath.Walk(path, visitFile)
}

func Run(
	paths, extensions []string,
	stdin io.Reader,
	stdout io.Writer,
	opts Options,
) error {
	if len(paths) == 0 {
		if opts.Write {
			return ErrWriteStdin
		}
		if err := processFile("<standard input>", stdin, stdout, true, opts); err != nil {
			return err
		}
		return nil
	}

	for _, path := range paths {
		switch dir, err := os.Stat(path); {
		case err != nil:
			return err
		case dir.IsDir():
			if err := walkDir(path, extensions, stdout, opts); err != nil {
				return err
			}
		default:
			if err := processFile(path, nil, stdout, false, opts); err != nil {
				return err
			}
		}
	}

	return nil
}

func diff(b1, b2 []byte) (data []byte, err error) {
	f1, err := ioutil.TempFile("", "")
	if err != nil {
		return
	}
	defer os.Remove(f1.Name())
	defer f1.Close()

	f2, err := ioutil.TempFile("", "")
	if err != nil {
		return
	}
	defer os.Remove(f2.Name())
	defer f2.Close()

	f1.Write(b1)
	f2.Write(b2)

	data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
	if len(data) > 0 {
		// diff exits with a non-zero status when the files don't match.
		// Ignore that failure as long as we get output.
		err = nil
	}
	return
}
<|endoftext|>


<commit_before>package printer

import (
	"bytes"
	"fmt"
	"sort"

	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/hashicorp/hcl/hcl/token"
)

const (
	blank    = byte(' ')
	newline  = byte('\n')
	tab      = byte('\t')
	infinity = 1 << 30 // offset or line
)

type printer struct {
	cfg  Config
	prev token.Pos

	comments           []*ast.CommentGroup // may be nil, contains all comments
	standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node)

	enableTrace bool
	indentTrace int
}

type ByPosition []*ast.CommentGroup

func (b ByPosition) Len() int           { return len(b) }
func (b ByPosition) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) }

// collectComments comments all standalone comments which are not lead or line
// comment
func (p *printer) collectComments(node ast.Node) {
	// first collect all comments. This is already stored in
	// ast.File.(comments)
	ast.Walk(node, func(nn ast.Node) bool {
		switch t := nn.(type) {
		case *ast.File:
			p.comments = t.Comments
			return false
		}
		return true
	})

	standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0)
	for _, c := range p.comments {
		standaloneComments[c.Pos()] = c
	}

	// next remove all lead and line comments from the overall comment map.
	// This will give us comments which are standalone, comments which are not
	// assigned to any kind of node.
	ast.Walk(node, func(nn ast.Node) bool {
		switch t := nn.(type) {
		case *ast.LiteralType:
			if t.LineComment != nil {
				for _, comment := range t.LineComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}
		case *ast.ObjectItem:
			if t.LeadComment != nil {
				for _, comment := range t.LeadComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}

			if t.LineComment != nil {
				for _, comment := range t.LineComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}
		}

		return true
	})

	for _, c := range standaloneComments {
		p.standaloneComments = append(p.standaloneComments, c)
	}

	sort.Sort(ByPosition(p.standaloneComments))

}

// output prints creates b printable HCL output and returns it.
func (p *printer) output(n interface{}) []byte {
	var buf bytes.Buffer

	switch t := n.(type) {
	case *ast.File:
		return p.output(t.Node)
	case *ast.ObjectList:
		var index int
		var nextItem token.Pos
		var commented bool
		for {
			// TODO(arslan): refactor below comment printing, we have the same in objectType
			for _, c := range p.standaloneComments {
				for _, comment := range c.List {
					if index != len(t.Items) {
						nextItem = t.Items[index].Pos()
					} else {
						nextItem = token.Pos{Offset: infinity, Line: infinity}
					}

					if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
						// if we hit the end add newlines so we can print the comment
						if index == len(t.Items) {
							buf.Write([]byte{newline, newline})
						}

						buf.WriteString(comment.Text)

						buf.WriteByte(newline)
						if index != len(t.Items) {
							buf.WriteByte(newline)
						}
					}
				}
			}

			if index == len(t.Items) {
				break
			}

			buf.Write(p.output(t.Items[index]))
			if !commented && index != len(t.Items)-1 {
				buf.Write([]byte{newline, newline})
			}
			index++
		}
	case *ast.ObjectKey:
		buf.WriteString(t.Token.Text)
	case *ast.ObjectItem:
		p.prev = t.Pos()
		buf.Write(p.objectItem(t))
	case *ast.LiteralType:
		buf.WriteString(t.Token.Text)
	case *ast.ListType:
		buf.Write(p.list(t))
	case *ast.ObjectType:
		buf.Write(p.objectType(t))
	default:
		fmt.Printf(" unknown type: %T\n", n)
	}

	return buf.Bytes()
}

// objectItem returns the printable HCL form of an object item. An object type
// starts with one/multiple keys and has a value. The value might be of any
// type.
func (p *printer) objectItem(o *ast.ObjectItem) []byte {
	defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))
	var buf bytes.Buffer

	if o.LeadComment != nil {
		for _, comment := range o.LeadComment.List {
			buf.WriteString(comment.Text)
			buf.WriteByte(newline)
		}
	}

	for i, k := range o.Keys {
		buf.WriteString(k.Token.Text)
		buf.WriteByte(blank)

		// reach end of key
		if i == len(o.Keys)-1 {
			if o.Assign.IsValid() && len(o.Keys) == 1 {
				buf.WriteString("=")
				buf.WriteByte(blank)
			}
		}
	}

	buf.Write(p.output(o.Val))

	if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil {
		buf.WriteByte(blank)
		for _, comment := range o.LineComment.List {
			buf.WriteString(comment.Text)
		}
	}

	return buf.Bytes()
}

// objectType returns the printable HCL form of an object type. An object type
// begins with a brace and ends with a brace.
func (p *printer) objectType(o *ast.ObjectType) []byte {
	defer un(trace(p, "ObjectType"))
	var buf bytes.Buffer
	buf.WriteString("{")
	buf.WriteByte(newline)

	var index int
	var nextItem token.Pos
	var commented bool
	for {
		// Print stand alone comments
		for _, c := range p.standaloneComments {
			for _, comment := range c.List {
				// if we hit the end, last item should be the brace
				if index != len(o.List.Items) {
					nextItem = o.List.Items[index].Pos()
				} else {
					nextItem = o.Rbrace
				}

				if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
					// add newline if it's between other printed nodes
					if index > 0 {
						commented = true
						buf.WriteByte(newline)
					}

					buf.Write(p.indent([]byte(comment.Text)))
					buf.WriteByte(newline)
					if index != len(o.List.Items) {
						buf.WriteByte(newline) // do not print on the end
					}
				}
			}
		}

		if index == len(o.List.Items) {
			p.prev = o.Rbrace
			break
		}

		// check if we have adjacent one liner items. If yes we'll going to align
		// the comments.
		var aligned []*ast.ObjectItem
		for _, item := range o.List.Items[index:] {
			// we don't group one line lists
			if len(o.List.Items) == 1 {
				break
			}

			// one means a oneliner with out any lead comment
			// two means a oneliner with lead comment
			// anything else might be something else
			cur := lines(string(p.objectItem(item)))
			if cur > 2 {
				break
			}

			curPos := item.Pos()

			nextPos := token.Pos{}
			if index != len(o.List.Items)-1 {
				nextPos = o.List.Items[index+1].Pos()
			}

			prevPos := token.Pos{}
			if index != 0 {
				prevPos = o.List.Items[index-1].Pos()
			}

			// fmt.Println("DEBUG ----------------")
			// fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
			// fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
			// fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)

			if curPos.Line+1 == nextPos.Line {
				aligned = append(aligned, item)
				index++
				continue
			}

			if curPos.Line-1 == prevPos.Line {
				aligned = append(aligned, item)
				index++

				// finish if we have a new line or comment next. This happens
				// if the next item is not adjacent
				if curPos.Line+1 != nextPos.Line {
					break
				}
				continue
			}

			break
		}

		// put newlines if the items are between other non aligned items.
		// newlines are also added if there is a standalone comment already, so
		// check it too
		if !commented && index != len(aligned) {
			buf.WriteByte(newline)
		}

		if len(aligned) >= 1 {
			p.prev = aligned[len(aligned)-1].Pos()

			items := p.alignedItems(aligned)
			buf.Write(p.indent(items))
		} else {
			p.prev = o.List.Items[index].Pos()

			buf.Write(p.indent(p.objectItem(o.List.Items[index])))
			index++
		}

		buf.WriteByte(newline)
	}

	buf.WriteString("}")
	return buf.Bytes()
}

func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
	var buf bytes.Buffer

	// find the longest key and value length, needed for alignment
	var longestKeyLen int // longest key length
	var longestValLen int // longest value length
	for _, item := range items {
		key := len(item.Keys[0].Token.Text)
		val := len(p.output(item.Val))

		if key > longestKeyLen {
			longestKeyLen = key
		}

		if val > longestValLen {
			longestValLen = val
		}
	}

	for i, item := range items {
		if item.LeadComment != nil {
			for _, comment := range item.LeadComment.List {
				buf.WriteString(comment.Text)
				buf.WriteByte(newline)
			}
		}

		for i, k := range item.Keys {
			keyLen := len(k.Token.Text)
			buf.WriteString(k.Token.Text)
			for i := 0; i < longestKeyLen-keyLen+1; i++ {
				buf.WriteByte(blank)
			}

			// reach end of key
			if i == len(item.Keys)-1 && len(item.Keys) == 1 {
				buf.WriteString("=")
				buf.WriteByte(blank)
			}
		}

		val := p.output(item.Val)
		valLen := len(val)
		buf.Write(val)

		if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil {
			for i := 0; i < longestValLen-valLen+1; i++ {
				buf.WriteByte(blank)
			}

			for _, comment := range item.LineComment.List {
				buf.WriteString(comment.Text)
			}
		}

		// do not print for the last item
		if i != len(items)-1 {
			buf.WriteByte(newline)
		}
	}

	return buf.Bytes()
}

// list returns the printable HCL form of an list type.
func (p *printer) list(l *ast.ListType) []byte {
	var buf bytes.Buffer
	buf.WriteString("[")

	var longestLine int
	for _, item := range l.List {
		// for now we assume that the list only contains literal types
		if lit, ok := item.(*ast.LiteralType); ok {
			lineLen := len(lit.Token.Text)
			if lineLen > longestLine {
				longestLine = lineLen
			}
		}
	}

	for i, item := range l.List {
		if item.Pos().Line != l.Lbrack.Line {
			// multiline list, add newline before we add each item
			buf.WriteByte(newline)
			// also indent each line
			val := p.output(item)
			curLen := len(val)
			buf.Write(p.indent(val))
			buf.WriteString(",")

			if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
				// if the next item doesn't have any comments, do not align
				buf.WriteByte(blank) // align one space
				if i != len(l.List)-1 {
					if lit, ok := l.List[i+1].(*ast.LiteralType); ok && lit.LineComment != nil {
						for i := 0; i < longestLine-curLen; i++ {
							buf.WriteByte(blank)
						}
					}
				}

				for _, comment := range lit.LineComment.List {
					buf.WriteString(comment.Text)
				}
			}

			if i == len(l.List)-1 {
				buf.WriteByte(newline)
			}
		} else {
			buf.Write(p.output(item))
			if i != len(l.List)-1 {
				buf.WriteString(",")
				buf.WriteByte(blank)
			}
		}

	}

	buf.WriteString("]")
	return buf.Bytes()
}

// indent indents the lines of the given buffer for each non-empty line
func (p *printer) indent(buf []byte) []byte {
	var prefix []byte
	if p.cfg.SpacesWidth != 0 {
		for i := 0; i < p.cfg.SpacesWidth; i++ {
			prefix = append(prefix, blank)
		}
	} else {
		prefix = []byte{tab}
	}

	var res []byte
	bol := true
	for _, c := range buf {
		if bol && c != '\n' {
			res = append(res, prefix...)
		}
		res = append(res, c)
		bol = c == '\n'
	}
	return res
}

func lines(txt string) int {
	endline := 1
	for i := 0; i < len(txt); i++ {
		if txt[i] == '\n' {
			endline++
		}
	}
	return endline
}

// ----------------------------------------------------------------------------
// Tracing support

func (p *printer) printTrace(a ...interface{}) {
	if !p.enableTrace {
		return
	}

	const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
	const n = len(dots)
	i := 2 * p.indentTrace
	for i > n {
		fmt.Print(dots)
		i -= n
	}
	// i <= n
	fmt.Print(dots[0:i])
	fmt.Println(a...)
}

func trace(p *printer, msg string) *printer {
	p.printTrace(msg, "(")
	p.indentTrace++
	return p
}

// Usage pattern: defer un(trace(p, "..."))
func un(p *printer) {
	p.indentTrace--
	p.printTrace(")")
}
<commit_msg>hcl/printer: make boolean logic one line<commit_after>package printer

import (
	"bytes"
	"fmt"
	"sort"

	"github.com/hashicorp/hcl/hcl/ast"
	"github.com/hashicorp/hcl/hcl/token"
)

const (
	blank    = byte(' ')
	newline  = byte('\n')
	tab      = byte('\t')
	infinity = 1 << 30 // offset or line
)

type printer struct {
	cfg  Config
	prev token.Pos

	comments           []*ast.CommentGroup // may be nil, contains all comments
	standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node)

	enableTrace bool
	indentTrace int
}

type ByPosition []*ast.CommentGroup

func (b ByPosition) Len() int           { return len(b) }
func (b ByPosition) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) }

// collectComments comments all standalone comments which are not lead or line
// comment
func (p *printer) collectComments(node ast.Node) {
	// first collect all comments. This is already stored in
	// ast.File.(comments)
	ast.Walk(node, func(nn ast.Node) bool {
		switch t := nn.(type) {
		case *ast.File:
			p.comments = t.Comments
			return false
		}
		return true
	})

	standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0)
	for _, c := range p.comments {
		standaloneComments[c.Pos()] = c
	}

	// next remove all lead and line comments from the overall comment map.
	// This will give us comments which are standalone, comments which are not
	// assigned to any kind of node.
	ast.Walk(node, func(nn ast.Node) bool {
		switch t := nn.(type) {
		case *ast.LiteralType:
			if t.LineComment != nil {
				for _, comment := range t.LineComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}
		case *ast.ObjectItem:
			if t.LeadComment != nil {
				for _, comment := range t.LeadComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}

			if t.LineComment != nil {
				for _, comment := range t.LineComment.List {
					if _, ok := standaloneComments[comment.Pos()]; ok {
						delete(standaloneComments, comment.Pos())
					}
				}
			}
		}

		return true
	})

	for _, c := range standaloneComments {
		p.standaloneComments = append(p.standaloneComments, c)
	}

	sort.Sort(ByPosition(p.standaloneComments))

}

// output prints creates b printable HCL output and returns it.
func (p *printer) output(n interface{}) []byte {
	var buf bytes.Buffer

	switch t := n.(type) {
	case *ast.File:
		return p.output(t.Node)
	case *ast.ObjectList:
		var index int
		var nextItem token.Pos
		var commented bool
		for {
			// TODO(arslan): refactor below comment printing, we have the same in objectType
			for _, c := range p.standaloneComments {
				for _, comment := range c.List {
					if index != len(t.Items) {
						nextItem = t.Items[index].Pos()
					} else {
						nextItem = token.Pos{Offset: infinity, Line: infinity}
					}

					if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
						// if we hit the end add newlines so we can print the comment
						if index == len(t.Items) {
							buf.Write([]byte{newline, newline})
						}

						buf.WriteString(comment.Text)

						buf.WriteByte(newline)
						if index != len(t.Items) {
							buf.WriteByte(newline)
						}
					}
				}
			}

			if index == len(t.Items) {
				break
			}

			buf.Write(p.output(t.Items[index]))
			if !commented && index != len(t.Items)-1 {
				buf.Write([]byte{newline, newline})
			}
			index++
		}
	case *ast.ObjectKey:
		buf.WriteString(t.Token.Text)
	case *ast.ObjectItem:
		p.prev = t.Pos()
		buf.Write(p.objectItem(t))
	case *ast.LiteralType:
		buf.WriteString(t.Token.Text)
	case *ast.ListType:
		buf.Write(p.list(t))
	case *ast.ObjectType:
		buf.Write(p.objectType(t))
	default:
		fmt.Printf(" unknown type: %T\n", n)
	}

	return buf.Bytes()
}

// objectItem returns the printable HCL form of an object item. An object type
// starts with one/multiple keys and has a value. The value might be of any
// type.
func (p *printer) objectItem(o *ast.ObjectItem) []byte {
	defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text)))
	var buf bytes.Buffer

	if o.LeadComment != nil {
		for _, comment := range o.LeadComment.List {
			buf.WriteString(comment.Text)
			buf.WriteByte(newline)
		}
	}

	for i, k := range o.Keys {
		buf.WriteString(k.Token.Text)
		buf.WriteByte(blank)

		// reach end of key
		if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 {
			buf.WriteString("=")
			buf.WriteByte(blank)
		}
	}

	buf.Write(p.output(o.Val))

	if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil {
		buf.WriteByte(blank)
		for _, comment := range o.LineComment.List {
			buf.WriteString(comment.Text)
		}
	}

	return buf.Bytes()
}

// objectType returns the printable HCL form of an object type. An object type
// begins with a brace and ends with a brace.
func (p *printer) objectType(o *ast.ObjectType) []byte {
	defer un(trace(p, "ObjectType"))
	var buf bytes.Buffer
	buf.WriteString("{")
	buf.WriteByte(newline)

	var index int
	var nextItem token.Pos
	var commented bool
	for {
		// Print stand alone comments
		for _, c := range p.standaloneComments {
			for _, comment := range c.List {
				// if we hit the end, last item should be the brace
				if index != len(o.List.Items) {
					nextItem = o.List.Items[index].Pos()
				} else {
					nextItem = o.Rbrace
				}

				if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) {
					// add newline if it's between other printed nodes
					if index > 0 {
						commented = true
						buf.WriteByte(newline)
					}

					buf.Write(p.indent([]byte(comment.Text)))
					buf.WriteByte(newline)
					if index != len(o.List.Items) {
						buf.WriteByte(newline) // do not print on the end
					}
				}
			}
		}

		if index == len(o.List.Items) {
			p.prev = o.Rbrace
			break
		}

		// check if we have adjacent one liner items. If yes we'll going to align
		// the comments.
		var aligned []*ast.ObjectItem
		for _, item := range o.List.Items[index:] {
			// we don't group one line lists
			if len(o.List.Items) == 1 {
				break
			}

			// one means a oneliner with out any lead comment
			// two means a oneliner with lead comment
			// anything else might be something else
			cur := lines(string(p.objectItem(item)))
			if cur > 2 {
				break
			}

			curPos := item.Pos()

			nextPos := token.Pos{}
			if index != len(o.List.Items)-1 {
				nextPos = o.List.Items[index+1].Pos()
			}

			prevPos := token.Pos{}
			if index != 0 {
				prevPos = o.List.Items[index-1].Pos()
			}

			// fmt.Println("DEBUG ----------------")
			// fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos)
			// fmt.Printf("cur = %+v curPos: %s\n", cur, curPos)
			// fmt.Printf("next = %+v nextPos: %s\n", next, nextPos)

			if curPos.Line+1 == nextPos.Line {
				aligned = append(aligned, item)
				index++
				continue
			}

			if curPos.Line-1 == prevPos.Line {
				aligned = append(aligned, item)
				index++

				// finish if we have a new line or comment next. This happens
				// if the next item is not adjacent
				if curPos.Line+1 != nextPos.Line {
					break
				}
				continue
			}

			break
		}

		// put newlines if the items are between other non aligned items.
		// newlines are also added if there is a standalone comment already, so
		// check it too
		if !commented && index != len(aligned) {
			buf.WriteByte(newline)
		}

		if len(aligned) >= 1 {
			p.prev = aligned[len(aligned)-1].Pos()

			items := p.alignedItems(aligned)
			buf.Write(p.indent(items))
		} else {
			p.prev = o.List.Items[index].Pos()

			buf.Write(p.indent(p.objectItem(o.List.Items[index])))
			index++
		}

		buf.WriteByte(newline)
	}

	buf.WriteString("}")
	return buf.Bytes()
}

func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
	var buf bytes.Buffer

	// find the longest key and value length, needed for alignment
	var longestKeyLen int // longest key length
	var longestValLen int // longest value length
	for _, item := range items {
		key := len(item.Keys[0].Token.Text)
		val := len(p.output(item.Val))

		if key > longestKeyLen {
			longestKeyLen = key
		}

		if val > longestValLen {
			longestValLen = val
		}
	}

	for i, item := range items {
		if item.LeadComment != nil {
			for _, comment := range item.LeadComment.List {
				buf.WriteString(comment.Text)
				buf.WriteByte(newline)
			}
		}

		for i, k := range item.Keys {
			keyLen := len(k.Token.Text)
			buf.WriteString(k.Token.Text)
			for i := 0; i < longestKeyLen-keyLen+1; i++ {
				buf.WriteByte(blank)
			}

			// reach end of key
			if i == len(item.Keys)-1 && len(item.Keys) == 1 {
				buf.WriteString("=")
				buf.WriteByte(blank)
			}
		}

		val := p.output(item.Val)
		valLen := len(val)
		buf.Write(val)

		if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil {
			for i := 0; i < longestValLen-valLen+1; i++ {
				buf.WriteByte(blank)
			}

			for _, comment := range item.LineComment.List {
				buf.WriteString(comment.Text)
			}
		}

		// do not print for the last item
		if i != len(items)-1 {
			buf.WriteByte(newline)
		}
	}

	return buf.Bytes()
}

// list returns the printable HCL form of an list type.
func (p *printer) list(l *ast.ListType) []byte {
	var buf bytes.Buffer
	buf.WriteString("[")

	var longestLine int
	for _, item := range l.List {
		// for now we assume that the list only contains literal types
		if lit, ok := item.(*ast.LiteralType); ok {
			lineLen := len(lit.Token.Text)
			if lineLen > longestLine {
				longestLine = lineLen
			}
		}
	}

	for i, item := range l.List {
		if item.Pos().Line != l.Lbrack.Line {
			// multiline list, add newline before we add each item
			buf.WriteByte(newline)
			// also indent each line
			val := p.output(item)
			curLen := len(val)
			buf.Write(p.indent(val))
			buf.WriteString(",")

			if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
				// if the next item doesn't have any comments, do not align
				buf.WriteByte(blank) // align one space
				if i != len(l.List)-1 {
					if lit, ok := l.List[i+1].(*ast.LiteralType); ok && lit.LineComment != nil {
						for i := 0; i < longestLine-curLen; i++ {
							buf.WriteByte(blank)
						}
					}
				}

				for _, comment := range lit.LineComment.List {
					buf.WriteString(comment.Text)
				}
			}

			if i == len(l.List)-1 {
				buf.WriteByte(newline)
			}
		} else {
			buf.Write(p.output(item))
			if i != len(l.List)-1 {
				buf.WriteString(",")
				buf.WriteByte(blank)
			}
		}

	}

	buf.WriteString("]")
	return buf.Bytes()
}

// indent indents the lines of the given buffer for each non-empty line
func (p *printer) indent(buf []byte) []byte {
	var prefix []byte
	if p.cfg.SpacesWidth != 0 {
		for i := 0; i < p.cfg.SpacesWidth; i++ {
			prefix = append(prefix, blank)
		}
	} else {
		prefix = []byte{tab}
	}

	var res []byte
	bol := true
	for _, c := range buf {
		if bol && c != '\n' {
			res = append(res, prefix...)
		}
		res = append(res, c)
		bol = c == '\n'
	}
	return res
}

func lines(txt string) int {
	endline := 1
	for i := 0; i < len(txt); i++ {
		if txt[i] == '\n' {
			endline++
		}
	}
	return endline
}

// ----------------------------------------------------------------------------
// Tracing support

func (p *printer) printTrace(a ...interface{}) {
	if !p.enableTrace {
		return
	}

	const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
	const n = len(dots)
	i := 2 * p.indentTrace
	for i > n {
		fmt.Print(dots)
		i -= n
	}
	// i <= n
	fmt.Print(dots[0:i])
	fmt.Println(a...)
}

func trace(p *printer, msg string) *printer {
	p.printTrace(msg, "(")
	p.indentTrace++
	return p
}

// Usage pattern: defer un(trace(p, "..."))
func un(p *printer) {
	p.indentTrace--
	p.printTrace(")")
}
<|endoftext|>


<commit_before>package configs

import (
	"fmt"

	"github.com/hashicorp/hcl2/gohcl"
	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/hcl2/hcl/hclsyntax"
)

// ManagedResource represents a "resource" block in a module or file.
type ManagedResource struct {
	Name    string
	Type    string
	Config  hcl.Body
	Count   hcl.Expression
	ForEach hcl.Expression

	ProviderConfigRef *ProviderConfigRef

	DependsOn []hcl.Traversal

	Connection   *Connection
	Provisioners []*Provisioner

	CreateBeforeDestroy bool
	PreventDestroy      bool
	IgnoreChanges       []hcl.Traversal
	IgnoreAllChanges    bool

	CreateBeforeDestroySet bool
	PreventDestroySet      bool

	DeclRange hcl.Range
	TypeRange hcl.Range
}

func (r *ManagedResource) moduleUniqueKey() string {
	return fmt.Sprintf("%s.%s", r.Name, r.Type)
}

func decodeResourceBlock(block *hcl.Block) (*ManagedResource, hcl.Diagnostics) {
	r := &ManagedResource{
		Type:      block.Labels[0],
		Name:      block.Labels[1],
		DeclRange: block.DefRange,
		TypeRange: block.LabelRanges[0],
	}

	content, remain, diags := block.Body.PartialContent(resourceBlockSchema)
	r.Config = remain

	if !hclsyntax.ValidIdentifier(r.Type) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid resource type name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}
	if !hclsyntax.ValidIdentifier(r.Name) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid resource name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}

	if attr, exists := content.Attributes["count"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["for_each"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["provider"]; exists {
		var providerDiags hcl.Diagnostics
		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
		diags = append(diags, providerDiags...)
	}

	if attr, exists := content.Attributes["depends_on"]; exists {
		deps, depsDiags := decodeDependsOn(attr)
		diags = append(diags, depsDiags...)
		r.DependsOn = append(r.DependsOn, deps...)
	}

	var seenLifecycle *hcl.Block
	var seenConnection *hcl.Block
	for _, block := range content.Blocks {
		switch block.Type {
		case "lifecycle":
			if seenLifecycle != nil {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Duplicate lifecycle block",
					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
					Subject:  &block.DefRange,
				})
				continue
			}
			seenLifecycle = block

			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
			diags = append(diags, lcDiags...)

			if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.CreateBeforeDestroy)
				diags = append(diags, valDiags...)
				r.CreateBeforeDestroySet = true
			}

			if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.PreventDestroy)
				diags = append(diags, valDiags...)
				r.PreventDestroySet = true
			}

			if attr, exists := lcContent.Attributes["ignore_changes"]; exists {

				// ignore_changes can either be a list of relative traversals
				// or it can be just the keyword "all" to ignore changes to this
				// resource entirely.
				//   ignore_changes = [ami, instance_type]
				//   ignore_changes = all
				// We also allow two legacy forms for compatibility with earlier
				// versions:
				//   ignore_changes = ["ami", "instance_type"]
				//   ignore_changes = ["*"]

				kw := hcl.ExprAsKeyword(attr.Expr)

				switch {
				case kw == "all":
					r.IgnoreAllChanges = true
				default:
					exprs, listDiags := hcl.ExprList(attr.Expr)
					diags = append(diags, listDiags...)

					var ignoreAllRange hcl.Range

					for _, expr := range exprs {

						// our expr might be the literal string "*", which
						// we accept as a deprecated way of saying "all".
						if shimIsIgnoreChangesStar(expr) {
							r.IgnoreAllChanges = true
							ignoreAllRange = expr.Range()
							diags = append(diags, &hcl.Diagnostic{
								Severity: hcl.DiagWarning,
								Summary:  "Deprecated ignore_changes wildcard",
								Detail:   "The [\"*\"] form of ignore_changes wildcard is reprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.",
								Subject:  attr.Expr.Range().Ptr(),
							})
							continue
						}

						expr, shimDiags := shimTraversalInString(expr, false)
						diags = append(diags, shimDiags...)

						traversal, travDiags := hcl.RelTraversalForExpr(expr)
						diags = append(diags, travDiags...)
						if len(traversal) != 0 {
							r.IgnoreChanges = append(r.IgnoreChanges, traversal)
						}
					}

					if r.IgnoreAllChanges && len(r.IgnoreChanges) != 0 {
						diags = append(diags, &hcl.Diagnostic{
							Severity: hcl.DiagError,
							Summary:  "Invalid ignore_changes ruleset",
							Detail:   "Cannot mix wildcard string \"*\" with non-wildcard references.",
							Subject:  &ignoreAllRange,
							Context:  attr.Expr.Range().Ptr(),
						})
					}

				}

			}

		case "connection":
			if seenConnection != nil {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Duplicate connection block",
					Detail:   fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
					Subject:  &block.DefRange,
				})
				continue
			}
			seenConnection = block

			conn, connDiags := decodeConnectionBlock(block)
			diags = append(diags, connDiags...)
			r.Connection = conn

		case "provisioner":
			pv, pvDiags := decodeProvisionerBlock(block)
			diags = append(diags, pvDiags...)
			if pv != nil {
				r.Provisioners = append(r.Provisioners, pv)
			}

		default:
			// Should never happen, because the above cases should always be
			// exhaustive for all the types specified in our schema.
			continue
		}
	}

	return r, diags
}

// DataResource represents a "data" block in a module or file.
type DataResource struct {
	Name    string
	Type    string
	Config  hcl.Body
	Count   hcl.Expression
	ForEach hcl.Expression

	ProviderConfigRef *ProviderConfigRef

	DependsOn []hcl.Traversal

	DeclRange hcl.Range
	TypeRange hcl.Range
}

func (r *DataResource) moduleUniqueKey() string {
	return fmt.Sprintf("data.%s.%s", r.Name, r.Type)
}

func decodeDataBlock(block *hcl.Block) (*DataResource, hcl.Diagnostics) {
	r := &DataResource{
		Type:      block.Labels[0],
		Name:      block.Labels[1],
		DeclRange: block.DefRange,
		TypeRange: block.LabelRanges[0],
	}

	content, remain, diags := block.Body.PartialContent(dataBlockSchema)
	r.Config = remain

	if !hclsyntax.ValidIdentifier(r.Type) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid data source name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}
	if !hclsyntax.ValidIdentifier(r.Name) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid data resource name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}

	if attr, exists := content.Attributes["count"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["for_each"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["provider"]; exists {
		var providerDiags hcl.Diagnostics
		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
		diags = append(diags, providerDiags...)
	}

	if attr, exists := content.Attributes["depends_on"]; exists {
		deps, depsDiags := decodeDependsOn(attr)
		diags = append(diags, depsDiags...)
		r.DependsOn = append(r.DependsOn, deps...)
	}

	for _, block := range content.Blocks {
		// Our schema only allows for "lifecycle" blocks, so we can assume
		// that this is all we will see here. We don't have any lifecycle
		// attributes for data resources currently, so we'll just produce
		// an error.
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Unsupported lifecycle block",
			Detail:   "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
			Subject:  &block.DefRange,
		})
		break
	}

	return r, diags
}

type ProviderConfigRef struct {
	Name       string
	NameRange  hcl.Range
	Alias      string
	AliasRange *hcl.Range // nil if alias not set
}

func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	expr, shimDiags := shimTraversalInString(attr.Expr, false)
	diags = append(diags, shimDiags...)

	traversal, travDiags := hcl.AbsTraversalForExpr(expr)

	// AbsTraversalForExpr produces only generic errors, so we'll discard
	// the errors given and produce our own with extra context. If we didn't
	// get any errors then we might still have warnings, though.
	if !travDiags.HasErrors() {
		diags = append(diags, travDiags...)
	}

	if len(traversal) < 1 && len(traversal) > 2 {
		// A provider reference was given as a string literal in the legacy
		// configuration language and there are lots of examples out there
		// showing that usage, so we'll sniff for that situation here and
		// produce a specialized error message for it to help users find
		// the new correct form.
		if exprIsNativeQuotedString(attr.Expr) {
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration reference",
				Detail:   "A provider configuration reference must not be given in quotes.",
				Subject:  expr.Range().Ptr(),
			})
			return nil, diags
		}

		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration reference",
			Detail:   fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", attr.Name),
			Subject:  expr.Range().Ptr(),
		})
		return nil, diags
	}

	ret := &ProviderConfigRef{
		Name:      traversal.RootName(),
		NameRange: traversal[0].SourceRange(),
	}

	if len(traversal) > 1 {
		aliasStep, ok := traversal[1].(hcl.TraverseAttr)
		if !ok {
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration reference",
				Detail:   "Provider name must either stand alone or be followed by a period and then a configuration alias.",
				Subject:  traversal[1].SourceRange().Ptr(),
			})
			return ret, diags
		}

		ret.Alias = aliasStep.Name
		ret.AliasRange = aliasStep.SourceRange().Ptr()
	}

	return ret, diags
}

var commonResourceAttributes = []hcl.AttributeSchema{
	{
		Name: "count",
	},
	{
		Name: "for_each",
	},
	{
		Name: "provider",
	},
	{
		Name: "depends_on",
	},
}

var resourceBlockSchema = &hcl.BodySchema{
	Attributes: commonResourceAttributes,
	Blocks: []hcl.BlockHeaderSchema{
		{
			Type: "lifecycle",
		},
		{
			Type: "connection",
		},
		{
			Type:       "provisioner",
			LabelNames: []string{"type"},
		},
	},
}

var dataBlockSchema = &hcl.BodySchema{
	Attributes: commonResourceAttributes,
	Blocks: []hcl.BlockHeaderSchema{
		{
			Type: "lifecycle",
		},
	},
}

var resourceLifecycleBlockSchema = &hcl.BodySchema{
	Attributes: []hcl.AttributeSchema{
		{
			Name: "create_before_destroy",
		},
		{
			Name: "prevent_destroy",
		},
		{
			Name: "ignore_changes",
		},
	},
}
<commit_msg>configs: highlight resource name in diags when invalid<commit_after>package configs

import (
	"fmt"

	"github.com/hashicorp/hcl2/gohcl"
	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/hcl2/hcl/hclsyntax"
)

// ManagedResource represents a "resource" block in a module or file.
type ManagedResource struct {
	Name    string
	Type    string
	Config  hcl.Body
	Count   hcl.Expression
	ForEach hcl.Expression

	ProviderConfigRef *ProviderConfigRef

	DependsOn []hcl.Traversal

	Connection   *Connection
	Provisioners []*Provisioner

	CreateBeforeDestroy bool
	PreventDestroy      bool
	IgnoreChanges       []hcl.Traversal
	IgnoreAllChanges    bool

	CreateBeforeDestroySet bool
	PreventDestroySet      bool

	DeclRange hcl.Range
	TypeRange hcl.Range
}

func (r *ManagedResource) moduleUniqueKey() string {
	return fmt.Sprintf("%s.%s", r.Name, r.Type)
}

func decodeResourceBlock(block *hcl.Block) (*ManagedResource, hcl.Diagnostics) {
	r := &ManagedResource{
		Type:      block.Labels[0],
		Name:      block.Labels[1],
		DeclRange: block.DefRange,
		TypeRange: block.LabelRanges[0],
	}

	content, remain, diags := block.Body.PartialContent(resourceBlockSchema)
	r.Config = remain

	if !hclsyntax.ValidIdentifier(r.Type) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid resource type name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}
	if !hclsyntax.ValidIdentifier(r.Name) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid resource name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[1],
		})
	}

	if attr, exists := content.Attributes["count"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["for_each"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["provider"]; exists {
		var providerDiags hcl.Diagnostics
		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
		diags = append(diags, providerDiags...)
	}

	if attr, exists := content.Attributes["depends_on"]; exists {
		deps, depsDiags := decodeDependsOn(attr)
		diags = append(diags, depsDiags...)
		r.DependsOn = append(r.DependsOn, deps...)
	}

	var seenLifecycle *hcl.Block
	var seenConnection *hcl.Block
	for _, block := range content.Blocks {
		switch block.Type {
		case "lifecycle":
			if seenLifecycle != nil {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Duplicate lifecycle block",
					Detail:   fmt.Sprintf("This resource already has a lifecycle block at %s.", seenLifecycle.DefRange),
					Subject:  &block.DefRange,
				})
				continue
			}
			seenLifecycle = block

			lcContent, lcDiags := block.Body.Content(resourceLifecycleBlockSchema)
			diags = append(diags, lcDiags...)

			if attr, exists := lcContent.Attributes["create_before_destroy"]; exists {
				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.CreateBeforeDestroy)
				diags = append(diags, valDiags...)
				r.CreateBeforeDestroySet = true
			}

			if attr, exists := lcContent.Attributes["prevent_destroy"]; exists {
				valDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.PreventDestroy)
				diags = append(diags, valDiags...)
				r.PreventDestroySet = true
			}

			if attr, exists := lcContent.Attributes["ignore_changes"]; exists {

				// ignore_changes can either be a list of relative traversals
				// or it can be just the keyword "all" to ignore changes to this
				// resource entirely.
				//   ignore_changes = [ami, instance_type]
				//   ignore_changes = all
				// We also allow two legacy forms for compatibility with earlier
				// versions:
				//   ignore_changes = ["ami", "instance_type"]
				//   ignore_changes = ["*"]

				kw := hcl.ExprAsKeyword(attr.Expr)

				switch {
				case kw == "all":
					r.IgnoreAllChanges = true
				default:
					exprs, listDiags := hcl.ExprList(attr.Expr)
					diags = append(diags, listDiags...)

					var ignoreAllRange hcl.Range

					for _, expr := range exprs {

						// our expr might be the literal string "*", which
						// we accept as a deprecated way of saying "all".
						if shimIsIgnoreChangesStar(expr) {
							r.IgnoreAllChanges = true
							ignoreAllRange = expr.Range()
							diags = append(diags, &hcl.Diagnostic{
								Severity: hcl.DiagWarning,
								Summary:  "Deprecated ignore_changes wildcard",
								Detail:   "The [\"*\"] form of ignore_changes wildcard is reprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.",
								Subject:  attr.Expr.Range().Ptr(),
							})
							continue
						}

						expr, shimDiags := shimTraversalInString(expr, false)
						diags = append(diags, shimDiags...)

						traversal, travDiags := hcl.RelTraversalForExpr(expr)
						diags = append(diags, travDiags...)
						if len(traversal) != 0 {
							r.IgnoreChanges = append(r.IgnoreChanges, traversal)
						}
					}

					if r.IgnoreAllChanges && len(r.IgnoreChanges) != 0 {
						diags = append(diags, &hcl.Diagnostic{
							Severity: hcl.DiagError,
							Summary:  "Invalid ignore_changes ruleset",
							Detail:   "Cannot mix wildcard string \"*\" with non-wildcard references.",
							Subject:  &ignoreAllRange,
							Context:  attr.Expr.Range().Ptr(),
						})
					}

				}

			}

		case "connection":
			if seenConnection != nil {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Duplicate connection block",
					Detail:   fmt.Sprintf("This resource already has a connection block at %s.", seenConnection.DefRange),
					Subject:  &block.DefRange,
				})
				continue
			}
			seenConnection = block

			conn, connDiags := decodeConnectionBlock(block)
			diags = append(diags, connDiags...)
			r.Connection = conn

		case "provisioner":
			pv, pvDiags := decodeProvisionerBlock(block)
			diags = append(diags, pvDiags...)
			if pv != nil {
				r.Provisioners = append(r.Provisioners, pv)
			}

		default:
			// Should never happen, because the above cases should always be
			// exhaustive for all the types specified in our schema.
			continue
		}
	}

	return r, diags
}

// DataResource represents a "data" block in a module or file.
type DataResource struct {
	Name    string
	Type    string
	Config  hcl.Body
	Count   hcl.Expression
	ForEach hcl.Expression

	ProviderConfigRef *ProviderConfigRef

	DependsOn []hcl.Traversal

	DeclRange hcl.Range
	TypeRange hcl.Range
}

func (r *DataResource) moduleUniqueKey() string {
	return fmt.Sprintf("data.%s.%s", r.Name, r.Type)
}

func decodeDataBlock(block *hcl.Block) (*DataResource, hcl.Diagnostics) {
	r := &DataResource{
		Type:      block.Labels[0],
		Name:      block.Labels[1],
		DeclRange: block.DefRange,
		TypeRange: block.LabelRanges[0],
	}

	content, remain, diags := block.Body.PartialContent(dataBlockSchema)
	r.Config = remain

	if !hclsyntax.ValidIdentifier(r.Type) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid data source name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[0],
		})
	}
	if !hclsyntax.ValidIdentifier(r.Name) {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid data resource name",
			Detail:   badIdentifierDetail,
			Subject:  &block.LabelRanges[1],
		})
	}

	if attr, exists := content.Attributes["count"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["for_each"]; exists {
		r.Count = attr.Expr
	}

	if attr, exists := content.Attributes["provider"]; exists {
		var providerDiags hcl.Diagnostics
		r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
		diags = append(diags, providerDiags...)
	}

	if attr, exists := content.Attributes["depends_on"]; exists {
		deps, depsDiags := decodeDependsOn(attr)
		diags = append(diags, depsDiags...)
		r.DependsOn = append(r.DependsOn, deps...)
	}

	for _, block := range content.Blocks {
		// Our schema only allows for "lifecycle" blocks, so we can assume
		// that this is all we will see here. We don't have any lifecycle
		// attributes for data resources currently, so we'll just produce
		// an error.
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Unsupported lifecycle block",
			Detail:   "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
			Subject:  &block.DefRange,
		})
		break
	}

	return r, diags
}

type ProviderConfigRef struct {
	Name       string
	NameRange  hcl.Range
	Alias      string
	AliasRange *hcl.Range // nil if alias not set
}

func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagnostics) {
	var diags hcl.Diagnostics

	expr, shimDiags := shimTraversalInString(attr.Expr, false)
	diags = append(diags, shimDiags...)

	traversal, travDiags := hcl.AbsTraversalForExpr(expr)

	// AbsTraversalForExpr produces only generic errors, so we'll discard
	// the errors given and produce our own with extra context. If we didn't
	// get any errors then we might still have warnings, though.
	if !travDiags.HasErrors() {
		diags = append(diags, travDiags...)
	}

	if len(traversal) < 1 && len(traversal) > 2 {
		// A provider reference was given as a string literal in the legacy
		// configuration language and there are lots of examples out there
		// showing that usage, so we'll sniff for that situation here and
		// produce a specialized error message for it to help users find
		// the new correct form.
		if exprIsNativeQuotedString(attr.Expr) {
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration reference",
				Detail:   "A provider configuration reference must not be given in quotes.",
				Subject:  expr.Range().Ptr(),
			})
			return nil, diags
		}

		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration reference",
			Detail:   fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", attr.Name),
			Subject:  expr.Range().Ptr(),
		})
		return nil, diags
	}

	ret := &ProviderConfigRef{
		Name:      traversal.RootName(),
		NameRange: traversal[0].SourceRange(),
	}

	if len(traversal) > 1 {
		aliasStep, ok := traversal[1].(hcl.TraverseAttr)
		if !ok {
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration reference",
				Detail:   "Provider name must either stand alone or be followed by a period and then a configuration alias.",
				Subject:  traversal[1].SourceRange().Ptr(),
			})
			return ret, diags
		}

		ret.Alias = aliasStep.Name
		ret.AliasRange = aliasStep.SourceRange().Ptr()
	}

	return ret, diags
}

var commonResourceAttributes = []hcl.AttributeSchema{
	{
		Name: "count",
	},
	{
		Name: "for_each",
	},
	{
		Name: "provider",
	},
	{
		Name: "depends_on",
	},
}

var resourceBlockSchema = &hcl.BodySchema{
	Attributes: commonResourceAttributes,
	Blocks: []hcl.BlockHeaderSchema{
		{
			Type: "lifecycle",
		},
		{
			Type: "connection",
		},
		{
			Type:       "provisioner",
			LabelNames: []string{"type"},
		},
	},
}

var dataBlockSchema = &hcl.BodySchema{
	Attributes: commonResourceAttributes,
	Blocks: []hcl.BlockHeaderSchema{
		{
			Type: "lifecycle",
		},
	},
}

var resourceLifecycleBlockSchema = &hcl.BodySchema{
	Attributes: []hcl.AttributeSchema{
		{
			Name: "create_before_destroy",
		},
		{
			Name: "prevent_destroy",
		},
		{
			Name: "ignore_changes",
		},
	},
}
<|endoftext|>


<commit_before>package nslabels

import (
	"fmt"
	"strings"

	typescorev1 "github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
)

const (
	ProjectIDFieldLabel = "field.cattle.io/projectId"
)

type namespaceHandler struct {
	nsClient typescorev1.NamespaceInterface
}

func Register(cluster *config.UserContext) {
	logrus.Infof("Registering namespaceHandler for adding labels ")
	nsh := &namespaceHandler{
		cluster.Core.Namespaces(""),
	}
	cluster.Core.Namespaces("").AddHandler("namespaceHandler", nsh.Sync)
}

func (nsh *namespaceHandler) Sync(key string, ns *corev1.Namespace) error {
	if ns == nil {
		return nil
	}
	logrus.Debugf("namespaceHandler: Sync: key=%v, ns=%+v", key, *ns)

	field, ok := ns.Annotations[ProjectIDFieldLabel]
	if !ok {
		return nil
	}

	splits := strings.Split(field, ":")
	if len(splits) != 2 {
		return nil
	}
	projectID := splits[1]
	logrus.Debugf("namespaceHandler: Sync: projectID=%v", projectID)

	if err := nsh.addProjectIDLabelToNamespace(ns, projectID); err != nil {
		logrus.Errorf("namespaceHandler: Sync: error adding project id label to namespace err=%v", err)
		return nil
	}

	return nil
}

func (nsh *namespaceHandler) addProjectIDLabelToNamespace(ns *corev1.Namespace, projectID string) error {
	if ns == nil {
		return fmt.Errorf("cannot add label to nil namespace")
	}
	if ns.Labels[ProjectIDFieldLabel] != projectID {
		logrus.Infof("namespaceHandler: addProjectIDLabelToNamespace: adding label %v=%v to namespace=%v", ProjectIDFieldLabel, projectID, ns.Name)
		nscopy := ns.DeepCopy()
		if nscopy.Labels == nil {
			nscopy.Labels = map[string]string{}
		}
		nscopy.Labels[ProjectIDFieldLabel] = projectID
		if _, err := nsh.nsClient.Update(nscopy); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Logic for secrets when namespace is moved between projects<commit_after>package nslabels

import (
	"fmt"
	"strings"

	"github.com/rancher/types/apis/core/v1"
	typescorev1 "github.com/rancher/types/apis/core/v1"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	ProjectIDFieldLabel           = "field.cattle.io/projectId"
	ProjectScopedSecretAnnotation = "secret.user.cattle.io/secret"
)

type namespaceHandler struct {
	secrets  v1.SecretInterface
	nsClient typescorev1.NamespaceInterface
}

func Register(cluster *config.UserContext) {
	logrus.Infof("Registering namespaceHandler for adding labels ")
	nsh := &namespaceHandler{
		secrets:  cluster.Core.Secrets(""),
		nsClient: cluster.Core.Namespaces(""),
	}
	cluster.Core.Namespaces("").AddHandler("namespaceHandler", nsh.Sync)
}

func (nsh *namespaceHandler) Sync(key string, ns *corev1.Namespace) error {
	if ns == nil {
		return nil
	}
	logrus.Debugf("namespaceHandler: Sync: key=%v, ns=%+v", key, *ns)

	field, ok := ns.Annotations[ProjectIDFieldLabel]
	if !ok {
		return nil
	}

	splits := strings.Split(field, ":")
	if len(splits) != 2 {
		return nil
	}
	projectID := splits[1]
	clusterID := splits[0]
	logrus.Debugf("namespaceHandler: Sync: projectID=%v", projectID)

	if err := nsh.addProjectIDLabelToNamespace(ns, projectID, clusterID); err != nil {
		logrus.Errorf("namespaceHandler: Sync: error adding project id label to namespace err=%v", err)
		return nil
	}

	return nil
}

func (nsh *namespaceHandler) addProjectIDLabelToNamespace(ns *corev1.Namespace, projectID string, clusterID string) error {
	if ns == nil {
		return fmt.Errorf("cannot add label to nil namespace")
	}
	if ns.Labels[ProjectIDFieldLabel] != projectID {
		nsh.updateProjectIDLabelForSecrets(ns.Labels[ProjectIDFieldLabel], projectID, ns.Name, clusterID)
		logrus.Infof("namespaceHandler: addProjectIDLabelToNamespace: adding label %v=%v to namespace=%v", ProjectIDFieldLabel, projectID, ns.Name)
		nscopy := ns.DeepCopy()
		if nscopy.Labels == nil {
			nscopy.Labels = map[string]string{}
		}
		nscopy.Labels[ProjectIDFieldLabel] = projectID
		if _, err := nsh.nsClient.Update(nscopy); err != nil {
			return err
		}
	}

	return nil
}

func (nsh *namespaceHandler) updateProjectIDLabelForSecrets(projectIDFieldValue, projectID string, namespace string, clusterID string) error {
	if projectIDFieldValue == "" {
		return nil
	}
	secrets, err := nsh.secrets.List(metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.namespace=%s", namespace)})
	if err != nil {
		return err
	}
	for _, secret := range secrets.Items {
		if secret.Annotations[ProjectScopedSecretAnnotation] == "true" {
			if err := nsh.secrets.DeleteNamespaced(namespace, secret.Name, &metav1.DeleteOptions{}); err != nil {
				return err
			}
		} else if secret.Annotations[ProjectIDFieldLabel] != "" {
			secret.Annotations[ProjectIDFieldLabel] = fmt.Sprintf("%s:%s", clusterID, projectID)
			// secret.Annotations[ProjectIDFieldLabel] = strings.Replace(secret.Annotations[ProjectIDFieldLabel], projectIDFieldValue, projectID, 1)
			if _, err := nsh.secrets.Update(&secret); err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package v2

import (
	"fmt"
	"strconv"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/cilium/cilium/pkg/iana"
	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
	lb "github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/policy/api"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories={cilium,ciliumpolicy},singular="ciliumlocalredirectpolicy",path="ciliumlocalredirectpolicies",scope="Namespaced",shortName={clrp}
// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type=date

// CiliumLocalRedirectPolicy is a Kubernetes Custom Resource that contains a
// specification to redirect traffic locally within a node.
type CiliumLocalRedirectPolicy struct {
	// +k8s:openapi-gen=false
	// +deepequal-gen=false
	metav1.TypeMeta `json:",inline"`
	// +k8s:openapi-gen=false
	// +deepequal-gen=false
	metav1.ObjectMeta `json:"metadata"`

	// Spec is the desired behavior of the local redirect policy.
	Spec CiliumLocalRedirectPolicySpec `json:"spec,omitempty"`

	// Status is the most recent status of the local redirect policy.
	// It is a read-only field.
	//
	// +deepequal-gen=false
	// +kubebuilder:validation:Optional
	Status CiliumLocalRedirectPolicyStatus `json:"status"`
}

type Frontend struct {
	// IP is a destination ip address for traffic to be redirected.
	//
	// Example:
	// When it is set to "169.254.169.254", traffic destined to
	// "169.254.169.254" is redirected.
	//
	// +kubebuilder:validation:Pattern=`((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))`
	// +kubebuilder:validation:Required
	IP string `json:"ip"`

	// ToPorts is a list of destination L4 ports with protocol for traffic
	// to be redirected.
	// When multiple ports are specified, the ports must be named.
	//
	// Example:
	// When set to Port: "53" and Protocol: UDP, traffic destined to port '53'
	// with UDP protocol is redirected.
	//
	// +kubebuilder:validation:Required
	ToPorts []PortInfo `json:"toPorts"`
}

// RedirectFrontend is a frontend configuration that matches traffic that needs to be redirected.
// The configuration must be specified using a ip/port tuple or a Kubernetes service.
type RedirectFrontend struct {
	// AddressMatcher is a tuple {IP, port, protocol} that matches traffic to be
	// redirected.
	//
	// +kubebuilder:validation:OneOf
	AddressMatcher *Frontend `json:"addressMatcher,omitempty"`

	// ServiceMatcher specifies Kubernetes service and port that matches
	// traffic to be redirected.
	//
	// +kubebuilder:validation:OneOf
	ServiceMatcher *ServiceInfo `json:"serviceMatcher,omitempty"`
}

// PortInfo specifies L4 port number and name along with the transport protocol
type PortInfo struct {
	// Port is an L4 port number. The string will be strictly parsed as a single uint16.
	//
	// +kubebuilder:validation:Pattern=`^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$`
	// +kubebuilder:validation:Required
	Port string `json:"port"`

	// Protocol is the L4 protocol.
	// Accepted values: "TCP", "UDP"
	//
	// +kubebuilder:validation:Enum=TCP;UDP
	// +kubebuilder:validation:Required
	Protocol api.L4Proto `json:"protocol"`

	// Name is a port name, which must contain at least one [a-z],
	// and may also contain [0-9] and '-' anywhere except adjacent to another
	// '-' or in the beginning or the end.
	//
	// +kubebuilder:validation:Pattern=`^([0-9]{1,4})|([a-zA-Z0-9]-?)*[a-zA-Z](-?[a-zA-Z0-9])*$`
	// +kubebuilder:validation:Optional
	Name string `json:"name"`
}

type ServiceInfo struct {
	// Name is the name of a destination Kubernetes service that identifies traffic
	// to be redirected.
	// The service type needs to be ClusterIP.
	//
	// Example:
	// When this field is populated with 'serviceName:myService', all the traffic
	// destined to the cluster IP of this service at the (specified)
	// service port(s) will be redirected.
	//
	// +kubebuilder:validation:Required
	Name string `json:"serviceName"`

	// Namespace is the Kubernetes service namespace.
	// The service namespace must match the namespace of the parent Local
	// Redirect Policy.  For Cluster-wide Local Redirect Policy, this
	// can be any namespace.
	// +kubebuilder:validation:Required
	Namespace string `json:"namespace"`

	// ToPorts is a list of destination service L4 ports with protocol for
	// traffic to be redirected. If not specified, traffic for all the service
	// ports will be redirected.
	// When multiple ports are specified, the ports must be named.
	//
	// +kubebuilder:validation:Optional
	ToPorts []PortInfo `json:"toPorts,omitempty"`
}

// RedirectBackend is a backend configuration that determines where traffic needs to be redirected to.
type RedirectBackend struct {
	// LocalEndpointSelector selects node local pod(s) where traffic is redirected to.
	//
	// +kubebuilder:validation:Required
	LocalEndpointSelector slim_metav1.LabelSelector `json:"localEndpointSelector"`

	// ToPorts is a list of L4 ports with protocol of node local pod(s) where traffic
	// is redirected to.
	// When multiple ports are specified, the ports must be named.
	//
	// +kubebuilder:validation:Required
	ToPorts []PortInfo `json:"toPorts"`
}

// CiliumLocalRedirectPolicySpec specifies the configurations for redirecting traffic
// within a node.
//
// +kubebuilder:validation:Type=object
type CiliumLocalRedirectPolicySpec struct {
	// RedirectFrontend specifies frontend configuration to redirect traffic from.
	// It can not be empty.
	//
	// +kubebuilder:validation:Required
	RedirectFrontend RedirectFrontend `json:"redirectFrontend"`

	// RedirectBackend specifies backend configuration to redirect traffic to.
	// It can not be empty.
	//
	// +kubebuilder:validation:Required
	RedirectBackend RedirectBackend `json:"redirectBackend"`

	// Description can be used by the creator of the policy to describe the
	// purpose of this policy.
	//
	// +kubebuilder:validation:Optional
	Description string `json:"description,omitempty"`
}

// CiliumLocalRedirectPolicyStatus is the status of a Local Redirect Policy.
type CiliumLocalRedirectPolicyStatus struct {
	// TODO Define status(aditi)
	//
	// +kubebuilder:validation:Type=object
	OK bool `json:"ok,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=false
// +deepequal-gen=false

// CiliumLocalRedirectPolicyList is a list of CiliumLocalRedirectPolicy objects.
type CiliumLocalRedirectPolicyList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	// Items is a list of CiliumLocalRedirectPolicy
	Items []CiliumLocalRedirectPolicy `json:"items"`
}

// SanitizePortInfo sanitizes all the fields in the PortInfo.
// It returns port number, name, and protocol derived from the given input  and error (failure cases).
func (pInfo *PortInfo) SanitizePortInfo(checkNamedPort bool) (uint16, string, lb.L4Type, error) {
	var (
		pInt     uint16
		pName    string
		protocol lb.L4Type
	)
	// Sanitize port
	if pInfo.Port == "" {
		return pInt, pName, protocol, fmt.Errorf("port must be specified")
	} else {
		p, err := strconv.ParseUint(pInfo.Port, 0, 16)
		if err != nil {
			return pInt, pName, protocol, fmt.Errorf("unable to parse port: %v", err)
		}
		if p == 0 {
			return pInt, pName, protocol, fmt.Errorf("port cannot be 0")
		}
		pInt = uint16(p)
	}
	// Sanitize name
	if checkNamedPort {
		if !iana.IsSvcName(pInfo.Name) {
			return pInt, pName, protocol, fmt.Errorf("valid port name is not present")
		}
	}
	pName = strings.ToLower(pInfo.Name) // Normalize for case insensitive comparison

	// Sanitize protocol
	var err error
	protocol, err = lb.NewL4Type(string(pInfo.Protocol))
	if err != nil {
		return pInt, pName, protocol, err
	}
	return pInt, pName, protocol, nil
}
<commit_msg>pkg/redirectpolicy: Improve error logs<commit_after>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package v2

import (
	"fmt"
	"strconv"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/cilium/cilium/pkg/iana"
	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
	lb "github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/policy/api"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories={cilium,ciliumpolicy},singular="ciliumlocalredirectpolicy",path="ciliumlocalredirectpolicies",scope="Namespaced",shortName={clrp}
// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type=date

// CiliumLocalRedirectPolicy is a Kubernetes Custom Resource that contains a
// specification to redirect traffic locally within a node.
type CiliumLocalRedirectPolicy struct {
	// +k8s:openapi-gen=false
	// +deepequal-gen=false
	metav1.TypeMeta `json:",inline"`
	// +k8s:openapi-gen=false
	// +deepequal-gen=false
	metav1.ObjectMeta `json:"metadata"`

	// Spec is the desired behavior of the local redirect policy.
	Spec CiliumLocalRedirectPolicySpec `json:"spec,omitempty"`

	// Status is the most recent status of the local redirect policy.
	// It is a read-only field.
	//
	// +deepequal-gen=false
	// +kubebuilder:validation:Optional
	Status CiliumLocalRedirectPolicyStatus `json:"status"`
}

type Frontend struct {
	// IP is a destination ip address for traffic to be redirected.
	//
	// Example:
	// When it is set to "169.254.169.254", traffic destined to
	// "169.254.169.254" is redirected.
	//
	// +kubebuilder:validation:Pattern=`((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))`
	// +kubebuilder:validation:Required
	IP string `json:"ip"`

	// ToPorts is a list of destination L4 ports with protocol for traffic
	// to be redirected.
	// When multiple ports are specified, the ports must be named.
	//
	// Example:
	// When set to Port: "53" and Protocol: UDP, traffic destined to port '53'
	// with UDP protocol is redirected.
	//
	// +kubebuilder:validation:Required
	ToPorts []PortInfo `json:"toPorts"`
}

// RedirectFrontend is a frontend configuration that matches traffic that needs to be redirected.
// The configuration must be specified using a ip/port tuple or a Kubernetes service.
type RedirectFrontend struct {
	// AddressMatcher is a tuple {IP, port, protocol} that matches traffic to be
	// redirected.
	//
	// +kubebuilder:validation:OneOf
	AddressMatcher *Frontend `json:"addressMatcher,omitempty"`

	// ServiceMatcher specifies Kubernetes service and port that matches
	// traffic to be redirected.
	//
	// +kubebuilder:validation:OneOf
	ServiceMatcher *ServiceInfo `json:"serviceMatcher,omitempty"`
}

// PortInfo specifies L4 port number and name along with the transport protocol
type PortInfo struct {
	// Port is an L4 port number. The string will be strictly parsed as a single uint16.
	//
	// +kubebuilder:validation:Pattern=`^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$`
	// +kubebuilder:validation:Required
	Port string `json:"port"`

	// Protocol is the L4 protocol.
	// Accepted values: "TCP", "UDP"
	//
	// +kubebuilder:validation:Enum=TCP;UDP
	// +kubebuilder:validation:Required
	Protocol api.L4Proto `json:"protocol"`

	// Name is a port name, which must contain at least one [a-z],
	// and may also contain [0-9] and '-' anywhere except adjacent to another
	// '-' or in the beginning or the end.
	//
	// +kubebuilder:validation:Pattern=`^([0-9]{1,4})|([a-zA-Z0-9]-?)*[a-zA-Z](-?[a-zA-Z0-9])*$`
	// +kubebuilder:validation:Optional
	Name string `json:"name"`
}

type ServiceInfo struct {
	// Name is the name of a destination Kubernetes service that identifies traffic
	// to be redirected.
	// The service type needs to be ClusterIP.
	//
	// Example:
	// When this field is populated with 'serviceName:myService', all the traffic
	// destined to the cluster IP of this service at the (specified)
	// service port(s) will be redirected.
	//
	// +kubebuilder:validation:Required
	Name string `json:"serviceName"`

	// Namespace is the Kubernetes service namespace.
	// The service namespace must match the namespace of the parent Local
	// Redirect Policy.  For Cluster-wide Local Redirect Policy, this
	// can be any namespace.
	// +kubebuilder:validation:Required
	Namespace string `json:"namespace"`

	// ToPorts is a list of destination service L4 ports with protocol for
	// traffic to be redirected. If not specified, traffic for all the service
	// ports will be redirected.
	// When multiple ports are specified, the ports must be named.
	//
	// +kubebuilder:validation:Optional
	ToPorts []PortInfo `json:"toPorts,omitempty"`
}

// RedirectBackend is a backend configuration that determines where traffic needs to be redirected to.
type RedirectBackend struct {
	// LocalEndpointSelector selects node local pod(s) where traffic is redirected to.
	//
	// +kubebuilder:validation:Required
	LocalEndpointSelector slim_metav1.LabelSelector `json:"localEndpointSelector"`

	// ToPorts is a list of L4 ports with protocol of node local pod(s) where traffic
	// is redirected to.
	// When multiple ports are specified, the ports must be named.
	//
	// +kubebuilder:validation:Required
	ToPorts []PortInfo `json:"toPorts"`
}

// CiliumLocalRedirectPolicySpec specifies the configurations for redirecting traffic
// within a node.
//
// +kubebuilder:validation:Type=object
type CiliumLocalRedirectPolicySpec struct {
	// RedirectFrontend specifies frontend configuration to redirect traffic from.
	// It can not be empty.
	//
	// +kubebuilder:validation:Required
	RedirectFrontend RedirectFrontend `json:"redirectFrontend"`

	// RedirectBackend specifies backend configuration to redirect traffic to.
	// It can not be empty.
	//
	// +kubebuilder:validation:Required
	RedirectBackend RedirectBackend `json:"redirectBackend"`

	// Description can be used by the creator of the policy to describe the
	// purpose of this policy.
	//
	// +kubebuilder:validation:Optional
	Description string `json:"description,omitempty"`
}

// CiliumLocalRedirectPolicyStatus is the status of a Local Redirect Policy.
type CiliumLocalRedirectPolicyStatus struct {
	// TODO Define status(aditi)
	//
	// +kubebuilder:validation:Type=object
	OK bool `json:"ok,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=false
// +deepequal-gen=false

// CiliumLocalRedirectPolicyList is a list of CiliumLocalRedirectPolicy objects.
type CiliumLocalRedirectPolicyList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	// Items is a list of CiliumLocalRedirectPolicy
	Items []CiliumLocalRedirectPolicy `json:"items"`
}

// SanitizePortInfo sanitizes all the fields in the PortInfo.
// It returns port number, name, and protocol derived from the given input  and error (failure cases).
func (pInfo *PortInfo) SanitizePortInfo(checkNamedPort bool) (uint16, string, lb.L4Type, error) {
	var (
		pInt     uint16
		pName    string
		protocol lb.L4Type
	)
	// Sanitize port
	if pInfo.Port == "" {
		return pInt, pName, protocol, fmt.Errorf("port must be specified")
	} else {
		p, err := strconv.ParseUint(pInfo.Port, 0, 16)
		if err != nil {
			return pInt, pName, protocol, fmt.Errorf("unable to parse port: %v", err)
		}
		if p == 0 {
			return pInt, pName, protocol, fmt.Errorf("port cannot be 0")
		}
		pInt = uint16(p)
	}
	// Sanitize name
	if checkNamedPort {
		if pInfo.Name == "" {
			return pInt, pName, protocol, fmt.Errorf("port %s in the local "+
				"redirect policy spec must have a valid IANA_SVC_NAME, as there are multiple ports", pInfo.Port)

		}
		if !iana.IsSvcName(pInfo.Name) {
			return pInt, pName, protocol, fmt.Errorf("port name %s isn't a "+
				"valid IANA_SVC_NAME", pInfo.Name)
		}
	}
	pName = strings.ToLower(pInfo.Name) // Normalize for case insensitive comparison

	// Sanitize protocol
	var err error
	protocol, err = lb.NewL4Type(string(pInfo.Protocol))
	if err != nil {
		return pInt, pName, protocol, err
	}
	return pInt, pName, protocol, nil
}
<|endoftext|>


<commit_before>package html5_test

import . "github.com/onsi/ginkgo"

var _ = Describe("ordered lists", func() {

	It("ordered list with title and role", func() {
		actualContent := `.title
[#myid]
[.myrole]
. item 1`
		expectedResult := `

title

item 1

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list item with explicit start only", func() {
		actualContent := `[start=5]
. item`
		expectedResult := `

item

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list item with explicit quoted numbering and start", func() {
		actualContent := `["lowerroman", start="5"]
. item`
		expectedResult := `

item

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with paragraph continuation", func() {
		actualContent := `. item 1
+
foo`
		expectedResult := `

item 1

foo

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with delimited block continuation", func() {
		actualContent := `. item 1
+
----
foo
----`
		expectedResult := `

item 1

foo

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with unnumbered items", func() {
		actualContent := `. item 1
		.. item 1.1
		... item 1.1.1
		... item 1.1.2
		.. item 1.2
		. item 2
		.. item 2.1`
		expectedResult := `

item 1

item 1.1

item 1.1.1

item 1.1.2

item 1.2

item 2

item 2.1

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list mixed with unordered list - simple case", func() {
		actualContent := `. Linux
* Fedora
* Ubuntu
* Slackware
. BSD
* FreeBSD
* NetBSD`
		expectedResult := `

Linux

Fedora

Ubuntu

Slackware

BSD

FreeBSD

NetBSD

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list mixed with unordered list - complex case", func() {
		actualContent := `- unordered 1
1. ordered 1.1
	a. ordered 1.1.a
	b. ordered 1.1.b
	c. ordered 1.1.c
2. ordered 1.2
	i)  ordered 1.2.i
	ii) ordered 1.2.ii
3. ordered 1.3
4. ordered 1.4
- unordered 2
* unordered 2.1
** unordered 2.1.1
with some
extra lines.
** unordered 2.1.2
* unordered 2.2
- unordered 3
. ordered 3.1
. ordered 3.2
[upperroman]
	.. ordered 3.2.I
	.. ordered 3.2.II
. ordered 3.3`
		expectedResult := `

unordered 1

ordered 1.1

ordered 1.1.a

ordered 1.1.b

ordered 1.1.c

ordered 1.2

ordered 1.2.i

ordered 1.2.ii

ordered 1.3

ordered 1.4

unordered 2

unordered 2.1

unordered 2.1.1
with some
extra lines.

unordered 2.1.2

unordered 2.2

unordered 3

ordered 3.1

ordered 3.2

ordered 3.2.I

ordered 3.2.II

ordered 3.3

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("all kinds of lists - complex case 3", func() {
		actualContent := `* foo
1. bar
a. foo
2. baz
* foo2
- bar2`
		expectedResult := `

foo

bar

foo

baz

foo2

bar2

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

})
<commit_msg>feat(renderer): support dropping of principal text in ordered list item (#283)<commit_after>package html5_test

import . "github.com/onsi/ginkgo"

var _ = Describe("ordered lists", func() {

	It("ordered list with title and role", func() {
		actualContent := `.title
[#myid]
[.myrole]
. item 1`
		expectedResult := `

title

item 1

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list item with explicit start only", func() {
		actualContent := `[start=5]
. item`
		expectedResult := `

item

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list item with explicit quoted numbering and start", func() {
		actualContent := `["lowerroman", start="5"]
. item`
		expectedResult := `

item

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with paragraph continuation", func() {
		actualContent := `. item 1
+
foo`
		expectedResult := `

item 1

foo

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with delimited block continuation", func() {
		actualContent := `. item 1
+
----
foo
----`
		expectedResult := `

item 1

foo

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list with unnumbered items", func() {
		actualContent := `. item 1
		.. item 1.1
		... item 1.1.1
		... item 1.1.2
		.. item 1.2
		. item 2
		.. item 2.1`
		expectedResult := `

item 1

item 1.1

item 1.1.1

item 1.1.2

item 1.2

item 2

item 2.1

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list mixed with unordered list - simple case", func() {
		actualContent := `. Linux
* Fedora
* Ubuntu
* Slackware
. BSD
* FreeBSD
* NetBSD`
		expectedResult := `

Linux

Fedora

Ubuntu

Slackware

BSD

FreeBSD

NetBSD

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("ordered list mixed with unordered list - complex case", func() {
		actualContent := `- unordered 1
1. ordered 1.1
	a. ordered 1.1.a
	b. ordered 1.1.b
	c. ordered 1.1.c
2. ordered 1.2
	i)  ordered 1.2.i
	ii) ordered 1.2.ii
3. ordered 1.3
4. ordered 1.4
- unordered 2
* unordered 2.1
** unordered 2.1.1
with some
extra lines.
** unordered 2.1.2
* unordered 2.2
- unordered 3
. ordered 3.1
. ordered 3.2
[upperroman]
	.. ordered 3.2.I
	.. ordered 3.2.II
. ordered 3.3`
		expectedResult := `

unordered 1

ordered 1.1

ordered 1.1.a

ordered 1.1.b

ordered 1.1.c

ordered 1.2

ordered 1.2.i

ordered 1.2.ii

ordered 1.3

ordered 1.4

unordered 2

unordered 2.1

unordered 2.1.1
with some
extra lines.

unordered 2.1.2

unordered 2.2

unordered 3

ordered 3.1

ordered 3.2

ordered 3.2.I

ordered 3.2.II

ordered 3.3

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("all kinds of lists - complex case 3", func() {
		actualContent := `* foo
1. bar
a. foo
2. baz
* foo2
- bar2`
		expectedResult := `

foo

bar

foo

baz

foo2

bar2

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

	It("drop principal text in list item", func() {
		actualContent := `. {blank}
+
----
print("one")
----
. {blank}
+
----
print("one")
----`
		expectedResult := `

print("one")

print("one")

`
		verify(GinkgoT(), expectedResult, actualContent)
	})

})
<|endoftext|>