<commit_before>package tracer

import (
	"fmt"
	"log"
	"time"

	"github.com/tracer/tracer/pb"

	"github.com/golang/protobuf/ptypes"
	"github.com/prometheus/client_golang/prometheus"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// GRPC is a gRPC-based transport for sending spans to a server.
type GRPC struct {
	client        pb.StorerClient
	queue         []RawSpan
	ch            chan RawSpan
	flushInterval time.Duration

	stored  prometheus.Counter
	dropped prometheus.Counter
}

// GRPCOptions are options for the GRPC storer.
type GRPCOptions struct {
	// How many spans to queue before sending them to the server.
	// Additionally, a buffer the size of 2*QueueSize will be used to
	// process new spans. If this buffer runs full, new spans will be
	// dropped.
	QueueSize int
	// How often to flush spans, even if the queue isn't full yet.
	FlushInterval time.Duration
}

// NewGRPC returns a new Storer that sends spans via gRPC to a server.
func NewGRPC(address string, grpcOpts *GRPCOptions, opts ...grpc.DialOption) (Storer, error) {
	if grpcOpts == nil {
		grpcOpts = &GRPCOptions{1024, 1 * time.Second}
	}
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		return nil, err
	}
	client := pb.NewStorerClient(conn)
	g := &GRPC{
		client:        client,
		queue:         make([]RawSpan, 0, grpcOpts.QueueSize),
		ch:            make(chan RawSpan, grpcOpts.QueueSize*2),
		flushInterval: grpcOpts.FlushInterval,

		stored: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "tracer_stored_spans_total",
			Help: "Number of stored spans",
		}),
		dropped: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "tracer_dropped_spans_total",
			Help: "Number of dropped spans",
		}),
	}
	err = prometheus.Register(g.dropped)
	if err != nil {
		log.Println("couldn't register prometheus counter:", err)
	}
	err = prometheus.Register(g.stored)
	if err != nil {
		log.Println("couldn't register prometheus counter:", err)
	}
	go g.loop()
	return g, nil
}

func (g *GRPC) loop() {
	t := time.NewTicker(g.flushInterval)
	for {
		select {
		case sp := <-g.ch:
			g.queue = append(g.queue, sp)
			if len(g.queue) == cap(g.queue) {
				log.Println("flushing")
				g.flush()
			}
		case <-t.C:
			g.flush()
		}
	}
}

func (g *GRPC) flush() {
	var pbs []*pb.Span
	for _, sp := range g.queue {
		pst, err := ptypes.TimestampProto(sp.StartTime)
		if err != nil {
			return // XXX
		}
		pft, err := ptypes.TimestampProto(sp.FinishTime)
		if err != nil {
			return // XXX
		}
		var tags []*pb.Tag
		for k, v := range sp.Tags {
			vs := fmt.Sprintf("%v", v) // XXX
			tags = append(tags, &pb.Tag{
				Key:   k,
				Value: vs,
			})
		}
		for _, log := range sp.Logs {
			t, err := ptypes.TimestampProto(log.Timestamp)
			if err != nil {
				return // XXX
			}
			ps := fmt.Sprintf("%v", log.Payload) // XXX
			tags = append(tags, &pb.Tag{
				Key:   log.Event,
				Value: ps,
				Time:  t,
			})
		}
		psp := &pb.Span{
			SpanId:        sp.SpanID,
			ParentId:      sp.ParentID,
			TraceId:       sp.TraceID,
			ServiceName:   sp.ServiceName,
			OperationName: sp.OperationName,
			StartTime:     pst,
			FinishTime:    pft,
			Flags:         sp.Flags,
			Tags:          tags,
		}
		pbs = append(pbs, psp)
	}
	if _, err := g.client.Store(context.Background(), &pb.StoreRequest{Spans: pbs}); err != nil {
		return // XXX
	}
	g.queue = g.queue[0:0]
}

// Store implements the tracer.Storer interface.
func (g *GRPC) Store(sp RawSpan) error {
	select {
	case g.ch <- sp:
		g.stored.Inc()
	default:
		g.dropped.Inc()
	}
	return nil
}
<commit_msg>Remove debug statement<commit_after>package tracer

import (
	"fmt"
	"log"
	"time"

	"github.com/tracer/tracer/pb"

	"github.com/golang/protobuf/ptypes"
	"github.com/prometheus/client_golang/prometheus"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// GRPC is a gRPC-based transport for sending spans to a server.
type GRPC struct {
	client        pb.StorerClient
	queue         []RawSpan
	ch            chan RawSpan
	flushInterval time.Duration

	stored  prometheus.Counter
	dropped prometheus.Counter
}

// GRPCOptions are options for the GRPC storer.
type GRPCOptions struct {
	// How many spans to queue before sending them to the server.
	// Additionally, a buffer the size of 2*QueueSize will be used to
	// process new spans. If this buffer runs full, new spans will be
	// dropped.
	QueueSize int
	// How often to flush spans, even if the queue isn't full yet.
	FlushInterval time.Duration
}

// NewGRPC returns a new Storer that sends spans via gRPC to a server.
func NewGRPC(address string, grpcOpts *GRPCOptions, opts ...grpc.DialOption) (Storer, error) {
	if grpcOpts == nil {
		grpcOpts = &GRPCOptions{1024, 1 * time.Second}
	}
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		return nil, err
	}
	client := pb.NewStorerClient(conn)
	g := &GRPC{
		client:        client,
		queue:         make([]RawSpan, 0, grpcOpts.QueueSize),
		ch:            make(chan RawSpan, grpcOpts.QueueSize*2),
		flushInterval: grpcOpts.FlushInterval,

		stored: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "tracer_stored_spans_total",
			Help: "Number of stored spans",
		}),
		dropped: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "tracer_dropped_spans_total",
			Help: "Number of dropped spans",
		}),
	}
	err = prometheus.Register(g.dropped)
	if err != nil {
		log.Println("couldn't register prometheus counter:", err)
	}
	err = prometheus.Register(g.stored)
	if err != nil {
		log.Println("couldn't register prometheus counter:", err)
	}
	go g.loop()
	return g, nil
}

func (g *GRPC) loop() {
	t := time.NewTicker(g.flushInterval)
	for {
		select {
		case sp := <-g.ch:
			g.queue = append(g.queue, sp)
			if len(g.queue) == cap(g.queue) {
				g.flush()
			}
		case <-t.C:
			g.flush()
		}
	}
}

func (g *GRPC) flush() {
	var pbs []*pb.Span
	for _, sp := range g.queue {
		pst, err := ptypes.TimestampProto(sp.StartTime)
		if err != nil {
			return // XXX
		}
		pft, err := ptypes.TimestampProto(sp.FinishTime)
		if err != nil {
			return // XXX
		}
		var tags []*pb.Tag
		for k, v := range sp.Tags {
			vs := fmt.Sprintf("%v", v) // XXX
			tags = append(tags, &pb.Tag{
				Key:   k,
				Value: vs,
			})
		}
		for _, log := range sp.Logs {
			t, err := ptypes.TimestampProto(log.Timestamp)
			if err != nil {
				return // XXX
			}
			ps := fmt.Sprintf("%v", log.Payload) // XXX
			tags = append(tags, &pb.Tag{
				Key:   log.Event,
				Value: ps,
				Time:  t,
			})
		}
		psp := &pb.Span{
			SpanId:        sp.SpanID,
			ParentId:      sp.ParentID,
			TraceId:       sp.TraceID,
			ServiceName:   sp.ServiceName,
			OperationName: sp.OperationName,
			StartTime:     pst,
			FinishTime:    pft,
			Flags:         sp.Flags,
			Tags:          tags,
		}
		pbs = append(pbs, psp)
	}
	if _, err := g.client.Store(context.Background(), &pb.StoreRequest{Spans: pbs}); err != nil {
		return // XXX
	}
	g.queue = g.queue[0:0]
}

// Store implements the tracer.Storer interface.
func (g *GRPC) Store(sp RawSpan) error {
	select {
	case g.ch <- sp:
		g.stored.Inc()
	default:
		g.dropped.Inc()
	}
	return nil
}
<|endoftext|>


<commit_before>package image

import (
	"bytes"
	"encoding/base64"
	"github.com/labstack/echo/v4"
	"golang.org/x/image/draw"
	goimg "image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"net/http"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/fileloader"
)

const (
	defaultThumbnailWidth  = 24
	defaultThumbnailHeight = 24
	defaultQuality         = 85
	defaultThumbQuality    = 50
)

type (
	// Image is an open image file
	Image interface {
		Duplicate() Image
		Resize(width, height int)
		ResizeFit(width, height int)
		ResizeLimit(width, height int)
		Crop(bounds goimg.Rectangle)
		ResizeFill(width, height int)
		ToJpeg(quality int) (*bytes.Buffer, error)
		ToPng(level PngCompressionOpt) (*bytes.Buffer, error)
		ToBase64(quality int) (string, error)
	}

	imageData struct {
		img goimg.Image
	}

	// Options represent the image options of the loaded image
	Options struct {
		Width        int
		Height       int
		ThumbWidth   int
		ThumbHeight  int
		Quality      int
		ThumbQuality int
		Fill         bool
	}
)

func FromImage(img goimg.Image) Image {
	return &imageData{
		img: img,
	}
}

