<commit_before>package adapters

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/gosimple/slug"

	"mirovarga.com/litepub/domain"
)

const (
	postsDir = "posts"
	draftDir = "draft"
)

// FSBlogRepository stores Blogs on the file system.
//
// Blogs are stored as subdirectories of the root directory and are named after
// the IDs of the Blogs they contain. Posts are stored as Markdown files in the
// posts subdirectory of their respective Blog directory. And finally, draft
// Posts (ones with Draft set to true) are stored in the draft subdirectory of
// the respective posts directory.
//
// So the structure looks like this:
//
// root directory, for example blogs/
// 	 blog1/
//     posts/
//       draft/
//         draft1.md
//         ...
//       post1.md
//       post2.md
//       ...
//   blog2/
//     posts/
//       draft/
//         draft1.md
//         ...
//       post1.md
//       post2.md
//       ...
//   ...
//
// The Markdown file has the following format:
//
// # Title
//
// *Jan 2, 2009*
//
// *tag1, tag2, ...*
//
// Content
type FSBlogRepository struct {
	dir string
}

// Store adds or updates a Blog in the repository. It creates all necessary
// directories if needed.
func (r FSBlogRepository) Store(blog domain.Blog) error {
	os.MkdirAll(filepath.Join(r.dir, blog.ID, postsDir, draftDir), 0700)

	for _, post := range blog.Posts {
		err := r.writePost(blog.ID, post)
		if err != nil {
			return fmt.Errorf("Failed to store post: %s", err)
		}
	}

	return nil
}

// FindByID gets a Blog with the ID from the repository.
//
// If the Blog isn't in the repository it returns an error.
func (r FSBlogRepository) FindByID(id string) (domain.Blog, error) {
	blogDir := filepath.Join(r.dir, id)
	if _, err := os.Stat(blogDir); err != nil {
		return domain.Blog{}, fmt.Errorf("Blog not found: %s", id)
	}

	postsPath := filepath.Join(blogDir, postsDir)
	posts, err := r.readPosts(postsPath)
	if err != nil {
		return domain.Blog{}, err
	}

	draftsPath := filepath.Join(postsPath, draftDir)
	drafts, err := r.readPosts(draftsPath)
	if err != nil {
		return domain.Blog{}, err
	}

	blog := domain.Blog{ID: id}
	blog.Posts = posts
	for _, draft := range drafts {
		draft.Draft = true
		blog.Posts = append(blog.Posts, draft)
	}

	return blog, nil
}

func (r FSBlogRepository) writePost(id string, post domain.Post) error {
	path := filepath.Join(r.dir, id, postsDir)
	if post.Draft {
		path = filepath.Join(path, draftDir)
	}
	path = filepath.Join(path, slug.Make(post.Title)+".md")

	var data string
	if len(post.Tags) == 0 {
		data = fmt.Sprintf("# %s\n\n*%s*\n\n%s\n", post.Title,
			post.Written.Format("Jan 2, 2006"), post.Content)
	} else {
		data = fmt.Sprintf("# %s\n\n*%s*\n\n*%s*\n\n%s\n", post.Title,
			post.Written.Format("Jan 2, 2006"), strings.Join(post.Tags, ", "),
			post.Content)
	}

	return ioutil.WriteFile(path, []byte(data), 0600)
}

func (r FSBlogRepository) readPosts(dir string) ([]domain.Post, error) {
	postFiles, err := ioutil.ReadDir(dir)
	if err != nil {
		return []domain.Post{}, fmt.Errorf("Failed to read posts: %s", err)
	}

	var posts []domain.Post
	for _, postFile := range postFiles {
		if postFile.IsDir() {
			continue
		}

		post, err := r.readPost(filepath.Join(dir, postFile.Name()))
		if err != nil {
			return []domain.Post{}, err
		}
		posts = append(posts, post)
	}
	return posts, nil
}

func (r FSBlogRepository) readPost(path string) (domain.Post, error) {
	markdown, err := ioutil.ReadFile(path)
	if err != nil {
		return domain.Post{}, fmt.Errorf("Failed to read post: %s", err)
	}

	paras := strings.Split(string(markdown), "\n\n")
	if len(paras) < 3 {
		return domain.Post{}, fmt.Errorf("Title, date or content is missing: %s", path)
	}

	title := strings.TrimSpace(strings.Replace(paras[0], "#", "", -1))

	written, err := time.Parse("*Jan 2, 2006*", paras[1])
	if err != nil {
		return domain.Post{}, fmt.Errorf("Failed to parse date: %s", err)
	}

	var tags []string
	if strings.HasPrefix(paras[2], "*") && !strings.Contains(paras[2], "\n") {
		tags = strings.Split(paras[2], ",")
		for i, tag := range tags {
			tags[i] = strings.TrimSpace(strings.Replace(tag, "*", "", -1))
		}
	}

	var content string
	if len(tags) == 0 {
		content = strings.Join(paras[2:], "\n\n")
	} else {
		content = strings.Join(paras[3:], "\n\n")
	}

	return domain.Post{title, content, written, tags, false}, nil
}

// NewFSBlogRepository creates a FSBlogRepository in the provided directory. If
// the directory doesn't exist it creates it.
func NewFSBlogRepository(dir string) domain.BlogRepository {
	if _, err := os.Stat(dir); err != nil {
		os.MkdirAll(dir, 0700)
	}
	return FSBlogRepository{dir}
}
<commit_msg>Ignore post files starting with a dot<commit_after>package adapters

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/gosimple/slug"

	"mirovarga.com/litepub/domain"
)

const (
	postsDir = "posts"
	draftDir = "draft"
)

// FSBlogRepository stores Blogs on the file system.
//
// Blogs are stored as subdirectories of the root directory and are named after
// the IDs of the Blogs they contain. Posts are stored as Markdown files in the
// posts subdirectory of their respective Blog directory. And finally, draft
// Posts (ones with Draft set to true) are stored in the draft subdirectory of
// the respective posts directory.
//
// So the structure looks like this:
//
// root directory, for example blogs/
// 	 blog1/
//     posts/
//       draft/
//         draft1.md
//         ...
//       post1.md
//       post2.md
//       ...
//   blog2/
//     posts/
//       draft/
//         draft1.md
//         ...
//       post1.md
//       post2.md
//       ...
//   ...
//
// The Markdown file has the following format:
//
// # Title
//
// *Jan 2, 2009*
//
// *tag1, tag2, ...*
//
// Content
type FSBlogRepository struct {
	dir string
}

// Store adds or updates a Blog in the repository. It creates all necessary
// directories if needed.
func (r FSBlogRepository) Store(blog domain.Blog) error {
	os.MkdirAll(filepath.Join(r.dir, blog.ID, postsDir, draftDir), 0700)

	for _, post := range blog.Posts {
		err := r.writePost(blog.ID, post)
		if err != nil {
			return fmt.Errorf("Failed to store post: %s", err)
		}
	}

	return nil
}

// FindByID gets a Blog with the ID from the repository.
//
// If the Blog isn't in the repository it returns an error.
func (r FSBlogRepository) FindByID(id string) (domain.Blog, error) {
	blogDir := filepath.Join(r.dir, id)
	if _, err := os.Stat(blogDir); err != nil {
		return domain.Blog{}, fmt.Errorf("Blog not found: %s", id)
	}

	postsPath := filepath.Join(blogDir, postsDir)
	posts, err := r.readPosts(postsPath)
	if err != nil {
		return domain.Blog{}, err
	}

	draftsPath := filepath.Join(postsPath, draftDir)
	drafts, err := r.readPosts(draftsPath)
	if err != nil {
		return domain.Blog{}, err
	}

	blog := domain.Blog{ID: id}
	blog.Posts = posts
	for _, draft := range drafts {
		draft.Draft = true
		blog.Posts = append(blog.Posts, draft)
	}

	return blog, nil
}

func (r FSBlogRepository) writePost(id string, post domain.Post) error {
	path := filepath.Join(r.dir, id, postsDir)
	if post.Draft {
		path = filepath.Join(path, draftDir)
	}
	path = filepath.Join(path, slug.Make(post.Title)+".md")

	var data string
	if len(post.Tags) == 0 {
		data = fmt.Sprintf("# %s\n\n*%s*\n\n%s\n", post.Title,
			post.Written.Format("Jan 2, 2006"), post.Content)
	} else {
		data = fmt.Sprintf("# %s\n\n*%s*\n\n*%s*\n\n%s\n", post.Title,
			post.Written.Format("Jan 2, 2006"), strings.Join(post.Tags, ", "),
			post.Content)
	}

	return ioutil.WriteFile(path, []byte(data), 0600)
}

func (r FSBlogRepository) readPosts(dir string) ([]domain.Post, error) {
	postFiles, err := ioutil.ReadDir(dir)
	if err != nil {
		return []domain.Post{}, fmt.Errorf("Failed to read posts: %s", err)
	}

	var posts []domain.Post
	for _, postFile := range postFiles {
		if postFile.IsDir() || strings.HasPrefix(postFile.Name(),".") {
			continue
		}

		post, err := r.readPost(filepath.Join(dir, postFile.Name()))
		if err != nil {
			return []domain.Post{}, err
		}
		posts = append(posts, post)
	}
	return posts, nil
}

func (r FSBlogRepository) readPost(path string) (domain.Post, error) {
	markdown, err := ioutil.ReadFile(path)
	if err != nil {
		return domain.Post{}, fmt.Errorf("Failed to read post: %s", err)
	}

	paras := strings.Split(string(markdown), "\n\n")
	if len(paras) < 3 {
		return domain.Post{}, fmt.Errorf("Title, date or content is missing: %s", path)
	}

	title := strings.TrimSpace(strings.Replace(paras[0], "#", "", -1))

	written, err := time.Parse("*Jan 2, 2006*", paras[1])
	if err != nil {
		return domain.Post{}, fmt.Errorf("Failed to parse date: %s", err)
	}

	var tags []string
	if strings.HasPrefix(paras[2], "*") && !strings.Contains(paras[2], "\n") {
		tags = strings.Split(paras[2], ",")
		for i, tag := range tags {
			tags[i] = strings.TrimSpace(strings.Replace(tag, "*", "", -1))
		}
	}

	var content string
	if len(tags) == 0 {
		content = strings.Join(paras[2:], "\n\n")
	} else {
		content = strings.Join(paras[3:], "\n\n")
	}

	return domain.Post{title, content, written, tags, false}, nil
}

// NewFSBlogRepository creates a FSBlogRepository in the provided directory. If
// the directory doesn't exist it creates it.
func NewFSBlogRepository(dir string) domain.BlogRepository {
	if _, err := os.Stat(dir); err != nil {
		os.MkdirAll(dir, 0700)
	}
	return FSBlogRepository{dir}
}
<|endoftext|>


<commit_before>package cloudpelican

// @author Robin Verlangen
// @todo Support bulk index requests
// Tool for logging data to CloudPelican directly from Go

// Imports
import (
    "net"
    "net/http"
    "net/url"
    "log"
    "sync"
    "time"
    "strconv"
)

// Settings
var ENDPOINT string = "https://app.cloudpelican.com/api"
var TOKEN string = ""
var backendTimeout = time.Duration(5 * time.Second)
var debugMode bool = false
var maxBulkSize int = 100

// Monitor drain status
var startCounter uint64 = uint64(0)
var startCounterMux sync.Mutex
var doneCounter uint64 = uint64(0)
var doneCounterMux sync.Mutex
var isDraining bool = false
var drained = make(chan bool);

// Log queue
var writeAheadBufferSize int = 1000
var writeAhead chan map[string]string = make(chan map[string]string, writeAheadBufferSize)
var writeAheadInit bool
var dropOnFullWriteAheadBuffer bool = true

// Set token
func SetToken(t string) {
    // Validate before setting
    validateToken(t)

    // Store
    TOKEN = t
}

// Set endpoint
func SetEndpoint(e string) {
    // Store
    ENDPOINT = e
}

