<commit_before>// Package streams provides robust event streams from Reddit.
//
// This package is not abstract. If you are looking for a simpler, high level
// interface, see graw.
//
// The streams provided by this package will not be deterred like naive
// implementations by a post getting caught in the spam filter, removed by mods,
// the author being shadowbanned, or an author deleting their post. These
// streams are in it to win it.
//
// All of the streams provisioned by this package depend on an api handle from
// the reddit package, and two control channels: one kill signal and one error
// feed.
//
// The kill channel can be shared by multiple streams as long as you signal kill
// by close()ing the channel. Sending data over it will kill an arbitrary one of
// the streams sharing the channel but not all of them.
//
// The error channel will return issues which may be intermittent. They are not
// wrapped, so you can check them against the definitions in the reddit package
// and choose to wait when Reddit is busy or the connection faults, instead of
// failing.
//
// If there is a problem setting up the stream, such as the endpoint being
// invalid, that will be caught in the initial construction of the stream; you
// don't need to worry about that on the error channel.
//
// These streams will consume "intervals" of the Reddit handle given to them.
// Since the reddit handlers are rate limited and do not allow bursts, there is
// essentially a schedule on which they execute requests, and the executions
// will be divided roughly evenly between the goroutines sharing the handle.
// E.g. if you create two user streams which depend on a handle with a rate
// limit of 5 seconds, each of them will be unblocked once every 10 seconds
// (ish), since they each consume one interval, and the interval is 5 seconds.
package streams

import (
	"strings"

	"github.com/turnage/graw/reddit"

	"github.com/turnage/graw/streams/internal/monitor"
	"github.com/turnage/graw/streams/internal/rsort"
)

// Subreddits returns a stream of new posts from the requested subreddits. This
// stream monitors the combination listing of all subreddits using Reddit's "+"
// feature e.g. /r/golang+rust. This will consume one interval of the handle per
// call, so it is best to gather all the subreddits needed and make invoke this
// function once.
//
// Be aware that these posts are unlikely to have comments. If you are
// interested in comment trees, save their permalinks and fetch them later.
func Subreddits(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subreddits ...string,
) (
	<-chan *reddit.Post,
	error,
) {
	path := "/r/" + strings.Join(subreddits, "+") + "/new"
	posts, _, _, err := streamFromPath(scanner, kill, errs, path)
	return posts, err
}

// SubredditComments returns a stream of new comments from the requested
// subreddits. This stream monitors the combination listing of all subreddits
// using Reddit's "+" feature e.g. /r/golang+rust. This will consume one
// interval of the handle per call, so it is best to gather all the subreddits
// needed and make invoke this function once.
//
// Be aware that these comments will not have reply trees. If you are interested
// in comment trees, save the permalinks of their parent posts and fetch them
// later..
func SubredditComments(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subreddits ...string,
) (
	<-chan *reddit.Comment,
	error,
) {
	path := "/r/" + strings.Join(subreddits, "+") + "/comments"
	_, comments, _, err := streamFromPath(scanner, kill, errs, path)
	return comments, err
}

// User returns a stream of new posts and comments made by a user. Each user
// stream consumes one interval of the handle.
func User(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	user string,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	error,
) {
	path := "/u/" + user
	posts, comments, _, err := streamFromPath(scanner, kill, errs, path)
	return posts, comments, err
}

// PostReplies returns a stream of top level replies to posts made by the bot's
// account. This stream consumes one interval of the handle.
func PostReplies(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "selfreply")
}

// CommentReplies returns a stream of replies to comments made by the bot's
// account. This stream consumes one interval of the handle.
func CommentReplies(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "comments")
}

// Mentions returns a stream of mentions of the bot's username anywhere on
// Reddit. It consumes one interval of the handle. Note, that a username mention
// which can reach the inbox in any other way (as a pm, or a reply), will not
// come through the mention stream because Reddit labels it differently.
func Mentions(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "mentions")
}

// Messages returns a stream of messages sent to the bot's inbox. It consumes
// one interval of the handle.
func Messages(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	onlyMessages := make(chan *reddit.Message)

	messages, err := inboxStream(bot, kill, errs, "inbox")
	go func() {
		for m := range messages {
			if !m.WasComment {
				onlyMessages <- m
			}
		}
	}()

	return onlyMessages, err
}

func inboxStream(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subpath string,
) (
	<-chan *reddit.Message,
	error,
) {
	path := "/message/" + subpath
	_, _, messages, err := streamFromPath(scanner, kill, errs, path)
	return messages, err
}

func streamFromPath(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	path string,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	<-chan *reddit.Message,
	error,
) {
	mon, err := monitorFromPath(path, scanner)
	if err != nil {
		return nil, nil, nil, err
	}

	posts, comments, messages := stream(mon, kill, errs)
	return posts, comments, messages, nil
}

func monitorFromPath(path string, sc reddit.Scanner) (monitor.Monitor, error) {
	return monitor.New(
		monitor.Config{
			Path:    path,
			Scanner: sc,
			Sorter:  rsort.New(),
		},
	)
}

func stream(
	mon monitor.Monitor,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	<-chan *reddit.Message,
) {
	posts := make(chan *reddit.Post)
	comments := make(chan *reddit.Comment)
	messages := make(chan *reddit.Message)

	go flow(mon, kill, errs, posts, comments, messages)

	return posts, comments, messages
}

func flow(
	mon monitor.Monitor,
	kill <-chan bool,
	errs chan<- error,
	posts chan<- *reddit.Post,
	comments chan<- *reddit.Comment,
	messages chan<- *reddit.Message,
) {
	for {
		select {
		// if the errors channel is closed, the master goroutine is
		// shutting us down.
		case <-kill:
			close(posts)
			close(comments)
			close(messages)
			return
		default:
			if h, err := mon.Update(); err != nil {
				errs <- err
			} else {
				// lol no generics
				for _, p := range h.Posts {
					posts <- p
				}
				for _, c := range h.Comments {
					comments <- c
				}
				for _, m := range h.Messages {
					messages <- m
				}
			}
		}
	}
}
<commit_msg>Fix comment docs on post and comment streams.<commit_after>// Package streams provides robust event streams from Reddit.
//
// This package is not abstract. If you are looking for a simpler, high level
// interface, see graw.
//
// The streams provided by this package will not be deterred like naive
// implementations by a post getting caught in the spam filter, removed by mods,
// the author being shadowbanned, or an author deleting their post. These
// streams are in it to win it.
//
// All of the streams provisioned by this package depend on an api handle from
// the reddit package, and two control channels: one kill signal and one error
// feed.
//
// The kill channel can be shared by multiple streams as long as you signal kill
// by close()ing the channel. Sending data over it will kill an arbitrary one of
// the streams sharing the channel but not all of them.
//
// The error channel will return issues which may be intermittent. They are not
// wrapped, so you can check them against the definitions in the reddit package
// and choose to wait when Reddit is busy or the connection faults, instead of
// failing.
//
// If there is a problem setting up the stream, such as the endpoint being
// invalid, that will be caught in the initial construction of the stream; you
// don't need to worry about that on the error channel.
//
// These streams will consume "intervals" of the Reddit handle given to them.
// Since the reddit handlers are rate limited and do not allow bursts, there is
// essentially a schedule on which they execute requests, and the executions
// will be divided roughly evenly between the goroutines sharing the handle.
// E.g. if you create two user streams which depend on a handle with a rate
// limit of 5 seconds, each of them will be unblocked once every 10 seconds
// (ish), since they each consume one interval, and the interval is 5 seconds.
package streams

import (
	"strings"

	"github.com/turnage/graw/reddit"

	"github.com/turnage/graw/streams/internal/monitor"
	"github.com/turnage/graw/streams/internal/rsort"
)

// Subreddits returns a stream of new posts from the requested subreddits. This
// stream monitors the combination listing of all subreddits using Reddit's "+"
// feature e.g. /r/golang+rust. This will consume one interval of the handle per
// call, so it is best to gather all the subreddits needed and invoke this
// function once.
//
// Be aware that these posts are new and will not have comments. If you are
// interested in comment trees, save their permalinks and fetch them later.
func Subreddits(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subreddits ...string,
) (
	<-chan *reddit.Post,
	error,
) {
	path := "/r/" + strings.Join(subreddits, "+") + "/new"
	posts, _, _, err := streamFromPath(scanner, kill, errs, path)
	return posts, err
}

// SubredditComments returns a stream of new comments from the requested
// subreddits. This stream monitors the combination listing of all subreddits
// using Reddit's "+" feature e.g. /r/golang+rust. This will consume one
// interval of the handle per call, so it is best to gather all the subreddits
// needed and invoke this function once.
//
// Be aware that these comments are new, and will not have reply trees. If you
// are interested in comment trees, save the permalinks of their parent posts
// and fetch them later once they may have had activity.
func SubredditComments(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subreddits ...string,
) (
	<-chan *reddit.Comment,
	error,
) {
	path := "/r/" + strings.Join(subreddits, "+") + "/comments"
	_, comments, _, err := streamFromPath(scanner, kill, errs, path)
	return comments, err
}

// User returns a stream of new posts and comments made by a user. Each user
// stream consumes one interval of the handle.
func User(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	user string,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	error,
) {
	path := "/u/" + user
	posts, comments, _, err := streamFromPath(scanner, kill, errs, path)
	return posts, comments, err
}

// PostReplies returns a stream of top level replies to posts made by the bot's
// account. This stream consumes one interval of the handle.
func PostReplies(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "selfreply")
}

// CommentReplies returns a stream of replies to comments made by the bot's
// account. This stream consumes one interval of the handle.
func CommentReplies(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "comments")
}

// Mentions returns a stream of mentions of the bot's username anywhere on
// Reddit. It consumes one interval of the handle. Note, that a username mention
// which can reach the inbox in any other way (as a pm, or a reply), will not
// come through the mention stream because Reddit labels it differently.
func Mentions(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	return inboxStream(bot, kill, errs, "mentions")
}