// LoadJpeg reads an image from a form and encodes it as a jpeg
func LoadJpeg(c echo.Context, formField string, opt Options) (*bytes.Buffer, string, error) {
	if opt.Width < 1 || opt.Height < 1 {
		opt.Width = defaultThumbnailWidth
		opt.Height = defaultThumbnailHeight
	}
	if opt.ThumbWidth < 1 || opt.ThumbHeight < 1 {
		opt.ThumbWidth = defaultThumbnailWidth
		opt.ThumbHeight = defaultThumbnailHeight
	}
	if opt.Quality < 1 || opt.Quality > 100 {
		opt.Quality = defaultQuality
	}
	if opt.ThumbQuality < 1 || opt.ThumbQuality > 100 {
		opt.ThumbQuality = defaultThumbQuality
	}

	img, err := LoadImage(c, formField)
	if err != nil {
		return nil, "", err
	}
	if opt.Fill {
		img.ResizeFill(opt.Width, opt.Height)
	} else {
		img.ResizeLimit(opt.Width, opt.Height)
	}
	thumb := img.Duplicate()
	thumb.ResizeLimit(opt.ThumbWidth, opt.ThumbHeight)

	b, err := img.ToJpeg(opt.Quality)
	if err != nil {
		return nil, "", governor.NewError("Failed to encode image as JPEG", http.StatusInternalServerError, err)
	}
	b2, err := thumb.ToBase64(opt.ThumbQuality)
	if err != nil {
		return nil, "", governor.NewError("Failed to encode thumbnail as JPEG", http.StatusInternalServerError, err)
	}
	return b, b2, nil
}

const (
	// MediaTypeJpeg is the mime type for jpeg images
	MediaTypeJpeg = "image/jpeg"
	// MediaTypePng is the mime type for png images
	MediaTypePng = "image/png"
	// MediaTypeGif is the mime type for gif images
	MediaTypeGif = "image/gif"
)

func LoadImage(c echo.Context, formField string) (Image, error) {
	file, mediaType, _, err := fileloader.LoadOpenFile(c, formField, []string{MediaTypePng, MediaTypeJpeg, MediaTypeGif})
	if err != nil {
		return nil, governor.NewErrorUser("Invalid image file", http.StatusBadRequest, err)
	}
	defer func() {
		err := file.Close()
		if err != nil {
		}
	}()
	var img goimg.Image
	switch mediaType {
	case MediaTypeJpeg:
		if i, err := jpeg.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid JPEG image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	case MediaTypePng:
		if i, err := png.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid PNG image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	case MediaTypeGif:
		if i, err := gif.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid GIF image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	}
	return &imageData{
		img: img,
	}, nil
}

func (i imageData) Duplicate() Image {
	bounds := i.img.Bounds()
	target := goimg.NewNRGBA(bounds)
	draw.Draw(target, target.Bounds(), i.img, bounds.Min, draw.Src)
	return &imageData{
		img: target,
	}
}

func (i *imageData) Resize(width, height int) {
	target := goimg.NewNRGBA(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, i.img.Bounds(), draw.Src, nil)
	i.img = target
}

func dimensionsFit(fromWidth, fromHeight, toWidth, toHeight int) (int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// height is fit
		return fromWidth * toHeight / fromHeight, toHeight
	} else {
		// width is fit
		return toWidth, fromHeight * toWidth / fromWidth
	}
}

func (i *imageData) ResizeFit(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight := dimensionsFit(s.X, s.Y, width, height)
	i.Resize(targetWidth, targetHeight)
}

func (i *imageData) ResizeLimit(width, height int) {
	s := i.img.Bounds().Size()
	if s.X < width && s.Y < height {
		return
	}
	i.ResizeFit(width, height)
}

func (i *imageData) Crop(bounds goimg.Rectangle) {
	size := bounds.Size()
	target := goimg.NewNRGBA(goimg.Rect(0, 0, size.X, size.Y))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.Draw(target, target.Bounds(), i.img, bounds.Min, draw.Src)
	i.img = target
}

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

func dimensionsFill(fromWidth, fromHeight, toWidth, toHeight int) (int, int, int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// width is fit
		height := toHeight * fromWidth / toWidth
		return fromWidth, height, 0, maxInt((fromHeight-height)/2, 0)
	} else {
		// height is fit
		width := toWidth * fromHeight / toHeight
		return width, fromHeight, maxInt((fromWidth-width)/2, 0), 0
	}
}

func (i *imageData) ResizeFill(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight, offsetX, offsetY := dimensionsFill(s.X, s.Y, width, height)
	target := goimg.NewNRGBA(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, goimg.Rect(offsetX, offsetY, offsetX+targetWidth, offsetY+targetHeight), draw.Src, nil)
	i.img = target
}

