<commit_before>package main

const Version string = "0.7.2"
<commit_msg>Bump version 0.7.3<commit_after>package main

const Version string = "0.7.3"
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
	"text/template"
	"time"

	"github.com/codegangsta/cli"
	"github.com/remind101/empire"
	"github.com/remind101/empire/server"
	"github.com/remind101/empire/server/auth"
	githubauth "github.com/remind101/empire/server/auth/github"
	"github.com/remind101/empire/server/github"
	"golang.org/x/oauth2"
)

func runServer(c *cli.Context) {
	port := c.String(FlagPort)

	if c.Bool(FlagAutoMigrate) {
		runMigrate(c)
	}

	e, err := newEmpire(c)
	if err != nil {
		log.Fatal(err)
	}

	s := newServer(c, e)
	log.Printf("Starting on port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, s))
}

func newServer(c *cli.Context, e *empire.Empire) http.Handler {
	var opts server.Options
	opts.Authenticator = newAuthenticator(c, e)
	opts.GitHub.Webhooks.Secret = c.String(FlagGithubWebhooksSecret)
	opts.GitHub.Deployments.Environments = strings.Split(c.String(FlagGithubDeploymentsEnvironments), ",")
	opts.GitHub.Deployments.ImageBuilder = newImageBuilder(c)
	opts.GitHub.Deployments.TugboatURL = c.String(FlagGithubDeploymentsTugboatURL)

	return server.New(e, opts)
}

func newImageBuilder(c *cli.Context) github.ImageBuilder {
	tmpl := template.Must(template.New("image").Parse(c.String(FlagGithubDeploymentsImageTemplate)))
	return github.ImageFromTemplate(tmpl)
}

func newAuthenticator(c *cli.Context, e *empire.Empire) auth.Authenticator {
	// an authenticator authenticating requests with a users empire acccess
	// token.
	authenticators := []auth.Authenticator{
		auth.NewAccessTokenAuthenticator(e),
	}

	var client *githubauth.Client
	// If a GitHub client id is provided, we'll use GitHub as an
	// authentication backend. Otherwise, we'll just use a static username
	// and password backend.
	if c.String(FlagGithubClient) == "" {
		log.Println("Using static authentication backend")
		// Fake authentication password where the user is "fake" and
		// password is blank.
		authenticators = append(authenticators, auth.StaticAuthenticator("fake", "", "", &empire.User{
			Name: "fake",
		}))
	} else {
		config := &oauth2.Config{
			ClientID:     c.String(FlagGithubClient),
			ClientSecret: c.String(FlagGithubClientSecret),
			Scopes:       []string{"repo_deployment", "read:org"},
		}

		client = githubauth.NewClient(config)
		client.URL = c.String(FlagGithubApiURL)

		log.Println("Using GitHub authentication backend with the following configuration:")
		log.Println(fmt.Sprintf("  ClientID: %v", config.ClientID))
		log.Println(fmt.Sprintf("  ClientSecret: ****"))
		log.Println(fmt.Sprintf("  Scopes: %v", config.Scopes))
		log.Println(fmt.Sprintf("  GitHubAPI: %v", client.URL))

		// an authenticator for authenticating requests with a users github
		// credentials.
		authenticators = append(authenticators, githubauth.NewAuthenticator(client))
	}

	// try access token before falling back to github.
	authenticator := auth.MultiAuthenticator(authenticators...)

	// After the user is authenticated, check their GitHub Organization membership.
	if org := c.String(FlagGithubOrg); org != "" {
		authorizer := githubauth.NewOrganizationAuthorizer(client)
		authorizer.Organization = org

		log.Println("Adding GitHub Organization authorizer with the following configuration:")
		log.Println(fmt.Sprintf("  Organization: %v ", org))

		return auth.WithAuthorization(
			authenticator,
			// Cache the organization check for 30 minutes since
			// it's pretty slow.
			auth.CacheAuthorization(authorizer, 30*time.Minute),
		)
	}

	// After the user is authenticated, check their GitHub Team membership.
	if teamId := c.String(FlagGithubTeam); teamId != "" {
		authorizer := github.NewTeamAuthorizer(client)
		authorizer.TeamId = teamId

		log.Println("Adding GitHub Team authorizer with the following configuration:")
		log.Println(fmt.Sprintf("  Team ID: %v ", teamId))

		return auth.WithAuthorization(authenticator, authorizer)
	}

	return authenticator
}
<commit_msg>Fix merge conflict<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
	"text/template"
	"time"

	"github.com/codegangsta/cli"
	"github.com/remind101/empire"
	"github.com/remind101/empire/server"
	"github.com/remind101/empire/server/auth"
	githubauth "github.com/remind101/empire/server/auth/github"
	"github.com/remind101/empire/server/github"
	"golang.org/x/oauth2"
)

func runServer(c *cli.Context) {
	port := c.String(FlagPort)

	if c.Bool(FlagAutoMigrate) {
		runMigrate(c)
	}

	e, err := newEmpire(c)
	if err != nil {
		log.Fatal(err)
	}

	s := newServer(c, e)
	log.Printf("Starting on port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, s))
}

func newServer(c *cli.Context, e *empire.Empire) http.Handler {
	var opts server.Options
	opts.Authenticator = newAuthenticator(c, e)
	opts.GitHub.Webhooks.Secret = c.String(FlagGithubWebhooksSecret)
	opts.GitHub.Deployments.Environments = strings.Split(c.String(FlagGithubDeploymentsEnvironments), ",")
	opts.GitHub.Deployments.ImageBuilder = newImageBuilder(c)
	opts.GitHub.Deployments.TugboatURL = c.String(FlagGithubDeploymentsTugboatURL)

	return server.New(e, opts)
}

func newImageBuilder(c *cli.Context) github.ImageBuilder {
	tmpl := template.Must(template.New("image").Parse(c.String(FlagGithubDeploymentsImageTemplate)))
	return github.ImageFromTemplate(tmpl)
}

func newAuthenticator(c *cli.Context, e *empire.Empire) auth.Authenticator {
	// an authenticator authenticating requests with a users empire acccess
	// token.
	authenticators := []auth.Authenticator{
		auth.NewAccessTokenAuthenticator(e),
	}

	var client *githubauth.Client
	// If a GitHub client id is provided, we'll use GitHub as an
	// authentication backend. Otherwise, we'll just use a static username
	// and password backend.
	if c.String(FlagGithubClient) == "" {
		log.Println("Using static authentication backend")
		// Fake authentication password where the user is "fake" and
		// password is blank.
		authenticators = append(authenticators, auth.StaticAuthenticator("fake", "", "", &empire.User{
			Name: "fake",
		}))
	} else {
		config := &oauth2.Config{
			ClientID:     c.String(FlagGithubClient),
			ClientSecret: c.String(FlagGithubClientSecret),
			Scopes:       []string{"repo_deployment", "read:org"},
		}

		client = githubauth.NewClient(config)
		client.URL = c.String(FlagGithubApiURL)

		log.Println("Using GitHub authentication backend with the following configuration:")
		log.Println(fmt.Sprintf("  ClientID: %v", config.ClientID))
		log.Println(fmt.Sprintf("  ClientSecret: ****"))
		log.Println(fmt.Sprintf("  Scopes: %v", config.Scopes))
		log.Println(fmt.Sprintf("  GitHubAPI: %v", client.URL))

		// an authenticator for authenticating requests with a users github
		// credentials.
		authenticators = append(authenticators, githubauth.NewAuthenticator(client))
	}

	// try access token before falling back to github.
	authenticator := auth.MultiAuthenticator(authenticators...)

	// After the user is authenticated, check their GitHub Organization membership.
	if org := c.String(FlagGithubOrg); org != "" {
		authorizer := githubauth.NewOrganizationAuthorizer(client)
		authorizer.Organization = org

		log.Println("Adding GitHub Organization authorizer with the following configuration:")
		log.Println(fmt.Sprintf("  Organization: %v ", org))

		return auth.WithAuthorization(
			authenticator,
			// Cache the organization check for 30 minutes since
			// it's pretty slow.
			auth.CacheAuthorization(authorizer, 30*time.Minute),
		)
	}

	// After the user is authenticated, check their GitHub Team membership.
	if teamId := c.String(FlagGithubTeam); teamId != "" {
		authorizer := githubauth.NewTeamAuthorizer(client)
		authorizer.TeamId = teamId

		log.Println("Adding GitHub Team authorizer with the following configuration:")
		log.Println(fmt.Sprintf("  Team ID: %v ", teamId))

		return auth.WithAuthorization(authenticator, authorizer)
	}

	return authenticator
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"

	"google.golang.org/grpc"

	"github.com/relab/gorums/gridq"
)

var localAddrs2x2 = []string{
	":8080", ":8081",
	":8082", ":8083",
}

var localAddrs3x3 = []string{
	":8080", ":8081", ":8082",
	":8083", ":8084", ":8085",
	":8086", ":8087", ":8088",
}

func main() {
	var (
		saddrs     = flag.String("addrs", "", "server addresses separated by ','")
		srows      = flag.Int("rows", 0, "number of rows")
		predefined = flag.String("predef", "", "predefined grids ('2x2' or '3x3') for local testing")
		printGrid  = flag.Bool("gprid", true, "print quorums to screen")
	)

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\nOptions:\n")
		flag.PrintDefaults()
	}
	flag.Parse()

	var rows int
	var addrs []string
	if *predefined != "" {
		switch *predefined {
		case "2x2":
			addrs = localAddrs2x2
			rows = 2
		case "3x3":
			rows = 3
			addrs = localAddrs3x3
		default:
			dief("unknown predefined grid: %q", *predefined)
		}
	} else {
		rows = *srows
		addrs = strings.Split(*saddrs, ",")
	}

	if len(addrs) == 0 {
		dief("no server addresses provided")
	}
	if rows == 0 {
		dief("rows must be > 0")
	}
	if len(addrs)%rows != 0 {
		dief("%d addresse(s) and %d row(s) does not form a complete grid", len(addrs), rows)
	}
	cols := len(addrs) / rows

	fmt.Println("#addrs:", len(addrs), "rows:", rows, "cols:", cols, "\n")

	mgr, err := gridq.NewManager(
		addrs,
		gridq.WithGrpcDialOptions(
			grpc.WithBlock(),
			grpc.WithInsecure(),
			grpc.WithTimeout(5*time.Second),
		),
	)
	if err != nil {
		dief("error creating manager: %v", err)
	}

	ids := mgr.NodeIDs()

	gqspec := gridq.NewGQSort(rows, cols, *printGrid)

	conf, err := mgr.NewConfiguration(ids, gqspec, time.Second)
	if err != nil {
		dief("error creating config: %v", err)
	}

	for {
		state := &gridq.State{
			Value:     int64(rand.Intn(1 << 8)),
			Timestamp: time.Now().Unix(),
		}

		fmt.Println("writing:", state)
		wreply, err := conf.Write(state)
		if err != nil {
			fmt.Println("error writing value:", err)
			os.Exit(2)
		}
		fmt.Println("write response:", wreply, "\n")

		time.Sleep(2 * time.Second)

		rreply, err := conf.Read(&gridq.Empty{})
		if err != nil {
			fmt.Println("error reading value:", err)
			os.Exit(2)
		}
		fmt.Println("read response:", rreply.Reply.State, "\n")

		time.Sleep(3 * time.Second)
	}
}

func dief(format string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, format, a...)
	fmt.Fprint(os.Stderr, "\n")
	flag.Usage()
	os.Exit(2)
}
<commit_msg>cmd/gqclient: vet<commit_after>package main

import (
	"flag"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"

	"google.golang.org/grpc"

	"github.com/relab/gorums/gridq"
)

var localAddrs2x2 = []string{
	":8080", ":8081",
	":8082", ":8083",
}