// Messages returns a stream of messages sent to the bot's inbox. It consumes
// one interval of the handle.
func Messages(
	bot reddit.Bot,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Message,
	error,
) {
	onlyMessages := make(chan *reddit.Message)

	messages, err := inboxStream(bot, kill, errs, "inbox")
	go func() {
		for m := range messages {
			if !m.WasComment {
				onlyMessages <- m
			}
		}
	}()

	return onlyMessages, err
}

func inboxStream(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	subpath string,
) (
	<-chan *reddit.Message,
	error,
) {
	path := "/message/" + subpath
	_, _, messages, err := streamFromPath(scanner, kill, errs, path)
	return messages, err
}

func streamFromPath(
	scanner reddit.Scanner,
	kill <-chan bool,
	errs chan<- error,
	path string,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	<-chan *reddit.Message,
	error,
) {
	mon, err := monitorFromPath(path, scanner)
	if err != nil {
		return nil, nil, nil, err
	}

	posts, comments, messages := stream(mon, kill, errs)
	return posts, comments, messages, nil
}

func monitorFromPath(path string, sc reddit.Scanner) (monitor.Monitor, error) {
	return monitor.New(
		monitor.Config{
			Path:    path,
			Scanner: sc,
			Sorter:  rsort.New(),
		},
	)
}

func stream(
	mon monitor.Monitor,
	kill <-chan bool,
	errs chan<- error,
) (
	<-chan *reddit.Post,
	<-chan *reddit.Comment,
	<-chan *reddit.Message,
) {
	posts := make(chan *reddit.Post)
	comments := make(chan *reddit.Comment)
	messages := make(chan *reddit.Message)

	go flow(mon, kill, errs, posts, comments, messages)

	return posts, comments, messages
}

func flow(
	mon monitor.Monitor,
	kill <-chan bool,
	errs chan<- error,
	posts chan<- *reddit.Post,
	comments chan<- *reddit.Comment,
	messages chan<- *reddit.Message,
) {
	for {
		select {
		// if the errors channel is closed, the master goroutine is
		// shutting us down.
		case <-kill:
			close(posts)
			close(comments)
			close(messages)
			return
		default:
			if h, err := mon.Update(); err != nil {
				errs <- err
			} else {
				// lol no generics
				for _, p := range h.Posts {
					posts <- p
				}
				for _, c := range h.Comments {
					comments <- c
				}
				for _, m := range h.Messages {
					messages <- m
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>// The chroot package is able to create an Amazon AMI without requiring
// the launch of a new instance for every build. It does this by attaching
// and mounting the root volume of another AMI and chrooting into that
// directory. It then creates an AMI from that attached drive.
package chroot

import (
	"github.com/mitchellh/goamz/ec2"
	"github.com/mitchellh/multistep"
	awscommon "github.com/mitchellh/packer/builder/amazon/common"
	"github.com/mitchellh/packer/builder/common"
	"github.com/mitchellh/packer/packer"
	"log"
)

// The unique ID for this builder
const BuilderId = "mitchellh.amazon.chroot"

// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
	common.PackerConfig    `mapstructure:",squash"`
	awscommon.AccessConfig `mapstructure:",squash"`

	SourceAmi string `mapstructure:"source_ami"`
}

type Builder struct {
	config Config
	runner multistep.Runner
}

func (b *Builder) Prepare(raws ...interface{}) error {
	md, err := common.DecodeConfig(&b.config, raws...)
	if err != nil {
		return err
	}

	// Defaults

	// Accumulate any errors
	errs := common.CheckUnusedConfig(md)
	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}

	log.Printf("Config: %+v", b.config)
	return nil
}

func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
	region, err := b.config.Region()
	if err != nil {
		return nil, err
	}

	auth, err := b.config.AccessConfig.Auth()
	if err != nil {
		return nil, err
	}

	ec2conn := ec2.New(auth, region)

	// Setup the state bag and initial state for the steps
	state := make(map[string]interface{})
	state["config"] = &b.config
	state["ec2"] = ec2conn
	state["hook"] = hook
	state["ui"] = ui

	// Build the steps
	steps := []multistep.Step{
		&StepInstanceInfo{},
		&StepSourceAMIInfo{},
		&StepCreateVolume{},
		&StepAttachVolume{},
	}

	// Run!
	if b.config.PackerDebug {
		b.runner = &multistep.DebugRunner{
			Steps:   steps,
			PauseFn: common.MultistepDebugFn(ui),
		}
	} else {
		b.runner = &multistep.BasicRunner{Steps: steps}
	}

	b.runner.Run(state)

	// If there was an error, return that
	if rawErr, ok := state["error"]; ok {
		return nil, rawErr.(error)
	}

	// If there are no AMIs, then just return
	if _, ok := state["amis"]; !ok {
		return nil, nil
	}

	// Build the artifact and return it
	artifact := &awscommon.Artifact{
		Amis:           state["amis"].(map[string]string),
		BuilderIdValue: BuilderId,
		Conn:           ec2conn,
	}

	return artifact, nil
}

func (b *Builder) Cancel() {
	if b.runner != nil {
		log.Println("Cancelling the step runner...")
		b.runner.Cancel()
	}
}
<commit_msg>builder/amazon/chroot: only let it run on Linux<commit_after>// The chroot package is able to create an Amazon AMI without requiring
// the launch of a new instance for every build. It does this by attaching
// and mounting the root volume of another AMI and chrooting into that
// directory. It then creates an AMI from that attached drive.
package chroot

import (
	"errors"
	"github.com/mitchellh/goamz/ec2"
	"github.com/mitchellh/multistep"
	awscommon "github.com/mitchellh/packer/builder/amazon/common"
	"github.com/mitchellh/packer/builder/common"
	"github.com/mitchellh/packer/packer"
	"log"
	"runtime"
)

// The unique ID for this builder
const BuilderId = "mitchellh.amazon.chroot"

// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
	common.PackerConfig    `mapstructure:",squash"`
	awscommon.AccessConfig `mapstructure:",squash"`

	SourceAmi string `mapstructure:"source_ami"`
}

type Builder struct {
	config Config
	runner multistep.Runner
}

func (b *Builder) Prepare(raws ...interface{}) error {
	md, err := common.DecodeConfig(&b.config, raws...)
	if err != nil {
		return err
	}

	// Defaults

	// Accumulate any errors
	errs := common.CheckUnusedConfig(md)
	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}

	log.Printf("Config: %+v", b.config)
	return nil
}

func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
	if runtime.GOOS != "linux" {
		return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
	}

	region, err := b.config.Region()
	if err != nil {
		return nil, err
	}

	auth, err := b.config.AccessConfig.Auth()
	if err != nil {
		return nil, err
	}

	ec2conn := ec2.New(auth, region)

	// Setup the state bag and initial state for the steps
	state := make(map[string]interface{})
	state["config"] = &b.config
	state["ec2"] = ec2conn
	state["hook"] = hook
	state["ui"] = ui

	// Build the steps
	steps := []multistep.Step{
		&StepInstanceInfo{},
		&StepSourceAMIInfo{},
		&StepCreateVolume{},
		&StepAttachVolume{},
	}

	// Run!
	if b.config.PackerDebug {
		b.runner = &multistep.DebugRunner{
			Steps:   steps,
			PauseFn: common.MultistepDebugFn(ui),
		}
	} else {
		b.runner = &multistep.BasicRunner{Steps: steps}
	}

	b.runner.Run(state)

	// If there was an error, return that
	if rawErr, ok := state["error"]; ok {
		return nil, rawErr.(error)
	}

	// If there are no AMIs, then just return
	if _, ok := state["amis"]; !ok {
		return nil, nil
	}

	// Build the artifact and return it
	artifact := &awscommon.Artifact{
		Amis:           state["amis"].(map[string]string),
		BuilderIdValue: BuilderId,
		Conn:           ec2conn,
	}

	return artifact, nil
}

func (b *Builder) Cancel() {
	if b.runner != nil {
		log.Println("Cancelling the step runner...")
		b.runner.Cancel()
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"os"

	log "github.com/sirupsen/logrus"

	"bufio"
	"regexp"
	"strings"

	"github.com/projectcalico/felix/logutils"
	"github.com/projectcalico/libcalico-go/lib/set"
)

var (
	whitelistedLicenses = set.From(
		"Apache License 2.0",
		"MIT License",
		"ISC License",
		"BSD 3-clause \"New\" or \"Revised\" License",
	)
	whitelistedPkgs = set.FromArray([]pkgInfo{
		// These packages are licensed under the LGPL, which is normally viral and hence
		// incompatible with our licensing! However, they include the linking exception,
		// allowing us to distribute a binary based on them as long as we don't modify them.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/juju/ratelimit",
			license: "GNU Lesser General Public License v3.0 (94%)"},

		// Variants on MIT/BSD; files tend to include updated copyright statement.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/PuerkitoBio/urlesc",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/beorn7/perks/quantile",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gogo/protobuf",
			license: "BSD 3-clause \"New\" or \"Revised\" License (90%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/golang/protobuf",
			license: "BSD 3-clause \"New\" or \"Revised\" License (92%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/golang/protobuf/proto",
			license: "BSD 3-clause \"New\" or \"Revised\" License (92%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gregjones/httpcache/diskcache",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/howeyc/gopass",
			license: "ISC License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/davecgh/go-spew/spew",
			license: "ISC License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/dgrijalva/jwt-go",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/imdario/mergo",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/kardianos/osext",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/kelseyhightower/envconfig",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/mailru/easyjson",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/pborman/uuid",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/peterbourgon/diskv",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/satori/go.uuid",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/spf13/pflag",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/google.golang.org/grpc",
			license: "BSD 3-clause \"New\" or \"Revised\" License (97%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/gopkg.in/inf.v0",
			license: "BSD 3-clause \"New\" or \"Revised\" License (97%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/dustin/go-humanize",
			license: "MIT License (96%)"},

		// Mixed license, Apache and some files under BSD-like.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/ghodss/yaml",
			license: "? (BSD 3-clause \"New\" or \"Revised\" License, 83%)"},

		// Apache license with copyright statement in file.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/Azure/go-autorest/autorest",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gophercloud/gophercloud",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/vishvananda/netlink/nl",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/vishvananda/netns",
			license: "Apache License 2.0 (96%)"},

		// Mozilla Public License.  Note, would prohibit us from ever releasing our code
		// under a *GPL license (if we wanted to do that).
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/hashicorp/golang-lru/simplelru",
			license: "Mozilla Public License 2.0"},
		// Not detected properly - But it's apache license - https://github.com/go-yaml/yaml/blob/v2.2.1/LICENSE
		{pkgName: "github.com/projectcalico/felix/vendor/gopkg.in/yaml.v2",
			license: "? (The Unlicense, 35%)"},
	})
	whitelistedPrefixes = []string{
		// Standard golang BSD-like license.
		"github.com/projectcalico/felix/vendor/golang.org/x/",
	}
)