func (i *imageData) ToJpeg(quality int) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	j := jpeg.Options{
		Quality: quality,
	}
	if err := jpeg.Encode(b, i.img, &j); err != nil {
		return nil, governor.NewError("Failed to encode JPEG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

type PngCompressionOpt int

const (
	PngDefault PngCompressionOpt = iota
	PngNone
	PngFast
	PngBest
)

func compressionOptTranslate(level PngCompressionOpt) png.CompressionLevel {
	switch level {
	case PngDefault:
		return png.DefaultCompression
	case PngNone:
		return png.NoCompression
	case PngFast:
		return png.BestSpeed
	case PngBest:
		return png.BestCompression
	default:
		return png.DefaultCompression
	}
}

func (i *imageData) ToPng(level PngCompressionOpt) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	encoder := png.Encoder{
		CompressionLevel: compressionOptTranslate(level),
	}
	if err := encoder.Encode(b, i.img); err != nil {
		return nil, governor.NewError("Failed to encode PNG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

const (
	dataURIPrefixJpeg = "data:image/jpeg;base64,"
)

func (i *imageData) ToBase64(quality int) (string, error) {
	b, err := i.ToJpeg(quality)
	if err != nil {
		return "", err
	}
	return dataURIPrefixJpeg + base64.RawStdEncoding.EncodeToString(b.Bytes()), nil
}
<commit_msg>Add draw operation to image interface<commit_after>package image

import (
	"bytes"
	"encoding/base64"
	"github.com/labstack/echo/v4"
	"golang.org/x/image/draw"
	goimg "image"
	"image/color"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"net/http"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/fileloader"
)

const (
	defaultThumbnailWidth  = 24
	defaultThumbnailHeight = 24
	defaultQuality         = 85
	defaultThumbQuality    = 50
)

type (
	Size struct {
		W, H int
	}

	// Image is an open image file
	Image interface {
		goimg.Image
		Duplicate() Image
		Size() Size
		Draw(img Image, x, y int)
		Resize(width, height int)
		ResizeFit(width, height int)
		ResizeLimit(width, height int)
		Crop(bounds goimg.Rectangle)
		ResizeFill(width, height int)
		ToJpeg(quality int) (*bytes.Buffer, error)
		ToPng(level PngCompressionOpt) (*bytes.Buffer, error)
		ToBase64(quality int) (string, error)
	}

	imageData struct {
		img draw.Image
	}

	// Options represent the image options of the loaded image
	Options struct {
		Width        int
		Height       int
		ThumbWidth   int
		ThumbHeight  int
		Quality      int
		ThumbQuality int
		Fill         bool
	}
)

func FromImage(img goimg.Image) Image {
	bounds := img.Bounds()
	target := goimg.NewNRGBA64(bounds)
	draw.Draw(target, target.Bounds(), img, bounds.Min, draw.Src)
	return &imageData{
		img: target,
	}
}

func FromJpeg(file io.Reader) (Image, error) {
	i, err := jpeg.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid JPEG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromPng(file io.Reader) (Image, error) {
	i, err := png.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid PNG image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

func FromGif(file io.Reader) (Image, error) {
	i, err := gif.Decode(file)
	if err != nil {
		return nil, governor.NewError("Invalid GIF image", http.StatusBadRequest, err)
	}
	return FromImage(i), nil
}

// LoadJpeg reads an image from a form and encodes it as a jpeg
func LoadJpeg(c echo.Context, formField string, opt Options) (*bytes.Buffer, string, error) {
	if opt.Width < 1 || opt.Height < 1 {
		opt.Width = defaultThumbnailWidth
		opt.Height = defaultThumbnailHeight
	}
	if opt.ThumbWidth < 1 || opt.ThumbHeight < 1 {
		opt.ThumbWidth = defaultThumbnailWidth
		opt.ThumbHeight = defaultThumbnailHeight
	}
	if opt.Quality < 1 || opt.Quality > 100 {
		opt.Quality = defaultQuality
	}
	if opt.ThumbQuality < 1 || opt.ThumbQuality > 100 {
		opt.ThumbQuality = defaultThumbQuality
	}

	img, err := LoadImage(c, formField)
	if err != nil {
		return nil, "", err
	}
	if opt.Fill {
		img.ResizeFill(opt.Width, opt.Height)
	} else {
		img.ResizeLimit(opt.Width, opt.Height)
	}
	thumb := img.Duplicate()
	thumb.ResizeLimit(opt.ThumbWidth, opt.ThumbHeight)

	b, err := img.ToJpeg(opt.Quality)
	if err != nil {
		return nil, "", governor.NewError("Failed to encode image as JPEG", http.StatusInternalServerError, err)
	}
	b2, err := thumb.ToBase64(opt.ThumbQuality)
	if err != nil {
		return nil, "", governor.NewError("Failed to encode thumbnail as JPEG", http.StatusInternalServerError, err)
	}
	return b, b2, nil
}

const (
	// MediaTypeJpeg is the mime type for jpeg images
	MediaTypeJpeg = "image/jpeg"
	// MediaTypePng is the mime type for png images
	MediaTypePng = "image/png"
	// MediaTypeGif is the mime type for gif images
	MediaTypeGif = "image/gif"
)

func LoadImage(c echo.Context, formField string) (Image, error) {
	file, mediaType, _, err := fileloader.LoadOpenFile(c, formField, []string{MediaTypePng, MediaTypeJpeg, MediaTypeGif})
	if err != nil {
		return nil, governor.NewErrorUser("Invalid image file", http.StatusBadRequest, err)
	}
	defer func() {
		err := file.Close()
		if err != nil {
		}
	}()
	var img goimg.Image
	switch mediaType {
	case MediaTypeJpeg:
		if i, err := jpeg.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid JPEG image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	case MediaTypePng:
		if i, err := png.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid PNG image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	case MediaTypeGif:
		if i, err := gif.Decode(file); err != nil {
			return nil, governor.NewErrorUser("Invalid GIF image", http.StatusBadRequest, err)
		} else {
			img = i
		}
	}
	return FromImage(img), nil
}

func (i imageData) ColorModel() color.Model {
	return i.img.ColorModel()
}

func (i imageData) Bounds() goimg.Rectangle {
	return i.img.Bounds()
}

func (i imageData) At(x, y int) color.Color {
	return i.img.At(x, y)
}

func (i imageData) Duplicate() Image {
	return FromImage(i.img)
}

func (i imageData) Size() Size {
	k := i.img.Bounds().Size()
	return Size{
		W: k.X,
		H: k.Y,
	}
}

func (i *imageData) Draw(img Image, x, y int) {
	source := img.Bounds()
	draw.Draw(i.img, source.Sub(source.Min).Add(i.img.Bounds().Min).Add(goimg.Pt(x, y)), img, source.Min, draw.Src)
}

func (i *imageData) Resize(width, height int) {
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, i.img.Bounds(), draw.Src, nil)
	i.img = target
}

func dimensionsFit(fromWidth, fromHeight, toWidth, toHeight int) (int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// height is fit
		return fromWidth * toHeight / fromHeight, toHeight
	} else {
		// width is fit
		return toWidth, fromHeight * toWidth / fromWidth
	}
}

func (i *imageData) ResizeFit(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight := dimensionsFit(s.X, s.Y, width, height)
	i.Resize(targetWidth, targetHeight)
}

func (i *imageData) ResizeLimit(width, height int) {
	s := i.img.Bounds().Size()
	if s.X < width && s.Y < height {
		return
	}
	i.ResizeFit(width, height)
}

func (i *imageData) Crop(bounds goimg.Rectangle) {
	size := bounds.Size()
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, size.X, size.Y))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.Draw(target, target.Bounds(), i.img, bounds.Min, draw.Src)
	i.img = target
}

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

func dimensionsFill(fromWidth, fromHeight, toWidth, toHeight int) (int, int, int, int) {
	// fromRatio < toRatio
	if fromWidth*toHeight < toWidth*fromHeight {
		// width is fit
		height := toHeight * fromWidth / toWidth
		return fromWidth, height, 0, maxInt((fromHeight-height)/2, 0)
	} else {
		// height is fit
		width := toWidth * fromHeight / toHeight
		return width, fromHeight, maxInt((fromWidth-width)/2, 0), 0
	}
}

func (i *imageData) ResizeFill(width, height int) {
	s := i.img.Bounds().Size()
	targetWidth, targetHeight, offsetX, offsetY := dimensionsFill(s.X, s.Y, width, height)
	target := goimg.NewNRGBA64(goimg.Rect(0, 0, width, height))
	draw.Draw(target, target.Bounds(), goimg.Transparent, goimg.ZP, draw.Src)
	draw.ApproxBiLinear.Scale(target, target.Bounds(), i.img, goimg.Rect(offsetX, offsetY, offsetX+targetWidth, offsetY+targetHeight), draw.Src, nil)
	i.img = target
}

func (i *imageData) ToJpeg(quality int) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	j := jpeg.Options{
		Quality: quality,
	}
	if err := jpeg.Encode(b, i.img, &j); err != nil {
		return nil, governor.NewError("Failed to encode JPEG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

type PngCompressionOpt int

const (
	PngDefault PngCompressionOpt = iota
	PngNone
	PngFast
	PngBest
)

func compressionOptTranslate(level PngCompressionOpt) png.CompressionLevel {
	switch level {
	case PngDefault:
		return png.DefaultCompression
	case PngNone:
		return png.NoCompression
	case PngFast:
		return png.BestSpeed
	case PngBest:
		return png.BestCompression
	default:
		return png.DefaultCompression
	}
}

func (i *imageData) ToPng(level PngCompressionOpt) (*bytes.Buffer, error) {
	b := &bytes.Buffer{}
	encoder := png.Encoder{
		CompressionLevel: compressionOptTranslate(level),
	}
	if err := encoder.Encode(b, i.img); err != nil {
		return nil, governor.NewError("Failed to encode PNG image", http.StatusInternalServerError, err)
	}
	return b, nil
}

const (
	dataURIPrefixJpeg = "data:image/jpeg;base64,"
)

func (i *imageData) ToBase64(quality int) (string, error) {
	b, err := i.ToJpeg(quality)
	if err != nil {
		return "", err
	}
	return dataURIPrefixJpeg + base64.RawStdEncoding.EncodeToString(b.Bytes()), nil
}
<|endoftext|>


<commit_before>package v2_test

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"sigs.k8s.io/yaml"

	ambV2 "github.com/datawire/ambassador/pkg/api/getambassador.io/v2"
)

func boolPtr(b bool) *bool {
	return &b
}

func stringPtr(s string) *string {
	return &s
}

func TestBoolOrString(t *testing.T) {
	t.Parallel()
	type TestResource struct {
		Field ambV2.BoolOrString `json:"field"`
	}
	type subtest struct {
		inputYAML      string
		expectedStruct TestResource
		expectedJSON   string
	}
	subtests := map[string]subtest{
		"empty":         {`{}`, TestResource{}, `{"field":null}`},
		"explicitEmpty": {`field:`, TestResource{}, `{"field":null}`},
		"explicitnull":  {`field: null`, TestResource{}, `{"field":null}`},
		"explicitNull":  {`field: Null`, TestResource{}, `{"field":null}`},
		"explicitNULL":  {`field: NULL`, TestResource{}, `{"field":null}`},
		"explicitTilde": {`field: ~`, TestResource{}, `{"field":null}`},
		"true":          {`field: true`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"True":          {`field: True`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"TRUE":          {`field: TRUE`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"false":         {`field: false`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"False":         {`field: False`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"FALSE":         {`field: FALSE`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"strTrue":       {`field: "true"`, TestResource{ambV2.BoolOrString{String: stringPtr("true")}}, `{"field":"true"}`}, // quoted
		"strTRue":       {`field: TRue`, TestResource{ambV2.BoolOrString{String: stringPtr("TRue")}}, `{"field":"TRue"}`},   // capitalized wrong
		"strBare":       {`field: bare`, TestResource{ambV2.BoolOrString{String: stringPtr("bare")}}, `{"field":"bare"}`},
	}
	for name, info := range subtests {
		info := info // capture loop variable
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			var parsed TestResource
			assert.NoError(t, yaml.Unmarshal([]byte(info.inputYAML), &parsed))
			assert.Equal(t, info.expectedStruct, parsed)
			jsonbytes, err := json.Marshal(parsed)
			assert.NoError(t, err)
			assert.Equal(t, info.expectedJSON, string(jsonbytes))
		})
	}
}
<commit_msg>(from AES) Better tests for BoolOrString (courtesy of @LukeShu)<commit_after>package v2_test

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"sigs.k8s.io/yaml"

	ambV2 "github.com/datawire/ambassador/pkg/api/getambassador.io/v2"
)

func boolPtr(b bool) *bool {
	return &b
}

func stringPtr(s string) *string {
	return &s
}

func TestBoolOrString(t *testing.T) {
	t.Parallel()
	type TestResource struct {
		Field ambV2.BoolOrString `json:"field,omitempty"`
	}
	type subtest struct {
		inputYAML      string
		expectedStruct TestResource
		expectedJSON   string
	}
	subtests := map[string]subtest{
		"empty":         {`{}`, TestResource{}, `{"field":null}`},
		"explicitEmpty": {`field:`, TestResource{}, `{"field":null}`},
		"explicitnull":  {`field: null`, TestResource{}, `{"field":null}`},
		"explicitNull":  {`field: Null`, TestResource{}, `{"field":null}`},
		"explicitNULL":  {`field: NULL`, TestResource{}, `{"field":null}`},
		"explicitTilde": {`field: ~`, TestResource{}, `{"field":null}`},
		"true":          {`field: true`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"True":          {`field: True`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"TRUE":          {`field: TRUE`, TestResource{ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"false":         {`field: false`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"False":         {`field: False`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"FALSE":         {`field: FALSE`, TestResource{ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"strTrue":       {`field: "true"`, TestResource{ambV2.BoolOrString{String: stringPtr("true")}}, `{"field":"true"}`}, // quoted
		"strTRue":       {`field: TRue`, TestResource{ambV2.BoolOrString{String: stringPtr("TRue")}}, `{"field":"TRue"}`},   // capitalized wrong
		"strBare":       {`field: bare`, TestResource{ambV2.BoolOrString{String: stringPtr("bare")}}, `{"field":"bare"}`},
	}
	for name, info := range subtests {
		info := info // capture loop variable
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			var parsed TestResource
			assert.NoError(t, yaml.Unmarshal([]byte(info.inputYAML), &parsed))
			assert.Equal(t, info.expectedStruct, parsed)
			jsonbytes, err := json.Marshal(parsed)
			assert.NoError(t, err)
			assert.Equal(t, info.expectedJSON, string(jsonbytes))
		})
	}
}

func TestBoolOrStringPtr(t *testing.T) {
	t.Parallel()
	type TestResource struct {
		Field *ambV2.BoolOrString `json:"field,omitempty"`
	}
	type subtest struct {
		inputYAML      string
		expectedStruct TestResource
		expectedJSON   string
	}
	subtests := map[string]subtest{
		"empty":         {`{}`, TestResource{}, `{}`},
		"explicitEmpty": {`field:`, TestResource{}, `{}`},
		"explicitnull":  {`field: null`, TestResource{}, `{}`},
		"explicitNull":  {`field: Null`, TestResource{}, `{}`},
		"explicitNULL":  {`field: NULL`, TestResource{}, `{}`},
		"explicitTilde": {`field: ~`, TestResource{}, `{}`},
		"true":          {`field: true`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"True":          {`field: True`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"TRUE":          {`field: TRUE`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(true)}}, `{"field":true}`},
		"false":         {`field: false`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"False":         {`field: False`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"FALSE":         {`field: FALSE`, TestResource{&ambV2.BoolOrString{Bool: boolPtr(false)}}, `{"field":false}`},
		"strTrue":       {`field: "true"`, TestResource{&ambV2.BoolOrString{String: stringPtr("true")}}, `{"field":"true"}`}, // quoted
		"strTRue":       {`field: TRue`, TestResource{&ambV2.BoolOrString{String: stringPtr("TRue")}}, `{"field":"TRue"}`},   // capitalized wrong
		"strBare":       {`field: bare`, TestResource{&ambV2.BoolOrString{String: stringPtr("bare")}}, `{"field":"bare"}`},
	}
	for name, info := range subtests {
		info := info // capture loop variable
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			var parsed TestResource
			assert.NoError(t, yaml.Unmarshal([]byte(info.inputYAML), &parsed))
			assert.Equal(t, info.expectedStruct, parsed)
			jsonbytes, err := json.Marshal(parsed)
			assert.NoError(t, err)
			assert.Equal(t, info.expectedJSON, string(jsonbytes))
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

const GroupName = "apiextensions.k8s.io"

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}

// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
	return SchemeGroupVersion.WithKind(kind).GroupKind()
}

// Resource takes an unqualified resource and returns back a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
	return SchemeGroupVersion.WithResource(resource).GroupResource()
}

var (
	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs)
	AddToScheme   = SchemeBuilder.AddToScheme
)

// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&CustomResourceDefinition{},
		&CustomResourceDefinitionList{},
	)
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}
<commit_msg>unify register.go formats: networking/v1/register.go staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1/register.go staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1/register.go staging/src/k8s.io/metrics/pkg/apis/metrics/v1alpha1 register.go<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

const GroupName = "apiextensions.k8s.io"

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}

// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
	return SchemeGroupVersion.WithKind(kind).GroupKind()
}

// Resource takes an unqualified resource and returns back a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
	return SchemeGroupVersion.WithResource(resource).GroupResource()
}

var (
	SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs)
	localSchemeBuilder = &SchemeBuilder
	AddToScheme        = localSchemeBuilder.AddToScheme
)

// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&CustomResourceDefinition{},
		&CustomResourceDefinitionList{},
	)
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}
<|endoftext|>


<commit_before>package file_reader

import (
	"bytes"
	"context"
	"fmt"
	"path/filepath"

	"github.com/werf/werf/pkg/util"

	"github.com/werf/logboek"

	"github.com/bmatcuk/doublestar"
)

func (r FileReader) relativeToGitWorkingDir() string {
	workingDir := r.sharedOptions.ProjectDir() // FIXME: rename project-dir to working dir and work-tree-dir to project-git-work-tree or project-git or project-work-tree or ...
	return util.GetRelativeToBaseFilepath(r.sharedOptions.LocalGitRepo().WorkTreeDir, workingDir)
}

func (r FileReader) relativeToGitPath(relPath string) string {
	return filepath.Join(r.relativeToGitWorkingDir(), relPath)
}

func (r FileReader) isCommitDirectoryExist(ctx context.Context, relPath string) (bool, error) {
	exist, err := r.sharedOptions.LocalGitRepo().IsCommitDirectoryExists(ctx, r.sharedOptions.HeadCommit(), r.relativeToGitPath(relPath))
	if err != nil {
		err := fmt.Errorf(
			"unable to check existence of directory %s in the project git repo commit %s: %s",
			relPath, r.sharedOptions.HeadCommit(), err,
		)
		return false, err
	}

	return exist, nil
}

func (r FileReader) isCommitFileExist(ctx context.Context, relPath string) (bool, error) {
	logboek.Context(ctx).Debug().LogF("-- giterminism_manager.FileReader.isCommitFileExist relPath=%q\n", relPath)

	exist, err := r.sharedOptions.LocalGitRepo().IsCommitFileExists(ctx, r.sharedOptions.HeadCommit(), r.relativeToGitPath(relPath))
	if err != nil {
		err := fmt.Errorf(
			"unable to check existence of file %s in the project git repo commit %s: %s",
			relPath, r.sharedOptions.HeadCommit(), err,
		)
		return false, err
	}

	return exist, nil
}

func (r FileReader) commitFilesGlob(ctx context.Context, pattern string) ([]string, error) {
	var result []string

	commitPathList, err := r.sharedOptions.LocalGitRepo().GetCommitFilePathList(ctx, r.sharedOptions.HeadCommit())
	if err != nil {
		return nil, fmt.Errorf("unable to get files list from local git repo: %s", err)
	}

	pattern = filepath.ToSlash(pattern)
	for _, relToGitFilepath := range commitPathList {
		relToGitPath := filepath.ToSlash(relToGitFilepath)
		relPath := util.GetRelativeToBaseFilepath(r.relativeToGitWorkingDir(), relToGitPath)

		if matched, err := doublestar.Match(pattern, relPath); err != nil {
			return nil, err
		} else if matched {
			result = append(result, relPath)
		}
	}

	return result, nil
}

func (r FileReader) readCommitFile(ctx context.Context, relPath string, handleUncommittedChangesFunc func(context.Context, string) error) ([]byte, error) {
	logboek.Context(ctx).Debug().LogF("-- giterminism_manager.FileReader.readCommitFile relPath=%q\n", relPath)

	fileRepoPath := r.relativeToGitPath(filepath.ToSlash(relPath))

	repoData, err := r.sharedOptions.LocalGitRepo().ReadCommitFile(ctx, r.sharedOptions.HeadCommit(), fileRepoPath)
	if err != nil {
		return nil, fmt.Errorf("unable to read file %q from the local git repo commit %s: %s", relPath, r.sharedOptions.HeadCommit(), err)
	}

	if handleUncommittedChangesFunc == nil {
		return repoData, nil
	}

	isDataIdentical, err := r.compareFileData(relPath, repoData)
	if err != nil {
		return nil, fmt.Errorf("unable to compare commit file %q with the local project file: %s", relPath, err)
	}

	if !isDataIdentical {
		if err := handleUncommittedChangesFunc(ctx, filepath.FromSlash(relPath)); err != nil {
			return nil, err
		}
	}

	return repoData, err
}

func (r FileReader) compareFileData(relPath string, data []byte) (bool, error) {
	var fileData []byte
	if exist, err := r.isFileExist(relPath); err != nil {
		return false, err
	} else if exist {
		fileData, err = r.readFile(relPath)
		if err != nil {
			return false, err
		}
	}

	isDataIdentical := bytes.Equal(fileData, data)
	fileDataWithForcedUnixLineBreak := bytes.ReplaceAll(fileData, []byte("\r\n"), []byte("\n"))
	if !isDataIdentical {
		isDataIdentical = bytes.Equal(fileDataWithForcedUnixLineBreak, data)
	}

	return isDataIdentical, nil
}
<commit_msg>[giterminism] Fix doublestar.Match matches system-dependent path<commit_after>package file_reader

import (
	"bytes"
	"context"
	"fmt"
	"path/filepath"

	"github.com/werf/werf/pkg/util"

	"github.com/werf/logboek"

	"github.com/bmatcuk/doublestar"
)

func (r FileReader) relativeToGitWorkingDir() string {
	workingDir := r.sharedOptions.ProjectDir() // FIXME: rename project-dir to working dir and work-tree-dir to project-git-work-tree or project-git or project-work-tree or ...
	return util.GetRelativeToBaseFilepath(r.sharedOptions.LocalGitRepo().WorkTreeDir, workingDir)
}

func (r FileReader) relativeToGitPath(relPath string) string {
	return filepath.Join(r.relativeToGitWorkingDir(), relPath)
}

func (r FileReader) isCommitDirectoryExist(ctx context.Context, relPath string) (bool, error) {
	exist, err := r.sharedOptions.LocalGitRepo().IsCommitDirectoryExists(ctx, r.sharedOptions.HeadCommit(), r.relativeToGitPath(relPath))
	if err != nil {
		err := fmt.Errorf(
			"unable to check existence of directory %s in the project git repo commit %s: %s",
			relPath, r.sharedOptions.HeadCommit(), err,
		)
		return false, err
	}

	return exist, nil
}

func (r FileReader) isCommitFileExist(ctx context.Context, relPath string) (bool, error) {
	logboek.Context(ctx).Debug().LogF("-- giterminism_manager.FileReader.isCommitFileExist relPath=%q\n", relPath)

	exist, err := r.sharedOptions.LocalGitRepo().IsCommitFileExists(ctx, r.sharedOptions.HeadCommit(), r.relativeToGitPath(relPath))
	if err != nil {
		err := fmt.Errorf(
			"unable to check existence of file %s in the project git repo commit %s: %s",
			relPath, r.sharedOptions.HeadCommit(), err,
		)
		return false, err
	}

	return exist, nil
}

func (r FileReader) commitFilesGlob(ctx context.Context, pattern string) ([]string, error) {
	var result []string

	commitPathList, err := r.sharedOptions.LocalGitRepo().GetCommitFilePathList(ctx, r.sharedOptions.HeadCommit())
	if err != nil {
		return nil, fmt.Errorf("unable to get files list from local git repo: %s", err)
	}

	pattern = filepath.ToSlash(pattern)
	for _, relToGitFilepath := range commitPathList {
		relToGitPath := filepath.ToSlash(relToGitFilepath)
		relPath := filepath.ToSlash(util.GetRelativeToBaseFilepath(r.relativeToGitWorkingDir(), relToGitPath))

		if matched, err := doublestar.Match(pattern, relPath); err != nil {
			return nil, err
		} else if matched {
			result = append(result, relPath)
		}
	}

	return result, nil
}

func (r FileReader) readCommitFile(ctx context.Context, relPath string, handleUncommittedChangesFunc func(context.Context, string) error) ([]byte, error) {
	logboek.Context(ctx).Debug().LogF("-- giterminism_manager.FileReader.readCommitFile relPath=%q\n", relPath)

	fileRepoPath := r.relativeToGitPath(filepath.ToSlash(relPath))

	repoData, err := r.sharedOptions.LocalGitRepo().ReadCommitFile(ctx, r.sharedOptions.HeadCommit(), fileRepoPath)
	if err != nil {
		return nil, fmt.Errorf("unable to read file %q from the local git repo commit %s: %s", relPath, r.sharedOptions.HeadCommit(), err)
	}

	if handleUncommittedChangesFunc == nil {
		return repoData, nil
	}

	isDataIdentical, err := r.compareFileData(relPath, repoData)
	if err != nil {
		return nil, fmt.Errorf("unable to compare commit file %q with the local project file: %s", relPath, err)
	}

	if !isDataIdentical {
		if err := handleUncommittedChangesFunc(ctx, filepath.FromSlash(relPath)); err != nil {
			return nil, err
		}
	}

	return repoData, err
}

func (r FileReader) compareFileData(relPath string, data []byte) (bool, error) {
	var fileData []byte
	if exist, err := r.isFileExist(relPath); err != nil {
		return false, err
	} else if exist {
		fileData, err = r.readFile(relPath)
		if err != nil {
			return false, err
		}
	}

	isDataIdentical := bytes.Equal(fileData, data)
	fileDataWithForcedUnixLineBreak := bytes.ReplaceAll(fileData, []byte("\r\n"), []byte("\n"))
	if !isDataIdentical {
		isDataIdentical = bytes.Equal(fileDataWithForcedUnixLineBreak, data)
	}

	return isDataIdentical, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package status

import (
	"fmt"
	"sync"

	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	clientset "k8s.io/client-go/kubernetes"
	utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
	"k8s.io/kubernetes/pkg/kubelet/metrics"
	nodeutil "k8s.io/kubernetes/pkg/util/node"
)

const (
	// LoadError indicates that the Kubelet failed to load the config checkpoint
	LoadError = "failed to load config, see Kubelet log for details"
	// ValidateError indicates that the Kubelet failed to validate the config checkpoint
	ValidateError = "failed to validate config, see Kubelet log for details"
	// AllNilSubfieldsError is used when no subfields are set
	// This could happen in the case that an old client tries to read an object from a newer API server with a set subfield it does not know about
	AllNilSubfieldsError = "invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil"
	// UIDMismatchErrorFmt is used when there is a UID mismatch between the referenced and downloaded ConfigMaps,
	// this can happen because objects must be downloaded by namespace/name, rather than by UID
	// TODO(mtaufen): remove this in #63221
	UIDMismatchErrorFmt = "invalid ConfigSource.ConfigMap.UID: %s does not match %s.UID: %s"
	// DownloadError is used when the download fails, e.g. due to network issues
	DownloadError = "failed to download config, see Kubelet log for details"
	// InternalError indicates that some internal error happened while trying to sync config, e.g. filesystem issues
	InternalError = "internal failure, see Kubelet log for details"

	// SyncErrorFmt is used when the system couldn't sync the config, due to a malformed Node.Spec.ConfigSource, a download failure, etc.
	SyncErrorFmt = "failed to sync: %s"
)

// NodeConfigStatus represents Node.Status.Config
type NodeConfigStatus interface {
	// SetActive sets the active source in the status
	SetActive(source *apiv1.NodeConfigSource)
	// SetAssigned sets the assigned source in the status
	SetAssigned(source *apiv1.NodeConfigSource)
	// SetLastKnownGood sets the last-known-good source in the status
	SetLastKnownGood(source *apiv1.NodeConfigSource)
	// SetError sets the error associated with the status
	SetError(err string)
	// SetErrorOverride sets an error that overrides the base error set by SetError.
	// If the override is set to the empty string, the base error is reported in
	// the status, otherwise the override is reported.
	SetErrorOverride(err string)
	// Sync patches the current status into the Node identified by `nodeName` if an update is pending
	Sync(client clientset.Interface, nodeName string)
}

type nodeConfigStatus struct {
	// status is the core NodeConfigStatus that we report
	status apiv1.NodeConfigStatus
	// mux is a mutex on the nodeConfigStatus, alternate between setting and syncing the status
	mux sync.Mutex
	// errorOverride is sent in place of the usual error if it is non-empty
	errorOverride string
	// syncCh; write to this channel to indicate that the status needs to be synced to the API server
	syncCh chan bool
}

// NewNodeConfigStatus returns a new NodeConfigStatus interface
func NewNodeConfigStatus() NodeConfigStatus {
	// channels must have capacity at least 1, since we signal with non-blocking writes
	syncCh := make(chan bool, 1)
	// prime new status managers to sync with the API server on the first call to Sync
	syncCh <- true
	return &nodeConfigStatus{
		syncCh: syncCh,
	}
}

// transact grabs the lock, performs the fn, records the need to sync, and releases the lock
func (s *nodeConfigStatus) transact(fn func()) {
	s.mux.Lock()
	defer s.mux.Unlock()
	fn()
	s.sync()
}

func (s *nodeConfigStatus) SetAssigned(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.Assigned = source
	})
}

func (s *nodeConfigStatus) SetActive(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.Active = source
	})
}

func (s *nodeConfigStatus) SetLastKnownGood(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.LastKnownGood = source
	})
}