// Set timeout
func SetBackendTimeout(to time.Duration) {
    backendTimeout = to
}

// Debug
func SetDebugMode(b bool) {
    debugMode = b
}

// Drain
func Drain() {
    isDraining = true
    if startCounter > doneCounter {
        // Wait for signal
        <- drained
    }
}

// Write a message
func LogMessage(msg string) bool {
    // Create fields map
    params := make(map[string]string)
    params["__token__"] = TOKEN
    params["msg"] = msg

    // Push to channel
    return requestAsync(params)
}

// Request async
func requestAsync(params map[string]string) bool {
    // Check amount of open items in the channel, if the channel is full, return false and drop this message
    if dropOnFullWriteAheadBuffer {
        var lwa int = len(writeAhead)
        if lwa == writeAheadBufferSize {
            log.Printf("Write ahead buffer is full and contains %d items. Dropping current log message", lwa)
        }
    }

    // Add counter
    startCounterMux.Lock()
    startCounter++
    startCounterMux.Unlock()

    // Do we have to start a writer?
    if writeAheadInit == false {
        writeAheadInit = true
        backendWriter()
    }

    // Insert into channel
    writeAhead <- params

    // OK
    return true
}

// Backend writer
func backendWriter() {
    go func() {
        // Client
        transport := &http.Transport{
            Dial: func(netw, addr string) (net.Conn, error) {
                    deadline := time.Now().Add(backendTimeout)
                    c, err := net.DialTimeout(netw, addr, time.Second)
                    if err != nil {
                            return nil, err
                    }
                    c.SetDeadline(deadline)
                    return c, nil
            }}
        httpclient := &http.Client{Transport: transport}

        // Wait for messages
        var urlParams url.Values
        var currentEventCount int = 0
        for {
            // Read from channel
            var fields map[string]string
            fields = <- writeAhead

            // Populate url params
            if currentEventCount == 0 {
                urlParams = url.Values{}
            }
            for k, _ := range fields {
                if k == "__token__" {
                    // Token
                    urlParams.Add("t", fields[k]);
                } else {
                    // Field
                    urlParams.Add("f[" + strconv.Itoa(currentEventCount) + "][" + k + "]", fields[k]);
                }
            }

            // Increase current count
            currentEventCount++

            // Queue length
            var qLen = len(writeAhead)
            if qLen > 0 && currentEventCount < maxBulkSize {
                // There is more in the current queue, bulk request
                continue
            }

            // Assemble url
            var url string = ENDPOINT + "/push/bulk"

            // Make request
            if debugMode {
                log.Printf("Write ahead queue %d\n", qLen)
                log.Println(urlParams.Encode())
            }
            resp, err := httpclient.PostForm(url, urlParams)
            if err != nil {
                log.Printf("Error while forwarding data: %s\n", err)
            } else {
                defer resp.Body.Close()
            }

            // Done counter
            doneCounterMux.Lock()
            doneCounter++
            doneCounterMux.Unlock()

            // Reset event count
            currentEventCount = 0

            // Are we draining the system?
            if isDraining && doneCounter >= startCounter {
                // Flag the drained channel
                drained <- true
            }
        }
        log.Printf("Stopping backend writer")
    }()
}

// Timeout helper
func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, backendTimeout)
}

// Validate the token
func validateToken(t string) {
    if len(t) == 0 {
        log.Println("Please set a valid token with cloudpelican.SetToken(token string)")
    }
}<commit_msg>Fix done counter<commit_after>package cloudpelican

// @author Robin Verlangen
// @todo Support bulk index requests
// Tool for logging data to CloudPelican directly from Go

// Imports
import (
    "net"
    "net/http"
    "net/url"
    "log"
    "sync"
    "time"
    "strconv"
)

// Settings
var ENDPOINT string = "https://app.cloudpelican.com/api"
var TOKEN string = ""
var backendTimeout = time.Duration(5 * time.Second)
var debugMode bool = false
var maxBulkSize int = 100

// Monitor drain status
var startCounter uint64 = uint64(0)
var startCounterMux sync.Mutex
var doneCounter uint64 = uint64(0)
var doneCounterMux sync.Mutex
var isDraining bool = false
var drained = make(chan bool);

// Log queue
var writeAheadBufferSize int = 1000
var writeAhead chan map[string]string = make(chan map[string]string, writeAheadBufferSize)
var writeAheadInit bool
var dropOnFullWriteAheadBuffer bool = true

// Set token
func SetToken(t string) {
    // Validate before setting
    validateToken(t)

    // Store
    TOKEN = t
}

// Set endpoint
func SetEndpoint(e string) {
    // Store
    ENDPOINT = e
}

// Set timeout
func SetBackendTimeout(to time.Duration) {
    backendTimeout = to
}

// Debug
func SetDebugMode(b bool) {
    debugMode = b
}

// Drain
func Drain() {
    isDraining = true
    if startCounter > doneCounter {
        // Wait for signal
        <- drained
    }
}

// Write a message
func LogMessage(msg string) bool {
    // Create fields map
    params := make(map[string]string)
    params["__token__"] = TOKEN
    params["msg"] = msg

    // Push to channel
    return requestAsync(params)
}

// Request async
func requestAsync(params map[string]string) bool {
    // Check amount of open items in the channel, if the channel is full, return false and drop this message
    if dropOnFullWriteAheadBuffer {
        var lwa int = len(writeAhead)
        if lwa == writeAheadBufferSize {
            log.Printf("Write ahead buffer is full and contains %d items. Dropping current log message", lwa)
        }
    }

    // Add counter
    startCounterMux.Lock()
    startCounter++
    startCounterMux.Unlock()

    // Do we have to start a writer?
    if writeAheadInit == false {
        writeAheadInit = true
        backendWriter()
    }

    // Insert into channel
    writeAhead <- params

    // OK
    return true
}

// Backend writer
func backendWriter() {
    go func() {
        // Client
        transport := &http.Transport{
            Dial: func(netw, addr string) (net.Conn, error) {
                    deadline := time.Now().Add(backendTimeout)
                    c, err := net.DialTimeout(netw, addr, time.Second)
                    if err != nil {
                            return nil, err
                    }
                    c.SetDeadline(deadline)
                    return c, nil
            }}
        httpclient := &http.Client{Transport: transport}

        // Wait for messages
        var urlParams url.Values
        var currentEventCount int = 0
        for {
            // Read from channel
            var fields map[string]string
            fields = <- writeAhead

            // Populate url params
            if currentEventCount == 0 {
                urlParams = url.Values{}
            }
            for k, _ := range fields {
                if k == "__token__" {
                    // Token
                    urlParams.Add("t", fields[k]);
                } else {
                    // Field
                    urlParams.Add("f[" + strconv.Itoa(currentEventCount) + "][" + k + "]", fields[k]);
                }
            }

            // Increase current count
            currentEventCount++

            // Queue length
            var qLen = len(writeAhead)
            if qLen > 0 && currentEventCount < maxBulkSize {
                // There is more in the current queue, bulk request
                continue
            }

            // Assemble url
            var url string = ENDPOINT + "/push/bulk"

            // Make request
            if debugMode {
                log.Printf("Write ahead queue %d\n", qLen)
                log.Println(urlParams.Encode())
            }
            resp, err := httpclient.PostForm(url, urlParams)
            if err != nil {
                log.Printf("Error while forwarding data: %s\n", err)
            } else {
                defer resp.Body.Close()
            }

            // Done counter
            doneCounterMux.Lock()
            doneCounter += currentEventCount
            doneCounterMux.Unlock()

            // Reset event count
            currentEventCount = 0

            // Are we draining the system?
            if isDraining && doneCounter >= startCounter {
                // Flag the drained channel
                drained <- true
            }
        }
        log.Printf("Stopping backend writer")
    }()
}

// Timeout helper
func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, backendTimeout)
}

// Validate the token
func validateToken(t string) {
    if len(t) == 0 {
        log.Println("Please set a valid token with cloudpelican.SetToken(token string)")
    }
}<|endoftext|>


<commit_before>package test

import (
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/behance/go-logrus"
	"github.com/servehub/serve/tools/github"
	"os"
	"path/filepath"
	"strings"

	"github.com/servehub/utils"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"

	"github.com/servehub/serve/manifest"
)

func init() {
	manifest.PluginRegestry.Add("test.coverage", CoverageUpload{})
}

type CoverageUpload struct{}

func (p CoverageUpload) Run(data manifest.Manifest) error {
	// get repo, commit information
	meta := Meta{
		Repo:     data.GetString("repo"),
		Branch:   data.GetString("branch"),
		Ref:      data.GetString("ref"),
		Version:  data.GetString("version"),
		TestType: data.GetString("test-type"),
	}

	execFilePath := data.GetString("exec-file")
	if _, err := os.Stat(execFilePath); errors.Is(err, os.ErrNotExist) {
		logrus.Warnf("coverage file doesn't exist - skipping: %s", execFilePath)
		return nil
	}

	coveragePercent, err := generateReportsAndGetCoveragePercent([]string{execFilePath}, data)
	if err != nil {
		return err
	}

	// main branch name
	mainBranchName := data.GetStringOr("main-branch", "master")

	connectionUrl := os.Getenv(data.GetString("database-connection-env"))
	db, err := gorm.Open(postgres.Open(connectionUrl), &gorm.Config{})
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}
	// Migrate the DB schema
	if err := db.AutoMigrate(&CoverageReport{}); err != nil {
		return fmt.Errorf("failed to apply database migration: %w", err)
	}
	if meta.Branch == mainBranchName {
		coverageData, err := os.ReadFile(execFilePath)
		if err != nil {
			return fmt.Errorf("failed to read coverage exec file: %w", err)
		}
		if err := db.Create(&CoverageReport{
			Meta:            meta,
			CoveragePercent: coveragePercent,
			CoverageFile:    coverageData,
		}).Error; err != nil {
			return fmt.Errorf("failed to upload coverage exec file: %w", err)
		}
		return nil
	}

	// check mode
	accessToken := os.Getenv("GITHUB_TOKEN")
	if accessToken == "" {
		return errors.New("`GITHUB_TOKEN` is required")
	}
	targetUrl := data.GetString("check.target-url")
	statusContext := data.GetStringOr("checkcontext", "coverage")
	// allow a small tolerance for decrease in coverage
	coverageTolerance := data.GetFloat("check.tolerance")
	// get latest coverage report from database
	var latestCoverage CoverageReport
	if err := db.Where(&CoverageReport{Meta: Meta{
		Repo:     meta.Repo,
		Branch:   mainBranchName,
		TestType: meta.TestType,
	}}).Last(&latestCoverage).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
				"No previous coverage exists - check skipped ",
				statusContext, targetUrl)
		}
		return err
	}

	diff := latestCoverage.CoveragePercent - coveragePercent
	if diff < 0 {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
			fmt.Sprintf("Thank you for increasing the test coverage by %.2f%", -diff),
			statusContext, targetUrl)
	} else if diff < coverageTolerance {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
			fmt.Sprintf("Coverage changed by %.2f%", diff),
			statusContext, targetUrl)
	} else {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "failure",
			fmt.Sprintf("Please increase test coverage at least by %.2f%", diff),
			statusContext, targetUrl)
	}
}

type Meta struct {
	Repo     string `json:"repo"   gorm:"index"`
	Branch   string `json:"branch" gorm:"index"`
	Ref      string `json:"ref"`
	Version  string `json:"version"`
	TestType string `json:"test_type"`
}

type CoverageReport struct {
	gorm.Model
	Meta
	// coverage percentage in the range [0.0, 100.0]
	CoveragePercent float64
	CoverageFile    []byte
}

type CounterXML struct {
	XMLName xml.Name `xml:"counter"`
	Type    string   `xml:"type,attr"`
	Missed  int      `xml:"missed,attr"`
	Covered int      `xml:"covered,attr"`
}

type CoverageReportXML struct {
	XMLName xml.Name     `xml:"report"`
	Counter []CounterXML `xml:"counter"`
}