func main() {
	logutils.ConfigureEarlyLogging()

	wd, _ := os.Getwd()
	log.WithField("PWD", wd).Info("Current directory")
	file, err := os.Open("check-licenses/dependency-licenses.txt") // For read access.
	if err != nil {
		log.WithError(err).Panic("Failed to open licenses file")
	}
	scanner := bufio.NewScanner(file)
	lineRE := regexp.MustCompile(`^(\S+)\s+(\S.*)$`)
	badPackages := []pkgInfo{}
lineLoop:
	for scanner.Scan() {
		line := scanner.Text()
		logCxt := log.WithField("line", line)
		submatches := lineRE.FindStringSubmatch(line)
		if len(submatches) != 3 {
			logCxt.Panic("Expected line to match regex")
		}
		pkgName := submatches[1]
		license := submatches[2]
		logCxt = logCxt.WithFields(log.Fields{
			"pkgName": pkgName,
			"license": license,
		})
		logCxt.Debug("Found package")
		pkgInfo := pkgInfo{
			pkgName: pkgName,
			license: license,
		}
		if strings.HasPrefix(pkgName, "github.com/projectcalico/felix/vendor/github.com/projectcalico/") ||
			(strings.HasPrefix(pkgName, "github.com/projectcalico/") &&
				!strings.Contains(pkgName, "vendor")) {
			logCxt.Info("One of our packages")
			continue
		}
		if whitelistedLicenses.Contains(license) {
			logCxt.Info("License is whitelisted")
			continue
		}
		if whitelistedPkgs.Contains(pkgInfo) {
			logCxt.Info("Package is whitelisted")
			continue
		}
		for _, prefix := range whitelistedPrefixes {
			if strings.HasPrefix(pkgName, prefix) {
				logCxt.WithField("prefix", prefix).Info("Package prefix is whitelisted")
				continue lineLoop
			}
		}
		logCxt.Error("License not whitelisted")
		badPackages = append(badPackages, pkgInfo)
	}

	if len(badPackages) > 0 {
		log.Error("Found bad licenses")
		for _, pkg := range badPackages {
			log.Errorf("\n\nNon-white-listed license:\n  Package: %v\n  License: %v\n", pkg.pkgName, pkg.license)
		}
		log.Info("")
		os.Exit(1)
	}
}

type pkgInfo struct {
	pkgName string
	license string
}
<commit_msg>Fix license warning.<commit_after>// Copyright (c) 2017 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"os"

	log "github.com/sirupsen/logrus"

	"bufio"
	"regexp"
	"strings"

	"github.com/projectcalico/felix/logutils"
	"github.com/projectcalico/libcalico-go/lib/set"
)

var (
	whitelistedLicenses = set.From(
		"Apache License 2.0",
		"MIT License",
		"ISC License",
		"BSD 3-clause \"New\" or \"Revised\" License",
	)
	whitelistedPkgs = set.FromArray([]pkgInfo{
		// These packages are licensed under the LGPL, which is normally viral and hence
		// incompatible with our licensing! However, they include the linking exception,
		// allowing us to distribute a binary based on them as long as we don't modify them.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/juju/ratelimit",
			license: "GNU Lesser General Public License v3.0 (94%)"},

		// Variants on MIT/BSD; files tend to include updated copyright statement.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/PuerkitoBio/urlesc",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/beorn7/perks/quantile",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gogo/protobuf",
			license: "BSD 3-clause \"New\" or \"Revised\" License (90%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/golang/protobuf",
			license: "BSD 3-clause \"New\" or \"Revised\" License (92%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/golang/protobuf/proto",
			license: "BSD 3-clause \"New\" or \"Revised\" License (92%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gregjones/httpcache/diskcache",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/howeyc/gopass",
			license: "ISC License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/davecgh/go-spew/spew",
			license: "ISC License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/dgrijalva/jwt-go",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/imdario/mergo",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/kardianos/osext",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/kelseyhightower/envconfig",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/mailru/easyjson",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/pborman/uuid",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/peterbourgon/diskv",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/satori/go.uuid",
			license: "MIT License (98%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/spf13/pflag",
			license: "BSD 3-clause \"New\" or \"Revised\" License (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/google.golang.org/grpc",
			license: "BSD 3-clause \"New\" or \"Revised\" License (97%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/gopkg.in/inf.v0",
			license: "BSD 3-clause \"New\" or \"Revised\" License (97%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/dustin/go-humanize",
			license: "MIT License (96%)"},

		// Mixed license, Apache and some files under BSD-like.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/ghodss/yaml",
			license: "? (BSD 3-clause \"New\" or \"Revised\" License, 83%)"},

		// Apache license with copyright statement in file.
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/Azure/go-autorest/autorest",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/gophercloud/gophercloud",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/vishvananda/netlink/nl",
			license: "Apache License 2.0 (96%)"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/vishvananda/netns",
			license: "Apache License 2.0 (96%)"},

		// Mozilla Public License.  Note, would prohibit us from ever releasing our code
		// under a *GPL license (if we wanted to do that).
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/hashicorp/golang-lru/simplelru",
			license: "Mozilla Public License 2.0"},
		{pkgName: "github.com/projectcalico/felix/vendor/github.com/hashicorp/go-version",
			license: "Mozilla Public License 2.0"},
		// Not detected properly - But it's apache license - https://github.com/go-yaml/yaml/blob/v2.2.1/LICENSE
		{pkgName: "github.com/projectcalico/felix/vendor/gopkg.in/yaml.v2",
			license: "? (The Unlicense, 35%)"},
	})
	whitelistedPrefixes = []string{
		// Standard golang BSD-like license.
		"github.com/projectcalico/felix/vendor/golang.org/x/",
	}
)

func main() {
	logutils.ConfigureEarlyLogging()

	wd, _ := os.Getwd()
	log.WithField("PWD", wd).Info("Current directory")
	file, err := os.Open("check-licenses/dependency-licenses.txt") // For read access.
	if err != nil {
		log.WithError(err).Panic("Failed to open licenses file")
	}
	scanner := bufio.NewScanner(file)
	lineRE := regexp.MustCompile(`^(\S+)\s+(\S.*)$`)
	badPackages := []pkgInfo{}
lineLoop:
	for scanner.Scan() {
		line := scanner.Text()
		logCxt := log.WithField("line", line)
		submatches := lineRE.FindStringSubmatch(line)
		if len(submatches) != 3 {
			logCxt.Panic("Expected line to match regex")
		}
		pkgName := submatches[1]
		license := submatches[2]
		logCxt = logCxt.WithFields(log.Fields{
			"pkgName": pkgName,
			"license": license,
		})
		logCxt.Debug("Found package")
		pkgInfo := pkgInfo{
			pkgName: pkgName,
			license: license,
		}
		if strings.HasPrefix(pkgName, "github.com/projectcalico/felix/vendor/github.com/projectcalico/") ||
			(strings.HasPrefix(pkgName, "github.com/projectcalico/") &&
				!strings.Contains(pkgName, "vendor")) {
			logCxt.Info("One of our packages")
			continue
		}
		if whitelistedLicenses.Contains(license) {
			logCxt.Info("License is whitelisted")
			continue
		}
		if whitelistedPkgs.Contains(pkgInfo) {
			logCxt.Info("Package is whitelisted")
			continue
		}
		for _, prefix := range whitelistedPrefixes {
			if strings.HasPrefix(pkgName, prefix) {
				logCxt.WithField("prefix", prefix).Info("Package prefix is whitelisted")
				continue lineLoop
			}
		}
		logCxt.Error("License not whitelisted")
		badPackages = append(badPackages, pkgInfo)
	}

	if len(badPackages) > 0 {
		log.Error("Found bad licenses")
		for _, pkg := range badPackages {
			log.Errorf("\n\nNon-white-listed license:\n  Package: %v\n  License: %v\n", pkg.pkgName, pkg.license)
		}
		log.Info("")
		os.Exit(1)
	}
}

type pkgInfo struct {
	pkgName string
	license string
}
<|endoftext|>


<commit_before>package allocdir

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/sys/unix"
)

var notFoundErr = fmt.Errorf("not found")

func isMount(path string) error {
	file, err := os.Open("/proc/self/mounts")
	if err != nil {
		return err
	}
	defer file.Close()
	reader := bufio.NewReaderSize(file, 64*1024)
	const max = 100000
	for i := 0; i < max; i++ {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				return notFoundErr
			}
			return err
		}
		parts := strings.SplitN(line, " ", 3)
		if len(parts) != 3 {
			return fmt.Errorf("unexpected line: %q", line)
		}
		if parts[1] == path {
			// Found it! Make sure it's a tmpfs
			if parts[0] != "tmpfs" {
				return fmt.Errorf("unexpected fs: %q", parts[1])
			}
			return nil
		}
	}
	return fmt.Errorf("exceeded max mount entries (%d)", max)
}