var localAddrs3x3 = []string{
	":8080", ":8081", ":8082",
	":8083", ":8084", ":8085",
	":8086", ":8087", ":8088",
}

func main() {
	var (
		saddrs     = flag.String("addrs", "", "server addresses separated by ','")
		srows      = flag.Int("rows", 0, "number of rows")
		predefined = flag.String("predef", "", "predefined grids ('2x2' or '3x3') for local testing")
		printGrid  = flag.Bool("gprid", true, "print quorums to screen")
	)

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\nOptions:\n")
		flag.PrintDefaults()
	}
	flag.Parse()

	var rows int
	var addrs []string
	if *predefined != "" {
		switch *predefined {
		case "2x2":
			addrs = localAddrs2x2
			rows = 2
		case "3x3":
			rows = 3
			addrs = localAddrs3x3
		default:
			dief("unknown predefined grid: %q", *predefined)
		}
	} else {
		rows = *srows
		addrs = strings.Split(*saddrs, ",")
	}

	if len(addrs) == 0 {
		dief("no server addresses provided")
	}
	if rows == 0 {
		dief("rows must be > 0")
	}
	if len(addrs)%rows != 0 {
		dief("%d addresse(s) and %d row(s) does not form a complete grid", len(addrs), rows)
	}
	cols := len(addrs) / rows

	fmt.Println("#addrs:", len(addrs), "rows:", rows, "cols:", cols)

	mgr, err := gridq.NewManager(
		addrs,
		gridq.WithGrpcDialOptions(
			grpc.WithBlock(),
			grpc.WithInsecure(),
			grpc.WithTimeout(5*time.Second),
		),
	)
	if err != nil {
		dief("error creating manager: %v", err)
	}

	ids := mgr.NodeIDs()

	gqspec := gridq.NewGQSort(rows, cols, *printGrid)

	conf, err := mgr.NewConfiguration(ids, gqspec, time.Second)
	if err != nil {
		dief("error creating config: %v", err)
	}

	for {
		state := &gridq.State{
			Value:     int64(rand.Intn(1 << 8)),
			Timestamp: time.Now().Unix(),
		}

		fmt.Println("writing:", state)
		wreply, err := conf.Write(state)
		if err != nil {
			fmt.Println("error writing value:", err)
			os.Exit(2)
		}
		fmt.Println("write response:", wreply)

		time.Sleep(2 * time.Second)

		rreply, err := conf.Read(&gridq.Empty{})
		if err != nil {
			fmt.Println("error reading value:", err)
			os.Exit(2)
		}
		fmt.Println("read response:", rreply.Reply.State)

		time.Sleep(3 * time.Second)
	}
}

func dief(format string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, format, a...)
	fmt.Fprint(os.Stderr, "\n")
	flag.Usage()
	os.Exit(2)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Michael Lihs
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"errors"

	"github.com/spf13/cobra"
	"github.com/xanzy/go-gitlab"
)

var accessLevel, source, target int

var expiresAt string

var remove bool

var groupMembersCmd = &cobra.Command{
	Use: "group-members",
	Short: "Access group members",
	Long: `Show members and access level of groups`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.New("check usage of `group-members` with `golab group-members -h`")
	},
}

var groupMembersLsCmd = &cobra.Command{
	Use: "ls",
	Short: "List all members of a group",
	Long: `Gets a list of groupmembers viewable by the authenticated user`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		opts := &gitlab.ListGroupMembersOptions{
			// TODO think about generic way of adding pagination...
			// ListOptions:
		}
		members, _, err := gitlabClient.Groups.ListGroupMembers(id, opts)
		if err != nil { return err }
		return OutputJson(members)
	},
}

var groupMemberGetCmd = &cobra.Command{
	Use: "get",
	Short: "Get a member of a group",
	Long: `Get a member of a group`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id`not given - exiting")
		}
		member, _, err := gitlabClient.GroupMembers.GetGroupMember(id, userId)
		if err != nil {
			return err
		}
		return OutputJson(member)
	},
}

var groupMemberAddCmd = &cobra.Command{
	Use: "add",
	Short: "Add a member to a group",
	Long: `Add a member to a group

  Access Levels:

	10 = Guest Permissions
	20 = Reporter Permissions
	30 = Developer Permissions
	40 = Master Permissions
	50 = Owner Permissions`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id`not given - exiting")
		}
		if accessLevel == 0 {
			return errors.New("required parameter `-a` or `--access_level` not given - exiting")
		}
		opts := &gitlab.AddGroupMemberOptions{
			UserID: &userId,
			AccessLevel: int2AccessLevel(accessLevel),
		}
		if expiresAt != "" {
			opts.ExpiresAt = &expiresAt
		}
		member, _, err := gitlabClient.GroupMembers.AddGroupMember(id, opts)
		if err != nil { return err }
		return OutputJson(member)
	},
}

var groupMemberEditCmd = &cobra.Command{
	Use: "edit",
	Short: "Edit a member of a group or project",
	Long: `Updates a member of a group or project.

  Access Levels:

	10 = Guest Permissions
	20 = Reporter Permissions
	30 = Developer Permissions
	40 = Master Permissions
	50 = Owner Permissions`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id` not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `-user_id` not given - exiting")
		}
		if accessLevel == 0 {
			return errors.New("required parameter `-a` or `-access_level` not given - exiting")
		}
		opts := &gitlab.EditGroupMemberOptions{
			AccessLevel: int2AccessLevel(accessLevel),
		}
		if expiresAt != "" {
			opts.ExpiresAt = &expiresAt
		}
		member, _, err := gitlabClient.GroupMembers.EditGroupMember(id, userId, opts)
		if err != nil { return err }
		return OutputJson(member)
	},
}

var groupMemberDeleteCmd = &cobra.Command{
	Use: "delete",
	Short: "Remove a member from a group or project",
	Long: `Removes a user from a group or project.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id` not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id` not given - exiting")
		}
		_, err := gitlabClient.GroupMembers.RemoveGroupMember(id, userId)
		return err
	},
}

var groupMemberSyncCmd = &cobra.Command{
	Use: "sync",
	Short: "Synchronizes members of 2 groups",
	Long: `Synchronizes the members of 2 groups, by either

* merging them (default) - members that exist in target group but not in source group are kept
* removing them (--remove) - members that exist in target group but not in source group are deleted`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if source == 0 {
			return errors.New("required parameter `--source` not given - exiting")
		}
		if target == 0 {
			return errors.New("required parameter `--target` not given - exiting")
		}

		opts := &gitlab.ListGroupMembersOptions{}
		opts.PerPage = 1000

		createNonExistingTargetUsers(source, target, opts)

		if remove {
			err := removeTargetMembers(target, source, opts)
			if err != nil { return err }
		}

		members, _, err := gitlabClient.Groups.ListGroupMembers(target, opts)
		if err != nil { return err }
		return OutputJson(members)
	},
}

func createNonExistingTargetUsers(source int, target int, opts *gitlab.ListGroupMembersOptions) error {
	sourceMembers, _, err := gitlabClient.Groups.ListGroupMembers(source, opts)
	if err != nil { return err }
	for _, sourceMember := range sourceMembers  {
		_, resp, err := gitlabClient.GroupMembers.GetGroupMember(target, sourceMember.ID)
		if resp.StatusCode == 404 {   // 404 means "Not Found" --> does not exist yet
			newMemberOpts := &gitlab.AddGroupMemberOptions{
				UserID: &sourceMember.ID,
				AccessLevel: &sourceMember.AccessLevel,
			}
			if sourceMember.ExpiresAt != nil {
				expires, err := isoTime2String(sourceMember.ExpiresAt)
				if err != nil { return err }
				newMemberOpts.ExpiresAt = &expires
			}
			gitlabClient.GroupMembers.AddGroupMember(target, newMemberOpts)
		} else if err != nil {
			return err
		}
	}
	return nil
}

func removeTargetMembers(target int, source int, opts *gitlab.ListGroupMembersOptions) error {
	targetMembers, _, err := gitlabClient.Groups.ListGroupMembers(target, opts)
	if err != nil { return err }
	for _, targetMember := range targetMembers {
		_, resp, err := gitlabClient.GroupMembers.GetGroupMember(source, targetMember.ID)
		if resp.StatusCode == 404 {
			_, err := gitlabClient.GroupMembers.RemoveGroupMember(target, targetMember.ID)
			if err != nil { return err }
		} else if err != nil {
			return err
		}
	}
	return nil
}

func int2AccessLevel(accessLevel int) *gitlab.AccessLevelValue {
	if accessLevel == 10 { return gitlab.AccessLevel(gitlab.GuestPermissions)}
	if accessLevel == 20 { return gitlab.AccessLevel(gitlab.ReporterPermissions)}
	if accessLevel == 30 { return gitlab.AccessLevel(gitlab.DeveloperPermissions)}
	if accessLevel == 40 { return gitlab.AccessLevel(gitlab.MasterPermissions)}
	if accessLevel == 50 { return gitlab.AccessLevel(gitlab.OwnerPermission)}
	return nil
}

func init() {
	initGroupMembersLsCmd()
	initGroupMembersGetCmd()
	initGroupMemberAddCmd()
	initGroupMemberUpdateCmd()
	initGroupMemberDeleteCmd()
	initGroupMemberSyncCmd()
	RootCmd.AddCommand(groupMembersCmd)
}
func initGroupMembersLsCmd() {
	groupMembersLsCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to show members for")
	groupMembersCmd.AddCommand(groupMembersLsCmd)
}
func initGroupMembersGetCmd() {
	groupMemberGetCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to get member from")
	groupMemberGetCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0,"(required) id of user to get group member infos")
	groupMembersCmd.AddCommand(groupMemberGetCmd)
}
func initGroupMemberAddCmd() {
	groupMemberAddCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to add new member to")
	groupMemberAddCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) id of user to be added as new group member")
	groupMemberAddCmd.PersistentFlags().IntVarP(&accessLevel, "access_level", "a", 0, "(required) access level of new group member")
	groupMemberAddCmd.PersistentFlags().StringVarP(&expiresAt, "expires_at", "e", "", "(optional) expiry date of membership (yyyy-mm-dd)")
	groupMembersCmd.AddCommand(groupMemberAddCmd)
}
func initGroupMemberUpdateCmd() {
	groupMemberEditCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to change membership for")
	groupMemberEditCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) id the user to change membership for")
	groupMemberEditCmd.PersistentFlags().IntVarP(&accessLevel, "access_level", "a", 0, "(required) a valid access level")
	groupMemberEditCmd.PersistentFlags().StringVarP(&expiresAt, "expires_at", "e", "", "(optional) expiry date of membership (yyy-mm-dd)")
	groupMembersCmd.AddCommand(groupMemberEditCmd)
}

func initGroupMemberDeleteCmd() {
	groupMemberDeleteCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) the id of the group to delete user from")
	groupMemberDeleteCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) the id of the user to be removed from group")
	groupMembersCmd.AddCommand(groupMemberDeleteCmd)
}

func initGroupMemberSyncCmd() {
	groupMemberSyncCmd.PersistentFlags().IntVarP(&source, "source", "s", 0, "(required) id of group to copy members from")
	groupMemberSyncCmd.PersistentFlags().IntVarP(&target, "target", "t", 0, "(required) id of group to copy members to")
	groupMemberSyncCmd.PersistentFlags().BoolVarP(&remove, "remove", "r", false, "(optional) remove members in target group that don't exist in source group")
	groupMembersCmd.AddCommand(groupMemberSyncCmd)
}
<commit_msg>refactoring initialization of ListOptions<commit_after>// Copyright © 2017 Michael Lihs
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"errors"

	"github.com/spf13/cobra"
	"github.com/xanzy/go-gitlab"
)

var accessLevel, source, target int

var expiresAt string

var remove bool