func (s *nodeConfigStatus) SetError(err string) {
	s.transact(func() {
		s.status.Error = err
	})
}

func (s *nodeConfigStatus) SetErrorOverride(err string) {
	s.transact(func() {
		s.errorOverride = err
	})
}

// sync notes that the status needs to be synced to the API server
func (s *nodeConfigStatus) sync() {
	select {
	case s.syncCh <- true:
	default:
	}
}

// Sync attempts to sync the status with the Node object for this Kubelet,
// if syncing fails, an error is logged, and work is queued for retry.
func (s *nodeConfigStatus) Sync(client clientset.Interface, nodeName string) {
	select {
	case <-s.syncCh:
	default:
		// no work to be done, return
		return
	}

	utillog.Infof("updating Node.Status.Config")

	// grab the lock
	s.mux.Lock()
	defer s.mux.Unlock()

	// if the sync fails, we want to retry
	var err error
	defer func() {
		if err != nil {
			utillog.Errorf(err.Error())
			s.sync()
		}
	}()

	// get the Node so we can check the current status
	oldNode, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
	if err != nil {
		err = fmt.Errorf("could not get Node %q, will not sync status, error: %v", nodeName, err)
		return
	}

	status := &s.status
	// override error, if necessary
	if len(s.errorOverride) > 0 {
		// copy the status, so we don't overwrite the prior error
		// with the override
		status = status.DeepCopy()
		status.Error = s.errorOverride
	}

	// update metrics based on the status we will sync
	metrics.SetConfigError(len(status.Error) > 0)
	err = metrics.SetAssignedConfig(status.Assigned)
	if err != nil {
		err = fmt.Errorf("failed to update Assigned config metric, error: %v", err)
		return
	}
	err = metrics.SetActiveConfig(status.Active)
	if err != nil {
		err = fmt.Errorf("failed to update Active config metric, error: %v", err)
		return
	}
	err = metrics.SetLastKnownGoodConfig(status.LastKnownGood)
	if err != nil {
		err = fmt.Errorf("failed to update LastKnownGood config metric, error: %v", err)
		return
	}

	// apply the status to a copy of the node so we don't modify the object in the informer's store
	newNode := oldNode.DeepCopy()
	newNode.Status.Config = status

	// patch the node with the new status
	if _, _, err := nodeutil.PatchNodeStatus(client.CoreV1(), types.NodeName(nodeName), oldNode, newNode); err != nil {
		utillog.Errorf("failed to patch node status, error: %v", err)
	}
}
<commit_msg>remove unused status per TODO<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package status