// TestLinuxRootSecretDir asserts secret dir creation and removal are
// idempotent.
func TestLinuxRootSecretDir(t *testing.T) {
	if unix.Geteuid() != 0 {
		t.Skip("Must be run as root")
	}
	tmpdir, err := ioutil.TempDir("", "nomadtest-rootsecretdir")
	if err != nil {
		t.Fatalf("unable to create tempdir for test: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	secretsDir := filepath.Join(tmpdir, TaskSecrets)

	// removing a nonExistent secrets dir should NOT error
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
	// run twice as it should be idemptotent
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}

	// creating a secrets dir should work
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}
	// creating it again should be a noop (NO error)
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}

	// ensure it exists and is a directory
	fi, err := os.Lstat(secretsDir)
	if err != nil {
		t.Fatalf("error stat'ing secrets dir %q: %v", secretsDir, err)
	}
	if !fi.IsDir() {
		t.Fatalf("secrets dir %q is not a directory and should be", secretsDir)
	}
	if err := isMount(secretsDir); err != nil {
		t.Fatalf("secrets dir %q is not a mount: %v", secretsDir, err)
	}

	// now remove it
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing secrets dir %q: %v", secretsDir, err)
	}

	// make sure it's gone
	if err := isMount(secretsDir); err != notFoundErr {
		t.Fatalf("error ensuring secrets dir %q isn't mounted: %v", secretsDir, err)
	}

	// removing again should be a noop
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
}

// TestLinuxUnprivilegedSecretDir asserts secret dir creation and removal are
// idempotent.
func TestLinuxUnprivilegedSecretDir(t *testing.T) {
	if unix.Geteuid() == 0 {
		t.Skip("Must not be run as root")
	}
	tmpdir, err := ioutil.TempDir("", "nomadtest-secretdir")
	if err != nil {
		t.Fatalf("unable to create tempdir for test: %s", err)
	}
	defer os.RemoveAll(tmpdir)

	secretsDir := filepath.Join(tmpdir, TaskSecrets)

	// removing a nonExistent secrets dir should NOT error
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
	// run twice as it should be idemptotent
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}

	// creating a secrets dir should work
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}
	// creating it again should be a noop (NO error)
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}

	// ensure it exists and is a directory
	fi, err := os.Lstat(secretsDir)
	if err != nil {
		t.Fatalf("error stat'ing secrets dir %q: %v", secretsDir, err)
	}
	if !fi.IsDir() {
		t.Fatalf("secrets dir %q is not a directory and should be", secretsDir)
	}
	if err := isMount(secretsDir); err != notFoundErr {
		t.Fatalf("error ensuring secrets dir %q isn't mounted: %v", secretsDir, err)
	}

	// now remove it
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing secrets dir %q: %v", secretsDir, err)
	}

	// make sure it's gone
	if _, err := os.Lstat(secretsDir); err == nil {
		t.Fatalf("expected secrets dir %q to be gone but it was found", secretsDir)
	}

	// removing again should be a noop
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
}
<commit_msg>spelling: idempotent<commit_after>package allocdir

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/sys/unix"
)

var notFoundErr = fmt.Errorf("not found")

func isMount(path string) error {
	file, err := os.Open("/proc/self/mounts")
	if err != nil {
		return err
	}
	defer file.Close()
	reader := bufio.NewReaderSize(file, 64*1024)
	const max = 100000
	for i := 0; i < max; i++ {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				return notFoundErr
			}
			return err
		}
		parts := strings.SplitN(line, " ", 3)
		if len(parts) != 3 {
			return fmt.Errorf("unexpected line: %q", line)
		}
		if parts[1] == path {
			// Found it! Make sure it's a tmpfs
			if parts[0] != "tmpfs" {
				return fmt.Errorf("unexpected fs: %q", parts[1])
			}
			return nil
		}
	}
	return fmt.Errorf("exceeded max mount entries (%d)", max)
}

// TestLinuxRootSecretDir asserts secret dir creation and removal are
// idempotent.
func TestLinuxRootSecretDir(t *testing.T) {
	if unix.Geteuid() != 0 {
		t.Skip("Must be run as root")
	}
	tmpdir, err := ioutil.TempDir("", "nomadtest-rootsecretdir")
	if err != nil {
		t.Fatalf("unable to create tempdir for test: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	secretsDir := filepath.Join(tmpdir, TaskSecrets)

	// removing a nonExistent secrets dir should NOT error
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
	// run twice as it should be idempotent
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}

	// creating a secrets dir should work
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}
	// creating it again should be a noop (NO error)
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}

	// ensure it exists and is a directory
	fi, err := os.Lstat(secretsDir)
	if err != nil {
		t.Fatalf("error stat'ing secrets dir %q: %v", secretsDir, err)
	}
	if !fi.IsDir() {
		t.Fatalf("secrets dir %q is not a directory and should be", secretsDir)
	}
	if err := isMount(secretsDir); err != nil {
		t.Fatalf("secrets dir %q is not a mount: %v", secretsDir, err)
	}

	// now remove it
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing secrets dir %q: %v", secretsDir, err)
	}

	// make sure it's gone
	if err := isMount(secretsDir); err != notFoundErr {
		t.Fatalf("error ensuring secrets dir %q isn't mounted: %v", secretsDir, err)
	}

	// removing again should be a noop
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
}

// TestLinuxUnprivilegedSecretDir asserts secret dir creation and removal are
// idempotent.
func TestLinuxUnprivilegedSecretDir(t *testing.T) {
	if unix.Geteuid() == 0 {
		t.Skip("Must not be run as root")
	}
	tmpdir, err := ioutil.TempDir("", "nomadtest-secretdir")
	if err != nil {
		t.Fatalf("unable to create tempdir for test: %s", err)
	}
	defer os.RemoveAll(tmpdir)

	secretsDir := filepath.Join(tmpdir, TaskSecrets)

	// removing a nonExistent secrets dir should NOT error
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
	// run twice as it should be idempotent
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}

	// creating a secrets dir should work
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}
	// creating it again should be a noop (NO error)
	if err := createSecretDir(secretsDir); err != nil {
		t.Fatalf("error creating secrets dir %q: %v", secretsDir, err)
	}

	// ensure it exists and is a directory
	fi, err := os.Lstat(secretsDir)
	if err != nil {
		t.Fatalf("error stat'ing secrets dir %q: %v", secretsDir, err)
	}
	if !fi.IsDir() {
		t.Fatalf("secrets dir %q is not a directory and should be", secretsDir)
	}
	if err := isMount(secretsDir); err != notFoundErr {
		t.Fatalf("error ensuring secrets dir %q isn't mounted: %v", secretsDir, err)
	}

	// now remove it
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing secrets dir %q: %v", secretsDir, err)
	}

	// make sure it's gone
	if _, err := os.Lstat(secretsDir); err == nil {
		t.Fatalf("expected secrets dir %q to be gone but it was found", secretsDir)
	}

	// removing again should be a noop
	if err := removeSecretDir(secretsDir); err != nil {
		t.Fatalf("error removing nonExistent secrets dir %q: %v", secretsDir, err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2022 Lumberjack authors (see AUTHORS file)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/signal"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"

	"github.com/abcxyz/lumberjack/clients/go/pkg/audit"
	"github.com/abcxyz/lumberjack/clients/go/pkg/auditopt"
	"github.com/abcxyz/lumberjack/clients/go/test/grpc-app/talkerpb"
)

var (
	port = flag.Int("port", 8080, "The server port")
)

func main() {
	if err := realMain(); err != nil {
		log.Fatal(err)
	}
}

func realMain() (outErr error) {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		return fmt.Errorf("failed to listen: %w", err)
	}
	interceptor, err := audit.NewInterceptor(auditopt.InterceptorFromConfigFile(auditopt.DefaultConfigFilePath))
	defer func() {
		if err := interceptor.Stop(); err != nil {
			outErr = fmt.Errorf("failed to stop interceptor: %w", err)
		}
	}()
	if err != nil {
		return fmt.Errorf("failed to setup audit interceptor: %w", err)
	}
	s := grpc.NewServer(grpc.UnaryInterceptor(interceptor.UnaryInterceptor), grpc.StreamInterceptor(interceptor.StreamInterceptor))
	talkerpb.RegisterTalkerServer(s, &server{})
	// Register the reflection service makes it easier for some clients.
	reflection.Register(s)

	// Gracefully stop the server on ctrl-c.
	intrCh := make(chan os.Signal, 1)
	signal.Notify(intrCh, os.Interrupt)
	go func() {
		<-intrCh
		log.Println("gracefully stopping...")
		s.GracefulStop()
	}()

	log.Printf("server listening at %v\n", lis.Addr())
	if err := s.Serve(lis); err != nil {
		return fmt.Errorf("failed to serve: %w", err)
	}

	log.Println("server stopped.")

	return nil
}

type server struct {
	talkerpb.UnimplementedTalkerServer
}

func (s *server) Hello(ctx context.Context, req *talkerpb.HelloRequest) (*talkerpb.HelloResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.HelloResponse{
		Message: fmt.Sprintf("Hi, I'm %s!", req.Target),
	}, nil
}

func (s *server) Whisper(ctx context.Context, req *talkerpb.WhisperRequest) (*talkerpb.WhisperResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.WhisperResponse{
		Message: fmt.Sprintf("Shush, I'm %s.", req.Target),
	}, nil
}

func (s *server) Bye(ctx context.Context, req *talkerpb.ByeRequest) (*talkerpb.ByeResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.ByeResponse{
		Message: "Bye!",
	}, nil
}