var groupMembersCmd = &cobra.Command{
	Use: "group-members",
	Short: "Access group members",
	Long: `Show members and access level of groups`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.New("check usage of `group-members` with `golab group-members -h`")
	},
}

var groupMembersLsCmd = &cobra.Command{
	Use: "ls",
	Short: "List all members of a group",
	Long: `Gets a list of groupmembers viewable by the authenticated user`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		opts := &gitlab.ListGroupMembersOptions{
			ListOptions: gitlab.ListOptions{Page: 1, PerPage: 1000},
		}
		members, _, err := gitlabClient.Groups.ListGroupMembers(id, opts)
		if err != nil { return err }
		return OutputJson(members)
	},
}

var groupMemberGetCmd = &cobra.Command{
	Use: "get",
	Short: "Get a member of a group",
	Long: `Get a member of a group`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id`not given - exiting")
		}
		member, _, err := gitlabClient.GroupMembers.GetGroupMember(id, userId)
		if err != nil {
			return err
		}
		return OutputJson(member)
	},
}

var groupMemberAddCmd = &cobra.Command{
	Use: "add",
	Short: "Add a member to a group",
	Long: `Add a member to a group

  Access Levels:

	10 = Guest Permissions
	20 = Reporter Permissions
	30 = Developer Permissions
	40 = Master Permissions
	50 = Owner Permissions`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id`not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id`not given - exiting")
		}
		if accessLevel == 0 {
			return errors.New("required parameter `-a` or `--access_level` not given - exiting")
		}
		opts := &gitlab.AddGroupMemberOptions{
			UserID: &userId,
			AccessLevel: int2AccessLevel(accessLevel),
		}
		if expiresAt != "" {
			opts.ExpiresAt = &expiresAt
		}
		member, _, err := gitlabClient.GroupMembers.AddGroupMember(id, opts)
		if err != nil { return err }
		return OutputJson(member)
	},
}

var groupMemberEditCmd = &cobra.Command{
	Use: "edit",
	Short: "Edit a member of a group or project",
	Long: `Updates a member of a group or project.

  Access Levels:

	10 = Guest Permissions
	20 = Reporter Permissions
	30 = Developer Permissions
	40 = Master Permissions
	50 = Owner Permissions`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id` not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `-user_id` not given - exiting")
		}
		if accessLevel == 0 {
			return errors.New("required parameter `-a` or `-access_level` not given - exiting")
		}
		opts := &gitlab.EditGroupMemberOptions{
			AccessLevel: int2AccessLevel(accessLevel),
		}
		if expiresAt != "" {
			opts.ExpiresAt = &expiresAt
		}
		member, _, err := gitlabClient.GroupMembers.EditGroupMember(id, userId, opts)
		if err != nil { return err }
		return OutputJson(member)
	},
}