func generateReportsAndGetCoveragePercent(execCoverageFiles []string, data manifest.Manifest) (float64, error) {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf("java -jar %s report %s",
		data.GetStringOr("generate.jacococli-jar", "jacococli.jar"),
		strings.Join(execCoverageFiles, " "),
	))
	if data.Has("generate.sourcefiles") {
		for _, sourceFiles := range data.GetArray("generate.sourcefiles") {
			builder.WriteString(fmt.Sprintf(" --sourcefiles %s", sourceFiles))
		}
	}
	for _, classFiles := range data.GetArray("generate.classfiles") {
		builder.WriteString(fmt.Sprintf(" --classfiles %s", classFiles))
	}
	if data.Has("generate.html-output-dir") {
		htmlOutputDir := data.GetString("generate.html-output-dir")
		if htmlOutputDir != "" {
			builder.WriteString(fmt.Sprintf(" --html %s", htmlOutputDir))
		}
	}

	dirName, err := os.MkdirTemp("", "xml-report")
	if err != nil {
		return 0, errors.New("failed to create directory for temporary XML report file")
	}
	defer func(path string) {
		err := os.RemoveAll(path)
		if err != nil {
			logrus.Fatal(err)
		}
	}(dirName)
	xmlReportFile := filepath.Join(dirName, "coverage.xml")
	builder.WriteString(fmt.Sprintf(" --xml %s", xmlReportFile))

	if err = utils.RunCmd(builder.String()); err != nil {
		return 0, err
	}
	return getCoveragePercent(xmlReportFile)
}

func getCoveragePercent(xmlReportFile string) (float64, error) {
	coverageXml, err := os.ReadFile(xmlReportFile)
	if err != nil {
		return 0, fmt.Errorf("failed to read xml coverage report file: %w", err)
	}

	// (partially) unmarshall report from xml
	var coverageReportXml CoverageReportXML
	if err = xml.Unmarshal(coverageXml, &coverageReportXml); err != nil {
		return 0, fmt.Errorf("failed to parse xml coverage report file: %w", err)
	}

	// search for the XML counter matching the desired coverage metric
	var coverageCounter CounterXML
	for _, counter := range coverageReportXml.Counter {
		if counter.Type == "INSTRUCTION" {
			coverageCounter = counter
			break
		}
	}

	return 100 * float64(coverageCounter.Covered) / float64(coverageCounter.Covered+coverageCounter.Missed), nil
}
<commit_msg>review fixups<commit_after>package test

import (
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/behance/go-logrus"
	"github.com/servehub/serve/tools/github"
	"os"
	"path/filepath"
	"strings"

	"github.com/servehub/utils"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"

	"github.com/servehub/serve/manifest"
)

func init() {
	manifest.PluginRegestry.Add("test.coverage", CoverageUpload{})
}

type CoverageUpload struct{}

func (p CoverageUpload) Run(data manifest.Manifest) error {
	// get repo, commit information
	meta := Meta{
		Repo:     data.GetString("repo"),
		Branch:   data.GetString("branch"),
		Ref:      data.GetString("ref"),
		Version:  data.GetString("version"),
		TestType: data.GetString("test-type"),
	}

	execFilePath := data.GetString("exec-file")
	if _, err := os.Stat(execFilePath); errors.Is(err, os.ErrNotExist) {
		logrus.Warnf("coverage file doesn't exist - skipping: %s", execFilePath)
		return nil
	}

	coveragePercent, err := generateReportsAndGetCoveragePercent([]string{execFilePath}, data)
	if err != nil {
		return err
	}

	// main branch name
	mainBranchName := data.GetStringOr("main-branch", "master")

	connectionUrl := os.Getenv(data.GetString("database-connection-env"))
	db, err := gorm.Open(postgres.Open(connectionUrl), &gorm.Config{})
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}
	// Migrate the DB schema
	if err := db.AutoMigrate(&CoverageReport{}); err != nil {
		return fmt.Errorf("failed to apply database migration: %w", err)
	}
	if meta.Branch == mainBranchName {
		coverageData, err := os.ReadFile(execFilePath)
		if err != nil {
			return fmt.Errorf("failed to read coverage exec file: %w", err)
		}
		if err := db.Create(&CoverageReport{
			Meta:            meta,
			CoveragePercent: coveragePercent,
			CoverageFile:    coverageData,
		}).Error; err != nil {
			return fmt.Errorf("failed to upload coverage exec file: %w", err)
		}
		return nil
	}

	// check mode
	accessToken := os.Getenv("GITHUB_TOKEN")
	if accessToken == "" {
		return errors.New("`GITHUB_TOKEN` is required")
	}
	targetUrl := data.GetString("check.target-url")
	statusContext := data.GetStringOr("check.context", "coverage")
	// allow a small tolerance for decrease in coverage
	coverageTolerance := data.GetFloat("check.tolerance")
	// get latest coverage report from database
	var latestCoverage CoverageReport
	if err := db.Where(&CoverageReport{Meta: Meta{
		Repo:     meta.Repo,
		Branch:   mainBranchName,
		TestType: meta.TestType,
	}}).Last(&latestCoverage).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
				"No previous coverage exists - check skipped ",
				statusContext, targetUrl)
		}
		return err
	}

	diff := latestCoverage.CoveragePercent - coveragePercent
	if diff < 0 {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
			fmt.Sprintf("Thank you for increasing the test coverage by %.2f%", -diff),
			statusContext, targetUrl)
	} else if diff < coverageTolerance {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "success",
			fmt.Sprintf("Coverage changed by %.2f%", diff),
			statusContext, targetUrl)
	} else {
		return github.SendStatus(accessToken, meta.Repo, meta.Ref, "failure",
			fmt.Sprintf("Please increase test coverage at least by %.2f%", diff),
			statusContext, targetUrl)
	}
}

type Meta struct {
	Repo     string `json:"repo"   gorm:"index"`
	Branch   string `json:"branch" gorm:"index"`
	Ref      string `json:"ref"`
	Version  string `json:"version"`
	TestType string `json:"test_type"`
}

type CoverageReport struct {
	gorm.Model
	Meta
	// coverage percentage in the range [0.0, 100.0]
	CoveragePercent float64
	CoverageFile    []byte
}

type CounterXML struct {
	XMLName xml.Name `xml:"counter"`
	Type    string   `xml:"type,attr"`
	Missed  int      `xml:"missed,attr"`
	Covered int      `xml:"covered,attr"`
}

type CoverageReportXML struct {
	XMLName xml.Name     `xml:"report"`
	Counter []CounterXML `xml:"counter"`
}

func generateReportsAndGetCoveragePercent(execCoverageFiles []string, data manifest.Manifest) (float64, error) {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf("java -jar %s report %s",
		data.GetStringOr("generate.jacococli-jar", "jacococli.jar"),
		strings.Join(execCoverageFiles, " "),
	))
	if data.Has("generate.sourcefiles") {
		for _, sourceFiles := range data.GetArray("generate.sourcefiles") {
			builder.WriteString(fmt.Sprintf(" --sourcefiles %s", sourceFiles))
		}
	}
	for _, classFiles := range data.GetArray("generate.classfiles") {
		builder.WriteString(fmt.Sprintf(" --classfiles %s", classFiles))
	}
	if data.Has("generate.html-output-dir") {
		htmlOutputDir := data.GetString("generate.html-output-dir")
		if htmlOutputDir != "" {
			builder.WriteString(fmt.Sprintf(" --html %s", htmlOutputDir))
		}
	}

	dirName, err := os.MkdirTemp("", "xml-report")
	if err != nil {
		return 0, errors.New("failed to create directory for temporary XML report file")
	}
	defer func(path string) {
		err := os.RemoveAll(path)
		if err != nil {
			logrus.Fatal(err)
		}
	}(dirName)
	xmlReportFile := filepath.Join(dirName, "coverage.xml")
	builder.WriteString(fmt.Sprintf(" --xml %s", xmlReportFile))

	if err = utils.RunCmd(builder.String()); err != nil {
		return 0, err
	}
	return getCoveragePercent(xmlReportFile)
}

func getCoveragePercent(xmlReportFile string) (float64, error) {
	coverageXml, err := os.ReadFile(xmlReportFile)
	if err != nil {
		return 0, fmt.Errorf("failed to read xml coverage report file: %w", err)
	}

	// (partially) unmarshall report from xml
	var coverageReportXml CoverageReportXML
	if err = xml.Unmarshal(coverageXml, &coverageReportXml); err != nil {
		return 0, fmt.Errorf("failed to parse xml coverage report file: %w", err)
	}

	// search for the XML counter matching the desired coverage metric
	var coverageCounter CounterXML
	for _, counter := range coverageReportXml.Counter {
		if counter.Type == "INSTRUCTION" {
			coverageCounter = counter
			break
		}
	}

	return 100 * float64(coverageCounter.Covered) / float64(coverageCounter.Covered+coverageCounter.Missed), nil
}
<|endoftext|>


<commit_before>// Package mem is an example REST backend storage that stores everything in memory.
package mem

import (
	"bytes"
	"context"
	"encoding/gob"
	"sort"
	"sync"
	"time"

	"github.com/rs/rest-layer/resource"
	"github.com/rs/rest-layer/schema/query"
)

// MemoryHandler is an example handler storing data in memory
type MemoryHandler struct {
	sync.RWMutex
	// If latency is set, the handler will introduce an artificial latency on
	// all operations
	Latency time.Duration
	items   map[interface{}][]byte
	ids     []interface{}
}

func init() {
	gob.Register([]interface{}{})
	gob.Register(map[string]interface{}{})
	gob.Register(time.Time{})
}

// NewHandler creates an empty memory handler
func NewHandler() *MemoryHandler {
	return &MemoryHandler{
		items: map[interface{}][]byte{},
		ids:   []interface{}{},
	}
}

// NewSlowHandler creates an empty memory handler with specified latency
func NewSlowHandler(latency time.Duration) *MemoryHandler {
	return &MemoryHandler{
		Latency: latency,
		items:   map[interface{}][]byte{},
		ids:     []interface{}{},
	}
}

// store serialize the item using gob and store it in the handler's items map
func (m *MemoryHandler) store(item *resource.Item) error {
	var data bytes.Buffer
	enc := gob.NewEncoder(&data)
	if err := enc.Encode(*item); err != nil {
		return err
	}
	m.items[item.ID] = data.Bytes()
	return nil
}

// fetch unserialize item's data and return a new item
func (m *MemoryHandler) fetch(id interface{}) (*resource.Item, bool, error) {
	data, found := m.items[id]
	if !found {
		return nil, false, nil
	}
	dec := gob.NewDecoder(bytes.NewBuffer(data))
	var item resource.Item
	if err := dec.Decode(&item); err != nil {
		return nil, true, err
	}
	return &item, true, nil
}

// delete removes an item by this id with no look
func (m *MemoryHandler) delete(id interface{}) {
	delete(m.items, id)
	// Remove id from id list
	for i, _id := range m.ids {
		if _id == id {
			if i >= len(m.ids)-1 {
				m.ids = m.ids[:i]
			} else {
				m.ids = append(m.ids[:i], m.ids[i+1:]...)
			}
			break
		}
	}
}

// Insert inserts new items in memory
func (m *MemoryHandler) Insert(ctx context.Context, items []*resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		for _, item := range items {
			if _, found := m.items[item.ID]; found {
				return resource.ErrConflict
			}
		}
		for _, item := range items {
			if err := m.store(item); err != nil {
				return err
			}
			// Store ids in ordered slice for sorting
			m.ids = append(m.ids, item.ID)
		}
		return nil
	})
	return err
}

// Update replace an item by a new one in memory
func (m *MemoryHandler) Update(ctx context.Context, item *resource.Item, original *resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		o, found, err := m.fetch(original.ID)
		if !found {
			return resource.ErrNotFound
		}
		if err != nil {
			return err
		}
		if original.ETag != o.ETag {
			return resource.ErrConflict
		}
		if err := m.store(item); err != nil {
			return err
		}
		return nil
	})
	return err
}