func (s *server) Fibonacci(req *talkerpb.FibonacciRequest, svr talkerpb.Talker_FibonacciServer) error {
	if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
		logReq.Payload.ResourceName = req.Target
	}

	var x, y uint32 = 0, 1
	for i := uint32(1); i <= req.Places; i++ {
		z := uint32(0)
		if i == 2 {
			z = 1
		} else if i > 2 {
			z = x + y
			x = y
			y = z
		}

		if err := svr.Send(&talkerpb.FibonacciResponse{
			Position: i,
			Value:    z,
		}); err != nil {
			return err
		}
	}

	return nil
}

func (s *server) Addition(svr talkerpb.Talker_AdditionServer) error {
	sum := 0

	for {
		req, err := svr.Recv()
		if err == io.EOF {
			// End of stream. Send the sum.
			if err := svr.SendAndClose(&talkerpb.AdditionResponse{
				Sum: uint64(sum),
			}); err != nil {
				return err
			}
			break
		}
		if err != nil {
			return err
		}

		if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
			logReq.Payload.ResourceName = req.Target
		}
		sum += int(req.Addend)
	}

	return nil
}

func (s *server) Fail(ctx context.Context, req *talkerpb.FailRequest) (*talkerpb.FailResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return nil, status.Errorf(codes.ResourceExhausted, "this call will always fail")
}

func (s *server) FailOnFour(svr talkerpb.Talker_FailOnFourServer) error {
	for {
		req, err := svr.Recv()
		if err == io.EOF {
			svr.SendAndClose(&talkerpb.FailOnFourResponse{
				Message: "closing...",
			})
			break
		}
		if err != nil {
			return err
		}

		if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
			logReq.Payload.ResourceName = req.Target
		}

		if req.Value == 4 {
			return status.Errorf(codes.InvalidArgument, "this call will always fail on four")
		}
	}
	return nil
}
<commit_msg>fix: don't run deferred interceptor cleanup on the error path, because interceptor is nil (#225)<commit_after>// Copyright 2022 Lumberjack authors (see AUTHORS file)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/signal"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"

	"github.com/abcxyz/lumberjack/clients/go/pkg/audit"
	"github.com/abcxyz/lumberjack/clients/go/pkg/auditopt"
	"github.com/abcxyz/lumberjack/clients/go/test/grpc-app/talkerpb"
)

var (
	port = flag.Int("port", 8080, "The server port")
)

func main() {
	if err := realMain(); err != nil {
		log.Fatal(err)
	}
}

func realMain() (outErr error) {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		return fmt.Errorf("failed to listen: %w", err)
	}
	interceptor, err := audit.NewInterceptor(auditopt.InterceptorFromConfigFile(auditopt.DefaultConfigFilePath))
	if err != nil {
		return fmt.Errorf("failed to setup audit interceptor: %w", err)
	}
	defer func() {
		if err := interceptor.Stop(); err != nil {
			outErr = fmt.Errorf("failed to stop interceptor: %w", err)
		}
	}()
	s := grpc.NewServer(grpc.UnaryInterceptor(interceptor.UnaryInterceptor), grpc.StreamInterceptor(interceptor.StreamInterceptor))
	talkerpb.RegisterTalkerServer(s, &server{})
	// Register the reflection service makes it easier for some clients.
	reflection.Register(s)

	// Gracefully stop the server on ctrl-c.
	intrCh := make(chan os.Signal, 1)
	signal.Notify(intrCh, os.Interrupt)
	go func() {
		<-intrCh
		log.Println("gracefully stopping...")
		s.GracefulStop()
	}()

	log.Printf("server listening at %v\n", lis.Addr())
	if err := s.Serve(lis); err != nil {
		return fmt.Errorf("failed to serve: %w", err)
	}

	log.Println("server stopped.")

	return nil
}

type server struct {
	talkerpb.UnimplementedTalkerServer
}

func (s *server) Hello(ctx context.Context, req *talkerpb.HelloRequest) (*talkerpb.HelloResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.HelloResponse{
		Message: fmt.Sprintf("Hi, I'm %s!", req.Target),
	}, nil
}

func (s *server) Whisper(ctx context.Context, req *talkerpb.WhisperRequest) (*talkerpb.WhisperResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.WhisperResponse{
		Message: fmt.Sprintf("Shush, I'm %s.", req.Target),
	}, nil
}

func (s *server) Bye(ctx context.Context, req *talkerpb.ByeRequest) (*talkerpb.ByeResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return &talkerpb.ByeResponse{
		Message: "Bye!",
	}, nil
}

func (s *server) Fibonacci(req *talkerpb.FibonacciRequest, svr talkerpb.Talker_FibonacciServer) error {
	if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
		logReq.Payload.ResourceName = req.Target
	}

	var x, y uint32 = 0, 1
	for i := uint32(1); i <= req.Places; i++ {
		z := uint32(0)
		if i == 2 {
			z = 1
		} else if i > 2 {
			z = x + y
			x = y
			y = z
		}

		if err := svr.Send(&talkerpb.FibonacciResponse{
			Position: i,
			Value:    z,
		}); err != nil {
			return err
		}
	}

	return nil
}

func (s *server) Addition(svr talkerpb.Talker_AdditionServer) error {
	sum := 0

	for {
		req, err := svr.Recv()
		if err == io.EOF {
			// End of stream. Send the sum.
			if err := svr.SendAndClose(&talkerpb.AdditionResponse{
				Sum: uint64(sum),
			}); err != nil {
				return err
			}
			break
		}
		if err != nil {
			return err
		}

		if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
			logReq.Payload.ResourceName = req.Target
		}
		sum += int(req.Addend)
	}

	return nil
}

func (s *server) Fail(ctx context.Context, req *talkerpb.FailRequest) (*talkerpb.FailResponse, error) {
	if logReq, ok := audit.LogReqFromCtx(ctx); ok {
		logReq.Payload.ResourceName = req.Target
	}
	return nil, status.Errorf(codes.ResourceExhausted, "this call will always fail")
}