import (
	"fmt"
	"sync"

	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	clientset "k8s.io/client-go/kubernetes"
	utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
	"k8s.io/kubernetes/pkg/kubelet/metrics"
	nodeutil "k8s.io/kubernetes/pkg/util/node"
)

const (
	// LoadError indicates that the Kubelet failed to load the config checkpoint
	LoadError = "failed to load config, see Kubelet log for details"
	// ValidateError indicates that the Kubelet failed to validate the config checkpoint
	ValidateError = "failed to validate config, see Kubelet log for details"
	// AllNilSubfieldsError is used when no subfields are set
	// This could happen in the case that an old client tries to read an object from a newer API server with a set subfield it does not know about
	AllNilSubfieldsError = "invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil"
	// DownloadError is used when the download fails, e.g. due to network issues
	DownloadError = "failed to download config, see Kubelet log for details"
	// InternalError indicates that some internal error happened while trying to sync config, e.g. filesystem issues
	InternalError = "internal failure, see Kubelet log for details"

	// SyncErrorFmt is used when the system couldn't sync the config, due to a malformed Node.Spec.ConfigSource, a download failure, etc.
	SyncErrorFmt = "failed to sync: %s"
)

// NodeConfigStatus represents Node.Status.Config
type NodeConfigStatus interface {
	// SetActive sets the active source in the status
	SetActive(source *apiv1.NodeConfigSource)
	// SetAssigned sets the assigned source in the status
	SetAssigned(source *apiv1.NodeConfigSource)
	// SetLastKnownGood sets the last-known-good source in the status
	SetLastKnownGood(source *apiv1.NodeConfigSource)
	// SetError sets the error associated with the status
	SetError(err string)
	// SetErrorOverride sets an error that overrides the base error set by SetError.
	// If the override is set to the empty string, the base error is reported in
	// the status, otherwise the override is reported.
	SetErrorOverride(err string)
	// Sync patches the current status into the Node identified by `nodeName` if an update is pending
	Sync(client clientset.Interface, nodeName string)
}

type nodeConfigStatus struct {
	// status is the core NodeConfigStatus that we report
	status apiv1.NodeConfigStatus
	// mux is a mutex on the nodeConfigStatus, alternate between setting and syncing the status
	mux sync.Mutex
	// errorOverride is sent in place of the usual error if it is non-empty
	errorOverride string
	// syncCh; write to this channel to indicate that the status needs to be synced to the API server
	syncCh chan bool
}