// Delete deletes an item from memory
func (m *MemoryHandler) Delete(ctx context.Context, item *resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		o, found, err := m.fetch(item.ID)
		if !found {
			return resource.ErrNotFound
		}
		if err != nil {
			return err
		}
		if item.ETag != o.ETag {
			return resource.ErrConflict
		}
		m.delete(item.ID)
		return nil
	})
	return err
}

// Clear clears all items from the memory store matching the lookup
func (m *MemoryHandler) Clear(ctx context.Context, q *query.Query) (total int, err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		ids := make([]interface{}, len(m.ids))
		copy(ids, m.ids)
		for _, id := range ids {
			item, _, err := m.fetch(id)
			if err != nil {
				return err
			}
			if !q.Predicate.Match(item.Payload) {
				continue
			}
			m.delete(item.ID)
			total++
		}
		return nil
	})
	return total, err
}

// Find items from memory matching the provided lookup
func (m *MemoryHandler) Find(ctx context.Context, q *query.Query) (list *resource.ItemList, err error) {
	m.RLock()
	defer m.RUnlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		items := []*resource.Item{}
		// Apply filter
		for _, id := range m.ids {
			item, _, err := m.fetch(id)
			if err != nil {
				return err
			}
			if !q.Predicate.Match(item.Payload) {
				continue
			}
			items = append(items, item)
		}
		// Apply sort
		if len(q.Sort) > 0 {
			s := sortableItems{q.Sort, items}
			sort.Sort(s)
		}
		// Apply pagination
		total := len(items)
		if q.Window == nil {
			list = &resource.ItemList{Total: total, Items: items}
			return nil
		}
		end := total
		start := q.Window.Offset

		if q.Window.Limit >= 0 {
			end = start + q.Window.Limit
			if end > total-1 {
				end = total
			}
		}
		if start > total-1 {
			start = 0
			end = 0
		}
		list = &resource.ItemList{Total: total, Items: items[start:end]}
		return nil
	})
	return list, err
}
<commit_msg>mem: Let Clear handle query.Sort and query.Window<commit_after>// Package mem is an example REST backend storage that stores everything in memory.
package mem

import (
	"bytes"
	"context"
	"encoding/gob"
	"sort"
	"sync"
	"time"

	"github.com/rs/rest-layer/resource"
	"github.com/rs/rest-layer/schema/query"
)

// MemoryHandler is an example handler storing data in memory.
type MemoryHandler struct {
	sync.RWMutex

	// If Latency is set, the handler will introduce an artificial latency on
	// all operations.
	Latency time.Duration

	items map[interface{}][]byte
	ids   []interface{}
}

func init() {
	gob.Register([]interface{}{})
	gob.Register(map[string]interface{}{})
	gob.Register(time.Time{})
}

// NewHandler creates an empty memory handler.
func NewHandler() *MemoryHandler {
	return &MemoryHandler{
		items: map[interface{}][]byte{},
		ids:   []interface{}{},
	}
}

// NewSlowHandler creates an empty memory handler with specified latency.
func NewSlowHandler(latency time.Duration) *MemoryHandler {
	return &MemoryHandler{
		Latency: latency,
		items:   map[interface{}][]byte{},
		ids:     []interface{}{},
	}
}

// store serialize the item using gob and store it in the handler's items map.
func (m *MemoryHandler) store(item *resource.Item) error {
	var data bytes.Buffer
	enc := gob.NewEncoder(&data)
	if err := enc.Encode(*item); err != nil {
		return err
	}
	m.items[item.ID] = data.Bytes()
	return nil
}

// fetch finds an item by ID and returns a new item with unserialized data.
func (m *MemoryHandler) fetch(id interface{}) (*resource.Item, bool, error) {
	data, found := m.items[id]
	if !found {
		return nil, false, nil
	}
	dec := gob.NewDecoder(bytes.NewBuffer(data))
	var item resource.Item
	if err := dec.Decode(&item); err != nil {
		return nil, true, err
	}
	return &item, true, nil
}

// delete removes an item by this id without locking.
func (m *MemoryHandler) delete(id interface{}) {
	delete(m.items, id)
	// Remove id from id list
	for i, _id := range m.ids {
		if _id == id {
			if i >= len(m.ids)-1 {
				m.ids = m.ids[:i]
			} else {
				m.ids = append(m.ids[:i], m.ids[i+1:]...)
			}
			break
		}
	}
}

// Insert inserts new items in memory.
func (m *MemoryHandler) Insert(ctx context.Context, items []*resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		for _, item := range items {
			if _, found := m.items[item.ID]; found {
				return resource.ErrConflict
			}
		}
		for _, item := range items {
			if err := m.store(item); err != nil {
				return err
			}
			// Store ids in ordered slice for sorting
			m.ids = append(m.ids, item.ID)
		}
		return nil
	})
	return err
}

// Update replace an item by a new one in memory.
func (m *MemoryHandler) Update(ctx context.Context, item *resource.Item, original *resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		o, found, err := m.fetch(original.ID)
		if !found {
			return resource.ErrNotFound
		}
		if err != nil {
			return err
		}
		if original.ETag != o.ETag {
			return resource.ErrConflict
		}
		return m.store(item)
	})
	return err
}

// Delete deletes an item from memory.
func (m *MemoryHandler) Delete(ctx context.Context, item *resource.Item) (err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		o, found, err := m.fetch(item.ID)
		if !found {
			return resource.ErrNotFound
		}
		if err != nil {
			return err
		}
		if item.ETag != o.ETag {
			return resource.ErrConflict
		}
		m.delete(item.ID)
		return nil
	})
	return err
}

// Clear clears all items from the memory store matching q.
func (m *MemoryHandler) Clear(ctx context.Context, q *query.Query) (total int, err error) {
	m.Lock()
	defer m.Unlock()
	err = handleWithLatency(m.Latency, ctx, func() error {
		list, err := m.find(ctx, q)
		if err != nil {
			return err
		}
		for _, item := range list.Items {
			m.delete(item.ID)
			total++
		}
		return nil
	})
	return total, err
}

// Find items from memory matching the q.
func (m *MemoryHandler) Find(ctx context.Context, q *query.Query) (list *resource.ItemList, err error) {
	m.RLock()
	defer m.RUnlock()

	err = handleWithLatency(m.Latency, ctx, func() error {
		list, err = m.find(ctx, q)
		return err
	})
	return list, err
}

func (m *MemoryHandler) find(ctx context.Context, q *query.Query) (*resource.ItemList, error) {
	// Fetch all items matching the filter
	list := resource.ItemList{Items: []*resource.Item{}}
	for _, id := range m.ids {
		item, _, err := m.fetch(id)
		if err != nil {
			return nil, err
		}
		if !q.Predicate.Match(item.Payload) {
			continue
		}
		list.Items = append(list.Items, item)
	}
	list.Total = len(list.Items)

	// Apply sort
	if len(q.Sort) > 0 {
		s := sortableItems{q.Sort, list.Items}
		sort.Sort(s)
	}
	// Apply pagination
	if q.Window != nil {
		list.Limit = q.Window.Limit
		list.Offset = q.Window.Offset

		if list.Offset > list.Total-1 {
			list.Items = nil
		} else {
			list.Items = list.Items[list.Offset:]
			if list.Limit >= 0 && list.Limit < len(list.Items) {
				list.Items = list.Items[:list.Limit]
			}
		}

	}
	return &list, nil
}
<|endoftext|>


<commit_before>//
// markup.go
// memeposting markup parser
//
package srnd

import (
  "html"
  "regexp"
  "strings"
)

// copypasted from https://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
// var re_external_link = regexp.MustCompile(`((?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?)`);
var re_external_link = regexp.MustCompile(`((https?|ftp):\/\/[\w\d\.\-_]*\/[\w\d\.\/\-_\?]*)`);
var re_backlink = regexp.MustCompile(`>> ?([0-9a-f]{10})`)

func formatline(line string) (markup string) {
  line = strings.Trim(line, "\t\r\n ")
  if len(line) > 0 {
    if strings.HasPrefix(line, ">") && ! strings.HasPrefix(line, ">>") {
      // le ebin meme arrows
      markup += "
"
      markup += html.EscapeString(line)
      markup += "
"
    } else if strings.HasPrefix(line, "==") && strings.HasSuffix(line, "==") {
      // redtext
      markup += "
"
      markup += html.EscapeString(line[2:len(line)-2])
      markup += "
"
    } else {
      // regular line
      markup += "
"
      // for each word
      for _, word := range strings.Split(line, " ") {
        // check for backlink
        link := re_backlink.FindString(word)
        if len(link) > 0 {
          url := template.findLink(link)
          if len(url) == 0 {
            url = "#"
          }
          // backlink exists
          markup += `<a href="`+url+`">>>` + link + ""

        } else {
          // linkify it as needed
          line = html.EscapeString(word)
          markup += re_external_link.ReplaceAllString(line, `<a href="$1">$1</a>`)
        }
        markup += " "
      }
      markup += "
"
    }
  }
  return
}

// format lines inside a code tag
func formatcodeline(line string) (markup string) {
  markup += "
"
  markup += html.EscapeString(line)
  markup += "
"
  return
}

func memeposting(src string) (markup string) {
  found_tag := false
  tag_content := ""
  tag := ""
  // for each line...
  for _, line := range strings.Split(src, "\n") {
    // beginning of code tag ?
    if strings.Count(line, "[code]") > 0 {
      // yes there's a code tag
      found_tag = true
      tag = "code"
    } else if strings.Count(line, "[spoiler]") > 0 {
      // spoiler tag
      found_tag = true
      tag = "spoiler"
    } else if strings.Count(line, "[psy]") > 0 {
      // psy tag
      found_tag = true
      tag = "psy"
    }
    if found_tag {
      // collect content of tag
      tag_content += line + "\n"
      // end of our tag ?
      if strings.Count(line, "[/"+tag+"]") == 1 {
        // yah
        found_tag = false
        var tag_open, tag_close string
        if tag == "code" {
          tag_open = "
"
          tag_close = "
"
        } else if tag == "spoiler" {
          tag_open = ""
          tag_close = ""
        } else if tag == "psy" {
          tag_open = "
"
          tag_close = "
"
        }
        markup += tag_open
        // remove open tag, only once so we can have a code tag verbatum inside
        tag_content = strings.Replace(tag_content, "["+tag+"]", "", 1)
        // remove all close tags, should only have 1
        tag_content = strings.Replace(tag_content, "[/"+tag+"]", "", -1)
        // make into lines
        for _, tag_line := range strings.Split(tag_content, "\n") {
          if tag == "code" {
            markup += formatcodeline(tag_line)
          } else {
            markup += formatline(tag_line)
          }
        }
        // close pre tag
        markup += tag_close
        // reset content buffer
        tag_content = ""
      }
      // next line
      continue
    }
    // format line regularlly
    markup += formatline(line)
  }
  // flush the rest of an incomplete code tag
  for _, line := range strings.Split(tag_content, "\n") {
    markup += formatline(line)
  }
  return
}
<commit_msg>try fixing backlinks<commit_after>//
// markup.go
// memeposting markup parser
//
package srnd

import (
  "html"
  "regexp"
  "strings"
)

// copypasted from https://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
// var re_external_link = regexp.MustCompile(`((?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?)`);
var re_external_link = regexp.MustCompile(`((https?|ftp):\/\/[\w\d\.\-_]*\/[\w\d\.\/\-_\?]*)`);
var re_backlink = regexp.MustCompile(`>> ?([0-9a-f]{10})`)