func (s *server) FailOnFour(svr talkerpb.Talker_FailOnFourServer) error {
	for {
		req, err := svr.Recv()
		if err == io.EOF {
			svr.SendAndClose(&talkerpb.FailOnFourResponse{
				Message: "closing...",
			})
			break
		}
		if err != nil {
			return err
		}

		if logReq, ok := audit.LogReqFromCtx(svr.Context()); ok {
			logReq.Payload.ResourceName = req.Target
		}

		if req.Value == 4 {
			return status.Errorf(codes.InvalidArgument, "this call will always fail on four")
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package summary

// BoeSummary is a struct to document the different dispositions
// and announcements that have been published on a certain date
type BoeSummary struct {
	Meta    Metadata `xml:"meta"`
	Diaries []Diary  `xml:"diario"`
}

// Metadata is used to provide metainformation that has been published
// in a certain date. Note that all dates from this struct has the
// format dd/mm/yyyy
type Metadata struct {
	BulletinType        string `xml:"pub"`
	PublicationDate     string `xml:"fecha"`
	LastPublicationDate string `xml:"fechaAnt"`
	NextPublicationDate string `xml:"fechaSig"`
}

// Diary contains the information of the dispositions that conform
// each of the bulletins on a certain date
type Diary struct {
	BulletinNumber string     `xml:"nbo,attr"`
	Summary        PdfSummary `xml:"sumario_nbo"`
}

// PdfSummary contains information of the pdf summary associated to the bulletin
type PdfSummary struct {
	ID   string  `xml:"id,attr"`
	Info PdfInfo `xml:"urlPdf"`
}

// PdfInfo contains file info, like number of pages and size
type PdfInfo struct {
	SizeBytes  string `xml:"szBytes,attr"`
	SizeKBytes string `xml:"szKBytes,attr"`
	Pages      string `xml:"numPages,attr"`
}
<commit_msg>Created new struct to store the diary sections<commit_after>package summary

// BoeSummary is a struct to document the different dispositions
// and announcements that have been published on a certain date
type BoeSummary struct {
	Meta    Metadata `xml:"meta"`
	Diaries []Diary  `xml:"diario"`
}

// Metadata is used to provide metainformation that has been published
// in a certain date. Note that all dates from this struct has the
// format dd/mm/yyyy
type Metadata struct {
	BulletinType        string `xml:"pub"`
	PublicationDate     string `xml:"fecha"`
	LastPublicationDate string `xml:"fechaAnt"`
	NextPublicationDate string `xml:"fechaSig"`
}

// Diary contains the information of the dispositions that conform
// each of the bulletins on a certain date
type Diary struct {
	BulletinNumber string     `xml:"nbo,attr"`
	Summary        PdfSummary `xml:"sumario_nbo"`
	Section        []Section  `xml:"seccion"`
}

// PdfSummary contains information of the pdf summary associated to the bulletin
type PdfSummary struct {
	ID   string  `xml:"id,attr"`
	Info PdfInfo `xml:"urlPdf"`
}

// PdfInfo contains file info, like number of pages and size
type PdfInfo struct {
	SizeBytes  string `xml:"szBytes,attr"`
	SizeKBytes string `xml:"szKBytes,attr"`
	Pages      string `xml:"numPages,attr"`
}

// Section is the set os sections that compose the diary
type Section struct {
	Number      string       `xml:"num,attr"`
	Name        string       `xml:"nombre,attr"`
	Departments []Department `xml:"departamento"`
}

// Department contains the relative data to the various departments  which have
// been published dispositions belonging the current section
type Department struct {
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"bytes"
	"fmt"
	"sync"

	k8syaml "github.com/ghodss/yaml"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"
	ifclient "github.com/weaveworks/flux/integrations/client/clientset/versioned"
	"gopkg.in/yaml.v2"
	apiv1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/discovery"
	k8sclient "k8s.io/client-go/kubernetes"
	v1beta1apps "k8s.io/client-go/kubernetes/typed/apps/v1beta1"
	v1beta1batch "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1beta1extensions "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"

	"github.com/weaveworks/flux"
	"github.com/weaveworks/flux/cluster"
	"github.com/weaveworks/flux/image"
	"github.com/weaveworks/flux/registry"
	"github.com/weaveworks/flux/resource"
	"github.com/weaveworks/flux/ssh"
)

const (
	StatusUnknown  = "unknown"
	StatusReady    = "ready"
	StatusUpdating = "updating"
)

type extendedClient struct {
	discovery.DiscoveryInterface
	v1core.CoreV1Interface
	v1beta1extensions.ExtensionsV1beta1Interface
	v1beta1apps.StatefulSetsGetter
	v1beta1batch.CronJobsGetter
	ifclient.Interface
}

// --- internal types for keeping track of syncing

type metadata struct {
	Name      string `yaml:"name"`
	Namespace string `yaml:"namespace"`
}

type apiObject struct {
	resource.Resource
	Kind     string   `yaml:"kind"`
	Metadata metadata `yaml:"metadata"`
}

// A convenience for getting an minimal object from some bytes.
func parseObj(def []byte) (*apiObject, error) {
	obj := apiObject{}
	return &obj, yaml.Unmarshal(def, &obj)
}

func (o *apiObject) hasNamespace() bool {
	return o.Metadata.Namespace != ""
}

// --- add-ons

// Kubernetes has a mechanism of "Add-ons", whereby manifest files
// left in a particular directory on the Kubernetes master will be
// applied. We can recognise these, because they:
//  1. Must be in the namespace `kube-system`; and,
//  2. Must have one of the labels below set, else the addon manager will ignore them.
//
// We want to ignore add-ons, since they are managed by the add-on
// manager, and attempts to control them via other means will fail.

// k8sObject represents an value from which you can obtain typical
// Kubernetes metadata. These methods are implemented by the
// Kubernetes API resource types.
type k8sObject interface {
	GetNamespace() string
	GetLabels() map[string]string
	GetAnnotations() map[string]string
}

func isAddon(obj k8sObject) bool {
	if obj.GetNamespace() != "kube-system" {
		return false
	}
	labels := obj.GetLabels()
	if labels["kubernetes.io/cluster-service"] == "true" ||
		labels["addonmanager.kubernetes.io/mode"] == "EnsureExists" ||
		labels["addonmanager.kubernetes.io/mode"] == "Reconcile" {
		return true
	}
	return false
}

// --- /add ons

// Cluster is a handle to a Kubernetes API server.
// (Typically, this code is deployed into the same cluster.)
type Cluster struct {
	client      extendedClient
	applier     Applier
	version     string // string response for the version command.
	logger      log.Logger
	sshKeyRing  ssh.KeyRing
	nsWhitelist []string

	mu sync.Mutex
}

// NewCluster returns a usable cluster.
func NewCluster(clientset k8sclient.Interface,
	ifclientset ifclient.Interface,
	applier Applier,
	sshKeyRing ssh.KeyRing,
	logger log.Logger,
	nsWhitelist []string) *Cluster {

	c := &Cluster{
		client: extendedClient{
			clientset.Discovery(),
			clientset.Core(),
			clientset.Extensions(),
			clientset.AppsV1beta1(),
			clientset.BatchV1beta1(),
			ifclientset,
		},
		applier:    applier,
		logger:     logger,
		sshKeyRing: sshKeyRing,
		nsWhitelist: nsWhitelist,
	}

	return c
}

// --- cluster.Cluster

// SomeControllers returns the controllers named, missing out any that don't
// exist in the cluster. They do not necessarily have to be returned
// in the order requested.
func (c *Cluster) SomeControllers(ids []flux.ResourceID) (res []cluster.Controller, err error) {
	var controllers []cluster.Controller
	for _, id := range ids {
		ns, kind, name := id.Components()

		resourceKind, ok := resourceKinds[kind]
		if !ok {
			return nil, fmt.Errorf("Unsupported kind %v", kind)
		}

		podController, err := resourceKind.getPodController(c, ns, name)
		if err != nil {
			return nil, err
		}

		if !isAddon(podController) {
			controllers = append(controllers, podController.toClusterController(id))
		}
	}
	return controllers, nil
}

// AllControllers returns all controllers matching the criteria; that is, in
// the namespace (or any namespace if that argument is empty)
func (c *Cluster) AllControllers(namespace string) (res []cluster.Controller, err error) {
	namespaces, err := c.client.Namespaces().List(meta_v1.ListOptions{})
	if err != nil {
		return nil, errors.Wrap(err, "getting namespaces")
	}

	var allControllers []cluster.Controller
	for _, ns := range namespaces.Items {
		if namespace != "" && ns.Name != namespace {
			continue
		}

		for kind, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
					continue
				} else {
					return nil, err
				}
			}

			for _, podController := range podControllers {
				if !isAddon(podController) {
					id := flux.MakeResourceID(ns.Name, kind, podController.name)
					allControllers = append(allControllers, podController.toClusterController(id))
				}
			}
		}
	}

	return allControllers, nil
}

// Sync performs the given actions on resources. Operations are
// asynchronous, but serialised.
func (c *Cluster) Sync(spec cluster.SyncDef) error {
	logger := log.With(c.logger, "method", "Sync")

	cs := makeChangeSet()
	var errs cluster.SyncError
	for _, action := range spec.Actions {
		stages := []struct {
			res resource.Resource
			cmd string
		}{
			{action.Delete, "delete"},
			{action.Apply, "apply"},
		}
		for _, stage := range stages {
			if stage.res == nil {
				continue
			}
			obj, err := parseObj(stage.res.Bytes())
			if err == nil {
				obj.Resource = stage.res
				cs.stage(stage.cmd, obj)
			} else {
				errs = append(errs, cluster.ResourceError{Resource: stage.res, Error: err})
				break
			}
		}
	}

	c.mu.Lock()
	defer c.mu.Unlock()
	if applyErrs := c.applier.apply(logger, cs); len(applyErrs) > 0 {
		errs = append(errs, applyErrs...)
	}

	// If `nil`, errs is a cluster.SyncError(nil) rather than error(nil)
	if errs != nil {
		return errs
	}
	return nil
}

func (c *Cluster) Ping() error {
	_, err := c.client.ServerVersion()
	return err
}

// Export exports cluster resources
func (c *Cluster) Export() ([]byte, error) {
	var config bytes.Buffer

	namespaces, err := c.getNamespaces()
	if err != nil {
		return nil, errors.Wrap(err, "getting namespaces")
	}

	for _, ns := range namespaces {
		err := appendYAML(&config, "v1", "Namespace", ns)
		if err != nil {
			return nil, errors.Wrap(err, "marshalling namespace to YAML")
		}

		for _, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
					continue
				} else {
					return nil, err
				}
			}

			for _, pc := range podControllers {
				if !isAddon(pc) {
					if err := appendYAML(&config, pc.apiVersion, pc.kind, pc.k8sObject); err != nil {
						return nil, err
					}
				}
			}
		}
	}
	return config.Bytes(), nil
}

// kind & apiVersion must be passed separately as the object's TypeMeta is not populated
func appendYAML(buffer *bytes.Buffer, apiVersion, kind string, object interface{}) error {
	yamlBytes, err := k8syaml.Marshal(object)
	if err != nil {
		return err
	}
	buffer.WriteString("---\n")
	buffer.WriteString("apiVersion: ")
	buffer.WriteString(apiVersion)
	buffer.WriteString("\nkind: ")
	buffer.WriteString(kind)
	buffer.WriteString("\n")
	buffer.Write(yamlBytes)
	return nil
}

func (c *Cluster) PublicSSHKey(regenerate bool) (ssh.PublicKey, error) {
	if regenerate {
		if err := c.sshKeyRing.Regenerate(); err != nil {
			return ssh.PublicKey{}, err
		}
	}
	publicKey, _ := c.sshKeyRing.KeyPair()
	return publicKey, nil
}

func mergeCredentials(c *Cluster, namespace string, podTemplate apiv1.PodTemplateSpec, imageCreds registry.ImageCreds) {
	creds := registry.NoCredentials()
	for _, imagePullSecret := range podTemplate.Spec.ImagePullSecrets {
		secret, err := c.client.Secrets(namespace).Get(imagePullSecret.Name, meta_v1.GetOptions{})
		if err != nil {
			c.logger.Log("err", errors.Wrapf(err, "getting secret %q from namespace %q", secret.Name, namespace))
			continue
		}

		var decoded []byte
		var ok bool
		// These differ in format; but, ParseCredentials will
		// handle either.
		switch apiv1.SecretType(secret.Type) {
		case apiv1.SecretTypeDockercfg:
			decoded, ok = secret.Data[apiv1.DockerConfigKey]
		case apiv1.SecretTypeDockerConfigJson:
			decoded, ok = secret.Data[apiv1.DockerConfigJsonKey]
		default:
			c.logger.Log("skip", "unknown type", "secret", namespace+"/"+secret.Name, "type", secret.Type)
			continue
		}

		if !ok {
			c.logger.Log("err", errors.Wrapf(err, "retrieving pod secret %q", secret.Name))
			continue
		}

		// Parse secret
		crd, err := registry.ParseCredentials(fmt.Sprintf("%s:secret/%s", namespace, imagePullSecret.Name), decoded)
		if err != nil {
			c.logger.Log("err", err.Error())
			continue
		}

		// Merge into the credentials for this PodSpec
		creds.Merge(crd)
	}

	// Now create the service and attach the credentials
	for _, container := range podTemplate.Spec.Containers {
		r, err := image.ParseRef(container.Image)
		if err != nil {
			c.logger.Log("err", err.Error())
			continue
		}
		imageCreds[r.Name] = creds
	}
}