// NewNodeConfigStatus returns a new NodeConfigStatus interface
func NewNodeConfigStatus() NodeConfigStatus {
	// channels must have capacity at least 1, since we signal with non-blocking writes
	syncCh := make(chan bool, 1)
	// prime new status managers to sync with the API server on the first call to Sync
	syncCh <- true
	return &nodeConfigStatus{
		syncCh: syncCh,
	}
}

// transact grabs the lock, performs the fn, records the need to sync, and releases the lock
func (s *nodeConfigStatus) transact(fn func()) {
	s.mux.Lock()
	defer s.mux.Unlock()
	fn()
	s.sync()
}

func (s *nodeConfigStatus) SetAssigned(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.Assigned = source
	})
}

func (s *nodeConfigStatus) SetActive(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.Active = source
	})
}

func (s *nodeConfigStatus) SetLastKnownGood(source *apiv1.NodeConfigSource) {
	s.transact(func() {
		s.status.LastKnownGood = source
	})
}

func (s *nodeConfigStatus) SetError(err string) {
	s.transact(func() {
		s.status.Error = err
	})
}

func (s *nodeConfigStatus) SetErrorOverride(err string) {
	s.transact(func() {
		s.errorOverride = err
	})
}

// sync notes that the status needs to be synced to the API server
func (s *nodeConfigStatus) sync() {
	select {
	case s.syncCh <- true:
	default:
	}
}

// Sync attempts to sync the status with the Node object for this Kubelet,
// if syncing fails, an error is logged, and work is queued for retry.
func (s *nodeConfigStatus) Sync(client clientset.Interface, nodeName string) {
	select {
	case <-s.syncCh:
	default:
		// no work to be done, return
		return
	}

	utillog.Infof("updating Node.Status.Config")

	// grab the lock
	s.mux.Lock()
	defer s.mux.Unlock()

	// if the sync fails, we want to retry
	var err error
	defer func() {
		if err != nil {
			utillog.Errorf(err.Error())
			s.sync()
		}
	}()

	// get the Node so we can check the current status
	oldNode, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
	if err != nil {
		err = fmt.Errorf("could not get Node %q, will not sync status, error: %v", nodeName, err)
		return
	}

	status := &s.status
	// override error, if necessary
	if len(s.errorOverride) > 0 {
		// copy the status, so we don't overwrite the prior error
		// with the override
		status = status.DeepCopy()
		status.Error = s.errorOverride
	}

	// update metrics based on the status we will sync
	metrics.SetConfigError(len(status.Error) > 0)
	err = metrics.SetAssignedConfig(status.Assigned)
	if err != nil {
		err = fmt.Errorf("failed to update Assigned config metric, error: %v", err)
		return
	}
	err = metrics.SetActiveConfig(status.Active)
	if err != nil {
		err = fmt.Errorf("failed to update Active config metric, error: %v", err)
		return
	}
	err = metrics.SetLastKnownGoodConfig(status.LastKnownGood)
	if err != nil {
		err = fmt.Errorf("failed to update LastKnownGood config metric, error: %v", err)
		return
	}

	// apply the status to a copy of the node so we don't modify the object in the informer's store
	newNode := oldNode.DeepCopy()
	newNode.Status.Config = status

	// patch the node with the new status
	if _, _, err := nodeutil.PatchNodeStatus(client.CoreV1(), types.NodeName(nodeName), oldNode, newNode); err != nil {
		utillog.Errorf("failed to patch node status, error: %v", err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative Authors

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

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

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

package taskrun

import (
	"context"
	"fmt"
	"reflect"

	buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
	"github.com/knative/build/pkg/builder"
	buildinformers "github.com/knative/build/pkg/client/informers/externalversions/build/v1alpha1"
	buildlisters "github.com/knative/build/pkg/client/listers/build/v1alpha1"
	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/tracker"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/tools/cache"

	"github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1"
	informers "github.com/knative/build-pipeline/pkg/client/informers/externalversions/pipeline/v1alpha1"
	listers "github.com/knative/build-pipeline/pkg/client/listers/pipeline/v1alpha1"
	"github.com/knative/build-pipeline/pkg/reconciler"
	resources "github.com/knative/build-pipeline/pkg/reconciler/v1alpha1/taskrun/resources"
)

const (
	// taskRunAgentName defines logging agent name for TaskRun Controller
	taskRunAgentName = "taskrun-controller"
	// taskRunControllerName defines name for TaskRun Controller
	taskRunControllerName = "TaskRun"
	taskRunNameLabelKey   = "taskrun.knative.dev/taskName"
)

var (
	groupVersionKind = schema.GroupVersionKind{
		Group:   v1alpha1.SchemeGroupVersion.Group,
		Version: v1alpha1.SchemeGroupVersion.Version,
		Kind:    "TaskRun",
	}
)

// Reconciler implements controller.Reconciler for Configuration resources.
type Reconciler struct {
	*reconciler.Base

	// listers index properties about resources
	taskRunLister  listers.TaskRunLister
	taskLister     listers.TaskLister
	buildLister    buildlisters.BuildLister
	resourceLister listers.PipelineResourceLister
	tracker        tracker.Interface
}

// Check that our Reconciler implements controller.Reconciler
var _ controller.Reconciler = (*Reconciler)(nil)

// NewController creates a new Configuration controller
func NewController(
	opt reconciler.Options,
	taskRunInformer informers.TaskRunInformer,
	taskInformer informers.TaskInformer,
	buildInformer buildinformers.BuildInformer,
	resourceInformer informers.PipelineResourceInformer,
) *controller.Impl {

	c := &Reconciler{
		Base:           reconciler.NewBase(opt, taskRunAgentName),
		taskRunLister:  taskRunInformer.Lister(),
		taskLister:     taskInformer.Lister(),
		buildLister:    buildInformer.Lister(),
		resourceLister: resourceInformer.Lister(),
	}
	impl := controller.NewImpl(c, c.Logger, taskRunControllerName)

	c.Logger.Info("Setting up event handlers")
	taskRunInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    impl.Enqueue,
		UpdateFunc: controller.PassNew(impl.Enqueue),
	})

	// TODO(aaron-prindle) what to do if a task is deleted?
	// taskInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
	// 	AddFunc:    impl.Enqueue,
	// 	UpdateFunc: controller.PassNew(impl.Enqueue),
	// 	DeleteFunc: impl.Enqueue,
	// })

	c.tracker = tracker.New(impl.EnqueueKey, opt.GetTrackerLease())
	buildInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.tracker.OnChanged,
		UpdateFunc: controller.PassNew(c.tracker.OnChanged),
	})

	return impl
}

// Reconcile compares the actual state with the desired, and attempts to
// converge the two. It then updates the Status block of the Task Run
// resource with the current status of the resource.
func (c *Reconciler) Reconcile(ctx context.Context, key string) error {
	// Convert the namespace/name string into a distinct namespace and name
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		c.Logger.Errorf("invalid resource key: %s", key)
		return nil
	}

	// Get the Task Run resource with this namespace/name
	original, err := c.taskRunLister.TaskRuns(namespace).Get(name)
	if errors.IsNotFound(err) {
		// The resource no longer exists, in which case we stop processing.
		c.Logger.Errorf("task run %q in work queue no longer exists", key)
		return nil
	} else if err != nil {
		return err
	}

	// Don't modify the informer's copy.
	tr := original.DeepCopy()
	// Reconcile this copy of the task run and then write back any status
	// updates regardless of whether the reconciliation errored out.
	err = c.reconcile(ctx, tr)
	if err != nil {
		c.Logger.Errorf("Reconcile error: %v", err.Error())
	}
	if equality.Semantic.DeepEqual(original.Status, tr.Status) {
		// If we didn't change anything then don't call updateStatus.
		// This is important because the copy we loaded from the informer's
		// cache may be stale and we don't want to overwrite a prior update
		// to status with this stale state.
	} else if _, err := c.updateStatus(tr); err != nil {
		c.Logger.Warn("Failed to update taskRun status", zap.Error(err))
		return err
	}
	return err
}

func (c *Reconciler) reconcile(ctx context.Context, tr *v1alpha1.TaskRun) error {
	// get build the same as the taskrun, this is the value we use for 1:1 mapping and retrieval
	//build, err := c.buildLister.Builds(tr.Namespace).Get(tr.Name)
	build, err := c.buildLister.Builds(tr.Namespace).Get(tr.Name)
	if errors.IsNotFound(err) {
		// Build is not present, create build
		build, err = c.createBuild(tr)
		if err != nil {
			c.Logger.Errorf("Failed to create build for task %q :%v", err, tr.Name)
			return err
		}
	} else if err != nil {
		c.Logger.Errorf("Failed to reconcile taskrun: %q, failed to get build %q; %v", tr.Name, tr.Name, err)
		return err
	}

	// TODO(aaron-prindle) verify that build does not have to exist to create a tracker
	if err := c.tracker.Track(tr.GetBuildRef(), tr); err != nil {
		c.Logger.Errorf("Failed to create tracker for build %q for taskrun %q: %v", tr.Name, tr.Name, err)
		return err
	}

	// switch ownerref := metav1.GetControllerOf(b); {
	// case ownerref == nil, ownerref.APIVersion != groupVersionKind.GroupVersion().String(), ownerref.Kind != groupVersionKind.Kind:
	// 	logger.Infof("build %s not controlled by taskrun controller", b.Name)
	// 	return nil
	// }

	// sync build status with taskrun status
	tr.Status.SetCondition(build.Status.GetCondition(duckv1alpha1.ConditionSucceeded))

	c.Logger.Infof("Successfully reconciled taskrun %s/%s", tr.Name, tr.Namespace)

	return nil
}