func formatline(line string) (markup string) {
  line = strings.Trim(line, "\t\r\n ")
  if len(line) > 0 {
    if strings.HasPrefix(line, ">") && ! strings.HasPrefix(line, ">>") {
      // le ebin meme arrows
      markup += "
"
      markup += html.EscapeString(line)
      markup += "
"
    } else if strings.HasPrefix(line, "==") && strings.HasSuffix(line, "==") {
      // redtext
      markup += "
"
      markup += html.EscapeString(line[2:len(line)-2])
      markup += "
"
    } else {
      // regular line
      markup += "
"
      // for each word
      for _, word := range strings.Split(line, " ") {
        // check for backlink
        link := re_backlink.FindString(word)
        if len(link) > 0 {
          link = strings.Trim(link[2:], " ")
          url := template.findLink(link)
          if len(url) == 0 {
            url = "#"
          }
          // backlink exists
          markup += `<a href="`+url+`">>>` + link + ""

        } else {
          // linkify it as needed
          line = html.EscapeString(word)
          markup += re_external_link.ReplaceAllString(line, `<a href="$1">$1</a>`)
        }
        markup += " "
      }
      markup += "
"
    }
  }
  return
}

// format lines inside a code tag
func formatcodeline(line string) (markup string) {
  markup += "
"
  markup += html.EscapeString(line)
  markup += "
"
  return
}

func memeposting(src string) (markup string) {
  found_tag := false
  tag_content := ""
  tag := ""
  // for each line...
  for _, line := range strings.Split(src, "\n") {
    // beginning of code tag ?
    if strings.Count(line, "[code]") > 0 {
      // yes there's a code tag
      found_tag = true
      tag = "code"
    } else if strings.Count(line, "[spoiler]") > 0 {
      // spoiler tag
      found_tag = true
      tag = "spoiler"
    } else if strings.Count(line, "[psy]") > 0 {
      // psy tag
      found_tag = true
      tag = "psy"
    }
    if found_tag {
      // collect content of tag
      tag_content += line + "\n"
      // end of our tag ?
      if strings.Count(line, "[/"+tag+"]") == 1 {
        // yah
        found_tag = false
        var tag_open, tag_close string
        if tag == "code" {
          tag_open = "
"
          tag_close = "
"
        } else if tag == "spoiler" {
          tag_open = ""
          tag_close = ""
        } else if tag == "psy" {
          tag_open = "
"
          tag_close = "
"
        }
        markup += tag_open
        // remove open tag, only once so we can have a code tag verbatum inside
        tag_content = strings.Replace(tag_content, "["+tag+"]", "", 1)
        // remove all close tags, should only have 1
        tag_content = strings.Replace(tag_content, "[/"+tag+"]", "", -1)
        // make into lines
        for _, tag_line := range strings.Split(tag_content, "\n") {
          if tag == "code" {
            markup += formatcodeline(tag_line)
          } else {
            markup += formatline(tag_line)
          }
        }
        // close pre tag
        markup += tag_close
        // reset content buffer
        tag_content = ""
      }
      // next line
      continue
    }
    // format line regularlly
    markup += formatline(line)
  }
  // flush the rest of an incomplete code tag
  for _, line := range strings.Split(tag_content, "\n") {
    markup += formatline(line)
  }
  return
}
<|endoftext|>


<commit_before>// Package bcrypt_pbkdf implements OpenBSD's bcrypt_pbkdf(3)
package bcrypt_pbkdf

import (
	"crypto/sha512"
	"github.com/ebfe/bcrypt_pbkdf/blowfish"
)