// ImagesToFetch is a k8s specific method to get a list of images to update along with their credentials
func (c *Cluster) ImagesToFetch() registry.ImageCreds {
	allImageCreds := make(registry.ImageCreds)

	namespaces, err := c.getNamespaces()
	if err != nil {
		c.logger.Log("err", errors.Wrap(err, "getting namespaces"))
		return allImageCreds
	}

	for _, ns := range namespaces {
		for kind, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
				} else {
					c.logger.Log("err", errors.Wrapf(err, "getting kind %s for namespace %s", kind, ns.Name))
				}
				continue
			}

			imageCreds := make(registry.ImageCreds)
			for _, podController := range podControllers {
				mergeCredentials(c, ns.Name, podController.podTemplate, imageCreds)
			}

			// Merge creds
			for imageID, creds := range imageCreds {
				existingCreds, ok := allImageCreds[imageID]
				if ok {
					existingCreds.Merge(creds)
				} else {
					allImageCreds[imageID] = creds
				}
			}
		}
	}

	return allImageCreds
}

// getNamespaces returns a list of namespaces that the Flux instance is expected
// to have access to and can look for resources inside of.
// It returns a list of all namespaces unless a namespace whitelist has been set on the Cluster
// instance, in which case it returns a list containing the namespaces from the whitelist
// that exist in the cluster.
func (c *Cluster) getNamespaces() ([]apiv1.Namespace, error) {
	nsList := []apiv1.Namespace{}
	validNamespaces := map[string]bool{}

	namespaces, err := c.client.Namespaces().List(meta_v1.ListOptions{})
	if err != nil {
		return nsList, err
	}

	for _, namespace := range c.nsWhitelist {
		validNamespaces[namespace] = true
	}

	for _, namespace := range namespaces.Items {
		if len(validNamespaces) > 0 && ! validNamespaces[namespace.ObjectMeta.Name] {
			continue
		}

		nsList = append(nsList, namespace)
  }

	return nsList, nil
}
<commit_msg>Limit AllControllers() to only search namespaces in the whitelist<commit_after>package kubernetes

import (
	"bytes"
	"fmt"
	"sync"

	k8syaml "github.com/ghodss/yaml"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"
	ifclient "github.com/weaveworks/flux/integrations/client/clientset/versioned"
	"gopkg.in/yaml.v2"
	apiv1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/discovery"
	k8sclient "k8s.io/client-go/kubernetes"
	v1beta1apps "k8s.io/client-go/kubernetes/typed/apps/v1beta1"
	v1beta1batch "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1beta1extensions "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"

	"github.com/weaveworks/flux"
	"github.com/weaveworks/flux/cluster"
	"github.com/weaveworks/flux/image"
	"github.com/weaveworks/flux/registry"
	"github.com/weaveworks/flux/resource"
	"github.com/weaveworks/flux/ssh"
)

const (
	StatusUnknown  = "unknown"
	StatusReady    = "ready"
	StatusUpdating = "updating"
)

type extendedClient struct {
	discovery.DiscoveryInterface
	v1core.CoreV1Interface
	v1beta1extensions.ExtensionsV1beta1Interface
	v1beta1apps.StatefulSetsGetter
	v1beta1batch.CronJobsGetter
	ifclient.Interface
}

// --- internal types for keeping track of syncing

type metadata struct {
	Name      string `yaml:"name"`
	Namespace string `yaml:"namespace"`
}

type apiObject struct {
	resource.Resource
	Kind     string   `yaml:"kind"`
	Metadata metadata `yaml:"metadata"`
}

// A convenience for getting an minimal object from some bytes.
func parseObj(def []byte) (*apiObject, error) {
	obj := apiObject{}
	return &obj, yaml.Unmarshal(def, &obj)
}

func (o *apiObject) hasNamespace() bool {
	return o.Metadata.Namespace != ""
}

// --- add-ons

// Kubernetes has a mechanism of "Add-ons", whereby manifest files
// left in a particular directory on the Kubernetes master will be
// applied. We can recognise these, because they:
//  1. Must be in the namespace `kube-system`; and,
//  2. Must have one of the labels below set, else the addon manager will ignore them.
//
// We want to ignore add-ons, since they are managed by the add-on
// manager, and attempts to control them via other means will fail.

// k8sObject represents an value from which you can obtain typical
// Kubernetes metadata. These methods are implemented by the
// Kubernetes API resource types.
type k8sObject interface {
	GetNamespace() string
	GetLabels() map[string]string
	GetAnnotations() map[string]string
}

func isAddon(obj k8sObject) bool {
	if obj.GetNamespace() != "kube-system" {
		return false
	}
	labels := obj.GetLabels()
	if labels["kubernetes.io/cluster-service"] == "true" ||
		labels["addonmanager.kubernetes.io/mode"] == "EnsureExists" ||
		labels["addonmanager.kubernetes.io/mode"] == "Reconcile" {
		return true
	}
	return false
}

// --- /add ons

// Cluster is a handle to a Kubernetes API server.
// (Typically, this code is deployed into the same cluster.)
type Cluster struct {
	client      extendedClient
	applier     Applier
	version     string // string response for the version command.
	logger      log.Logger
	sshKeyRing  ssh.KeyRing
	nsWhitelist []string

	mu sync.Mutex
}

// NewCluster returns a usable cluster.
func NewCluster(clientset k8sclient.Interface,
	ifclientset ifclient.Interface,
	applier Applier,
	sshKeyRing ssh.KeyRing,
	logger log.Logger,
	nsWhitelist []string) *Cluster {

	c := &Cluster{
		client: extendedClient{
			clientset.Discovery(),
			clientset.Core(),
			clientset.Extensions(),
			clientset.AppsV1beta1(),
			clientset.BatchV1beta1(),
			ifclientset,
		},
		applier:    applier,
		logger:     logger,
		sshKeyRing: sshKeyRing,
		nsWhitelist: nsWhitelist,
	}

	return c
}

// --- cluster.Cluster

// SomeControllers returns the controllers named, missing out any that don't
// exist in the cluster. They do not necessarily have to be returned
// in the order requested.
func (c *Cluster) SomeControllers(ids []flux.ResourceID) (res []cluster.Controller, err error) {
	var controllers []cluster.Controller
	for _, id := range ids {
		ns, kind, name := id.Components()

		resourceKind, ok := resourceKinds[kind]
		if !ok {
			return nil, fmt.Errorf("Unsupported kind %v", kind)
		}

		podController, err := resourceKind.getPodController(c, ns, name)
		if err != nil {
			return nil, err
		}

		if !isAddon(podController) {
			controllers = append(controllers, podController.toClusterController(id))
		}
	}
	return controllers, nil
}

// AllControllers returns all controllers matching the criteria; that is, in
// the namespace (or any namespace if that argument is empty)
func (c *Cluster) AllControllers(namespace string) (res []cluster.Controller, err error) {
	namespaces, err := c.getNamespaces()
	if err != nil {
		return nil, errors.Wrap(err, "getting namespaces")
	}

	var allControllers []cluster.Controller
	for _, ns := range namespaces {
		if namespace != "" && ns.Name != namespace {
			continue
		}

		for kind, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
					continue
				} else {
					return nil, err
				}
			}

			for _, podController := range podControllers {
				if !isAddon(podController) {
					id := flux.MakeResourceID(ns.Name, kind, podController.name)
					allControllers = append(allControllers, podController.toClusterController(id))
				}
			}
		}
	}

	return allControllers, nil
}

// Sync performs the given actions on resources. Operations are
// asynchronous, but serialised.
func (c *Cluster) Sync(spec cluster.SyncDef) error {
	logger := log.With(c.logger, "method", "Sync")

	cs := makeChangeSet()
	var errs cluster.SyncError
	for _, action := range spec.Actions {
		stages := []struct {
			res resource.Resource
			cmd string
		}{
			{action.Delete, "delete"},
			{action.Apply, "apply"},
		}
		for _, stage := range stages {
			if stage.res == nil {
				continue
			}
			obj, err := parseObj(stage.res.Bytes())
			if err == nil {
				obj.Resource = stage.res
				cs.stage(stage.cmd, obj)
			} else {
				errs = append(errs, cluster.ResourceError{Resource: stage.res, Error: err})
				break
			}
		}
	}

	c.mu.Lock()
	defer c.mu.Unlock()
	if applyErrs := c.applier.apply(logger, cs); len(applyErrs) > 0 {
		errs = append(errs, applyErrs...)
	}

	// If `nil`, errs is a cluster.SyncError(nil) rather than error(nil)
	if errs != nil {
		return errs
	}
	return nil
}

func (c *Cluster) Ping() error {
	_, err := c.client.ServerVersion()
	return err
}

// Export exports cluster resources
func (c *Cluster) Export() ([]byte, error) {
	var config bytes.Buffer

	namespaces, err := c.getNamespaces()
	if err != nil {
		return nil, errors.Wrap(err, "getting namespaces")
	}

	for _, ns := range namespaces {
		err := appendYAML(&config, "v1", "Namespace", ns)
		if err != nil {
			return nil, errors.Wrap(err, "marshalling namespace to YAML")
		}

		for _, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
					continue
				} else {
					return nil, err
				}
			}

			for _, pc := range podControllers {
				if !isAddon(pc) {
					if err := appendYAML(&config, pc.apiVersion, pc.kind, pc.k8sObject); err != nil {
						return nil, err
					}
				}
			}
		}
	}
	return config.Bytes(), nil
}

// kind & apiVersion must be passed separately as the object's TypeMeta is not populated
func appendYAML(buffer *bytes.Buffer, apiVersion, kind string, object interface{}) error {
	yamlBytes, err := k8syaml.Marshal(object)
	if err != nil {
		return err
	}
	buffer.WriteString("---\n")
	buffer.WriteString("apiVersion: ")
	buffer.WriteString(apiVersion)
	buffer.WriteString("\nkind: ")
	buffer.WriteString(kind)
	buffer.WriteString("\n")
	buffer.Write(yamlBytes)
	return nil
}