func (c *Reconciler) updateStatus(taskrun *v1alpha1.TaskRun) (*v1alpha1.TaskRun, error) {
	newtaskrun, err := c.taskRunLister.TaskRuns(taskrun.Namespace).Get(taskrun.Name)
	if err != nil {
		return nil, err
	}
	if !reflect.DeepEqual(taskrun.Status, newtaskrun.Status) {
		newtaskrun.Status = taskrun.Status
		return c.PipelineClientSet.PipelineV1alpha1().TaskRuns(taskrun.Namespace).Update(newtaskrun)
	}
	return newtaskrun, nil
}

// createBuild creates a build from the task, using the task's buildspec.
func (c *Reconciler) createBuild(tr *v1alpha1.TaskRun) (*buildv1alpha1.Build, error) {
	// Get related task for taskrun
	t, err := c.taskLister.Tasks(tr.Namespace).Get(tr.Spec.TaskRef.Name)
	if err != nil {
		return nil, fmt.Errorf("Error when listing tasks %v", err)
	}

	// TODO: Preferably use Validate on task.spec to catch validation error
	if t.Spec.BuildSpec == nil {
		return nil, fmt.Errorf("Task has nil BuildSpec")
	}

	b := &buildv1alpha1.Build{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tr.Name,
			Namespace: tr.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(tr, groupVersionKind),
			},
			// Attach new label and pass taskrun labels to build
			Labels: makeLabels(tr),
		},
		Spec: *t.Spec.BuildSpec,
	}

	build, err := resources.AddInputResource(b, t, tr, c.resourceLister, c.Logger)
	if err != nil {
		c.Logger.Errorf("Failed to create a build for taskrun: %s due to input resource error %v", tr.Name, err)
		return nil, err
	}

	// Apply parameters from the taskrun.
	build = applyParameters(b, t, tr)

	return c.BuildClientSet.BuildV1alpha1().Builds(tr.Namespace).Create(build)
}