var groupMemberDeleteCmd = &cobra.Command{
	Use: "delete",
	Short: "Remove a member from a group or project",
	Long: `Removes a user from a group or project.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if id == 0 {
			return errors.New("required parameter `-i` or `--id` not given - exiting")
		}
		if userId == 0 {
			return errors.New("required parameter `-u` or `--user_id` not given - exiting")
		}
		_, err := gitlabClient.GroupMembers.RemoveGroupMember(id, userId)
		return err
	},
}

var groupMemberSyncCmd = &cobra.Command{
	Use: "sync",
	Short: "Synchronizes members of 2 groups",
	Long: `Synchronizes the members of 2 groups, by either

* merging them (default) - members that exist in target group but not in source group are kept
* removing them (--remove) - members that exist in target group but not in source group are deleted`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if source == 0 {
			return errors.New("required parameter `--source` not given - exiting")
		}
		if target == 0 {
			return errors.New("required parameter `--target` not given - exiting")
		}

		opts := &gitlab.ListGroupMembersOptions{
			ListOptions: gitlab.ListOptions{Page: 1, PerPage: 1000},
		}

		createNonExistingTargetUsers(source, target, opts)

		if remove {
			err := removeTargetMembers(target, source, opts)
			if err != nil { return err }
		}

		members, _, err := gitlabClient.Groups.ListGroupMembers(target, opts)
		if err != nil { return err }
		return OutputJson(members)
	},
}

func createNonExistingTargetUsers(source int, target int, opts *gitlab.ListGroupMembersOptions) error {
	sourceMembers, _, err := gitlabClient.Groups.ListGroupMembers(source, opts)
	if err != nil { return err }
	for _, sourceMember := range sourceMembers  {
		_, resp, err := gitlabClient.GroupMembers.GetGroupMember(target, sourceMember.ID)
		if resp.StatusCode == 404 {   // 404 means "Not Found" --> does not exist yet
			newMemberOpts := &gitlab.AddGroupMemberOptions{
				UserID: &sourceMember.ID,
				AccessLevel: &sourceMember.AccessLevel,
			}
			if sourceMember.ExpiresAt != nil {
				expires, err := isoTime2String(sourceMember.ExpiresAt)
				if err != nil { return err }
				newMemberOpts.ExpiresAt = &expires
			}
			gitlabClient.GroupMembers.AddGroupMember(target, newMemberOpts)
		} else if err != nil {
			return err
		}
	}
	return nil
}

func removeTargetMembers(target int, source int, opts *gitlab.ListGroupMembersOptions) error {
	targetMembers, _, err := gitlabClient.Groups.ListGroupMembers(target, opts)
	if err != nil { return err }
	for _, targetMember := range targetMembers {
		_, resp, err := gitlabClient.GroupMembers.GetGroupMember(source, targetMember.ID)
		if resp.StatusCode == 404 {
			_, err := gitlabClient.GroupMembers.RemoveGroupMember(target, targetMember.ID)
			if err != nil { return err }
		} else if err != nil {
			return err
		}
	}
	return nil
}

func int2AccessLevel(accessLevel int) *gitlab.AccessLevelValue {
	if accessLevel == 10 { return gitlab.AccessLevel(gitlab.GuestPermissions)}
	if accessLevel == 20 { return gitlab.AccessLevel(gitlab.ReporterPermissions)}
	if accessLevel == 30 { return gitlab.AccessLevel(gitlab.DeveloperPermissions)}
	if accessLevel == 40 { return gitlab.AccessLevel(gitlab.MasterPermissions)}
	if accessLevel == 50 { return gitlab.AccessLevel(gitlab.OwnerPermission)}
	return nil
}

func init() {
	initGroupMembersLsCmd()
	initGroupMembersGetCmd()
	initGroupMemberAddCmd()
	initGroupMemberUpdateCmd()
	initGroupMemberDeleteCmd()
	initGroupMemberSyncCmd()
	RootCmd.AddCommand(groupMembersCmd)
}
func initGroupMembersLsCmd() {
	groupMembersLsCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to show members for")
	groupMembersCmd.AddCommand(groupMembersLsCmd)
}
func initGroupMembersGetCmd() {
	groupMemberGetCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to get member from")
	groupMemberGetCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0,"(required) id of user to get group member infos")
	groupMembersCmd.AddCommand(groupMemberGetCmd)
}
func initGroupMemberAddCmd() {
	groupMemberAddCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to add new member to")
	groupMemberAddCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) id of user to be added as new group member")
	groupMemberAddCmd.PersistentFlags().IntVarP(&accessLevel, "access_level", "a", 0, "(required) access level of new group member")
	groupMemberAddCmd.PersistentFlags().StringVarP(&expiresAt, "expires_at", "e", "", "(optional) expiry date of membership (yyyy-mm-dd)")
	groupMembersCmd.AddCommand(groupMemberAddCmd)
}
func initGroupMemberUpdateCmd() {
	groupMemberEditCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) id of group to change membership for")
	groupMemberEditCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) id the user to change membership for")
	groupMemberEditCmd.PersistentFlags().IntVarP(&accessLevel, "access_level", "a", 0, "(required) a valid access level")
	groupMemberEditCmd.PersistentFlags().StringVarP(&expiresAt, "expires_at", "e", "", "(optional) expiry date of membership (yyy-mm-dd)")
	groupMembersCmd.AddCommand(groupMemberEditCmd)
}

func initGroupMemberDeleteCmd() {
	groupMemberDeleteCmd.PersistentFlags().IntVarP(&id, "id", "i", 0, "(required) the id of the group to delete user from")
	groupMemberDeleteCmd.PersistentFlags().IntVarP(&userId, "user_id", "u", 0, "(required) the id of the user to be removed from group")
	groupMembersCmd.AddCommand(groupMemberDeleteCmd)
}

func initGroupMemberSyncCmd() {
	groupMemberSyncCmd.PersistentFlags().IntVarP(&source, "source", "s", 0, "(required) id of group to copy members from")
	groupMemberSyncCmd.PersistentFlags().IntVarP(&target, "target", "t", 0, "(required) id of group to copy members to")
	groupMemberSyncCmd.PersistentFlags().BoolVarP(&remove, "remove", "r", false, "(optional) remove members in target group that don't exist in source group")
	groupMembersCmd.AddCommand(groupMemberSyncCmd)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors 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 (
	"fmt"
	"io"
	"path/filepath"

	"github.com/spf13/cobra"

	"k8s.io/helm/cmd/helm/helmpath"
	"k8s.io/helm/pkg/repo"
)

type repoAddCmd struct {
	name     string
	url      string
	home     helmpath.Home
	out      io.Writer
	noupdate bool
}

func newRepoAddCmd(out io.Writer) *cobra.Command {
	add := &repoAddCmd{
		out: out,
	}

	cmd := &cobra.Command{
		Use:   "add [flags] [NAME] [URL]",
		Short: "add a chart repository",
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil {
				return err
			}

			add.name = args[0]
			add.url = args[1]
			add.home = helmpath.Home(homePath())

			return add.run()
		},
	}
	f := cmd.Flags()
	f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
	return cmd
}

func (a *repoAddCmd) run() error {
	var err error
	if a.noupdate {
		err = addRepository(a.name, a.url, a.home)
	} else {
		err = updateRepository(a.name, a.url, a.home)
	}
	if err != nil {
		return err
	}
	fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name)
	return nil
}

func addRepository(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	if err := repo.DownloadIndexFile(name, url, cif); err != nil {
		return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error())
	}

	return insertRepoLine(name, url, home)
}

func insertRepoLine(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
	if err != nil {
		return err
	}

	if f.Has(name) {
		return fmt.Errorf("The repository name you provided (%s) already exists. Please specify a different name.", name)
	}
	f.Add(&repo.Entry{
		Name:  name,
		URL:   url,
		Cache: filepath.Base(cif),
	})
	return f.WriteFile(home.RepositoryFile(), 0644)
}

func updateRepository(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	if err := repo.DownloadIndexFile(name, url, cif); err != nil {
		return err
	}

	return updateRepoLine(name, url, home)
}

func updateRepoLine(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
	if err != nil {
		return err
	}

	f.Update(&repo.Entry{
		Name:  name,
		URL:   url,
		Cache: filepath.Base(cif),
	})

	return f.WriteFile(home.RepositoryFile(), 0666)
}
<commit_msg>Remove trailing slash (if any) from repo url<commit_after>/*
Copyright 2016 The Kubernetes Authors 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 (
	"fmt"
	"io"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"

	"k8s.io/helm/cmd/helm/helmpath"
	"k8s.io/helm/pkg/repo"
)

type repoAddCmd struct {
	name     string
	url      string
	home     helmpath.Home
	out      io.Writer
	noupdate bool
}

func newRepoAddCmd(out io.Writer) *cobra.Command {
	add := &repoAddCmd{
		out: out,
	}

	cmd := &cobra.Command{
		Use:   "add [flags] [NAME] [URL]",
		Short: "add a chart repository",
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil {
				return err
			}

			add.name = args[0]
			add.url = args[1]
			add.home = helmpath.Home(homePath())

			return add.run()
		},
	}
	f := cmd.Flags()
	f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
	return cmd
}

func (a *repoAddCmd) run() error {
	var err error
	if a.noupdate {
		err = addRepository(a.name, a.url, a.home)
	} else {
		err = updateRepository(a.name, a.url, a.home)
	}
	if err != nil {
		return err
	}
	fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name)
	return nil
}

func addRepository(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	if err := repo.DownloadIndexFile(name, url, cif); err != nil {
		return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error())
	}

	return insertRepoLine(name, url, home)
}

func insertRepoLine(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
	if err != nil {
		return err
	}

	if f.Has(name) {
		return fmt.Errorf("The repository name you provided (%s) already exists. Please specify a different name.", name)
	}
	f.Add(&repo.Entry{
		Name:  name,
		URL:   strings.TrimSuffix(url, "/"),
		Cache: filepath.Base(cif),
	})
	return f.WriteFile(home.RepositoryFile(), 0644)
}

func updateRepository(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	if err := repo.DownloadIndexFile(name, url, cif); err != nil {
		return err
	}

	return updateRepoLine(name, url, home)
}

func updateRepoLine(name, url string, home helmpath.Home) error {
	cif := home.CacheIndex(name)
	f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
	if err != nil {
		return err
	}

	f.Update(&repo.Entry{
		Name:  name,
		URL:   url,
		Cache: filepath.Base(cif),
	})

	return f.WriteFile(home.RepositoryFile(), 0666)
}
<|endoftext|>


<commit_before>// Copyright 2020 The Kubeflow 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
//
// 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 server

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/kubeflow/pipelines/backend/src/cache/client"
	"github.com/kubeflow/pipelines/backend/src/cache/model"
	"github.com/kubeflow/pipelines/backend/src/cache/storage"
	"k8s.io/api/admission/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	KFPCacheEnabledLabelKey    string = "pipelines.kubeflow.org/cache_enabled"
	KFPCacheEnabledLabelValue  string = "true"
	KFPCachedLabelKey          string = "pipelines.kubeflow.org/reused_from_cache"
	KFPCachedLabelValue        string = "true"
	ArgoWorkflowNodeName       string = "workflows.argoproj.io/node-name"
	ArgoWorkflowTemplate       string = "workflows.argoproj.io/template"
	ExecutionKey               string = "pipelines.kubeflow.org/execution_cache_key"
	CacheIDLabelKey            string = "pipelines.kubeflow.org/cache_id"
	ArgoWorkflowOutputs        string = "workflows.argoproj.io/outputs"
	MetadataWrittenKey         string = "pipelines.kubeflow.org/metadata_written"
	AnnotationPath             string = "/metadata/annotations"
	LabelPath                  string = "/metadata/labels"
	SpecContainersPath         string = "/spec/containers"
	SpecInitContainersPath     string = "/spec/initContainers"
	TFXPodSuffix               string = "tfx/orchestration/kubeflow/container_entrypoint.py"
	SdkTypeLabel               string = "pipelines.kubeflow.org/pipeline-sdk-type"
	TfxSdkTypeLabel            string = "tfx"
	V2ComponentAnnotationKey   string = "pipelines.kubeflow.org/v2_component"
	V2ComponentAnnotationValue string = "true"
)

var (
	podResource = metav1.GroupVersionResource{Version: "v1", Resource: "pods"}
)

type ClientManagerInterface interface {
	CacheStore() storage.ExecutionCacheStoreInterface
	KubernetesCoreClient() client.KubernetesCoreInterface
}

// MutatePodIfCached will check whether the execution has already been run before from MLMD and apply the output into pod.metadata.output
func MutatePodIfCached(req *v1beta1.AdmissionRequest, clientMgr ClientManagerInterface) ([]patchOperation, error) {
	// This handler should only get called on Pod objects as per the MutatingWebhookConfiguration in the YAML file.
	// However, if (for whatever reason) this gets invoked on an object of a different kind, issue a log message but
	// let the object request pass through otherwise.
	if req.Resource != podResource {
		log.Printf("Expect resource to be %q, but found %q", podResource, req.Resource)
		return nil, nil
	}

	// Parse the Pod object.
	raw := req.Object.Raw
	pod := corev1.Pod{}
	if _, _, err := universalDeserializer.Decode(raw, nil, &pod); err != nil {
		return nil, fmt.Errorf("could not deserialize pod object: %v", err)
	}

	// Pod filtering to only cache KFP argo pods except TFX pods
	// TODO: Switch to objectSelector once Kubernetes 1.15 hits the GKE stable channel. See
	// https://github.com/kubernetes/kubernetes/pull/78505
	// https://cloud.google.com/kubernetes-engine/docs/release-notes-stable
	if !isKFPCacheEnabled(&pod) {
		log.Printf("This pod %s does not enable cache.", pod.ObjectMeta.Name)
		return nil, nil
	}

	if isTFXPod(&pod) {
		log.Printf("This pod %s is created by tfx pipelines.", pod.ObjectMeta.Name)
		return nil, nil
	}

	if isV2Pod(&pod) {
		// KFP v2 handles caching by its driver.
		log.Printf("This pod %s is created by KFP v2 pipelines.", pod.ObjectMeta.Name)
		return nil, nil
	}

	var patches []patchOperation
	annotations := pod.ObjectMeta.Annotations
	labels := pod.ObjectMeta.Labels
	template, exists := annotations[ArgoWorkflowTemplate]
	var executionHashKey string
	if !exists {
		return patches, nil
	}

	// Generate the executionHashKey based on pod.metadata.annotations.workflows.argoproj.io/template
	executionHashKey, err := generateCacheKeyFromTemplate(template)
	log.Println(executionHashKey)
	if err != nil {
		log.Printf("Unable to generate cache key for pod %s : %s", pod.ObjectMeta.Name, err.Error())
		return patches, nil
	}

	annotations[ExecutionKey] = executionHashKey
	labels[CacheIDLabelKey] = ""
	var maxCacheStalenessInSeconds int64 = -1
	maxCacheStaleness, exists := annotations[MaxCacheStalenessKey]
	if exists {
		maxCacheStalenessInSeconds = getMaxCacheStaleness(maxCacheStaleness)
	}

	var cachedExecution *model.ExecutionCache
	cachedExecution, err = clientMgr.CacheStore().GetExecutionCache(executionHashKey, maxCacheStalenessInSeconds)
	if err != nil {
		log.Println(err.Error())
	}
	// Found cached execution, add cached output and cache_id and replace container images.
	if cachedExecution != nil {
		log.Println("Cached output: " + cachedExecution.ExecutionOutput)

		annotations[ArgoWorkflowOutputs] = getValueFromSerializedMap(cachedExecution.ExecutionOutput, ArgoWorkflowOutputs)
		labels[CacheIDLabelKey] = strconv.FormatInt(cachedExecution.ID, 10)
		labels[KFPCachedLabelKey] = KFPCachedLabelValue // This label indicates the pod is taken from cache.

		// These labels cache results for metadata-writer.
		labels[MetadataExecutionIDKey] = getValueFromSerializedMap(cachedExecution.ExecutionOutput, MetadataExecutionIDKey)
		labels[MetadataWrittenKey] = "true"

		// Image selected from Google Container Register(gcr) for it small size, gcr since there
		// is not image pull rate limit. For more info see issue: https://github.com/kubeflow/pipelines/issues/4099
		image := "gcr.io/google-containers/busybox"
		if v, ok := os.LookupEnv("CACHE_IMAGE"); ok {
			image = v
		}
		dummyContainer := corev1.Container{
			Name:    "main",
			Image:   image,
			Command: []string{`echo`, `"This step output is taken from cache."`},
		}
		dummyContainers := []corev1.Container{
			dummyContainer,
		}
		patches = append(patches, patchOperation{
			Op:    OperationTypeReplace,
			Path:  SpecContainersPath,
			Value: dummyContainers,
		})
		node_restrictions, err := getEnvBool("CACHE_NODE_RESTRICTIONS")
		if err != nil {
			return nil, err
		}
		if !node_restrictions {
			if pod.Spec.Affinity != nil {
				patches = append(patches, patchOperation{
					Op:   OperationTypeRemove,
					Path: "/spec/affinity",
				})
			}
			if pod.Spec.NodeSelector != nil {
				patches = append(patches, patchOperation{
					Op:   OperationTypeRemove,
					Path: "/spec/nodeSelector",
				})
			}
		}
		if pod.Spec.InitContainers != nil || len(pod.Spec.InitContainers) != 0 {
			patches = append(patches, patchOperation{
				Op:   OperationTypeRemove,
				Path: SpecInitContainersPath,
			})
		}
	}

	// Add executionKey to pod.metadata.annotations
	patches = append(patches, patchOperation{
		Op:    OperationTypeAdd,
		Path:  AnnotationPath,
		Value: annotations,
	})

	// Add cache_id label key
	patches = append(patches, patchOperation{
		Op:    OperationTypeAdd,
		Path:  LabelPath,
		Value: labels,
	})

	return patches, nil
}

// intersectStructureWithSkeleton recursively intersects two maps
// nil values in the skeleton map mean that the whole value (which can also be a map) should be kept.
func intersectStructureWithSkeleton(src map[string]interface{}, skeleton map[string]interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	for key, skeletonValue := range skeleton {
		if value, ok := src[key]; ok {
			if skeletonValue == nil {
				result[key] = value
			} else {
				result[key] = intersectStructureWithSkeleton(value.(map[string]interface{}), skeletonValue.(map[string]interface{}))
			}
		}
	}
	return result
}

func generateCacheKeyFromTemplate(template string) (string, error) {
	var templateMap map[string]interface{}
	b := []byte(template)
	err := json.Unmarshal(b, &templateMap)
	if err != nil {
		return "", err
	}

	// Selectively copying parts of the template that should affect the cache
	templateSkeleton := map[string]interface{}{
		"container": map[string]interface{}{
			"image":        nil,
			"command":      nil,
			"args":         nil,
			"env":          nil,
			"volumeMounts": nil,
		},
		"inputs":         nil,
		"outputs":        nil, // Output artifact/parameter names and paths are important and need to be considered. We can include the whole section since Argo does not seem to put the artifact s3 specifications here, leaving them in archiveLocation.
		"volumes":        nil,
		"initContainers": nil,
		"sidecars":       nil,
	}
	cacheKeyMap := intersectStructureWithSkeleton(templateMap, templateSkeleton)

	b, err = json.Marshal(cacheKeyMap)
	if err != nil {
		return "", err
	}
	hash := sha256.New()
	hash.Write(b)
	md := hash.Sum(nil)
	executionHashKey := hex.EncodeToString(md)

	return executionHashKey, nil
}

func getValueFromSerializedMap(serializedMap string, key string) string {
	var outputMap map[string]interface{}
	b := []byte(serializedMap)
	err := json.Unmarshal(b, &outputMap)
	if err != nil {
		return ""
	}

	value, exist := outputMap[key].(string)
	if !exist || value == "" {
		return ""
	}
	return value
}

func isKFPCacheEnabled(pod *corev1.Pod) bool {
	cacheEnabled, exists := pod.ObjectMeta.Labels[KFPCacheEnabledLabelKey]
	if !exists {
		log.Printf("This pod %s is not created by KFP.", pod.ObjectMeta.Name)
		return false
	}
	return cacheEnabled == KFPCacheEnabledLabelValue
}

func isTFXPod(pod *corev1.Pod) bool {
	// The label defaults to 'tfx', but is overridable.
	// Official tfx templates override the value to 'tfx-template', so
	// we loosely match the word 'tfx'.
	if strings.Contains(pod.Labels[SdkTypeLabel], TfxSdkTypeLabel) {
		return true
	}
	containers := pod.Spec.Containers
	if containers == nil || len(containers) == 0 {
		log.Printf("This pod container does not exist.")
		return true
	}
	var mainContainers []corev1.Container
	for _, c := range containers {
		if c.Name != "" && c.Name == "main" {
			mainContainers = append(mainContainers, c)
		}
	}
	if len(mainContainers) != 1 {
		return false
	}
	mainContainer := mainContainers[0]
	return len(mainContainer.Command) != 0 && strings.HasSuffix(mainContainer.Command[len(mainContainer.Command)-1], TFXPodSuffix)
}

func isV2Pod(pod *corev1.Pod) bool {
	return pod.Annotations[V2ComponentAnnotationKey] == V2ComponentAnnotationValue
}

func getEnvBool(key string) (bool, error) {
	v, ok := os.LookupEnv("CACHE_NODE_RESTRICTIONS")
	if !ok {
		return false, nil
	}
	b, err := strconv.ParseBool(v)
	if err != nil {
		return false, err
	}
	return b, nil
}
<commit_msg>fix: the cache server in combination with kubeflow profile quotas (#5743)<commit_after>// Copyright 2020 The Kubeflow 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
//
// 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 server

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/kubeflow/pipelines/backend/src/cache/client"
	"github.com/kubeflow/pipelines/backend/src/cache/model"
	"github.com/kubeflow/pipelines/backend/src/cache/storage"
	"k8s.io/api/admission/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/api/resource"
)

const (
	KFPCacheEnabledLabelKey    string = "pipelines.kubeflow.org/cache_enabled"
	KFPCacheEnabledLabelValue  string = "true"
	KFPCachedLabelKey          string = "pipelines.kubeflow.org/reused_from_cache"
	KFPCachedLabelValue        string = "true"
	ArgoWorkflowNodeName       string = "workflows.argoproj.io/node-name"
	ArgoWorkflowTemplate       string = "workflows.argoproj.io/template"
	ExecutionKey               string = "pipelines.kubeflow.org/execution_cache_key"
	CacheIDLabelKey            string = "pipelines.kubeflow.org/cache_id"
	ArgoWorkflowOutputs        string = "workflows.argoproj.io/outputs"
	MetadataWrittenKey         string = "pipelines.kubeflow.org/metadata_written"
	AnnotationPath             string = "/metadata/annotations"
	LabelPath                  string = "/metadata/labels"
	SpecContainersPath         string = "/spec/containers"
	SpecInitContainersPath     string = "/spec/initContainers"
	TFXPodSuffix               string = "tfx/orchestration/kubeflow/container_entrypoint.py"
	SdkTypeLabel               string = "pipelines.kubeflow.org/pipeline-sdk-type"
	TfxSdkTypeLabel            string = "tfx"
	V2ComponentAnnotationKey   string = "pipelines.kubeflow.org/v2_component"
	V2ComponentAnnotationValue string = "true"
)

var (
	podResource = metav1.GroupVersionResource{Version: "v1", Resource: "pods"}
)

type ClientManagerInterface interface {
	CacheStore() storage.ExecutionCacheStoreInterface
	KubernetesCoreClient() client.KubernetesCoreInterface
}

// MutatePodIfCached will check whether the execution has already been run before from MLMD and apply the output into pod.metadata.output
func MutatePodIfCached(req *v1beta1.AdmissionRequest, clientMgr ClientManagerInterface) ([]patchOperation, error) {
	// This handler should only get called on Pod objects as per the MutatingWebhookConfiguration in the YAML file.
	// However, if (for whatever reason) this gets invoked on an object of a different kind, issue a log message but
	// let the object request pass through otherwise.
	if req.Resource != podResource {
		log.Printf("Expect resource to be %q, but found %q", podResource, req.Resource)
		return nil, nil
	}

	// Parse the Pod object.
	raw := req.Object.Raw
	pod := corev1.Pod{}
	if _, _, err := universalDeserializer.Decode(raw, nil, &pod); err != nil {
		return nil, fmt.Errorf("could not deserialize pod object: %v", err)
	}

	// Pod filtering to only cache KFP argo pods except TFX pods
	// TODO: Switch to objectSelector once Kubernetes 1.15 hits the GKE stable channel. See
	// https://github.com/kubernetes/kubernetes/pull/78505
	// https://cloud.google.com/kubernetes-engine/docs/release-notes-stable
	if !isKFPCacheEnabled(&pod) {
		log.Printf("This pod %s does not enable cache.", pod.ObjectMeta.Name)
		return nil, nil
	}

	if isTFXPod(&pod) {
		log.Printf("This pod %s is created by tfx pipelines.", pod.ObjectMeta.Name)
		return nil, nil
	}

	if isV2Pod(&pod) {
		// KFP v2 handles caching by its driver.
		log.Printf("This pod %s is created by KFP v2 pipelines.", pod.ObjectMeta.Name)
		return nil, nil
	}

	var patches []patchOperation
	annotations := pod.ObjectMeta.Annotations
	labels := pod.ObjectMeta.Labels
	template, exists := annotations[ArgoWorkflowTemplate]
	var executionHashKey string
	if !exists {
		return patches, nil
	}

	// Generate the executionHashKey based on pod.metadata.annotations.workflows.argoproj.io/template
	executionHashKey, err := generateCacheKeyFromTemplate(template)
	log.Println(executionHashKey)
	if err != nil {
		log.Printf("Unable to generate cache key for pod %s : %s", pod.ObjectMeta.Name, err.Error())
		return patches, nil
	}

	annotations[ExecutionKey] = executionHashKey
	labels[CacheIDLabelKey] = ""
	var maxCacheStalenessInSeconds int64 = -1
	maxCacheStaleness, exists := annotations[MaxCacheStalenessKey]
	if exists {
		maxCacheStalenessInSeconds = getMaxCacheStaleness(maxCacheStaleness)
	}

	var cachedExecution *model.ExecutionCache
	cachedExecution, err = clientMgr.CacheStore().GetExecutionCache(executionHashKey, maxCacheStalenessInSeconds)
	if err != nil {
		log.Println(err.Error())
	}
	// Found cached execution, add cached output and cache_id and replace container images.
	if cachedExecution != nil {
		log.Println("Cached output: " + cachedExecution.ExecutionOutput)

		annotations[ArgoWorkflowOutputs] = getValueFromSerializedMap(cachedExecution.ExecutionOutput, ArgoWorkflowOutputs)
		labels[CacheIDLabelKey] = strconv.FormatInt(cachedExecution.ID, 10)
		labels[KFPCachedLabelKey] = KFPCachedLabelValue // This label indicates the pod is taken from cache.

		// These labels cache results for metadata-writer.
		labels[MetadataExecutionIDKey] = getValueFromSerializedMap(cachedExecution.ExecutionOutput, MetadataExecutionIDKey)
		labels[MetadataWrittenKey] = "true"

		// Image selected from Google Container Register(gcr) for it small size, gcr since there
		// is not image pull rate limit. For more info see issue: https://github.com/kubeflow/pipelines/issues/4099
		image := "gcr.io/google-containers/busybox"
		if v, ok := os.LookupEnv("CACHE_IMAGE"); ok {
			image = v
		}
		dummyContainer := corev1.Container{
			Name:    "main",
			Image:   image,
			Command: []string{`echo`, `"This step output is taken from cache."`},
			Resources: corev1.ResourceRequirements{
				Requests: corev1.ResourceList{
					corev1.ResourceCPU:    resource.MustParse("0.01"),
					corev1.ResourceMemory: resource.MustParse("16Mi"),
				},
			},
		}
		dummyContainers := []corev1.Container{
			dummyContainer,
		}
		patches = append(patches, patchOperation{
			Op:    OperationTypeReplace,
			Path:  SpecContainersPath,
			Value: dummyContainers,
		})
		node_restrictions, err := getEnvBool("CACHE_NODE_RESTRICTIONS")
		if err != nil {
			return nil, err
		}
		if !node_restrictions {
			if pod.Spec.Affinity != nil {
				patches = append(patches, patchOperation{
					Op:   OperationTypeRemove,
					Path: "/spec/affinity",
				})
			}
			if pod.Spec.NodeSelector != nil {
				patches = append(patches, patchOperation{
					Op:   OperationTypeRemove,
					Path: "/spec/nodeSelector",
				})
			}
		}
		if pod.Spec.InitContainers != nil || len(pod.Spec.InitContainers) != 0 {
			patches = append(patches, patchOperation{
				Op:   OperationTypeRemove,
				Path: SpecInitContainersPath,
			})
		}
	}

	// Add executionKey to pod.metadata.annotations
	patches = append(patches, patchOperation{
		Op:    OperationTypeAdd,
		Path:  AnnotationPath,
		Value: annotations,
	})

	// Add cache_id label key
	patches = append(patches, patchOperation{
		Op:    OperationTypeAdd,
		Path:  LabelPath,
		Value: labels,
	})

	return patches, nil
}

// intersectStructureWithSkeleton recursively intersects two maps
// nil values in the skeleton map mean that the whole value (which can also be a map) should be kept.
func intersectStructureWithSkeleton(src map[string]interface{}, skeleton map[string]interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	for key, skeletonValue := range skeleton {
		if value, ok := src[key]; ok {
			if skeletonValue == nil {
				result[key] = value
			} else {
				result[key] = intersectStructureWithSkeleton(value.(map[string]interface{}), skeletonValue.(map[string]interface{}))
			}
		}
	}
	return result
}

func generateCacheKeyFromTemplate(template string) (string, error) {
	var templateMap map[string]interface{}
	b := []byte(template)
	err := json.Unmarshal(b, &templateMap)
	if err != nil {
		return "", err
	}

	// Selectively copying parts of the template that should affect the cache
	templateSkeleton := map[string]interface{}{
		"container": map[string]interface{}{
			"image":        nil,
			"command":      nil,
			"args":         nil,
			"env":          nil,
			"volumeMounts": nil,
		},
		"inputs":         nil,
		"outputs":        nil, // Output artifact/parameter names and paths are important and need to be considered. We can include the whole section since Argo does not seem to put the artifact s3 specifications here, leaving them in archiveLocation.
		"volumes":        nil,
		"initContainers": nil,
		"sidecars":       nil,
	}
	cacheKeyMap := intersectStructureWithSkeleton(templateMap, templateSkeleton)

	b, err = json.Marshal(cacheKeyMap)
	if err != nil {
		return "", err
	}
	hash := sha256.New()
	hash.Write(b)
	md := hash.Sum(nil)
	executionHashKey := hex.EncodeToString(md)

	return executionHashKey, nil
}

func getValueFromSerializedMap(serializedMap string, key string) string {
	var outputMap map[string]interface{}
	b := []byte(serializedMap)
	err := json.Unmarshal(b, &outputMap)
	if err != nil {
		return ""
	}

	value, exist := outputMap[key].(string)
	if !exist || value == "" {
		return ""
	}
	return value
}

func isKFPCacheEnabled(pod *corev1.Pod) bool {
	cacheEnabled, exists := pod.ObjectMeta.Labels[KFPCacheEnabledLabelKey]
	if !exists {
		log.Printf("This pod %s is not created by KFP.", pod.ObjectMeta.Name)
		return false
	}
	return cacheEnabled == KFPCacheEnabledLabelValue
}

func isTFXPod(pod *corev1.Pod) bool {
	// The label defaults to 'tfx', but is overridable.
	// Official tfx templates override the value to 'tfx-template', so
	// we loosely match the word 'tfx'.
	if strings.Contains(pod.Labels[SdkTypeLabel], TfxSdkTypeLabel) {
		return true
	}
	containers := pod.Spec.Containers
	if containers == nil || len(containers) == 0 {
		log.Printf("This pod container does not exist.")
		return true
	}
	var mainContainers []corev1.Container
	for _, c := range containers {
		if c.Name != "" && c.Name == "main" {
			mainContainers = append(mainContainers, c)
		}
	}
	if len(mainContainers) != 1 {
		return false
	}
	mainContainer := mainContainers[0]
	return len(mainContainer.Command) != 0 && strings.HasSuffix(mainContainer.Command[len(mainContainer.Command)-1], TFXPodSuffix)
}

func isV2Pod(pod *corev1.Pod) bool {
	return pod.Annotations[V2ComponentAnnotationKey] == V2ComponentAnnotationValue
}

func getEnvBool(key string) (bool, error) {
	v, ok := os.LookupEnv("CACHE_NODE_RESTRICTIONS")
	if !ok {
		return false, nil
	}
	b, err := strconv.ParseBool(v)
	if err != nil {
		return false, err
	}
	return b, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/sha1"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/StackExchange/httpunit"
	"github.com/StackExchange/httpunit/_third_party/github.com/BurntSushi/toml"
	"github.com/StackExchange/httpunit/_third_party/github.com/bradfitz/slice"
)

var (
	filter   = flag.String("filter", "", `if specified, only uses this IP address; may end with "." to filter by IPs that start with the filter`)
	no10     = flag.Bool("no10", false, "no RFC1918 addresses")
	hiera    = flag.String("hiera", "", "tests listeners members from the specified hieradata/sets.json file")
	tomlLoc  = flag.String("toml", "", "httpunit.toml location")
	verbose1 = flag.Bool("v", false, "verbose output: show successes")
	verbose2 = flag.Bool("vv", false, "more verbose output: show -header, cert details")
	header   = flag.String("header", "X-Request-Guid", "an HTTP to header to print in verbose mode")
	timeout  = flag.Duration("timeout", httpunit.Timeout, "connection timeout")
	ipMap    = flag.String("ipmap", "", `override or set one entry if the IPs table, in "key=value" format, where value is a JSON array of strings; for example: -ipmap='BASEIP=["10.2.3.", "1.4.5."]'`)
	tags     = flag.String("tags", "", "if specified, only run plans that are tagged with these tags. Use comma seperated to include mutiple tags, e.g. \"normal,extended\"")
	protos   = flag.String("protos", "", "if specified, only run tests that use the designated protocol. Valid choices are: http,https,tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6. e.g. \"http,https\"")
)

func printNames(x []pkix.AttributeTypeAndValue) []string {
	var r []string
	for _, v := range x {
		r = append(r, v.Value.(string))
	}
	return r
}

func printStringsIfExist(w *bufio.Writer, label string, list []string) {
	switch {
	case len(list) == 1:
		fmt.Fprintf(w, `%s: %q`, label, list[0])
	case len(list) > 1:
		fmt.Fprintf(w, `%s: %q`, label, list)
		//fmt.Fprintf(w, `%s: "{%s"}`, label, strings.Join(list, `" "`))
	default:
		// do nothing.
	}
}

func printCert(w *bufio.Writer, c *x509.Certificate) {
	h := "\n\t\t"
	fmt.Fprintf(w, h+"expires: %v", c.NotAfter)
	fmt.Fprintf(w, h+"serial: %d", c.SerialNumber)
	fmt.Fprintf(w, h+"fingerprint: %x", sha1.Sum(c.Raw))
	fmt.Fprintf(w, h+"version: %d", c.Version)
	fmt.Fprintf(w, h+"domains: %q", c.DNSNames)
	if len(c.EmailAddresses) > 0 {
		fmt.Fprintf(w, h+"emails: %q", c.EmailAddresses)
	}
	if len(c.IPAddresses) > 0 {
		fmt.Fprintf(w, h+"IPs: %q", c.IPAddresses)
	}
	printStringsIfExist(w, h+"Names", printNames(c.Issuer.Names))
	printStringsIfExist(w, h+"ExtraNames", printNames(c.Issuer.ExtraNames))
	printStringsIfExist(w, h+"Country", c.Issuer.Country)
	printStringsIfExist(w, h+"Organization", c.Issuer.Organization)
	printStringsIfExist(w, h+"OrganizationalUnit", c.Issuer.OrganizationalUnit)
	printStringsIfExist(w, h+"Locality", c.Issuer.Locality)
	printStringsIfExist(w, h+"Province", c.Issuer.Province)

}

func doMain() int {
	var seen_error int
	flag.Parse()
	if *verbose2 {
		*verbose1 = true
	}
	plans := &httpunit.Plans{
		IPs: make(httpunit.IPMap),
	}
	var err error
	if *timeout > 0 {
		httpunit.Timeout = *timeout
	}
	if *tomlLoc != "" {
		if _, err := toml.DecodeFile(*tomlLoc, &plans); err != nil {
			log.Fatal(err)
		}
	}
	if *hiera != "" {
		p, err := httpunit.ExtractHiera(*hiera)
		plans.Plans = append(plans.Plans, p...)
		if err != nil {
			log.Fatal(err)
		}
	}
	if args := flag.Args(); len(args) > 0 {
		if plans.Plans != nil {
			log.Fatal("cannot manually specify URLs with other modes")
		}
		u := args[0]
		args = args[1:]
		tp := httpunit.TestPlan{
			Label: u,
		}
		if !strings.Contains(u, "://") {
			u = "http://" + u
		}
		tp.URL = u
		if len(args) > 0 {
			if args[0] != "" {
				tp.IPs = []string{args[0]}
			}
			args = args[1:]
		}
		if len(args) > 0 {
			code, err := strconv.Atoi(args[0])
			if err != nil {
				log.Fatalf("bad status code: %v: %v", args[0], err)
			}
			args = args[1:]
			tp.Code = code
		}
		if len(args) > 0 {
			_, err := regexp.Compile(args[0])
			if err != nil {
				log.Fatalf("bad regex: %v: %v", args[0], err)
			}
			tp.Regex = args[0]
			args = args[1:]
		}
		if len(args) > 0 {
			log.Fatalf("too many arguments")
		}
		plans.Plans = []*httpunit.TestPlan{&tp}
	}
	if *ipMap != "" {
		sp := strings.SplitN(*ipMap, "=", 2)
		if len(sp) != 2 {
			log.Fatalf("expected key=value in -ipmap")
		}
		var vals []string
		if err := json.Unmarshal([]byte(sp[1]), &vals); err != nil {
			log.Fatalf("ipmap: %v", err)
		}
		plans.IPs[sp[0]] = vals
	}
	if len(plans.Plans) == 0 {
		log.Fatalf("no tests specified")
	}
	var tagFilter []string
	if *tags != "" {
		tagFilter = strings.Split(*tags, ",")
	}
	var protoFilter []string
	if *protos != "" {
		protoFilter = strings.Split(*protos, ",")
	}
	rch, count, err := plans.Test(*filter, *no10, tagFilter, protoFilter)
	if err != nil {
		log.Fatal(err)
	}
	var res httpunit.Results
	delay := time.Second / 2
	next := time.After(delay)
	got := 0
	start := time.Now()

Loop:
	for {
		select {
		case <-next:
			fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start))
			next = time.After(delay)
		case r, ok := <-rch:
			if !ok {
				fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start))
				break Loop
			}
			res = append(res, r)
			got++
		}
	}

	pidx := make(map[*httpunit.TestPlan]int)
	for i, p := range plans.Plans {
		pidx[p] = i
	}
	slice.Sort(res, func(i, j int) bool {
		a := res[i].Plan
		b := res[j].Plan
		pa := pidx[a]
		pb := pidx[b]
		if pa != pb {
			return pa < pb
		}
		ai := res[i].Case.IP
		bi := res[j].Case.IP
		if len(ai) != len(bi) {
			return len(ai) < len(bi)
		}
		for k, v := range ai {
			if bi[k] != v {
				return ai[k] < bi[k]
			}
		}
		return false
	})
	for _, r := range res {
		fromDNS := "IP"
		if r.Case.FromDNS {
			fromDNS = "DNS"
		}
		ip := fmt.Sprintf("%v=%v", fromDNS, r.Case.IP)
		var status string
		var verbose bytes.Buffer
		ver := bufio.NewWriter(&verbose)
		if r.Case.ExpectCode > 0 {
			status += fmt.Sprint(r.Case.ExpectCode)
		}
		if r.Case.ExpectText != "" {
			status += " T"
		}
		if r.Case.ExpectRegex != nil {
			status += " R"
		}
		if status != "" {
			status = " (" + status + ")"
		}
		if !*verbose1 && r.Result.Result == nil {
			continue
		}
		if *verbose2 {
			if resp := r.Result.Resp; resp != nil {
				if *header != "" {
					if h := resp.Header.Get(*header); h != "" {
						fmt.Fprintf(ver, "\n\theader %s: %s", *header, h)
					}
				}
				if t := resp.TLS; t != nil {
					for i, c := range t.PeerCertificates {
						fmt.Fprintf(ver, "\n\tcert %v:", i)
						printCert(ver, c)

					}
					for j, v := range t.VerifiedChains {
						for i, c := range v {
							fmt.Fprintf(ver, "\n\tVerified chain %d.%d:", j, i)
							printCert(ver, c)
						}
					}
				}
			}

		}
		ver.Flush()
		fmt.Printf("==== %v: %v %s%s%s\n", r.Plan.Label, r.Plan.URL, ip, status, verbose.String())
		if r.Result.Result != nil {
			fmt.Println("ERROR:", r.Result.Result)
			seen_error = 1
		}
	}
	return seen_error
}

func main() {
	os.Exit(doMain()) // http://stackoverflow.com/a/27629493/71978
}
<commit_msg>Print SSL serials as hex, not decimal.<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/sha1"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/StackExchange/httpunit"
	"github.com/StackExchange/httpunit/_third_party/github.com/BurntSushi/toml"
	"github.com/StackExchange/httpunit/_third_party/github.com/bradfitz/slice"
)

var (
	filter   = flag.String("filter", "", `if specified, only uses this IP address; may end with "." to filter by IPs that start with the filter`)
	no10     = flag.Bool("no10", false, "no RFC1918 addresses")
	hiera    = flag.String("hiera", "", "tests listeners members from the specified hieradata/sets.json file")
	tomlLoc  = flag.String("toml", "", "httpunit.toml location")
	verbose1 = flag.Bool("v", false, "verbose output: show successes")
	verbose2 = flag.Bool("vv", false, "more verbose output: show -header, cert details")
	header   = flag.String("header", "X-Request-Guid", "an HTTP to header to print in verbose mode")
	timeout  = flag.Duration("timeout", httpunit.Timeout, "connection timeout")
	ipMap    = flag.String("ipmap", "", `override or set one entry if the IPs table, in "key=value" format, where value is a JSON array of strings; for example: -ipmap='BASEIP=["10.2.3.", "1.4.5."]'`)
	tags     = flag.String("tags", "", "if specified, only run plans that are tagged with these tags. Use comma seperated to include mutiple tags, e.g. \"normal,extended\"")
	protos   = flag.String("protos", "", "if specified, only run tests that use the designated protocol. Valid choices are: http,https,tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6. e.g. \"http,https\"")
)

func printNames(x []pkix.AttributeTypeAndValue) []string {
	var r []string
	for _, v := range x {
		r = append(r, v.Value.(string))
	}
	return r
}

func printStringsIfExist(w *bufio.Writer, label string, list []string) {
	switch {
	case len(list) == 1:
		fmt.Fprintf(w, `%s: %q`, label, list[0])
	case len(list) > 1:
		fmt.Fprintf(w, `%s: %q`, label, list)
		//fmt.Fprintf(w, `%s: "{%s"}`, label, strings.Join(list, `" "`))
	default:
		// do nothing.
	}
}

func printCert(w *bufio.Writer, c *x509.Certificate) {
	h := "\n\t\t"
	fmt.Fprintf(w, h+"expires: %v", c.NotAfter)
	fmt.Fprintf(w, h+"serial: %x", c.SerialNumber)
	fmt.Fprintf(w, h+"fingerprint: %x", sha1.Sum(c.Raw))
	fmt.Fprintf(w, h+"version: %d", c.Version)
	fmt.Fprintf(w, h+"domains: %q", c.DNSNames)
	if len(c.EmailAddresses) > 0 {
		fmt.Fprintf(w, h+"emails: %q", c.EmailAddresses)
	}
	if len(c.IPAddresses) > 0 {
		fmt.Fprintf(w, h+"IPs: %q", c.IPAddresses)
	}
	printStringsIfExist(w, h+"Names", printNames(c.Issuer.Names))
	printStringsIfExist(w, h+"ExtraNames", printNames(c.Issuer.ExtraNames))
	printStringsIfExist(w, h+"Country", c.Issuer.Country)
	printStringsIfExist(w, h+"Organization", c.Issuer.Organization)
	printStringsIfExist(w, h+"OrganizationalUnit", c.Issuer.OrganizationalUnit)
	printStringsIfExist(w, h+"Locality", c.Issuer.Locality)
	printStringsIfExist(w, h+"Province", c.Issuer.Province)

}

func doMain() int {
	var seen_error int
	flag.Parse()
	if *verbose2 {
		*verbose1 = true
	}
	plans := &httpunit.Plans{
		IPs: make(httpunit.IPMap),
	}
	var err error
	if *timeout > 0 {
		httpunit.Timeout = *timeout
	}
	if *tomlLoc != "" {
		if _, err := toml.DecodeFile(*tomlLoc, &plans); err != nil {
			log.Fatal(err)
		}
	}
	if *hiera != "" {
		p, err := httpunit.ExtractHiera(*hiera)
		plans.Plans = append(plans.Plans, p...)
		if err != nil {
			log.Fatal(err)
		}
	}
	if args := flag.Args(); len(args) > 0 {
		if plans.Plans != nil {
			log.Fatal("cannot manually specify URLs with other modes")
		}
		u := args[0]
		args = args[1:]
		tp := httpunit.TestPlan{
			Label: u,
		}
		if !strings.Contains(u, "://") {
			u = "http://" + u
		}
		tp.URL = u
		if len(args) > 0 {
			if args[0] != "" {
				tp.IPs = []string{args[0]}
			}
			args = args[1:]
		}
		if len(args) > 0 {
			code, err := strconv.Atoi(args[0])
			if err != nil {
				log.Fatalf("bad status code: %v: %v", args[0], err)
			}
			args = args[1:]
			tp.Code = code
		}
		if len(args) > 0 {
			_, err := regexp.Compile(args[0])
			if err != nil {
				log.Fatalf("bad regex: %v: %v", args[0], err)
			}
			tp.Regex = args[0]
			args = args[1:]
		}
		if len(args) > 0 {
			log.Fatalf("too many arguments")
		}
		plans.Plans = []*httpunit.TestPlan{&tp}
	}
	if *ipMap != "" {
		sp := strings.SplitN(*ipMap, "=", 2)
		if len(sp) != 2 {
			log.Fatalf("expected key=value in -ipmap")
		}
		var vals []string
		if err := json.Unmarshal([]byte(sp[1]), &vals); err != nil {
			log.Fatalf("ipmap: %v", err)
		}
		plans.IPs[sp[0]] = vals
	}
	if len(plans.Plans) == 0 {
		log.Fatalf("no tests specified")
	}
	var tagFilter []string
	if *tags != "" {
		tagFilter = strings.Split(*tags, ",")
	}
	var protoFilter []string
	if *protos != "" {
		protoFilter = strings.Split(*protos, ",")
	}
	rch, count, err := plans.Test(*filter, *no10, tagFilter, protoFilter)
	if err != nil {
		log.Fatal(err)
	}
	var res httpunit.Results
	delay := time.Second / 2
	next := time.After(delay)
	got := 0
	start := time.Now()

Loop:
	for {
		select {
		case <-next:
			fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start))
			next = time.After(delay)
		case r, ok := <-rch:
			if !ok {
				fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start))
				break Loop
			}
			res = append(res, r)
			got++
		}
	}

	pidx := make(map[*httpunit.TestPlan]int)
	for i, p := range plans.Plans {
		pidx[p] = i
	}
	slice.Sort(res, func(i, j int) bool {
		a := res[i].Plan
		b := res[j].Plan
		pa := pidx[a]
		pb := pidx[b]
		if pa != pb {
			return pa < pb
		}
		ai := res[i].Case.IP
		bi := res[j].Case.IP
		if len(ai) != len(bi) {
			return len(ai) < len(bi)
		}
		for k, v := range ai {
			if bi[k] != v {
				return ai[k] < bi[k]
			}
		}
		return false
	})
	for _, r := range res {
		fromDNS := "IP"
		if r.Case.FromDNS {
			fromDNS = "DNS"
		}
		ip := fmt.Sprintf("%v=%v", fromDNS, r.Case.IP)
		var status string
		var verbose bytes.Buffer
		ver := bufio.NewWriter(&verbose)
		if r.Case.ExpectCode > 0 {
			status += fmt.Sprint(r.Case.ExpectCode)
		}
		if r.Case.ExpectText != "" {
			status += " T"
		}
		if r.Case.ExpectRegex != nil {
			status += " R"
		}
		if status != "" {
			status = " (" + status + ")"
		}
		if !*verbose1 && r.Result.Result == nil {
			continue
		}
		if *verbose2 {
			if resp := r.Result.Resp; resp != nil {
				if *header != "" {
					if h := resp.Header.Get(*header); h != "" {
						fmt.Fprintf(ver, "\n\theader %s: %s", *header, h)
					}
				}
				if t := resp.TLS; t != nil {
					for i, c := range t.PeerCertificates {
						fmt.Fprintf(ver, "\n\tcert %v:", i)
						printCert(ver, c)

					}
					for j, v := range t.VerifiedChains {
						for i, c := range v {
							fmt.Fprintf(ver, "\n\tVerified chain %d.%d:", j, i)
							printCert(ver, c)
						}
					}
				}
			}

		}
		ver.Flush()
		fmt.Printf("==== %v: %v %s%s%s\n", r.Plan.Label, r.Plan.URL, ip, status, verbose.String())
		if r.Result.Result != nil {
			fmt.Println("ERROR:", r.Result.Result)
			seen_error = 1
		}
	}
	return seen_error
}

func main() {
	os.Exit(doMain()) // http://stackoverflow.com/a/27629493/71978
}
<|endoftext|>


<commit_before>/*
i-sudoku is an interactive command-line sudoku tool
*/

package main

import (
	"fmt"
	"github.com/jkomoros/sudoku"
	"github.com/nsf/termbox-go"
	"log"
	"strconv"
	"strings"
	"unicode/utf8"
)

//A debug override; if true will print a color palette to the screen, wait for
//a keypress, and then quit. Useful for seeing what different colors are
//available to use.
const DRAW_PALETTE = false

func main() {

	//TODO: should be possible to run it and pass in a puzzle to use.

	if err := termbox.Init(); err != nil {
		log.Fatal("Termbox initialization failed:", err)
	}
	defer termbox.Close()

	termbox.SetOutputMode(termbox.Output256)

	model := newModel()

	width, _ := termbox.Size()
	model.outputWidth = width

	if DRAW_PALETTE {
		drawColorPalette()
		//Wait until something happens, generally a key is pressed.
		termbox.PollEvent()
		return
	}

	draw(model)

mainloop:
	for {
		evt := termbox.PollEvent()
		switch evt.Type {
		case termbox.EventKey:
			model.state.handleInput(model, evt)
		}
		draw(model)
		if model.exitNow {
			break mainloop
		}
		model.EndOfEventLoop()
	}
}

func clearScreen() {
	width, height := termbox.Size()
	for x := 0; x < width; x++ {
		for y := 0; y < height; y++ {
			termbox.SetCell(x, y, ' ', termbox.ColorDefault, termbox.ColorDefault)
		}
	}
}

func drawColorPalette() {
	clearScreen()
	x := 0
	y := 0

	for i := 0x00; i <= 0xFF; i++ {
		numToPrint := "  " + fmt.Sprintf("%02X", i) + "  "
		for _, ch := range numToPrint {
			termbox.SetCell(x, y, ch, termbox.ColorBlack, termbox.Attribute(i))
			x++
		}
		//Fit 8 print outs on a line before creating a new one
		if i%8 == 0 {
			x = 0
			y++
		}
	}

	termbox.Flush()
}

func drawGrid(y int, model *mainModel) (endY int) {
	var x int
	grid := model.grid

	fg := termbox.ColorBlack
	bg := termbox.ColorGreen

	//Iterate through toggles backwards, since earlier ones have higher preference
	for i := len(model.toggles) - 1; i >= 0; i-- {
		toggle := model.toggles[i]
		if toggle.Value() {
			bg = toggle.GridColor
		}
	}

	if model.grid.Invalid() {
		bg = termbox.ColorRed
	} else if model.grid.Solved() {
		bg = termbox.ColorYellow
	}

	//The column where the grid starts
	gridLeft := 1
	gridTop := 1

	termbox.SetCell(0, y, ' ', fg, bg)
	x++

	for i := 0; i < sudoku.DIM; i++ {

		cellLeft, _, _, _ := grid.Cell(0, i).DiagramExtents()
		cellLeft += gridLeft
		//Pad until we get to the start of this cell area
		for x < cellLeft {
			termbox.SetCell(x, y, '|', fg, bg)
			x++
		}
		for _, ch := range " " + strconv.Itoa(i) + " " {
			termbox.SetCell(x, y, ch, fg, bg)
			x++
		}
	}

	y++

	//Draw diagram down left rail
	x = 0
	tempY := y
	for i := 0; i < sudoku.DIM; i++ {

		_, cellTop, _, _ := grid.Cell(i, 0).DiagramExtents()
		cellTop += gridTop
		//Pad until we get to the start of this cell area
		for tempY < cellTop {
			termbox.SetCell(x, tempY, '-', fg, bg)
			tempY++
		}
		for _, ch := range " " + strconv.Itoa(i) + " " {
			termbox.SetCell(x, tempY, ch, fg, bg)
			tempY++
		}
	}

	fg, bg = bg, fg

	//TODO: I'm pretty sure top/left are reversed
	selectedTop, selectedLeft, selectedHeight, selectedWidth := model.Selected().DiagramExtents()
	//Correct the selected coordinate for the offset of the grid from the top.
	selectedLeft += gridTop
	selectedTop += gridLeft
	for _, line := range strings.Split(grid.Diagram(true), "\n") {
		//Grid starts at 1 cell over from left edge
		x = gridLeft
		//The first number in range will be byte offset, but for some items like the bullet, it's two bytes.
		//But what we care about is that each item is a character.
		for _, ch := range line {

			defaultColor := fg

			numberRune, _ := utf8.DecodeRuneInString(sudoku.DIAGRAM_NUMBER)
			lockedRune, _ := utf8.DecodeRuneInString(sudoku.DIAGRAM_LOCKED)

			if ch == numberRune {
				defaultColor = 0x12
			} else if ch == lockedRune {
				defaultColor = 0x35
			} else if runeIsNum(ch) {
				defaultColor = termbox.ColorWhite | termbox.AttrBold
			}

			backgroundColor := bg
			if x >= selectedTop && x < (selectedTop+selectedHeight) && y >= selectedLeft && y < (selectedLeft+selectedWidth) {
				//We're on the selected cell
				backgroundColor = 0xf0
			}

			termbox.SetCell(x, y, ch, defaultColor, backgroundColor)
			x++
		}
		y++
	}
	//The last loop added one extra to y.
	y--
	return y
}

func drawToggleLine(y int, model *mainModel) (newY int) {
	x := 0

	for _, toggle := range model.toggles {
		msg := toggle.OffText
		fg := toggle.GridColor
		bg := termbox.ColorBlack
		if toggle.Value() {
			msg = toggle.OnText
			fg, bg = bg, fg
		}
		for _, ch := range msg {
			termbox.SetCell(x, y, ch, fg, bg)
			x++
		}
	}
	return y
}

func drawStatusLine(y int, model *mainModel) (newY int) {
	x := 0
	var fg termbox.Attribute
	underlined := false
	for _, ch := range ">>> " + model.StatusLine() {
		//The ( and ) are non-printing control characters
		if ch == '{' {
			underlined = true
			continue
		} else if ch == '}' {
			underlined = false
			continue
		}
		fg = termbox.ColorBlack
		if underlined {
			fg = fg | termbox.AttrUnderline | termbox.AttrBold
		}

		termbox.SetCell(x, y, ch, fg, termbox.ColorWhite)
		x++
	}

	width, _ := termbox.Size()

	for x < width {
		termbox.SetCell(x, y, ' ', fg, termbox.ColorWhite)
		x++
	}
	return y
}

func drawConsole(y int, model *mainModel) (newY int) {

	x := 0

	underlined := false

	splitMessage := strings.Split(model.consoleMessage, "\n")

	for _, line := range splitMessage {

		x = 0
		for _, ch := range line {
			if ch == '{' {
				underlined = true
				continue
			} else if ch == '}' {
				underlined = false
				continue
			}
			fg := termbox.Attribute(0xf6)
			if underlined {
				fg = termbox.Attribute(0xFC) | termbox.AttrBold
			}
			termbox.SetCell(x, y, ch, fg, termbox.ColorBlack)
			x++
		}
		y++
	}
	//y is one too many
	y--
	return y
}

func draw(model *mainModel) {

	clearScreen()

	y := 0

	y = drawGrid(y, model)
	y++
	y = drawToggleLine(y, model)
	y++
	y = drawStatusLine(y, model)
	y++
	y = drawConsole(y, model)

	termbox.Flush()
}
<commit_msg>Refactor main event loop to tick every 1 second and redraw, while incrementing global tickCount. Not used for anything, but will allow drawing flashing things. Related to #177.<commit_after>/*
i-sudoku is an interactive command-line sudoku tool
*/

package main

import (
	"fmt"
	"github.com/jkomoros/sudoku"
	"github.com/nsf/termbox-go"
	"log"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

//A debug override; if true will print a color palette to the screen, wait for
//a keypress, and then quit. Useful for seeing what different colors are
//available to use.
const DRAW_PALETTE = false

var tickCount int

func main() {

	//TODO: should be possible to run it and pass in a puzzle to use.

	if err := termbox.Init(); err != nil {
		log.Fatal("Termbox initialization failed:", err)
	}
	defer termbox.Close()

	termbox.SetOutputMode(termbox.Output256)

	model := newModel()

	width, _ := termbox.Size()
	model.outputWidth = width

	if DRAW_PALETTE {
		drawColorPalette()
		//Wait until something happens, generally a key is pressed.
		termbox.PollEvent()
		return
	}

	draw(model)

	eventChan := make(chan termbox.Event, 1)
	go func() {
		for {
			eventChan <- termbox.PollEvent()
		}
	}()

	timeTick := time.Tick(time.Second * 1)

mainloop:
	for {
		select {
		case evt := <-eventChan:
			switch evt.Type {
			case termbox.EventKey:
				model.state.handleInput(model, evt)
			}
		case <-timeTick:
			tickCount++
		}

		draw(model)
		if model.exitNow {
			break mainloop
		}
		model.EndOfEventLoop()
	}
}

func clearScreen() {
	width, height := termbox.Size()
	for x := 0; x < width; x++ {
		for y := 0; y < height; y++ {
			termbox.SetCell(x, y, ' ', termbox.ColorDefault, termbox.ColorDefault)
		}
	}
}

func drawColorPalette() {
	clearScreen()
	x := 0
	y := 0

	for i := 0x00; i <= 0xFF; i++ {
		numToPrint := "  " + fmt.Sprintf("%02X", i) + "  "
		for _, ch := range numToPrint {
			termbox.SetCell(x, y, ch, termbox.ColorBlack, termbox.Attribute(i))
			x++
		}
		//Fit 8 print outs on a line before creating a new one
		if i%8 == 0 {
			x = 0
			y++
		}
	}

	termbox.Flush()
}

func drawGrid(y int, model *mainModel) (endY int) {
	var x int
	grid := model.grid

	fg := termbox.ColorBlack
	bg := termbox.ColorGreen

	//Iterate through toggles backwards, since earlier ones have higher preference
	for i := len(model.toggles) - 1; i >= 0; i-- {
		toggle := model.toggles[i]
		if toggle.Value() {
			bg = toggle.GridColor
		}
	}

	if model.grid.Invalid() {
		bg = termbox.ColorRed
	} else if model.grid.Solved() {
		bg = termbox.ColorYellow
	}

	//The column where the grid starts
	gridLeft := 1
	gridTop := 1

	termbox.SetCell(0, y, ' ', fg, bg)
	x++

	for i := 0; i < sudoku.DIM; i++ {

		cellLeft, _, _, _ := grid.Cell(0, i).DiagramExtents()
		cellLeft += gridLeft
		//Pad until we get to the start of this cell area
		for x < cellLeft {
			termbox.SetCell(x, y, '|', fg, bg)
			x++
		}
		for _, ch := range " " + strconv.Itoa(i) + " " {
			termbox.SetCell(x, y, ch, fg, bg)
			x++
		}
	}

	y++

	//Draw diagram down left rail
	x = 0
	tempY := y
	for i := 0; i < sudoku.DIM; i++ {

		_, cellTop, _, _ := grid.Cell(i, 0).DiagramExtents()
		cellTop += gridTop
		//Pad until we get to the start of this cell area
		for tempY < cellTop {
			termbox.SetCell(x, tempY, '-', fg, bg)
			tempY++
		}
		for _, ch := range " " + strconv.Itoa(i) + " " {
			termbox.SetCell(x, tempY, ch, fg, bg)
			tempY++
		}
	}

	fg, bg = bg, fg

	//TODO: I'm pretty sure top/left are reversed
	selectedTop, selectedLeft, selectedHeight, selectedWidth := model.Selected().DiagramExtents()
	//Correct the selected coordinate for the offset of the grid from the top.
	selectedLeft += gridTop
	selectedTop += gridLeft
	for _, line := range strings.Split(grid.Diagram(true), "\n") {
		//Grid starts at 1 cell over from left edge
		x = gridLeft
		//The first number in range will be byte offset, but for some items like the bullet, it's two bytes.
		//But what we care about is that each item is a character.
		for _, ch := range line {

			defaultColor := fg

			numberRune, _ := utf8.DecodeRuneInString(sudoku.DIAGRAM_NUMBER)
			lockedRune, _ := utf8.DecodeRuneInString(sudoku.DIAGRAM_LOCKED)

			if ch == numberRune {
				defaultColor = 0x12
			} else if ch == lockedRune {
				defaultColor = 0x35
			} else if runeIsNum(ch) {
				defaultColor = termbox.ColorWhite | termbox.AttrBold
			}

			backgroundColor := bg
			if x >= selectedTop && x < (selectedTop+selectedHeight) && y >= selectedLeft && y < (selectedLeft+selectedWidth) {
				//We're on the selected cell
				backgroundColor = 0xf0
			}

			termbox.SetCell(x, y, ch, defaultColor, backgroundColor)
			x++
		}
		y++
	}
	//The last loop added one extra to y.
	y--
	return y
}

func drawToggleLine(y int, model *mainModel) (newY int) {
	x := 0

	for _, toggle := range model.toggles {
		msg := toggle.OffText
		fg := toggle.GridColor
		bg := termbox.ColorBlack
		if toggle.Value() {
			msg = toggle.OnText
			fg, bg = bg, fg
		}
		for _, ch := range msg {
			termbox.SetCell(x, y, ch, fg, bg)
			x++
		}
	}
	return y
}

func drawStatusLine(y int, model *mainModel) (newY int) {
	x := 0
	var fg termbox.Attribute
	underlined := false
	for _, ch := range ">>> " + model.StatusLine() {
		//The ( and ) are non-printing control characters
		if ch == '{' {
			underlined = true
			continue
		} else if ch == '}' {
			underlined = false
			continue
		}
		fg = termbox.ColorBlack
		if underlined {
			fg = fg | termbox.AttrUnderline | termbox.AttrBold
		}

		termbox.SetCell(x, y, ch, fg, termbox.ColorWhite)
		x++
	}

	width, _ := termbox.Size()

	for x < width {
		termbox.SetCell(x, y, ' ', fg, termbox.ColorWhite)
		x++
	}
	return y
}

func drawConsole(y int, model *mainModel) (newY int) {

	x := 0

	underlined := false

	splitMessage := strings.Split(model.consoleMessage, "\n")

	for _, line := range splitMessage {

		x = 0
		for _, ch := range line {
			if ch == '{' {
				underlined = true
				continue
			} else if ch == '}' {
				underlined = false
				continue
			}
			fg := termbox.Attribute(0xf6)
			if underlined {
				fg = termbox.Attribute(0xFC) | termbox.AttrBold
			}
			termbox.SetCell(x, y, ch, fg, termbox.ColorBlack)
			x++
		}
		y++
	}
	//y is one too many
	y--
	return y
}

func draw(model *mainModel) {

	clearScreen()

	y := 0

	y = drawGrid(y, model)
	y++
	y = drawToggleLine(y, model)
	y++
	y = drawStatusLine(y, model)
	y++
	y = drawConsole(y, model)

	termbox.Flush()
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Pani Networks
// 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.

// Main entry point for Kubernetes listener
package main

import (
	"flag"
	"fmt"
	"github.com/romana/core/common"
	kubernetes "github.com/romana/kube/kubernetes/listener"
)

func main() {
	rootURL := flag.String("rootURL", "", "Romana Root service URL")
	version := flag.Bool("version", false, "Build Information.")
	username := flag.String("username", "", "Username")
	password := flag.String("password", "", "Password")

	flag.Parse()

	if *version {
		fmt.Println(common.BuildInfo())
		return
	}
	if *rootURL == "" {
		fmt.Println("Must specify rootURL.")
		return
	}
	cred := common.MakeCredentialFromCliArgs(*username, *password)
	svcInfo, err := kubernetes.Run(*rootURL, cred)
	if err != nil {
		panic(err)
	}
	for {
		msg := <-svcInfo.Channel
		fmt.Println(msg)
	}
}
<commit_msg>listener: fix versioning info.<commit_after>// Copyright (c) 2016 Pani Networks
// 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.

// Main entry point for Kubernetes listener
package main

import (
	"flag"
	"fmt"

	rcommon "github.com/romana/core/common"
	kcommon "github.com/romana/kube/common"
	kubernetes "github.com/romana/kube/kubernetes/listener"
)

func main() {
	rootURL := flag.String("rootURL", "", "Romana Root service URL")
	version := flag.Bool("version", false, "Build Information.")
	username := flag.String("username", "", "Username")
	password := flag.String("password", "", "Password")

	flag.Parse()

	if *version {
		fmt.Println(kcommon.BuildInfo())
		return
	}
	if *rootURL == "" {
		fmt.Println("Must specify rootURL.")
		return
	}
	cred := rcommon.MakeCredentialFromCliArgs(*username, *password)
	svcInfo, err := kubernetes.Run(*rootURL, cred)
	if err != nil {
		panic(err)
	}
	for {
		msg := <-svcInfo.Channel
		fmt.Println(msg)
	}
}
<|endoftext|>