func (c *Cluster) PublicSSHKey(regenerate bool) (ssh.PublicKey, error) {
	if regenerate {
		if err := c.sshKeyRing.Regenerate(); err != nil {
			return ssh.PublicKey{}, err
		}
	}
	publicKey, _ := c.sshKeyRing.KeyPair()
	return publicKey, nil
}

func mergeCredentials(c *Cluster, namespace string, podTemplate apiv1.PodTemplateSpec, imageCreds registry.ImageCreds) {
	creds := registry.NoCredentials()
	for _, imagePullSecret := range podTemplate.Spec.ImagePullSecrets {
		secret, err := c.client.Secrets(namespace).Get(imagePullSecret.Name, meta_v1.GetOptions{})
		if err != nil {
			c.logger.Log("err", errors.Wrapf(err, "getting secret %q from namespace %q", secret.Name, namespace))
			continue
		}

		var decoded []byte
		var ok bool
		// These differ in format; but, ParseCredentials will
		// handle either.
		switch apiv1.SecretType(secret.Type) {
		case apiv1.SecretTypeDockercfg:
			decoded, ok = secret.Data[apiv1.DockerConfigKey]
		case apiv1.SecretTypeDockerConfigJson:
			decoded, ok = secret.Data[apiv1.DockerConfigJsonKey]
		default:
			c.logger.Log("skip", "unknown type", "secret", namespace+"/"+secret.Name, "type", secret.Type)
			continue
		}

		if !ok {
			c.logger.Log("err", errors.Wrapf(err, "retrieving pod secret %q", secret.Name))
			continue
		}

		// Parse secret
		crd, err := registry.ParseCredentials(fmt.Sprintf("%s:secret/%s", namespace, imagePullSecret.Name), decoded)
		if err != nil {
			c.logger.Log("err", err.Error())
			continue
		}

		// Merge into the credentials for this PodSpec
		creds.Merge(crd)
	}

	// Now create the service and attach the credentials
	for _, container := range podTemplate.Spec.Containers {
		r, err := image.ParseRef(container.Image)
		if err != nil {
			c.logger.Log("err", err.Error())
			continue
		}
		imageCreds[r.Name] = creds
	}
}

// ImagesToFetch is a k8s specific method to get a list of images to update along with their credentials
func (c *Cluster) ImagesToFetch() registry.ImageCreds {
	allImageCreds := make(registry.ImageCreds)

	namespaces, err := c.getNamespaces()
	if err != nil {
		c.logger.Log("err", errors.Wrap(err, "getting namespaces"))
		return allImageCreds
	}

	for _, ns := range namespaces {
		for kind, resourceKind := range resourceKinds {
			podControllers, err := resourceKind.getPodControllers(c, ns.Name)
			if err != nil {
				if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
					// Kind not supported by API server, skip
				} else {
					c.logger.Log("err", errors.Wrapf(err, "getting kind %s for namespace %s", kind, ns.Name))
				}
				continue
			}

			imageCreds := make(registry.ImageCreds)
			for _, podController := range podControllers {
				mergeCredentials(c, ns.Name, podController.podTemplate, imageCreds)
			}

			// Merge creds
			for imageID, creds := range imageCreds {
				existingCreds, ok := allImageCreds[imageID]
				if ok {
					existingCreds.Merge(creds)
				} else {
					allImageCreds[imageID] = creds
				}
			}
		}
	}

	return allImageCreds
}

// getNamespaces returns a list of namespaces that the Flux instance is expected
// to have access to and can look for resources inside of.
// It returns a list of all namespaces unless a namespace whitelist has been set on the Cluster
// instance, in which case it returns a list containing the namespaces from the whitelist
// that exist in the cluster.
func (c *Cluster) getNamespaces() ([]apiv1.Namespace, error) {
	nsList := []apiv1.Namespace{}
	validNamespaces := map[string]bool{}

	namespaces, err := c.client.Namespaces().List(meta_v1.ListOptions{})
	if err != nil {
		return nsList, err
	}

	for _, namespace := range c.nsWhitelist {
		validNamespaces[namespace] = true
	}

	for _, namespace := range namespaces.Items {
		if len(validNamespaces) > 0 && ! validNamespaces[namespace.ObjectMeta.Name] {
			continue
		}

		nsList = append(nsList, namespace)
  }

	return nsList, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package app

import (
	"fmt"
	"net"
	"strconv"
	"strings"

	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/validation/field"
	apivalidation "k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/apis/componentconfig"
)

// Validate validates the configuration of kube-proxy
func Validate(config *componentconfig.KubeProxyConfiguration) field.ErrorList {
	allErrs := field.ErrorList{}

	newPath := field.NewPath("KubeProxyConfiguration")

	allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...)
	allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...)
	allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...)
	allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...)

	if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) {
		allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]"))
	}

	if config.UDPIdleTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(newPath.Child("UDPIdleTimeout"), config.UDPIdleTimeout, "must be greater than 0"))
	}

	if config.ConfigSyncPeriod.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0"))
	}

	if net.ParseIP(config.BindAddress) == nil {
		allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address"))
	}

	allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...)
	allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...)

	if _, _, err := net.ParseCIDR(config.ClusterCIDR); err != nil {
		allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid CIDR block (e.g. 10.100.0.0/16)"))
	}

	if _, err := utilnet.ParsePortRange(config.PortRange); err != nil {
		allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)"))
	}

	return allErrs
}

func validateKubeProxyIPTablesConfiguration(config componentconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]"))
	}

	if config.SyncPeriod.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0"))
	}

	if config.MinSyncPeriod.Duration < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0"))
	}

	return allErrs
}

func validateKubeProxyConntrackConfiguration(config componentconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if config.Max < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("Max"), config.Max, "must be greater than or equal to 0"))
	}

	if config.MaxPerCore < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0"))
	}

	if config.Min < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0"))
	}

	if config.TCPEstablishedTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than 0"))
	}

	if config.TCPCloseWaitTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than 0"))
	}

	return allErrs
}

func validateProxyMode(mode componentconfig.ProxyMode, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}
	switch mode {
	case componentconfig.ProxyModeUserspace:
	case componentconfig.ProxyModeIPTables:
	case "":
	default:
		modes := []string{string(componentconfig.ProxyModeUserspace), string(componentconfig.ProxyModeIPTables)}
		errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy (currently iptables)", strings.Join(modes, ","))
		allErrs = append(allErrs, field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg))
	}
	return allErrs
}

func validateClientConnectionConfiguration(config componentconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}
	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...)
	return allErrs
}

func validateHostPort(input string, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	hostIP, port, err := net.SplitHostPort(input)
	if err != nil {
		allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port"))
		return allErrs
	}

	if ip := net.ParseIP(hostIP); ip == nil {
		allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP"))
	}

	if p, err := strconv.Atoi(port); err != nil {
		allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
	} else if p < 1 || p > 65535 {
		allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
	}

	return allErrs
}
<commit_msg>Parse cluster-cidr only if it is specified<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package app

import (
	"fmt"
	"net"
	"strconv"
	"strings"

	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/validation/field"
	apivalidation "k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/apis/componentconfig"
)

// Validate validates the configuration of kube-proxy
func Validate(config *componentconfig.KubeProxyConfiguration) field.ErrorList {
	allErrs := field.ErrorList{}

	newPath := field.NewPath("KubeProxyConfiguration")

	allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...)
	allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...)
	allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...)
	allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...)

	if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) {
		allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]"))
	}

	if config.UDPIdleTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(newPath.Child("UDPIdleTimeout"), config.UDPIdleTimeout, "must be greater than 0"))
	}

	if config.ConfigSyncPeriod.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0"))
	}

	if net.ParseIP(config.BindAddress) == nil {
		allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address"))
	}

	allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...)
	allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...)

	if config.ClusterCIDR != "" {
		if _, _, err := net.ParseCIDR(config.ClusterCIDR); err != nil {
			allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid CIDR block (e.g. 10.100.0.0/16)"))
		}
	}

	if _, err := utilnet.ParsePortRange(config.PortRange); err != nil {
		allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)"))
	}

	return allErrs
}

func validateKubeProxyIPTablesConfiguration(config componentconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]"))
	}

	if config.SyncPeriod.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0"))
	}

	if config.MinSyncPeriod.Duration < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0"))
	}

	return allErrs
}

func validateKubeProxyConntrackConfiguration(config componentconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	if config.Max < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("Max"), config.Max, "must be greater than or equal to 0"))
	}

	if config.MaxPerCore < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0"))
	}

	if config.Min < 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0"))
	}

	if config.TCPEstablishedTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than 0"))
	}

	if config.TCPCloseWaitTimeout.Duration <= 0 {
		allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than 0"))
	}

	return allErrs
}

func validateProxyMode(mode componentconfig.ProxyMode, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}
	switch mode {
	case componentconfig.ProxyModeUserspace:
	case componentconfig.ProxyModeIPTables:
	case "":
	default:
		modes := []string{string(componentconfig.ProxyModeUserspace), string(componentconfig.ProxyModeIPTables)}
		errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy (currently iptables)", strings.Join(modes, ","))
		allErrs = append(allErrs, field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg))
	}
	return allErrs
}

func validateClientConnectionConfiguration(config componentconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}
	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...)
	return allErrs
}

func validateHostPort(input string, fldPath *field.Path) field.ErrorList {
	allErrs := field.ErrorList{}

	hostIP, port, err := net.SplitHostPort(input)
	if err != nil {
		allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port"))
		return allErrs
	}

	if ip := net.ParseIP(hostIP); ip == nil {
		allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP"))
	}

	if p, err := strconv.Atoi(port); err != nil {
		allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
	} else if p < 1 || p > 65535 {
		allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
	}

	return allErrs
}
<|endoftext|>