// makeLabels constructs the labels we will apply to TaskRun resources.
func makeLabels(s *v1alpha1.TaskRun) map[string]string {
	labels := make(map[string]string, len(s.ObjectMeta.Labels)+1)
	labels[taskRunNameLabelKey] = s.Name
	for k, v := range s.ObjectMeta.Labels {
		labels[k] = v
	}
	return labels

}
func applyParameters(b *buildv1alpha1.Build, t *v1alpha1.Task, tr *v1alpha1.TaskRun) *buildv1alpha1.Build {
	// This assumes that the TaskRun inputs have been validated against what the Task requests.
	replacements := map[string]string{}
	for _, p := range tr.Spec.Inputs.Params {
		replacements[fmt.Sprintf("inputs.params.%s", p.Name)] = p.Value
	}

	return builder.ApplyReplacements(b, replacements)
}
<commit_msg>Address comments<commit_after>/*
Copyright 2018 The Knative Authors

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

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

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

package taskrun

import (
	"context"
	"fmt"
	"reflect"

	buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
	"github.com/knative/build/pkg/builder"
	buildinformers "github.com/knative/build/pkg/client/informers/externalversions/build/v1alpha1"
	buildlisters "github.com/knative/build/pkg/client/listers/build/v1alpha1"
	duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/tracker"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/tools/cache"

	"github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1"
	informers "github.com/knative/build-pipeline/pkg/client/informers/externalversions/pipeline/v1alpha1"
	listers "github.com/knative/build-pipeline/pkg/client/listers/pipeline/v1alpha1"
	"github.com/knative/build-pipeline/pkg/reconciler"
	resources "github.com/knative/build-pipeline/pkg/reconciler/v1alpha1/taskrun/resources"
)

const (
	// taskRunAgentName defines logging agent name for TaskRun Controller
	taskRunAgentName = "taskrun-controller"
	// taskRunControllerName defines name for TaskRun Controller
	taskRunControllerName = "TaskRun"
	taskRunNameLabelKey   = "taskrun.knative.dev/taskName"
)

var (
	groupVersionKind = schema.GroupVersionKind{
		Group:   v1alpha1.SchemeGroupVersion.Group,
		Version: v1alpha1.SchemeGroupVersion.Version,
		Kind:    "TaskRun",
	}
)

// Reconciler implements controller.Reconciler for Configuration resources.
type Reconciler struct {
	*reconciler.Base

	// listers index properties about resources
	taskRunLister  listers.TaskRunLister
	taskLister     listers.TaskLister
	buildLister    buildlisters.BuildLister
	resourceLister listers.PipelineResourceLister
	tracker        tracker.Interface
}

// Check that our Reconciler implements controller.Reconciler
var _ controller.Reconciler = (*Reconciler)(nil)

// NewController creates a new Configuration controller
func NewController(
	opt reconciler.Options,
	taskRunInformer informers.TaskRunInformer,
	taskInformer informers.TaskInformer,
	buildInformer buildinformers.BuildInformer,
	resourceInformer informers.PipelineResourceInformer,
) *controller.Impl {

	c := &Reconciler{
		Base:           reconciler.NewBase(opt, taskRunAgentName),
		taskRunLister:  taskRunInformer.Lister(),
		taskLister:     taskInformer.Lister(),
		buildLister:    buildInformer.Lister(),
		resourceLister: resourceInformer.Lister(),
	}
	impl := controller.NewImpl(c, c.Logger, taskRunControllerName)

	c.Logger.Info("Setting up event handlers")
	taskRunInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    impl.Enqueue,
		UpdateFunc: controller.PassNew(impl.Enqueue),
	})

	// TODO(aaron-prindle) what to do if a task is deleted?
	// taskInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
	// 	AddFunc:    impl.Enqueue,
	// 	UpdateFunc: controller.PassNew(impl.Enqueue),
	// 	DeleteFunc: impl.Enqueue,
	// })

	c.tracker = tracker.New(impl.EnqueueKey, opt.GetTrackerLease())
	buildInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.tracker.OnChanged,
		UpdateFunc: controller.PassNew(c.tracker.OnChanged),
	})

	return impl
}

// Reconcile compares the actual state with the desired, and attempts to
// converge the two. It then updates the Status block of the Task Run
// resource with the current status of the resource.
func (c *Reconciler) Reconcile(ctx context.Context, key string) error {
	// Convert the namespace/name string into a distinct namespace and name
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		c.Logger.Errorf("invalid resource key: %s", key)
		return nil
	}

	// Get the Task Run resource with this namespace/name
	original, err := c.taskRunLister.TaskRuns(namespace).Get(name)
	if errors.IsNotFound(err) {
		// The resource no longer exists, in which case we stop processing.
		c.Logger.Errorf("task run %q in work queue no longer exists", key)
		return nil
	} else if err != nil {
		return err
	}

	// Don't modify the informer's copy.
	tr := original.DeepCopy()
	// Reconcile this copy of the task run and then write back any status
	// updates regardless of whether the reconciliation errored out.
	err = c.reconcile(ctx, tr)
	if err != nil {
		c.Logger.Errorf("Reconcile error: %v", err.Error())
	}
	if equality.Semantic.DeepEqual(original.Status, tr.Status) {
		// If we didn't change anything then don't call updateStatus.
		// This is important because the copy we loaded from the informer's
		// cache may be stale and we don't want to overwrite a prior update
		// to status with this stale state.
	} else if _, err := c.updateStatus(tr); err != nil {
		c.Logger.Warn("Failed to update taskRun status", zap.Error(err))
		return err
	}
	return err
}

func (c *Reconciler) reconcile(ctx context.Context, tr *v1alpha1.TaskRun) error {
	// get build the same as the taskrun, this is the value we use for 1:1 mapping and retrieval
	build, err := c.buildLister.Builds(tr.Namespace).Get(tr.Name)
	if errors.IsNotFound(err) {
		// Build is not present, create build
		build, err = c.createBuild(tr)
		if err != nil {
			c.Logger.Errorf("Failed to create build for task %q :%v", err, tr.Name)
			return err
		}
	} else if err != nil {
		c.Logger.Errorf("Failed to reconcile taskrun: %q, failed to get build %q; %v", tr.Name, tr.Name, err)
		return err
	}

	if err := c.tracker.Track(tr.GetBuildRef(), tr); err != nil {
		c.Logger.Errorf("Failed to create tracker for build %q for taskrun %q: %v", tr.Name, tr.Name, err)
		return err
	}

	// switch ownerref := metav1.GetControllerOf(b); {
	// case ownerref == nil, ownerref.APIVersion != groupVersionKind.GroupVersion().String(), ownerref.Kind != groupVersionKind.Kind:
	// 	logger.Infof("build %s not controlled by taskrun controller", b.Name)
	// 	return nil
	// }

	// sync build status with taskrun status
	tr.Status.SetCondition(build.Status.GetCondition(duckv1alpha1.ConditionSucceeded))

	c.Logger.Infof("Successfully reconciled taskrun %s/%s", tr.Name, tr.Namespace)

	return nil
}

func (c *Reconciler) updateStatus(taskrun *v1alpha1.TaskRun) (*v1alpha1.TaskRun, error) {
	newtaskrun, err := c.taskRunLister.TaskRuns(taskrun.Namespace).Get(taskrun.Name)
	if err != nil {
		return nil, err
	}
	if !reflect.DeepEqual(taskrun.Status, newtaskrun.Status) {
		newtaskrun.Status = taskrun.Status
		return c.PipelineClientSet.PipelineV1alpha1().TaskRuns(taskrun.Namespace).Update(newtaskrun)
	}
	return newtaskrun, nil
}

// createBuild creates a build from the task, using the task's buildspec.
func (c *Reconciler) createBuild(tr *v1alpha1.TaskRun) (*buildv1alpha1.Build, error) {
	// Get related task for taskrun
	t, err := c.taskLister.Tasks(tr.Namespace).Get(tr.Spec.TaskRef.Name)
	if err != nil {
		return nil, fmt.Errorf("Error when listing tasks %v", err)
	}

	// TODO: Preferably use Validate on task.spec to catch validation error
	if t.Spec.BuildSpec == nil {
		return nil, fmt.Errorf("Task has nil BuildSpec")
	}

	b := &buildv1alpha1.Build{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tr.Name,
			Namespace: tr.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(tr, groupVersionKind),
			},
			// Attach new label and pass taskrun labels to build
			Labels: makeLabels(tr),
		},
		Spec: *t.Spec.BuildSpec,
	}

	build, err := resources.AddInputResource(b, t, tr, c.resourceLister, c.Logger)
	if err != nil {
		c.Logger.Errorf("Failed to create a build for taskrun: %s due to input resource error %v", tr.Name, err)
		return nil, err
	}

	// Apply parameters from the taskrun.
	build = applyParameters(b, t, tr)

	return c.BuildClientSet.BuildV1alpha1().Builds(tr.Namespace).Create(build)
}

// makeLabels constructs the labels we will apply to TaskRun resources.
func makeLabels(s *v1alpha1.TaskRun) map[string]string {
	labels := make(map[string]string, len(s.ObjectMeta.Labels)+1)
	labels[taskRunNameLabelKey] = s.Name
	for k, v := range s.ObjectMeta.Labels {
		labels[k] = v
	}
	return labels

}
func applyParameters(b *buildv1alpha1.Build, t *v1alpha1.Task, tr *v1alpha1.TaskRun) *buildv1alpha1.Build {
	// This assumes that the TaskRun inputs have been validated against what the Task requests.
	replacements := map[string]string{}
	for _, p := range tr.Spec.Inputs.Params {
		replacements[fmt.Sprintf("inputs.params.%s", p.Name)] = p.Value
	}

	return builder.ApplyReplacements(b, replacements)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package testing

import (
	schedulerapi "k8s.io/kubernetes/pkg/scheduler/apis/config"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
)

// NewFramework creates a Framework from the register functions and options.
func NewFramework(fns []RegisterPluginFunc, opts ...runtime.Option) (framework.Framework, error) {
	registry := runtime.Registry{}
	plugins := &schedulerapi.Plugins{}
	var pluginConfigs []schedulerapi.PluginConfig
	for _, f := range fns {
		f(&registry, plugins, pluginConfigs)
	}
	return runtime.NewFramework(registry, plugins, pluginConfigs, opts...)
}

// RegisterPluginFunc is a function signature used in method RegisterFilterPlugin()
// to register a Filter Plugin to a given registry.
type RegisterPluginFunc func(reg *runtime.Registry, plugins *schedulerapi.Plugins, pluginConfigs []schedulerapi.PluginConfig)

// RegisterQueueSortPlugin returns a function to register a QueueSort Plugin to a given registry.
func RegisterQueueSortPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "QueueSort")
}

// RegisterPreFilterPlugin returns a function to register a PreFilter Plugin to a given registry.
func RegisterPreFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreFilter")
}

// RegisterFilterPlugin returns a function to register a Filter Plugin to a given registry.
func RegisterFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Filter")
}

// RegisterReservePlugin returns a function to register a Reserve Plugin to a given registry.
func RegisterReservePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Reserve")
}

// RegisterPermitPlugin returns a function to register a Permit Plugin to a given registry.
func RegisterPermitPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Permit")
}

// RegisterPreBindPlugin returns a function to register a PreBind Plugin to a given registry.
func RegisterPreBindPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreBind")
}

// RegisterScorePlugin returns a function to register a Score Plugin to a given registry.
func RegisterScorePlugin(pluginName string, pluginNewFunc runtime.PluginFactory, weight int32) RegisterPluginFunc {
	return RegisterPluginAsExtensionsWithWeight(pluginName, weight, pluginNewFunc, "Score")
}

// RegisterPreScorePlugin returns a function to register a Score Plugin to a given registry.
func RegisterPreScorePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreScore")
}

// RegisterBindPlugin returns a function to register a Bind Plugin to a given registry.
func RegisterBindPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Bind")
}

// RegisterPluginAsExtensions returns a function to register a Plugin as given extensionPoints to a given registry.
func RegisterPluginAsExtensions(pluginName string, pluginNewFunc runtime.PluginFactory, extensions ...string) RegisterPluginFunc {
	return RegisterPluginAsExtensionsWithWeight(pluginName, 1, pluginNewFunc, extensions...)
}

// RegisterPluginAsExtensionsWithWeight returns a function to register a Plugin as given extensionPoints with weight to a given registry.
func RegisterPluginAsExtensionsWithWeight(pluginName string, weight int32, pluginNewFunc runtime.PluginFactory, extensions ...string) RegisterPluginFunc {
	return func(reg *runtime.Registry, plugins *schedulerapi.Plugins, pluginConfigs []schedulerapi.PluginConfig) {
		reg.Register(pluginName, pluginNewFunc)
		for _, extension := range extensions {
			ps := getPluginSetByExtension(plugins, extension)
			if ps == nil {
				continue
			}
			ps.Enabled = append(ps.Enabled, schedulerapi.Plugin{Name: pluginName, Weight: weight})
		}
		//lint:ignore SA4006 this value of pluginConfigs is never used.
		//lint:ignore SA4010 this result of append is never used.
		pluginConfigs = append(pluginConfigs, schedulerapi.PluginConfig{Name: pluginName})
	}
}

func getPluginSetByExtension(plugins *schedulerapi.Plugins, extension string) *schedulerapi.PluginSet {
	switch extension {
	case "QueueSort":
		return &plugins.QueueSort
	case "Filter":
		return &plugins.Filter
	case "PreFilter":
		return &plugins.PreFilter
	case "PreScore":
		return &plugins.PreScore
	case "Score":
		return &plugins.Score
	case "Bind":
		return &plugins.Bind
	case "Reserve":
		return &plugins.Reserve
	case "Permit":
		return &plugins.Permit
	case "PreBind":
		return &plugins.PreBind
	case "PostBind":
		return &plugins.PostBind
	default:
		return nil
	}
}
<commit_msg>remove useless pluginConfigs<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package testing

import (
	schedulerapi "k8s.io/kubernetes/pkg/scheduler/apis/config"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
)

// NewFramework creates a Framework from the register functions and options.
func NewFramework(fns []RegisterPluginFunc, opts ...runtime.Option) (framework.Framework, error) {
	registry := runtime.Registry{}
	plugins := &schedulerapi.Plugins{}
	for _, f := range fns {
		f(&registry, plugins)
	}
	return runtime.NewFramework(registry, plugins, nil, opts...)
}

// RegisterPluginFunc is a function signature used in method RegisterFilterPlugin()
// to register a Filter Plugin to a given registry.
type RegisterPluginFunc func(reg *runtime.Registry, plugins *schedulerapi.Plugins)

// RegisterQueueSortPlugin returns a function to register a QueueSort Plugin to a given registry.
func RegisterQueueSortPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "QueueSort")
}

// RegisterPreFilterPlugin returns a function to register a PreFilter Plugin to a given registry.
func RegisterPreFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreFilter")
}

// RegisterFilterPlugin returns a function to register a Filter Plugin to a given registry.
func RegisterFilterPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Filter")
}

// RegisterReservePlugin returns a function to register a Reserve Plugin to a given registry.
func RegisterReservePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Reserve")
}

// RegisterPermitPlugin returns a function to register a Permit Plugin to a given registry.
func RegisterPermitPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Permit")
}

// RegisterPreBindPlugin returns a function to register a PreBind Plugin to a given registry.
func RegisterPreBindPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreBind")
}

// RegisterScorePlugin returns a function to register a Score Plugin to a given registry.
func RegisterScorePlugin(pluginName string, pluginNewFunc runtime.PluginFactory, weight int32) RegisterPluginFunc {
	return RegisterPluginAsExtensionsWithWeight(pluginName, weight, pluginNewFunc, "Score")
}

// RegisterPreScorePlugin returns a function to register a Score Plugin to a given registry.
func RegisterPreScorePlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "PreScore")
}

// RegisterBindPlugin returns a function to register a Bind Plugin to a given registry.
func RegisterBindPlugin(pluginName string, pluginNewFunc runtime.PluginFactory) RegisterPluginFunc {
	return RegisterPluginAsExtensions(pluginName, pluginNewFunc, "Bind")
}

// RegisterPluginAsExtensions returns a function to register a Plugin as given extensionPoints to a given registry.
func RegisterPluginAsExtensions(pluginName string, pluginNewFunc runtime.PluginFactory, extensions ...string) RegisterPluginFunc {
	return RegisterPluginAsExtensionsWithWeight(pluginName, 1, pluginNewFunc, extensions...)
}

// RegisterPluginAsExtensionsWithWeight returns a function to register a Plugin as given extensionPoints with weight to a given registry.
func RegisterPluginAsExtensionsWithWeight(pluginName string, weight int32, pluginNewFunc runtime.PluginFactory, extensions ...string) RegisterPluginFunc {
	return func(reg *runtime.Registry, plugins *schedulerapi.Plugins) {
		reg.Register(pluginName, pluginNewFunc)
		for _, extension := range extensions {
			ps := getPluginSetByExtension(plugins, extension)
			if ps == nil {
				continue
			}
			ps.Enabled = append(ps.Enabled, schedulerapi.Plugin{Name: pluginName, Weight: weight})
		}
	}
}

func getPluginSetByExtension(plugins *schedulerapi.Plugins, extension string) *schedulerapi.PluginSet {
	switch extension {
	case "QueueSort":
		return &plugins.QueueSort
	case "Filter":
		return &plugins.Filter
	case "PreFilter":
		return &plugins.PreFilter
	case "PreScore":
		return &plugins.PreScore
	case "Score":
		return &plugins.Score
	case "Bind":
		return &plugins.Bind
	case "Reserve":
		return &plugins.Reserve
	case "Permit":
		return &plugins.Permit
	case "PreBind":
		return &plugins.PreBind
	case "PostBind":
		return &plugins.PostBind
	default:
		return nil
	}
}
<|endoftext|>