//  derived from /usr/src/lib/libutil/bcrypt_pbkdf.c
/*
	$OpenBSD: bcrypt_pbkdf.c,v 1.6 2014/01/31 16:56:32 tedu Exp $

	Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>

	Permission to use, copy, modify, and distribute this software for any
	purpose with or without fee is hereby granted, provided that the above
	copyright notice and this permission notice appear in all copies.

	THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
	WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
	ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
	WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
	ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
	OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

const (
	bcryptBlocks   = 8
	bcryptHashSize = 4 * bcryptBlocks
	magic          = "OxychromaticBlowfishSwatDynamite"
)

func bcryptHash(bf *blowfish.Cipher, hpass, hsalt, out []byte) {
	err := bf.InitSaltedCipher(hpass, hsalt)
	if err != nil {
		panic(err)
	}

	for i := 0; i < 64; i++ {
		blowfish.ExpandKey(hsalt, bf)
		blowfish.ExpandKey(hpass, bf)
	}

	copy(out, magic)

	for i := 0; i < 64; i++ {
		for j := 0; j < bcryptHashSize/blowfish.BlockSize; j++ {
			bf.Encrypt(out[j*blowfish.BlockSize:], out[j*blowfish.BlockSize:])
		}
	}

	for i := 0; i < len(out); i += 4 {
		out[i+0], out[i+1], out[i+2], out[i+3] = out[i+3], out[i+2], out[i+1], out[i+0]
	}
}

func bcryptPBKDF(password, salt []byte, rounds, keyLen int) []byte {
	countsalt := make([]byte, 4)
	out := make([]byte, bcryptHashSize)
	tmp := make([]byte, bcryptHashSize)
	key := make([]byte, keyLen)
	cipher := &blowfish.Cipher{}

	stride := (keyLen + bcryptHashSize - 1) / bcryptHashSize
	amt := (keyLen + stride - 1) / stride

	sha := sha512.New()
	sha.Write(password)
	hpassword := sha.Sum(nil)

	for count := uint32(1); keyLen > 0; count++ {
		sha.Reset()
		sha.Write(salt)
		countsalt[0] = byte(count >> 24)
		countsalt[1] = byte(count >> 16)
		countsalt[2] = byte(count >> 8)
		countsalt[3] = byte(count)
		sha.Write(countsalt)
		hsalt := sha.Sum(nil)

		bcryptHash(cipher, hpassword, hsalt, tmp)
		copy(out, tmp)

		for i := 1; i < rounds; i++ {
			sha.Reset()
			sha.Write(tmp)
			hsalt = sha.Sum(nil)
			bcryptHash(cipher, hpassword, hsalt, tmp)
			for i := range out {
				out[i] ^= tmp[i]
			}
		}

		if amt > keyLen {
			amt = keyLen
		}

		for i := 0; i < amt; i++ {
			key[i*stride+(int(count)-1)] = out[i]
		}
		keyLen -= amt
	}

	return key
}

func Key(password, salt []byte, rounds, keyLen int) []byte {
	return bcryptPBKDF(password, salt, rounds, keyLen)
}
<commit_msg>eliminate per round allocs<commit_after>// Package bcrypt_pbkdf implements OpenBSD's bcrypt_pbkdf(3)
package bcrypt_pbkdf

import (
	"crypto/sha512"
	"github.com/ebfe/bcrypt_pbkdf/blowfish"
)

//  derived from /usr/src/lib/libutil/bcrypt_pbkdf.c
/*
	$OpenBSD: bcrypt_pbkdf.c,v 1.6 2014/01/31 16:56:32 tedu Exp $

	Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>

	Permission to use, copy, modify, and distribute this software for any
	purpose with or without fee is hereby granted, provided that the above
	copyright notice and this permission notice appear in all copies.

	THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
	WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
	ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
	WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
	ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
	OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

const (
	bcryptBlocks   = 8
	bcryptHashSize = 4 * bcryptBlocks
	magic          = "OxychromaticBlowfishSwatDynamite"
)

func bcryptHash(bf *blowfish.Cipher, hpass, hsalt, out []byte) {
	err := bf.InitSaltedCipher(hpass, hsalt)
	if err != nil {
		panic(err)
	}

	for i := 0; i < 64; i++ {
		blowfish.ExpandKey(hsalt, bf)
		blowfish.ExpandKey(hpass, bf)
	}

	copy(out, magic)

	for i := 0; i < 64; i++ {
		for j := 0; j < bcryptHashSize/blowfish.BlockSize; j++ {
			bf.Encrypt(out[j*blowfish.BlockSize:], out[j*blowfish.BlockSize:])
		}
	}

	for i := 0; i < len(out); i += 4 {
		out[i+0], out[i+1], out[i+2], out[i+3] = out[i+3], out[i+2], out[i+1], out[i+0]
	}
}

func bcryptPBKDF(password, salt []byte, rounds, keyLen int) []byte {
	countsalt := make([]byte, 4)
	hpass := make([]byte, sha512.Size)
	hsalt := make([]byte, sha512.Size)
	out := make([]byte, bcryptHashSize)
	tmp := make([]byte, bcryptHashSize)
	key := make([]byte, keyLen)
	cipher := &blowfish.Cipher{}

	stride := (keyLen + bcryptHashSize - 1) / bcryptHashSize
	amt := (keyLen + stride - 1) / stride

	sha := sha512.New()
	sha.Write(password)
	hpass = sha.Sum(hpass[:0])

	for count := uint32(1); keyLen > 0; count++ {
		sha.Reset()
		sha.Write(salt)
		countsalt[0] = byte(count >> 24)
		countsalt[1] = byte(count >> 16)
		countsalt[2] = byte(count >> 8)
		countsalt[3] = byte(count)
		sha.Write(countsalt)
		hsalt = sha.Sum(hsalt[:0])

		bcryptHash(cipher, hpass, hsalt, tmp)
		copy(out, tmp)

		for i := 1; i < rounds; i++ {
			sha.Reset()
			sha.Write(tmp)
			hsalt = sha.Sum(hsalt[:0])
			bcryptHash(cipher, hpass, hsalt, tmp)
			for i := range out {
				out[i] ^= tmp[i]
			}
		}

		if amt > keyLen {
			amt = keyLen
		}

		for i := 0; i < amt; i++ {
			key[i*stride+(int(count)-1)] = out[i]
		}
		keyLen -= amt
	}

	return key
}

func Key(password, salt []byte, rounds, keyLen int) []byte {
	return bcryptPBKDF(password, salt, rounds, keyLen)
}
<|endoftext|>


<commit_before>package awsecs

import (
	"fmt"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/report"
)

// TaskFamily is the key that stores the task family of an ECS Task
const (
	Cluster             = "ecs_cluster"
	CreatedAt           = "ecs_created_at"
	TaskFamily          = "ecs_task_family"
	ServiceDesiredCount = "ecs_service_desired_count"
	ServiceRunningCount = "ecs_service_running_count"
)

var (
	taskMetadata = report.MetadataTemplates{
		Cluster:    {ID: Cluster, Label: "Cluster", From: report.FromLatest, Priority: 0},
		CreatedAt:  {ID: CreatedAt, Label: "Created At", From: report.FromLatest, Priority: 1, Datatype: "datetime"},
		TaskFamily: {ID: TaskFamily, Label: "Family", From: report.FromLatest, Priority: 2},
	}
	serviceMetadata = report.MetadataTemplates{
		Cluster:             {ID: Cluster, Label: "Cluster", From: report.FromLatest, Priority: 0},
		CreatedAt:           {ID: CreatedAt, Label: "Created At", From: report.FromLatest, Priority: 1, Datatype: "datetime"},
		ServiceDesiredCount: {ID: ServiceDesiredCount, Label: "Desired Tasks", From: report.FromLatest, Priority: 2, Datatype: "number"},
		ServiceRunningCount: {ID: ServiceRunningCount, Label: "Running Tasks", From: report.FromLatest, Priority: 3, Datatype: "number"},
	}
)

type taskLabelInfo struct {
	containerIDs []string
	family       string
}

// return map from cluster to map of task arns to task infos
func getLabelInfo(rpt report.Report) map[string]map[string]*taskLabelInfo {
	results := map[string]map[string]*taskLabelInfo{}
	log.Debug("scanning for ECS containers")
	for nodeID, node := range rpt.Container.Nodes {

		taskArn, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.task-arn")
		if !ok {
			continue
		}

		cluster, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.cluster")
		if !ok {
			continue
		}

		family, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.task-definition-family")
		if !ok {
			continue
		}

		taskMap, ok := results[cluster]
		if !ok {
			taskMap = map[string]*taskLabelInfo{}
			results[cluster] = taskMap
		}

		task, ok := taskMap[taskArn]
		if !ok {
			task = &taskLabelInfo{containerIDs: []string{}, family: family}
			taskMap[taskArn] = task
		}

		task.containerIDs = append(task.containerIDs, nodeID)
	}
	log.Debug("Got ECS container info: %v", results)
	return results
}

// Reporter implements Tagger, Reporter
type Reporter struct {
	clientsByCluster map[string]*ecsClient
}

// New creates a new Reporter
func Make() Reporter {
	return Reporter{
		clientsByCluster: map[string]*ecsClient{},
	}
}

// Tag needed for Tagger
func (r Reporter) Tag(rpt report.Report) (report.Report, error) {
	rpt = rpt.Copy()

	clusterMap := getLabelInfo(rpt)

	for cluster, taskMap := range clusterMap {
		log.Debugf("Fetching ECS info for cluster %v with %v tasks", cluster, len(taskMap))

		client, ok := r.clientsByCluster[cluster]
		if !ok {
			log.Debugf("Creating new ECS client")
			var err error
			client, err = newClient(cluster)
			if err != nil {
				return rpt, err
			}
			r.clientsByCluster[cluster] = client
		}

		taskArns := make([]string, 0, len(taskMap))
		for taskArn := range taskMap {
			taskArns = append(taskArns, taskArn)
		}

		ecsInfo := client.getInfo(taskArns)
		log.Debugf("Got info from ECS: %d tasks, %d services", len(ecsInfo.tasks), len(ecsInfo.services))

		// Create all the services first
		for serviceName, service := range ecsInfo.services {
			serviceID := report.MakeECSServiceNodeID(serviceName)
			rpt.ECSService = rpt.ECSService.AddNode(report.MakeNodeWith(serviceID, map[string]string{
				Cluster:             cluster,
				ServiceDesiredCount: fmt.Sprintf("%d", service.desiredCount),
				ServiceRunningCount: fmt.Sprintf("%d", service.runningCount),
			}))
		}
		log.Debugf("Created %v ECS service nodes", len(ecsInfo.services))

		for taskArn, info := range taskMap {
			task, ok := ecsInfo.tasks[taskArn]
			if !ok {
				// can happen due to partial failures, just skip it
				continue
			}

			// new task node
			taskID := report.MakeECSTaskNodeID(taskArn)
			node := report.MakeNodeWith(taskID, map[string]string{
				TaskFamily: info.family,
				Cluster:    cluster,
				CreatedAt:  task.createdAt.Format(time.RFC3339Nano),
			})
			rpt.ECSTask = rpt.ECSTask.AddNode(node)

			// parents sets to merge into all matching container nodes
			parentsSets := report.MakeSets()
			parentsSets = parentsSets.Add(report.ECSTask, report.MakeStringSet(taskID))
			if serviceName, ok := ecsInfo.taskServiceMap[taskArn]; ok {
				serviceID := report.MakeECSServiceNodeID(serviceName)
				parentsSets = parentsSets.Add(report.ECSService, report.MakeStringSet(serviceID))
			}
			for _, containerID := range info.containerIDs {
				if containerNode, ok := rpt.Container.Nodes[containerID]; ok {
					rpt.Container.Nodes[containerID] = containerNode.WithParents(parentsSets)
				} else {
					log.Warnf("Got task info for non-existent container %v, this shouldn't be able to happen", containerID)
				}
			}
		}

	}

	return rpt, nil
}

// Report needed for Reporter
func (Reporter) Report() (report.Report, error) {
	result := report.MakeReport()
	taskTopology := report.MakeTopology().WithMetadataTemplates(taskMetadata)
	result.ECSTask = result.ECSTask.Merge(taskTopology)
	serviceTopology := report.MakeTopology().WithMetadataTemplates(serviceMetadata)
	result.ECSService = result.ECSService.Merge(serviceTopology)
	return result, nil
}

// Name needed for Tagger, Reporter
func (r Reporter) Name() string {
	return "awsecs"
}
<commit_msg>Appease linter<commit_after>package awsecs

import (
	"fmt"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/report"
)

// TaskFamily is the key that stores the task family of an ECS Task
const (
	Cluster             = "ecs_cluster"
	CreatedAt           = "ecs_created_at"
	TaskFamily          = "ecs_task_family"
	ServiceDesiredCount = "ecs_service_desired_count"
	ServiceRunningCount = "ecs_service_running_count"
)

var (
	taskMetadata = report.MetadataTemplates{
		Cluster:    {ID: Cluster, Label: "Cluster", From: report.FromLatest, Priority: 0},
		CreatedAt:  {ID: CreatedAt, Label: "Created At", From: report.FromLatest, Priority: 1, Datatype: "datetime"},
		TaskFamily: {ID: TaskFamily, Label: "Family", From: report.FromLatest, Priority: 2},
	}
	serviceMetadata = report.MetadataTemplates{
		Cluster:             {ID: Cluster, Label: "Cluster", From: report.FromLatest, Priority: 0},
		CreatedAt:           {ID: CreatedAt, Label: "Created At", From: report.FromLatest, Priority: 1, Datatype: "datetime"},
		ServiceDesiredCount: {ID: ServiceDesiredCount, Label: "Desired Tasks", From: report.FromLatest, Priority: 2, Datatype: "number"},
		ServiceRunningCount: {ID: ServiceRunningCount, Label: "Running Tasks", From: report.FromLatest, Priority: 3, Datatype: "number"},
	}
)

type taskLabelInfo struct {
	containerIDs []string
	family       string
}

// return map from cluster to map of task arns to task infos
func getLabelInfo(rpt report.Report) map[string]map[string]*taskLabelInfo {
	results := map[string]map[string]*taskLabelInfo{}
	log.Debug("scanning for ECS containers")
	for nodeID, node := range rpt.Container.Nodes {

		taskArn, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.task-arn")
		if !ok {
			continue
		}

		cluster, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.cluster")
		if !ok {
			continue
		}

		family, ok := node.Latest.Lookup(docker.LabelPrefix + "com.amazonaws.ecs.task-definition-family")
		if !ok {
			continue
		}

		taskMap, ok := results[cluster]
		if !ok {
			taskMap = map[string]*taskLabelInfo{}
			results[cluster] = taskMap
		}

		task, ok := taskMap[taskArn]
		if !ok {
			task = &taskLabelInfo{containerIDs: []string{}, family: family}
			taskMap[taskArn] = task
		}

		task.containerIDs = append(task.containerIDs, nodeID)
	}
	log.Debug("Got ECS container info: %v", results)
	return results
}

// Reporter implements Tagger, Reporter
type Reporter struct {
	clientsByCluster map[string]*ecsClient
}

// Make creates a new Reporter
func Make() Reporter {
	return Reporter{
		clientsByCluster: map[string]*ecsClient{},
	}
}

// Tag needed for Tagger
func (r Reporter) Tag(rpt report.Report) (report.Report, error) {
	rpt = rpt.Copy()

	clusterMap := getLabelInfo(rpt)

	for cluster, taskMap := range clusterMap {
		log.Debugf("Fetching ECS info for cluster %v with %v tasks", cluster, len(taskMap))

		client, ok := r.clientsByCluster[cluster]
		if !ok {
			log.Debugf("Creating new ECS client")
			var err error
			client, err = newClient(cluster)
			if err != nil {
				return rpt, err
			}
			r.clientsByCluster[cluster] = client
		}

		taskArns := make([]string, 0, len(taskMap))
		for taskArn := range taskMap {
			taskArns = append(taskArns, taskArn)
		}

		ecsInfo := client.getInfo(taskArns)
		log.Debugf("Got info from ECS: %d tasks, %d services", len(ecsInfo.tasks), len(ecsInfo.services))

		// Create all the services first
		for serviceName, service := range ecsInfo.services {
			serviceID := report.MakeECSServiceNodeID(serviceName)
			rpt.ECSService = rpt.ECSService.AddNode(report.MakeNodeWith(serviceID, map[string]string{
				Cluster:             cluster,
				ServiceDesiredCount: fmt.Sprintf("%d", service.desiredCount),
				ServiceRunningCount: fmt.Sprintf("%d", service.runningCount),
			}))
		}
		log.Debugf("Created %v ECS service nodes", len(ecsInfo.services))

		for taskArn, info := range taskMap {
			task, ok := ecsInfo.tasks[taskArn]
			if !ok {
				// can happen due to partial failures, just skip it
				continue
			}

			// new task node
			taskID := report.MakeECSTaskNodeID(taskArn)
			node := report.MakeNodeWith(taskID, map[string]string{
				TaskFamily: info.family,
				Cluster:    cluster,
				CreatedAt:  task.createdAt.Format(time.RFC3339Nano),
			})
			rpt.ECSTask = rpt.ECSTask.AddNode(node)

			// parents sets to merge into all matching container nodes
			parentsSets := report.MakeSets()
			parentsSets = parentsSets.Add(report.ECSTask, report.MakeStringSet(taskID))
			if serviceName, ok := ecsInfo.taskServiceMap[taskArn]; ok {
				serviceID := report.MakeECSServiceNodeID(serviceName)
				parentsSets = parentsSets.Add(report.ECSService, report.MakeStringSet(serviceID))
			}
			for _, containerID := range info.containerIDs {
				if containerNode, ok := rpt.Container.Nodes[containerID]; ok {
					rpt.Container.Nodes[containerID] = containerNode.WithParents(parentsSets)
				} else {
					log.Warnf("Got task info for non-existent container %v, this shouldn't be able to happen", containerID)
				}
			}
		}

	}

	return rpt, nil
}

// Report needed for Reporter
func (Reporter) Report() (report.Report, error) {
	result := report.MakeReport()
	taskTopology := report.MakeTopology().WithMetadataTemplates(taskMetadata)
	result.ECSTask = result.ECSTask.Merge(taskTopology)
	serviceTopology := report.MakeTopology().WithMetadataTemplates(serviceMetadata)
	result.ECSService = result.ECSService.Merge(serviceTopology)
	return result, nil
}

// Name needed for Tagger, Reporter
func (r Reporter) Name() string {
	return "awsecs"
}
<|endoftext|>


<commit_before>// Sample
package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/m-lab/etl/metrics"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	// Enable profiling. For more background and usage information, see:
	//   https://blog.golang.org/profiling-go-programs
	_ "net/http/pprof"
)

// Task Queue can always submit to an admin restricted URL.
//   login: admin
// Return 200 status code.
// Track reqeusts that last longer than 24 hrs.
// Is task handling idempotent?

// Useful headers added by AppEngine when sending Tasks via Push.
//   X-AppEngine-QueueName
//   X-AppEngine-TaskETA
//   X-AppEngine-TaskName
//   X-AppEngine-TaskRetryCount
//   X-AppEngine-TaskExecutionCount

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	fmt.Fprint(w, "Hello world!")
}

func worker(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	for key, value := range r.Form {
		log.Printf("%q == %q\n", key, value)
	}

	log.Printf("Received filename: %q\n", r.FormValue("filename"))

	// TODO(dev): Remove fake delay.
	t := 10 * rand.ExpFloat64()
	log.Printf("Simulating work by sleeping for %f seconds\n", t)
	time.Sleep(t)

	fmt.Fprintf(w, `{"message": "Success"}`)
}

func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(soltesz): provide a real health check.
	fmt.Fprint(w, "ok")
}

func main() {
	http.HandleFunc("/", handler)
	http.HandleFunc("/worker", metrics.DurationHandler("generic", worker))
	http.HandleFunc("/_ah/health", healthCheckHandler)

	// Assign the default prometheus handler to the standard exporter path.
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8080", nil)
}
<commit_msg>Type case the sleep duration.<commit_after>// Sample
package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/m-lab/etl/metrics"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	// Enable profiling. For more background and usage information, see:
	//   https://blog.golang.org/profiling-go-programs
	_ "net/http/pprof"
)

// Task Queue can always submit to an admin restricted URL.
//   login: admin
// Return 200 status code.
// Track reqeusts that last longer than 24 hrs.
// Is task handling idempotent?

// Useful headers added by AppEngine when sending Tasks via Push.
//   X-AppEngine-QueueName
//   X-AppEngine-TaskETA
//   X-AppEngine-TaskName
//   X-AppEngine-TaskRetryCount
//   X-AppEngine-TaskExecutionCount

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	fmt.Fprint(w, "Hello world!")
}

func worker(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	for key, value := range r.Form {
		log.Printf("%q == %q\n", key, value)
	}

	log.Printf("Received filename: %q\n", r.FormValue("filename"))

	// TODO(dev): Remove fake delay.
	t := 10 * rand.ExpFloat64()
	log.Printf("Simulating work by sleeping for %f seconds\n", t)
	time.Sleep(time.Duration(t))

	fmt.Fprintf(w, `{"message": "Success"}`)
}

func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(soltesz): provide a real health check.
	fmt.Fprint(w, "ok")
}

func main() {
	http.HandleFunc("/", handler)
	http.HandleFunc("/worker", metrics.DurationHandler("generic", worker))
	http.HandleFunc("/_ah/health", healthCheckHandler)

	// Assign the default prometheus handler to the standard exporter path.
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package daemon

import (
	"context"
	"time"

	"github.com/pkg/errors"

	"google.golang.org/grpc"

	"github.com/mutagen-io/mutagen/cmd"
	"github.com/mutagen-io/mutagen/pkg/daemon"
	"github.com/mutagen-io/mutagen/pkg/grpcutil"
	"github.com/mutagen-io/mutagen/pkg/ipc"
	"github.com/mutagen-io/mutagen/pkg/mutagen"
	daemonsvc "github.com/mutagen-io/mutagen/pkg/service/daemon"
)

const (
	// dialTimeout is the timeout to use when attempting to connect to the
	// daemon IPC endpoint.
	dialTimeout = 100 * time.Millisecond
	// autostartWaitInterval is the wait period between reconnect attempts after
	// autostarting the daemon.
	autostartWaitInterval = 100 * time.Millisecond
	// autostartRetryCount is the number of times to try reconnecting after
	// autostarting the daemon.
	autostartRetryCount = 10
)

// CreateClientConnection creates a new daemon client connection and optionally
// verifies that the daemon version matches the current process' version.
func CreateClientConnection(autostart, enforceVersionMatch bool) (*grpc.ClientConn, error) {
	// Compute the path to the daemon IPC endpoint.
	endpoint, err := daemon.EndpointPath()
	if err != nil {
		return nil, errors.Wrap(err, "unable to compute endpoint path")
	}

	// Check if autostart has been globally disabled.
	if daemon.AutostartDisabled {
		autostart = false
	}

	// Create a status line printer and defer a clear.
	statusLinePrinter := &cmd.StatusLinePrinter{}
	defer statusLinePrinter.BreakIfNonEmpty()

	// Perform dialing in a loop until failure or success.
	remainingPostAutostatAttempts := autostartRetryCount
	invokedStart := false
	var connection *grpc.ClientConn
	for {
		// Create a context to timeout the dial.
		dialContext, dialCancel := context.WithTimeout(context.Background(), dialTimeout)

		// Attempt to dial.
		connection, err = grpc.DialContext(
			dialContext, endpoint,
			grpc.WithInsecure(),
			grpc.WithContextDialer(ipc.DialContext),
			grpc.WithBlock(),
			grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcutil.MaximumMessageSize)),
			grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutil.MaximumMessageSize)),
		)

		// Cancel the dialing context. If the dialing operation has already
		// succeeded, this has no effect, but it is necessary to clean up the
		// Goroutine that backs the context.
		dialCancel()

		// Check for errors.
		if err != nil {
			// Handle failure due to timeouts.
			if err == context.DeadlineExceeded {
				// If autostart is enabled, and we have attempts remaining, then
				// try autostarting, waiting, and retrying.
				if autostart && remainingPostAutostatAttempts > 0 {
					if !invokedStart {
						statusLinePrinter.Print("Attempting to start Mutagen daemon...")
						startMain(nil, nil)
						invokedStart = true
					}
					time.Sleep(autostartWaitInterval)
					remainingPostAutostatAttempts--
					continue
				}

				// Otherwise just fail due to the timeout.
				return nil, errors.New("connection timed out (is the daemon running?)")
			}

			// If we failed for any other reason, then bail.
			return nil, err
		}

		// Print a notice if we started the daemon.
		if invokedStart {
			statusLinePrinter.Clear()
			statusLinePrinter.Print("Started Mutagen daemon in background (terminate with \"mutagen daemon stop\")")
		}

		// We've successfully dialed, so break out of the dialing loop.
		break
	}

	// If requested, verify that the daemon version matches the current process'
	// version.
	if enforceVersionMatch {
		daemonService := daemonsvc.NewDaemonClient(connection)
		version, err := daemonService.Version(context.Background(), &daemonsvc.VersionRequest{})
		if err != nil {
			connection.Close()
			return nil, errors.Wrap(err, "unable to query daemon version")
		}
		versionMatch := version.Major == mutagen.VersionMajor &&
			version.Minor == mutagen.VersionMinor &&
			version.Patch == mutagen.VersionPatch &&
			version.Tag == mutagen.VersionTag
		if !versionMatch {
			connection.Close()
			return nil, errors.New("client/daemon version mismatch (daemon restart recommended)")
		}
	}

	// Success.
	return connection, nil
}
<commit_msg>Switched daemon autostart messages to print to standard error.<commit_after>package daemon

import (
	"context"
	"time"

	"github.com/pkg/errors"

	"google.golang.org/grpc"

	"github.com/mutagen-io/mutagen/cmd"
	"github.com/mutagen-io/mutagen/pkg/daemon"
	"github.com/mutagen-io/mutagen/pkg/grpcutil"
	"github.com/mutagen-io/mutagen/pkg/ipc"
	"github.com/mutagen-io/mutagen/pkg/mutagen"
	daemonsvc "github.com/mutagen-io/mutagen/pkg/service/daemon"
)

const (
	// dialTimeout is the timeout to use when attempting to connect to the
	// daemon IPC endpoint.
	dialTimeout = 100 * time.Millisecond
	// autostartWaitInterval is the wait period between reconnect attempts after
	// autostarting the daemon.
	autostartWaitInterval = 100 * time.Millisecond
	// autostartRetryCount is the number of times to try reconnecting after
	// autostarting the daemon.
	autostartRetryCount = 10
)

// CreateClientConnection creates a new daemon client connection and optionally
// verifies that the daemon version matches the current process' version.
func CreateClientConnection(autostart, enforceVersionMatch bool) (*grpc.ClientConn, error) {
	// Compute the path to the daemon IPC endpoint.
	endpoint, err := daemon.EndpointPath()
	if err != nil {
		return nil, errors.Wrap(err, "unable to compute endpoint path")
	}

	// Check if autostart has been globally disabled.
	if daemon.AutostartDisabled {
		autostart = false
	}

	// Create a status line printer and defer a clear.
	statusLinePrinter := &cmd.StatusLinePrinter{UseStandardError: true}
	defer statusLinePrinter.BreakIfNonEmpty()

	// Perform dialing in a loop until failure or success.
	remainingPostAutostatAttempts := autostartRetryCount
	invokedStart := false
	var connection *grpc.ClientConn
	for {
		// Create a context to timeout the dial.
		dialContext, dialCancel := context.WithTimeout(context.Background(), dialTimeout)

		// Attempt to dial.
		connection, err = grpc.DialContext(
			dialContext, endpoint,
			grpc.WithInsecure(),
			grpc.WithContextDialer(ipc.DialContext),
			grpc.WithBlock(),
			grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcutil.MaximumMessageSize)),
			grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutil.MaximumMessageSize)),
		)

		// Cancel the dialing context. If the dialing operation has already
		// succeeded, this has no effect, but it is necessary to clean up the
		// Goroutine that backs the context.
		dialCancel()

		// Check for errors.
		if err != nil {
			// Handle failure due to timeouts.
			if err == context.DeadlineExceeded {
				// If autostart is enabled, and we have attempts remaining, then
				// try autostarting, waiting, and retrying.
				if autostart && remainingPostAutostatAttempts > 0 {
					if !invokedStart {
						statusLinePrinter.Print("Attempting to start Mutagen daemon...")
						startMain(nil, nil)
						invokedStart = true
					}
					time.Sleep(autostartWaitInterval)
					remainingPostAutostatAttempts--
					continue
				}

				// Otherwise just fail due to the timeout.
				return nil, errors.New("connection timed out (is the daemon running?)")
			}

			// If we failed for any other reason, then bail.
			return nil, err
		}

		// Print a notice if we started the daemon.
		if invokedStart {
			statusLinePrinter.Clear()
			statusLinePrinter.Print("Started Mutagen daemon in background (terminate with \"mutagen daemon stop\")")
		}

		// We've successfully dialed, so break out of the dialing loop.
		break
	}

	// If requested, verify that the daemon version matches the current process'
	// version.
	if enforceVersionMatch {
		daemonService := daemonsvc.NewDaemonClient(connection)
		version, err := daemonService.Version(context.Background(), &daemonsvc.VersionRequest{})
		if err != nil {
			connection.Close()
			return nil, errors.Wrap(err, "unable to query daemon version")
		}
		versionMatch := version.Major == mutagen.VersionMajor &&
			version.Minor == mutagen.VersionMinor &&
			version.Patch == mutagen.VersionPatch &&
			version.Tag == mutagen.VersionTag
		if !versionMatch {
			connection.Close()
			return nil, errors.New("client/daemon version mismatch (daemon restart recommended)")
		}
	}

	// Success.
	return connection, nil
}
<|endoftext|>


<commit_before>// Code generated by 'go generate'
// source: fmts/gendoc.go
// DO NOT EDIT!
// Please run '$ go generate ./...' instead to update this file

// Package fmts holds defined errorformats.
//
// Defined formats:
//
// 	ansible
// 		ansible-lint	(ansible-lint -p playbook.yml) Checks playbooks for practices and behaviour that could potentially be improved - https://github.com/ansible/ansible-lint
// 	csharp
// 		dotnet	(dotnet build -clp:NoSummary -p:GenerateFullPaths=true --no-incremental --nologo -v q) .NET Core CLI - https://docs.microsoft.com/en-us/dotnet/core/tools/
// 		msbuild	(msbuild /property:GenerateFullPaths=true /nologo /v:q) Microsoft Build Engine - https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
// 	css
// 		stylelint	A mighty modern CSS linter - https://github.com/stylelint/stylelint
// 	env
// 		dotenv-linter	Linter for .env files - https://github.com/mgrachev/dotenv-linter
// 	go
// 		go-consistent	Source code analyzer that helps you to make your Go programs more consistent - https://github.com/quasilyte/go-consistent
// 		golangci-lint	(golangci-lint run --out-format=line-number) GolangCI-Lint is a linters aggregator. - https://github.com/golangci/golangci-lint
// 		golint	linter for Go source code - https://github.com/golang/lint
// 		gosec	(gosec -fmt=golint) Golang Security Checker - https://github.com/securego/gosec
// 		govet	Vet examines Go source code and reports suspicious problems - https://golang.org/cmd/vet/
// 	haml
// 		haml-lint	Tool for writing clean and consistent HAML - https://github.com/sds/haml-lint
// 	haskell
// 		hlint	Linter for Haskell source code - https://github.com/ndmitchell/hlint
// 	javascript
// 		eslint	(eslint [-f stylish]) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		eslint-compact	(eslint -f compact) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		standardjs	(standard) JavaScript style guide, linter, and formatter - https://github.com/standard/standard
// 	php
// 		phpstan	(phpstan --error-format=raw) PHP Static Analysis Tool - discover bugs in your code without running it! - https://github.com/phpstan/phpstan
// 		psalm	(psalm --output-format=text) Psalm is a static analysis tool for finding errors in PHP - https://github.com/vimeo/psalm
// 	puppet
// 		puppet-lint	Check that your Puppet manifests conform to the style guide - https://github.com/rodjek/puppet-lint
// 	python
// 		pep8	Python style guide checker - https://pypi.python.org/pypi/pep8
// 	ruby
// 		brakeman	(brakeman --quiet --format tabs) A static analysis security vulnerability scanner for Ruby on Rails applications - https://github.com/presidentbeef/brakeman
// 		fasterer	Speed improvements suggester - https://github.com/DamirSvrtan/fasterer
// 		reek	(reek --single-line) Code smell detector for Ruby - https://github.com/troessner/reek
// 		rubocop	A Ruby static code analyzer, based on the community Ruby style guide - https://github.com/rubocop-hq/rubocop
// 		sorbet	A fast, powerful type checker designed for Ruby - https://github.com/sorbet/sorbet
// 		standardrb	(standard) Ruby style guide, linter, and formatter - https://github.com/testdouble/standard
// 	rust
// 		cargo-check	(cargo check -q --message-format=short) Check a local package and all of its dependencies for errors - https://github.com/rust-lang/cargo
// 		clippy	(cargo clippy -q --message-format=short) A bunch of lints to catch common mistakes and improve your Rust code - https://github.com/rust-lang/rust-clippy
// 	scala
// 		sbt	the interactive build tool - http://www.scala-sbt.org/
// 		sbt-scalastyle	Scalastyle - SBT plugin - http://www.scalastyle.org/sbt.html
// 		scalac	Scala compiler - http://www.scala-lang.org/
// 		scalastyle	Scalastyle - Command line - http://www.scalastyle.org/command-line.html
// 	slim
// 		slim-lint	Tool to help keep your Slim files clean and readable - https://github.com/sds/slim-lint
// 	typescript
// 		tsc	TypeScript compiler - https://www.typescriptlang.org/
// 		tslint	An extensible linter for the TypeScript language - https://github.com/palantir/tslint
package fmts
<commit_msg>Update doc.go<commit_after>// Code generated by 'go generate'
// source: fmts/gendoc.go
// DO NOT EDIT!
// Please run '$ go generate ./...' instead to update this file

// Package fmts holds defined errorformats.
//
// Defined formats:
//
// 	ansible
// 		ansible-lint	(ansible-lint -p playbook.yml) Checks playbooks for practices and behaviour that could potentially be improved - https://github.com/ansible/ansible-lint
// 	csharp
// 		dotnet	(dotnet build -clp:NoSummary -p:GenerateFullPaths=true --no-incremental --nologo -v q) .NET Core CLI - https://docs.microsoft.com/en-us/dotnet/core/tools/
// 		msbuild	(msbuild /property:GenerateFullPaths=true /nologo /v:q) Microsoft Build Engine - https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
// 	css
// 		stylelint	A mighty modern CSS linter - https://github.com/stylelint/stylelint
// 	env
// 		dotenv-linter	Lightning-fast linter for .env files. Written in Rust - https://github.com/dotenv-linter/dotenv-linter
// 	go
// 		go-consistent	Source code analyzer that helps you to make your Go programs more consistent - https://github.com/quasilyte/go-consistent
// 		golangci-lint	(golangci-lint run --out-format=line-number) GolangCI-Lint is a linters aggregator. - https://github.com/golangci/golangci-lint
// 		golint	linter for Go source code - https://github.com/golang/lint
// 		gosec	(gosec -fmt=golint) Golang Security Checker - https://github.com/securego/gosec
// 		govet	Vet examines Go source code and reports suspicious problems - https://golang.org/cmd/vet/
// 	haml
// 		haml-lint	Tool for writing clean and consistent HAML - https://github.com/sds/haml-lint
// 	haskell
// 		hlint	Linter for Haskell source code - https://github.com/ndmitchell/hlint
// 	javascript
// 		eslint	(eslint [-f stylish]) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		eslint-compact	(eslint -f compact) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		standardjs	(standard) JavaScript style guide, linter, and formatter - https://github.com/standard/standard
// 	php
// 		phpstan	(phpstan --error-format=raw) PHP Static Analysis Tool - discover bugs in your code without running it! - https://github.com/phpstan/phpstan
// 		psalm	(psalm --output-format=text) Psalm is a static analysis tool for finding errors in PHP - https://github.com/vimeo/psalm
// 	puppet
// 		puppet-lint	Check that your Puppet manifests conform to the style guide - https://github.com/rodjek/puppet-lint
// 	python
// 		pep8	Python style guide checker - https://pypi.python.org/pypi/pep8
// 	ruby
// 		brakeman	(brakeman --quiet --format tabs) A static analysis security vulnerability scanner for Ruby on Rails applications - https://github.com/presidentbeef/brakeman
// 		fasterer	Speed improvements suggester - https://github.com/DamirSvrtan/fasterer
// 		reek	(reek --single-line) Code smell detector for Ruby - https://github.com/troessner/reek
// 		rubocop	A Ruby static code analyzer, based on the community Ruby style guide - https://github.com/rubocop-hq/rubocop
// 		sorbet	A fast, powerful type checker designed for Ruby - https://github.com/sorbet/sorbet
// 		standardrb	(standard) Ruby style guide, linter, and formatter - https://github.com/testdouble/standard
// 	rust
// 		cargo-check	(cargo check -q --message-format=short) Check a local package and all of its dependencies for errors - https://github.com/rust-lang/cargo
// 		clippy	(cargo clippy -q --message-format=short) A bunch of lints to catch common mistakes and improve your Rust code - https://github.com/rust-lang/rust-clippy
// 	scala
// 		sbt	the interactive build tool - http://www.scala-sbt.org/
// 		sbt-scalastyle	Scalastyle - SBT plugin - http://www.scalastyle.org/sbt.html
// 		scalac	Scala compiler - http://www.scala-lang.org/
// 		scalastyle	Scalastyle - Command line - http://www.scalastyle.org/command-line.html
// 	slim
// 		slim-lint	Tool to help keep your Slim files clean and readable - https://github.com/sds/slim-lint
// 	typescript
// 		tsc	TypeScript compiler - https://www.typescriptlang.org/
// 		tslint	An extensible linter for the TypeScript language - https://github.com/palantir/tslint
package fmts
<|endoftext|>


<commit_before>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command pxeboot implements PXE-based booting.
//
// pxeboot combines a DHCP client with a TFTP/HTTP client to download files as
// well as pxelinux and iPXE configuration file parsing.
//
// PXE-based booting requests a DHCP lease, and looks at the BootFileName and
// ServerName options (which may be embedded in the original BOOTP message, or
// as option codes) to find something to boot.
//
// This BootFileName may point to
//
// - an iPXE script beginning with #!ipxe
//
// - a pxelinux.0, in which case we will ignore the pxelinux and try to parse
//   pxelinux.cfg/<files>
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/dhclient"
	"github.com/u-root/u-root/pkg/netboot"
	"github.com/u-root/u-root/pkg/urlfetch"
)

var (
	noLoad  = flag.Bool("no-load", false, "get DHCP response, but don't load the kernel")
	dryRun  = flag.Bool("dry-run", false, "download kernel, but don't kexec it")
	verbose = flag.Bool("v", false, "Verbose output")
)

const (
	dhcpTimeout = 5 * time.Second
	dhcpTries   = 3
)

// Netboot boots all interfaces matched by the regex in ifaceNames.
func Netboot(ifaceNames string) error {
	filteredIfs, err := dhclient.Interfaces(ifaceNames)
	if err != nil {
		return err
	}

	ctx, cancel := context.WithTimeout(context.Background(), (1<<dhcpTries)*dhcpTimeout)
	defer cancel()

	c := dhclient.Config{
		Timeout: dhcpTimeout,
		Retries: dhcpTries,
	}
	if *verbose {
		c.LogLevel = dhclient.LogSummary
	}
	r := dhclient.SendRequests(ctx, filteredIfs, true, true, c)

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()

		case result, ok := <-r:
			if !ok {
				log.Printf("Configured all interfaces.")
				return fmt.Errorf("nothing bootable found")
			}
			if result.Err != nil {
				continue
			}

			if err := result.Lease.Configure(); err != nil {
				log.Printf("Failed to configure lease %s: %v", result.Lease, err)
				continue
			}
			img, err := netboot.BootImage(urlfetch.DefaultSchemes, result.Lease)
			if err != nil {
				log.Printf("Failed to boot lease %v: %v", result.Lease, err)
				continue
			}

			// Cancel other DHCP requests in flight.
			cancel()
			log.Printf("Got configuration: %s", img)

			if *noLoad {
				return nil
			}
			if err := img.Load(*dryRun); err != nil {
				return fmt.Errorf("kexec load of %v failed: %v", img, err)
			}
			if *dryRun {
				return nil
			}
			if err := boot.Execute(); err != nil {
				return fmt.Errorf("kexec of %v failed: %v", img, err)
			}

			// Kexec should either return an error or not return.
			panic("unreachable")
		}
	}
}

func main() {
	flag.Parse()

	if err := Netboot("eth0"); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Allow pxeboot to use any ethernet<commit_after>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command pxeboot implements PXE-based booting.
//
// pxeboot combines a DHCP client with a TFTP/HTTP client to download files as
// well as pxelinux and iPXE configuration file parsing.
//
// PXE-based booting requests a DHCP lease, and looks at the BootFileName and
// ServerName options (which may be embedded in the original BOOTP message, or
// as option codes) to find something to boot.
//
// This BootFileName may point to
//
// - an iPXE script beginning with #!ipxe
//
// - a pxelinux.0, in which case we will ignore the pxelinux and try to parse
//   pxelinux.cfg/<files>
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/dhclient"
	"github.com/u-root/u-root/pkg/netboot"
	"github.com/u-root/u-root/pkg/urlfetch"
)

var (
	ifName  = "^e.*"
	noLoad  = flag.Bool("no-load", false, "get DHCP response, but don't load the kernel")
	dryRun  = flag.Bool("dry-run", false, "download kernel, but don't kexec it")
	verbose = flag.Bool("v", false, "Verbose output")
)

const (
	dhcpTimeout = 5 * time.Second
	dhcpTries   = 3
)

// Netboot boots all interfaces matched by the regex in ifaceNames.
func Netboot(ifaceNames string) error {
	filteredIfs, err := dhclient.Interfaces(ifaceNames)
	if err != nil {
		return err
	}

	ctx, cancel := context.WithTimeout(context.Background(), (1<<dhcpTries)*dhcpTimeout)
	defer cancel()

	c := dhclient.Config{
		Timeout: dhcpTimeout,
		Retries: dhcpTries,
	}
	if *verbose {
		c.LogLevel = dhclient.LogSummary
	}
	r := dhclient.SendRequests(ctx, filteredIfs, true, true, c)

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()

		case result, ok := <-r:
			if !ok {
				log.Printf("Configured all interfaces.")
				return fmt.Errorf("nothing bootable found")
			}
			if result.Err != nil {
				continue
			}

			if err := result.Lease.Configure(); err != nil {
				log.Printf("Failed to configure lease %s: %v", result.Lease, err)
				continue
			}
			img, err := netboot.BootImage(urlfetch.DefaultSchemes, result.Lease)
			if err != nil {
				log.Printf("Failed to boot lease %v: %v", result.Lease, err)
				continue
			}

			// Cancel other DHCP requests in flight.
			cancel()
			log.Printf("Got configuration: %s", img)

			if *noLoad {
				return nil
			}
			if err := img.Load(*dryRun); err != nil {
				return fmt.Errorf("kexec load of %v failed: %v", img, err)
			}
			if *dryRun {
				return nil
			}
			if err := boot.Execute(); err != nil {
				return fmt.Errorf("kexec of %v failed: %v", img, err)
			}

			// Kexec should either return an error or not return.
			panic("unreachable")
		}
	}
}

func main() {
	flag.Parse()
	if len(flag.Args()) > 1 {
		log.Fatalf("Only one regexp-style argument is allowed, e.g.: " + ifName)
	}

	if len(flag.Args()) > 0 {
		ifName = flag.Args()[0]
	}

	if err := Netboot(ifName); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>