<commit_before>package stmtcache_test

import (
	"context"
	"fmt"
	"math/rand"
	"os"
	"testing"
	"time"

	"github.com/jackc/pgconn"
	"github.com/jackc/pgconn/stmtcache"

	"github.com/stretchr/testify/require"
)

func TestLRUModePrepare(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 2, cache.Cap())
	require.EqualValues(t, stmtcache.ModePrepare, cache.Mode())

	psd, err := cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.ElementsMatch(t, []string{"select 1", "select 2"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 3")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.ElementsMatch(t, []string{"select 2", "select 3"}, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))
}

func TestLRUStmtInvalidation(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	// we construct a fake error because its not super straightforward to actually call
	// a prepared statement from the LRU cache without the helper routines which live
	// in pgx proper.
	fakeInvalidCachePlanError := &pgconn.PgError{
		Severity: "ERROR",
		Code:     "0A000",
		Message:  "cached plan must not change result type",
	}

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)

	//
	// outside of a transaction, we eagerly flush the statement
	//

	_, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	cache.StatementErrored("select 1", fakeInvalidCachePlanError)
	_, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 2"}, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)

	//
	// within an errored transaction, we defer the flush to after the first get
	// that happens after the transaction is rolled back
	//

	_, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	res := conn.Exec(ctx, "begin")
	require.NoError(t, res.Close())
	require.Equal(t, byte('T'), conn.TxStatus())

	res = conn.Exec(ctx, "selec")
	require.Error(t, res.Close())
	require.Equal(t, byte('E'), conn.TxStatus())

	cache.StatementErrored("select 1", fakeInvalidCachePlanError)
	require.EqualValues(t, 1, cache.Len())

	res = conn.Exec(ctx, "rollback")
	require.NoError(t, res.Close())

	_, err = cache.Get(ctx, "select 2")
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 2"}, fetchServerStatements(t, ctx, conn))
}

func TestLRUStmtInvalidationIntegration(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)

	result := conn.ExecParams(ctx, "create temporary table stmtcache_table (a text)", nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	sql := "select * from stmtcache_table"
	sd1, err := cache.Get(ctx, sql)
	require.NoError(t, err)

	result = conn.ExecPrepared(ctx, sd1.Name, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	result = conn.ExecParams(ctx, "alter table stmtcache_table add column b text", nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	result = conn.ExecPrepared(ctx, sd1.Name, nil, nil, nil).Read()
	require.EqualError(t, result.Err, "ERROR: cached plan must not change result type (SQLSTATE 0A000)")

	cache.StatementErrored(sql, result.Err)

	sd2, err := cache.Get(ctx, sql)
	require.NoError(t, err)
	require.NotEqual(t, sd1.Name, sd2.Name)

	result = conn.ExecPrepared(ctx, sd2.Name, nil, nil, nil).Read()
	require.NoError(t, result.Err)
}

func TestLRUModePrepareStress(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 8)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 8, cache.Cap())
	require.EqualValues(t, stmtcache.ModePrepare, cache.Mode())

	for i := 0; i < 1000; i++ {
		psd, err := cache.Get(ctx, fmt.Sprintf("select %d", rand.Intn(50)))
		require.NoError(t, err)
		require.NotNil(t, psd)
		result := conn.ExecPrepared(ctx, psd.Name, nil, nil, nil).Read()
		require.NoError(t, result.Err)
	}
}

func TestLRUModeDescribe(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModeDescribe, 2)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 2, cache.Cap())
	require.EqualValues(t, stmtcache.ModeDescribe, cache.Mode())

	psd, err := cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 3")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))
}

func fetchServerStatements(t testing.TB, ctx context.Context, conn *pgconn.PgConn) []string {
	result := conn.ExecParams(ctx, `select statement from pg_prepared_statements`, nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)
	var statements []string
	for _, r := range result.Rows {
		statements = append(statements, string(r[0]))
	}
	return statements
}
<commit_msg>LRU statement cache tests handle CockroackDB<commit_after>package stmtcache_test

import (
	"context"
	"fmt"
	"math/rand"
	"os"
	"regexp"
	"testing"
	"time"

	"github.com/jackc/pgconn"
	"github.com/jackc/pgconn/stmtcache"

	"github.com/stretchr/testify/require"
)

func TestLRUModePrepare(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 2, cache.Cap())
	require.EqualValues(t, stmtcache.ModePrepare, cache.Mode())

	psd, err := cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.ElementsMatch(t, []string{"select 1", "select 2"}, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 3")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.ElementsMatch(t, []string{"select 2", "select 3"}, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))
}

func TestLRUStmtInvalidation(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	// we construct a fake error because its not super straightforward to actually call
	// a prepared statement from the LRU cache without the helper routines which live
	// in pgx proper.
	fakeInvalidCachePlanError := &pgconn.PgError{
		Severity: "ERROR",
		Code:     "0A000",
		Message:  "cached plan must not change result type",
	}

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)

	//
	// outside of a transaction, we eagerly flush the statement
	//

	_, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	cache.StatementErrored("select 1", fakeInvalidCachePlanError)
	_, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 2"}, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)

	//
	// within an errored transaction, we defer the flush to after the first get
	// that happens after the transaction is rolled back
	//

	_, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 1"}, fetchServerStatements(t, ctx, conn))

	res := conn.Exec(ctx, "begin")
	require.NoError(t, res.Close())
	require.Equal(t, byte('T'), conn.TxStatus())

	res = conn.Exec(ctx, "selec")
	require.Error(t, res.Close())
	require.Equal(t, byte('E'), conn.TxStatus())

	cache.StatementErrored("select 1", fakeInvalidCachePlanError)
	require.EqualValues(t, 1, cache.Len())

	res = conn.Exec(ctx, "rollback")
	require.NoError(t, res.Close())

	_, err = cache.Get(ctx, "select 2")
	require.EqualValues(t, 1, cache.Len())
	require.ElementsMatch(t, []string{"select 2"}, fetchServerStatements(t, ctx, conn))
}

func TestLRUStmtInvalidationIntegration(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 2)

	result := conn.ExecParams(ctx, "create temporary table stmtcache_table (a text)", nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	sql := "select * from stmtcache_table"
	sd1, err := cache.Get(ctx, sql)
	require.NoError(t, err)

	result = conn.ExecPrepared(ctx, sd1.Name, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	result = conn.ExecParams(ctx, "alter table stmtcache_table add column b text", nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)

	result = conn.ExecPrepared(ctx, sd1.Name, nil, nil, nil).Read()
	require.EqualError(t, result.Err, "ERROR: cached plan must not change result type (SQLSTATE 0A000)")

	cache.StatementErrored(sql, result.Err)

	sd2, err := cache.Get(ctx, sql)
	require.NoError(t, err)
	require.NotEqual(t, sd1.Name, sd2.Name)

	result = conn.ExecPrepared(ctx, sd2.Name, nil, nil, nil).Read()
	require.NoError(t, result.Err)
}

func TestLRUModePrepareStress(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModePrepare, 8)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 8, cache.Cap())
	require.EqualValues(t, stmtcache.ModePrepare, cache.Mode())

	for i := 0; i < 1000; i++ {
		psd, err := cache.Get(ctx, fmt.Sprintf("select %d", rand.Intn(50)))
		require.NoError(t, err)
		require.NotNil(t, psd)
		result := conn.ExecPrepared(ctx, psd.Name, nil, nil, nil).Read()
		require.NoError(t, result.Err)
	}
}

func TestLRUModeDescribe(t *testing.T) {
	t.Parallel()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()

	conn, err := pgconn.Connect(ctx, os.Getenv("PGX_TEST_CONN_STRING"))
	require.NoError(t, err)
	defer conn.Close(ctx)

	cache := stmtcache.NewLRU(conn, stmtcache.ModeDescribe, 2)
	require.EqualValues(t, 0, cache.Len())
	require.EqualValues(t, 2, cache.Cap())
	require.EqualValues(t, stmtcache.ModeDescribe, cache.Mode())

	psd, err := cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 1")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 1, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 2")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	psd, err = cache.Get(ctx, "select 3")
	require.NoError(t, err)
	require.NotNil(t, psd)
	require.EqualValues(t, 2, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))

	err = cache.Clear(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, cache.Len())
	require.Empty(t, fetchServerStatements(t, ctx, conn))
}

func fetchServerStatements(t testing.TB, ctx context.Context, conn *pgconn.PgConn) []string {
	result := conn.ExecParams(ctx, `select statement from pg_prepared_statements`, nil, nil, nil, nil).Read()
	require.NoError(t, result.Err)
	var statements []string
	for _, r := range result.Rows {
		statement := string(r[0])
		if conn.ParameterStatus("crdb_version") != "" {
			if statement == "PREPARE  AS select statement from pg_prepared_statements" {
				// CockroachDB includes the currently running unnamed prepared statement while PostgreSQL does not. Ignore it.
				continue
			}

			// CockroachDB includes the "PREPARE ... AS" text in the statement even if it was prepared through the extended
			// protocol will PostgreSQL does not. Normalize the statement.
			re := regexp.MustCompile(`^PREPARE lrupsc[0-9_]+ AS `)
			statement = re.ReplaceAllString(statement, "")
		}
		statements = append(statements, statement)
	}
	return statements
}
<|endoftext|>


<commit_before>/*

bolt provides a bolt-backed database that implements both
boardgame.StorageManager and boardgame/server.StorageManager.

*/
package mysql

import (
	"database/sql"
	"errors"
	"github.com/go-gorp/gorp"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jkomoros/boardgame"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"strings"
)

const (
	TableGames  = "games"
	TableUsers  = "users"
	TableStates = "states"
)

type StorageManager struct {
	db    *sql.DB
	dbMap *gorp.DbMap
	//If in test mode we'll... mock stuff, I guess?
	testMode bool
	//The config string that we were provided in connect.
	config string
}

func NewStorageManager(testMode bool) *StorageManager {
	//We actually don't do much; we do more of our work in Connect()
	return &StorageManager{
		testMode: testMode,
	}
}

func (s *StorageManager) Connect(config string) error {

	db, err := sql.Open("mysql", config)
	if err != nil {
		return errors.New("Unable to open database: " + err.Error())
	}

	s.config = config

	s.db = db

	s.dbMap = &gorp.DbMap{
		Db: db,
		Dialect: gorp.MySQLDialect{
			Engine:   "MyISAM",
			Encoding: "utf8",
		},
	}

	s.dbMap.AddTableWithName(UserStorageRecord{}, TableUsers).SetKeys(false, "Id")
	s.dbMap.AddTableWithName(GameStorageRecord{}, TableGames).SetKeys(false, "Id")
	s.dbMap.AddTableWithName(StateStorageRecord{}, TableStates).SetKeys(true, "Id")
	//TODO: Add other to DBMap

	if err := s.dbMap.CreateTablesIfNotExists(); err != nil {
		return errors.New("Couldn't create tables: " + err.Error())
	}

	return nil

}

func (s *StorageManager) Close() {
	if s.db == nil {
		return
	}
	s.db.Close()
	s.db = nil
}

//Cleanup will only drop tables if we're in test mode, and the config string
//used to open the database talked about a test database on localhost (as
//sanity check).
func (s *StorageManager) CleanUp() {

	if !s.testMode {
		return
	}

	if !strings.Contains(s.config, "_test") {
		log.Println("Sanity check on boardgame config before cleanup didn't find _test")
		return
	}

	if !strings.Contains(s.config, "localhost") {
		log.Println("Sanity check on boardgame config before cleanup didn't find localhost")
	}

	if s.db == nil {
		log.Println("Couldn't clean up; db already closed")
		return
	}

	log.Println("Sanity checks passed. Dropping tables to cleanup...")

	if err := s.dbMap.DropTables(); err != nil {
		log.Println("Error dropping tables:", err)
		return
	}
}

func (s *StorageManager) Name() string {
	return "mysql"
}

func (s *StorageManager) State(gameId string, version int) (boardgame.StateStorageRecord, error) {
	var state StateStorageRecord

	err := s.dbMap.SelectOne(&state, "select * from "+TableStates+" where GameId=? and Version=?", gameId, version)

	if err == sql.ErrNoRows {
		return nil, errors.New("No such state")
	}

	if err != nil {
		return nil, errors.New("Unexpected error: " + err.Error())
	}

	return (&state).ToStorageRecord(), nil
}

func (s *StorageManager) Game(id string) (*boardgame.GameStorageRecord, error) {
	var game GameStorageRecord

	err := s.dbMap.SelectOne(&game, "select * from "+TableGames+" where Id=?", id)

	if err == sql.ErrNoRows {
		return nil, errors.New("No such game")
	}

	if err != nil {
		return nil, errors.New("Unexpected error: " + err.Error())
	}

	return (&game).ToStorageRecord(), nil
}

func (s *StorageManager) SaveGameAndCurrentState(game *boardgame.GameStorageRecord, state boardgame.StateStorageRecord) error {

	version := game.Version

	gameRecord := NewGameStorageRecord(game)
	stateRecord := NewStateStorageRecord(game.Id, version, state)

	err := s.dbMap.Insert(gameRecord)

	if err != nil {
		return errors.New("Couldn't insert game: " + err.Error())
	}

	err = s.dbMap.Insert(stateRecord)

	if err != nil {
		return errors.New("Couldn't insert state: " + err.Error())
	}

	return nil
}

func (s *StorageManager) ListGames(max int) []*boardgame.GameStorageRecord {
	return nil
}

func (s *StorageManager) SetPlayerForGame(gameId string, playerIndex boardgame.PlayerIndex, userId string) error {

	return errors.New("Not yet implemented")

}

func (s *StorageManager) UserIdsForGame(gameId string) []string {
	return nil
}

func (s *StorageManager) UpdateUser(user *users.StorageRecord) error {
	return errors.New("not yet implemented")
}

func (s *StorageManager) GetUserById(uid string) *users.StorageRecord {
	var user UserStorageRecord

	err := s.dbMap.SelectOne(&user, "select * from "+TableUsers+" where Id=?", uid)

	if err == sql.ErrNoRows {
		//Normal
		return nil
	}

	if err != nil {
		log.Println("Unexpected error getting user:", err)
		return nil
	}

	return (&user).ToStorageRecord()
}

func (s *StorageManager) GetUserByCookie(cookie string) *users.StorageRecord {

	return nil

}

func (s *StorageManager) ConnectCookieToUser(cookie string, user *users.StorageRecord) error {
	return errors.New("Not yet implemented")
}
<commit_msg>Implemented UpdateUser (i think?) Part of #255.<commit_after>/*

bolt provides a bolt-backed database that implements both
boardgame.StorageManager and boardgame/server.StorageManager.

*/
package mysql

import (
	"database/sql"
	"errors"
	"github.com/go-gorp/gorp"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jkomoros/boardgame"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"strings"
)

const (
	TableGames  = "games"
	TableUsers  = "users"
	TableStates = "states"
)

type StorageManager struct {
	db    *sql.DB
	dbMap *gorp.DbMap
	//If in test mode we'll... mock stuff, I guess?
	testMode bool
	//The config string that we were provided in connect.
	config string
}

func NewStorageManager(testMode bool) *StorageManager {
	//We actually don't do much; we do more of our work in Connect()
	return &StorageManager{
		testMode: testMode,
	}
}

func (s *StorageManager) Connect(config string) error {

	db, err := sql.Open("mysql", config)
	if err != nil {
		return errors.New("Unable to open database: " + err.Error())
	}

	s.config = config

	s.db = db

	s.dbMap = &gorp.DbMap{
		Db: db,
		Dialect: gorp.MySQLDialect{
			Engine:   "MyISAM",
			Encoding: "utf8",
		},
	}

	s.dbMap.AddTableWithName(UserStorageRecord{}, TableUsers).SetKeys(false, "Id")
	s.dbMap.AddTableWithName(GameStorageRecord{}, TableGames).SetKeys(false, "Id")
	s.dbMap.AddTableWithName(StateStorageRecord{}, TableStates).SetKeys(true, "Id")
	//TODO: Add other to DBMap

	if err := s.dbMap.CreateTablesIfNotExists(); err != nil {
		return errors.New("Couldn't create tables: " + err.Error())
	}

	return nil

}

func (s *StorageManager) Close() {
	if s.db == nil {
		return
	}
	s.db.Close()
	s.db = nil
}

//Cleanup will only drop tables if we're in test mode, and the config string
//used to open the database talked about a test database on localhost (as
//sanity check).
func (s *StorageManager) CleanUp() {

	if !s.testMode {
		return
	}

	if !strings.Contains(s.config, "_test") {
		log.Println("Sanity check on boardgame config before cleanup didn't find _test")
		return
	}

	if !strings.Contains(s.config, "localhost") {
		log.Println("Sanity check on boardgame config before cleanup didn't find localhost")
	}

	if s.db == nil {
		log.Println("Couldn't clean up; db already closed")
		return
	}

	log.Println("Sanity checks passed. Dropping tables to cleanup...")

	if err := s.dbMap.DropTables(); err != nil {
		log.Println("Error dropping tables:", err)
		return
	}
}

func (s *StorageManager) Name() string {
	return "mysql"
}

func (s *StorageManager) State(gameId string, version int) (boardgame.StateStorageRecord, error) {
	var state StateStorageRecord

	err := s.dbMap.SelectOne(&state, "select * from "+TableStates+" where GameId=? and Version=?", gameId, version)

	if err == sql.ErrNoRows {
		return nil, errors.New("No such state")
	}

	if err != nil {
		return nil, errors.New("Unexpected error: " + err.Error())
	}

	return (&state).ToStorageRecord(), nil
}

func (s *StorageManager) Game(id string) (*boardgame.GameStorageRecord, error) {
	var game GameStorageRecord

	err := s.dbMap.SelectOne(&game, "select * from "+TableGames+" where Id=?", id)

	if err == sql.ErrNoRows {
		return nil, errors.New("No such game")
	}

	if err != nil {
		return nil, errors.New("Unexpected error: " + err.Error())
	}

	return (&game).ToStorageRecord(), nil
}

func (s *StorageManager) SaveGameAndCurrentState(game *boardgame.GameStorageRecord, state boardgame.StateStorageRecord) error {

	version := game.Version

	gameRecord := NewGameStorageRecord(game)
	stateRecord := NewStateStorageRecord(game.Id, version, state)

	err := s.dbMap.Insert(gameRecord)

	if err != nil {
		return errors.New("Couldn't insert game: " + err.Error())
	}

	err = s.dbMap.Insert(stateRecord)

	if err != nil {
		return errors.New("Couldn't insert state: " + err.Error())
	}

	return nil
}

func (s *StorageManager) ListGames(max int) []*boardgame.GameStorageRecord {
	return nil
}

func (s *StorageManager) SetPlayerForGame(gameId string, playerIndex boardgame.PlayerIndex, userId string) error {

	return errors.New("Not yet implemented")

}

func (s *StorageManager) UserIdsForGame(gameId string) []string {
	return nil
}

func (s *StorageManager) UpdateUser(user *users.StorageRecord) error {
	userRecord := NewUserStorageRecord(user)

	//TODO: I wonder if this will fail if the user is not yet in the database.
	count, err := s.dbMap.Update(userRecord)

	if err != nil {
		return errors.New("Couldn't update user: " + err.Error())
	}

	if count < 1 {
		return errors.New("Row could not be updated.")
	}

	return nil
}

func (s *StorageManager) GetUserById(uid string) *users.StorageRecord {
	var user UserStorageRecord

	err := s.dbMap.SelectOne(&user, "select * from "+TableUsers+" where Id=?", uid)

	if err == sql.ErrNoRows {
		//Normal
		return nil
	}

	if err != nil {
		log.Println("Unexpected error getting user:", err)
		return nil
	}

	return (&user).ToStorageRecord()
}

func (s *StorageManager) GetUserByCookie(cookie string) *users.StorageRecord {

	return nil

}

func (s *StorageManager) ConnectCookieToUser(cookie string, user *users.StorageRecord) error {
	return errors.New("Not yet implemented")
}
<|endoftext|>


<commit_before>package netpol

import (
	"crypto/sha256"
	"encoding/base32"
	"reflect"
	"strings"

	api "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog/v2"
)

func (npc *NetworkPolicyController) newPodEventHandler() cache.ResourceEventHandler {
	return cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			npc.OnPodUpdate(obj)

		},
		UpdateFunc: func(oldObj, newObj interface{}) {
			newPodObj := newObj.(*api.Pod)
			oldPodObj := oldObj.(*api.Pod)
			if newPodObj.Status.Phase != oldPodObj.Status.Phase ||
				newPodObj.Status.PodIP != oldPodObj.Status.PodIP ||
				!reflect.DeepEqual(newPodObj.Labels, oldPodObj.Labels) {
				// for the network policies, we are only interested in pod status phase change or IP change
				npc.OnPodUpdate(newObj)
			}
		},
		DeleteFunc: func(obj interface{}) {
			npc.handlePodDelete(obj)
		},
	}
}

// OnPodUpdate handles updates to pods from the Kubernetes api server
func (npc *NetworkPolicyController) OnPodUpdate(obj interface{}) {
	pod := obj.(*api.Pod)
	klog.V(2).Infof("Received update to pod: %s/%s", pod.Namespace, pod.Name)

	npc.RequestFullSync()
}

func (npc *NetworkPolicyController) handlePodDelete(obj interface{}) {
	pod, ok := obj.(*api.Pod)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			klog.Errorf("unexpected object type: %v", obj)
			return
		}
		if pod, ok = tombstone.Obj.(*api.Pod); !ok {
			klog.Errorf("unexpected object type: %v", obj)
			return
		}
	}
	klog.V(2).Infof("Received pod: %s/%s delete event", pod.Namespace, pod.Name)

	npc.RequestFullSync()
}

func (npc *NetworkPolicyController) syncPodFirewallChains(networkPoliciesInfo []networkPolicyInfo, version string) (map[string]bool, error) {

	activePodFwChains := make(map[string]bool)

	dropUnmarkedTrafficRules := func(podName, podNamespace, podFwChainName string) error {
		// add rule to log the packets that will be dropped due to network policy enforcement
		comment := "\"rule to log dropped traffic POD name:" + podName + " namespace: " + podNamespace + "\""
		args := []string{"-A", podFwChainName, "-m", "comment", "--comment", comment, "-m", "mark", "!", "--mark", "0x10000/0x10000", "-j", "NFLOG", "--nflog-group", "100", "-m", "limit", "--limit", "10/minute", "--limit-burst", "10", "\n"}
		// This used to be AppendUnique when we were using iptables directly, this checks to make sure we didn't drop unmarked for this chain already
		if strings.Contains(npc.filterTableRules.String(), strings.Join(args, " ")) {
			return nil
		}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// add rule to DROP if no applicable network policy permits the traffic
		comment = "\"rule to REJECT traffic destined for POD name:" + podName + " namespace: " + podNamespace + "\""
		args = []string{"-A", podFwChainName, "-m", "comment", "--comment", comment, "-m", "mark", "!", "--mark", "0x10000/0x10000", "-j", "REJECT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// reset mark to let traffic pass through rest of the chains
		args = []string{"-A", podFwChainName, "-j", "MARK", "--set-mark", "0/0x10000", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		return nil
	}

	// loop through the pods running on the node which to which ingress network policies to be applied
	ingressNetworkPolicyEnabledPods, err := npc.getIngressNetworkPolicyEnabledPods(networkPoliciesInfo, npc.nodeIP.String())
	if err != nil {
		return nil, err
	}
	for _, pod := range *ingressNetworkPolicyEnabledPods {

		// below condition occurs when we get trasient update while removing or adding pod
		// subseqent update will do the correct action
		if len(pod.ip) == 0 || pod.ip == "" {
			continue
		}

		// ensure pod specific firewall chain exist for all the pods that need ingress firewall
		podFwChainName := podFirewallChainName(pod.namespace, pod.name, version)
		npc.filterTableRules.WriteString(":" + podFwChainName + "\n")

		activePodFwChains[podFwChainName] = true

		// add entries in pod firewall to run through required network policies
		for _, policy := range networkPoliciesInfo {
			if _, ok := policy.targetPods[pod.ip]; ok {
				comment := "\"run through nw policy " + policy.name + "\""
				policyChainName := networkPolicyChainName(policy.namespace, policy.name, version)
				args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-j", policyChainName, "\n"}
				npc.filterTableRules.WriteString(strings.Join(args, " "))
			}
		}

		comment := "\"rule to permit the traffic traffic to pods when source is the pod's local node\""
		args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "addrtype", "--src-type", "LOCAL", "-d", pod.ip, "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure statefull firewall, that permits return traffic for the traffic originated by the pod
		comment = "\"rule for stateful firewall for pod\""
		args = []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and FORWARD chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting routed (coming for other node pods)
		comment = "\"rule to jump traffic destined to POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "comment", "--comment", comment, "-d", pod.ip, "-j", podFwChainName + "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and OUTPUT chain to jump to pod specific firewall chain
		// this rule applies to the traffic from a pod getting routed back to another pod on same node by service proxy
		args = []string{"-I", kubeOutputChainName, "1", "-m", "comment", "--comment", comment, "-d", pod.ip, "-j", podFwChainName + "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and forward chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting switched (coming for same node pods)
		comment = "\"rule to jump traffic destined to POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "physdev", "--physdev-is-bridged",
			"-m", "comment", "--comment", comment,
			"-d", pod.ip,
			"-j", podFwChainName, "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		err = dropUnmarkedTrafficRules(pod.name, pod.namespace, podFwChainName)
		if err != nil {
			return nil, err
		}
	}

	// loop through the pods running on the node which egress network policies to be applied
	egressNetworkPolicyEnabledPods, err := npc.getEgressNetworkPolicyEnabledPods(networkPoliciesInfo, npc.nodeIP.String())
	if err != nil {
		return nil, err
	}
	for _, pod := range *egressNetworkPolicyEnabledPods {

		// below condition occurs when we get trasient update while removing or adding pod
		// subseqent update will do the correct action
		if len(pod.ip) == 0 || pod.ip == "" {
			continue
		}

		// ensure pod specific firewall chain exist for all the pods that need egress firewall
		podFwChainName := podFirewallChainName(pod.namespace, pod.name, version)
		if !activePodFwChains[podFwChainName] {
			npc.filterTableRules.WriteString(":" + podFwChainName + "\n")
		}
		activePodFwChains[podFwChainName] = true

		// add entries in pod firewall to run through required network policies
		for _, policy := range networkPoliciesInfo {
			if _, ok := policy.targetPods[pod.ip]; ok {
				comment := "\"run through nw policy " + policy.name + "\""
				policyChainName := networkPolicyChainName(policy.namespace, policy.name, version)
				args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-j", policyChainName, "\n"}
				npc.filterTableRules.WriteString(strings.Join(args, " "))
			}
		}

		// ensure statefull firewall, that permits return traffic for the traffic originated by the pod
		comment := "\"rule for stateful firewall for pod\""
		args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		egressFilterChains := []string{kubeInputChainName, kubeForwardChainName, kubeOutputChainName}
		for _, chain := range egressFilterChains {
			// ensure there is rule in filter table and FORWARD chain to jump to pod specific firewall chain
			// this rule applies to the traffic getting forwarded/routed (traffic from the pod destinted
			// to pod on a different node)
			comment = "\"rule to jump traffic from POD name:" + pod.name + " namespace: " + pod.namespace +
				" to chain " + podFwChainName + "\""
			args = []string{"-A", chain, "-m", "comment", "--comment", comment, "-s", pod.ip, "-j", podFwChainName, "\n"}
			npc.filterTableRules.WriteString(strings.Join(args, " "))
		}

		// ensure there is rule in filter table and forward chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting switched (coming for same node pods)
		comment = "\"rule to jump traffic from POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "physdev", "--physdev-is-bridged",
			"-m", "comment", "--comment", comment,
			"-s", pod.ip,
			"-j", podFwChainName, "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		err = dropUnmarkedTrafficRules(pod.name, pod.namespace, podFwChainName)
		if err != nil {
			return nil, err
		}
	}

	return activePodFwChains, nil
}

func (npc *NetworkPolicyController) getIngressNetworkPolicyEnabledPods(networkPoliciesInfo []networkPolicyInfo, nodeIP string) (*map[string]podInfo, error) {
	nodePods := make(map[string]podInfo)

	for _, obj := range npc.podLister.List() {
		pod := obj.(*api.Pod)

		// ignore the pods running on the different node and pods that are not actionable
		if strings.Compare(pod.Status.HostIP, nodeIP) != 0 || !isNetPolActionable(pod) {
			continue
		}

		for _, policy := range networkPoliciesInfo {
			if policy.namespace != pod.ObjectMeta.Namespace {
				continue
			}
			_, ok := policy.targetPods[pod.Status.PodIP]
			if ok && (policy.policyType == "both" || policy.policyType == "ingress") {
				klog.V(2).Infof("Found pod name: " + pod.ObjectMeta.Name + " namespace: " + pod.ObjectMeta.Namespace + " for which network policies need to be applied.")
				nodePods[pod.Status.PodIP] = podInfo{ip: pod.Status.PodIP,
					name:      pod.ObjectMeta.Name,
					namespace: pod.ObjectMeta.Namespace,
					labels:    pod.ObjectMeta.Labels}
				break
			}
		}
	}

	return &nodePods, nil
}

func (npc *NetworkPolicyController) getEgressNetworkPolicyEnabledPods(networkPoliciesInfo []networkPolicyInfo, nodeIP string) (*map[string]podInfo, error) {

	nodePods := make(map[string]podInfo)

	for _, obj := range npc.podLister.List() {
		pod := obj.(*api.Pod)

		// ignore the pods running on the different node and pods that are not actionable
		if strings.Compare(pod.Status.HostIP, nodeIP) != 0 || !isNetPolActionable(pod) {
			continue
		}

		for _, policy := range networkPoliciesInfo {
			if policy.namespace != pod.ObjectMeta.Namespace {
				continue
			}
			_, ok := policy.targetPods[pod.Status.PodIP]
			if ok && (policy.policyType == "both" || policy.policyType == "egress") {
				klog.V(2).Infof("Found pod name: " + pod.ObjectMeta.Name + " namespace: " + pod.ObjectMeta.Namespace + " for which network policies need to be applied.")
				nodePods[pod.Status.PodIP] = podInfo{ip: pod.Status.PodIP,
					name:      pod.ObjectMeta.Name,
					namespace: pod.ObjectMeta.Namespace,
					labels:    pod.ObjectMeta.Labels}
				break
			}
		}
	}

	return &nodePods, nil
}

func podFirewallChainName(namespace, podName string, version string) string {
	hash := sha256.Sum256([]byte(namespace + podName + version))
	encoded := base32.StdEncoding.EncodeToString(hash[:])
	return kubePodFirewallChainPrefix + encoded[:16]
}
<commit_msg>fix(NPC): check if new pod is actionable<commit_after>package netpol

import (
	"crypto/sha256"
	"encoding/base32"
	"reflect"
	"strings"

	api "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog/v2"
)

func (npc *NetworkPolicyController) newPodEventHandler() cache.ResourceEventHandler {
	return cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			if podObj, ok := obj.(*api.Pod); ok {
				// If the pod isn't yet actionable there is no action to take here anyway, so skip it. When it becomes
				// actionable, we'll get an update below.
				if isNetPolActionable(podObj) {
					npc.OnPodUpdate(obj)
				}
			}
		},
		UpdateFunc: func(oldObj, newObj interface{}) {
			newPodObj := newObj.(*api.Pod)
			oldPodObj := oldObj.(*api.Pod)
			if newPodObj.Status.Phase != oldPodObj.Status.Phase ||
				newPodObj.Status.PodIP != oldPodObj.Status.PodIP ||
				!reflect.DeepEqual(newPodObj.Labels, oldPodObj.Labels) {
				// for the network policies, we are only interested in pod status phase change or IP change
				npc.OnPodUpdate(newObj)
			}
		},
		DeleteFunc: func(obj interface{}) {
			npc.handlePodDelete(obj)
		},
	}
}

// OnPodUpdate handles updates to pods from the Kubernetes api server
func (npc *NetworkPolicyController) OnPodUpdate(obj interface{}) {
	pod := obj.(*api.Pod)
	klog.V(2).Infof("Received update to pod: %s/%s", pod.Namespace, pod.Name)

	npc.RequestFullSync()
}

func (npc *NetworkPolicyController) handlePodDelete(obj interface{}) {
	pod, ok := obj.(*api.Pod)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			klog.Errorf("unexpected object type: %v", obj)
			return
		}
		if pod, ok = tombstone.Obj.(*api.Pod); !ok {
			klog.Errorf("unexpected object type: %v", obj)
			return
		}
	}
	klog.V(2).Infof("Received pod: %s/%s delete event", pod.Namespace, pod.Name)

	npc.RequestFullSync()
}

func (npc *NetworkPolicyController) syncPodFirewallChains(networkPoliciesInfo []networkPolicyInfo, version string) (map[string]bool, error) {

	activePodFwChains := make(map[string]bool)

	dropUnmarkedTrafficRules := func(podName, podNamespace, podFwChainName string) error {
		// add rule to log the packets that will be dropped due to network policy enforcement
		comment := "\"rule to log dropped traffic POD name:" + podName + " namespace: " + podNamespace + "\""
		args := []string{"-A", podFwChainName, "-m", "comment", "--comment", comment, "-m", "mark", "!", "--mark", "0x10000/0x10000", "-j", "NFLOG", "--nflog-group", "100", "-m", "limit", "--limit", "10/minute", "--limit-burst", "10", "\n"}
		// This used to be AppendUnique when we were using iptables directly, this checks to make sure we didn't drop unmarked for this chain already
		if strings.Contains(npc.filterTableRules.String(), strings.Join(args, " ")) {
			return nil
		}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// add rule to DROP if no applicable network policy permits the traffic
		comment = "\"rule to REJECT traffic destined for POD name:" + podName + " namespace: " + podNamespace + "\""
		args = []string{"-A", podFwChainName, "-m", "comment", "--comment", comment, "-m", "mark", "!", "--mark", "0x10000/0x10000", "-j", "REJECT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// reset mark to let traffic pass through rest of the chains
		args = []string{"-A", podFwChainName, "-j", "MARK", "--set-mark", "0/0x10000", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		return nil
	}

	// loop through the pods running on the node which to which ingress network policies to be applied
	ingressNetworkPolicyEnabledPods, err := npc.getIngressNetworkPolicyEnabledPods(networkPoliciesInfo, npc.nodeIP.String())
	if err != nil {
		return nil, err
	}
	for _, pod := range *ingressNetworkPolicyEnabledPods {

		// below condition occurs when we get trasient update while removing or adding pod
		// subseqent update will do the correct action
		if len(pod.ip) == 0 || pod.ip == "" {
			continue
		}

		// ensure pod specific firewall chain exist for all the pods that need ingress firewall
		podFwChainName := podFirewallChainName(pod.namespace, pod.name, version)
		npc.filterTableRules.WriteString(":" + podFwChainName + "\n")

		activePodFwChains[podFwChainName] = true

		// add entries in pod firewall to run through required network policies
		for _, policy := range networkPoliciesInfo {
			if _, ok := policy.targetPods[pod.ip]; ok {
				comment := "\"run through nw policy " + policy.name + "\""
				policyChainName := networkPolicyChainName(policy.namespace, policy.name, version)
				args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-j", policyChainName, "\n"}
				npc.filterTableRules.WriteString(strings.Join(args, " "))
			}
		}

		comment := "\"rule to permit the traffic traffic to pods when source is the pod's local node\""
		args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "addrtype", "--src-type", "LOCAL", "-d", pod.ip, "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure statefull firewall, that permits return traffic for the traffic originated by the pod
		comment = "\"rule for stateful firewall for pod\""
		args = []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and FORWARD chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting routed (coming for other node pods)
		comment = "\"rule to jump traffic destined to POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "comment", "--comment", comment, "-d", pod.ip, "-j", podFwChainName + "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and OUTPUT chain to jump to pod specific firewall chain
		// this rule applies to the traffic from a pod getting routed back to another pod on same node by service proxy
		args = []string{"-I", kubeOutputChainName, "1", "-m", "comment", "--comment", comment, "-d", pod.ip, "-j", podFwChainName + "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		// ensure there is rule in filter table and forward chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting switched (coming for same node pods)
		comment = "\"rule to jump traffic destined to POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "physdev", "--physdev-is-bridged",
			"-m", "comment", "--comment", comment,
			"-d", pod.ip,
			"-j", podFwChainName, "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		err = dropUnmarkedTrafficRules(pod.name, pod.namespace, podFwChainName)
		if err != nil {
			return nil, err
		}
	}

	// loop through the pods running on the node which egress network policies to be applied
	egressNetworkPolicyEnabledPods, err := npc.getEgressNetworkPolicyEnabledPods(networkPoliciesInfo, npc.nodeIP.String())
	if err != nil {
		return nil, err
	}
	for _, pod := range *egressNetworkPolicyEnabledPods {

		// below condition occurs when we get trasient update while removing or adding pod
		// subseqent update will do the correct action
		if len(pod.ip) == 0 || pod.ip == "" {
			continue
		}

		// ensure pod specific firewall chain exist for all the pods that need egress firewall
		podFwChainName := podFirewallChainName(pod.namespace, pod.name, version)
		if !activePodFwChains[podFwChainName] {
			npc.filterTableRules.WriteString(":" + podFwChainName + "\n")
		}
		activePodFwChains[podFwChainName] = true

		// add entries in pod firewall to run through required network policies
		for _, policy := range networkPoliciesInfo {
			if _, ok := policy.targetPods[pod.ip]; ok {
				comment := "\"run through nw policy " + policy.name + "\""
				policyChainName := networkPolicyChainName(policy.namespace, policy.name, version)
				args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-j", policyChainName, "\n"}
				npc.filterTableRules.WriteString(strings.Join(args, " "))
			}
		}

		// ensure statefull firewall, that permits return traffic for the traffic originated by the pod
		comment := "\"rule for stateful firewall for pod\""
		args := []string{"-I", podFwChainName, "1", "-m", "comment", "--comment", comment, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		egressFilterChains := []string{kubeInputChainName, kubeForwardChainName, kubeOutputChainName}
		for _, chain := range egressFilterChains {
			// ensure there is rule in filter table and FORWARD chain to jump to pod specific firewall chain
			// this rule applies to the traffic getting forwarded/routed (traffic from the pod destinted
			// to pod on a different node)
			comment = "\"rule to jump traffic from POD name:" + pod.name + " namespace: " + pod.namespace +
				" to chain " + podFwChainName + "\""
			args = []string{"-A", chain, "-m", "comment", "--comment", comment, "-s", pod.ip, "-j", podFwChainName, "\n"}
			npc.filterTableRules.WriteString(strings.Join(args, " "))
		}

		// ensure there is rule in filter table and forward chain to jump to pod specific firewall chain
		// this rule applies to the traffic getting switched (coming for same node pods)
		comment = "\"rule to jump traffic from POD name:" + pod.name + " namespace: " + pod.namespace +
			" to chain " + podFwChainName + "\""
		args = []string{"-I", kubeForwardChainName, "1", "-m", "physdev", "--physdev-is-bridged",
			"-m", "comment", "--comment", comment,
			"-s", pod.ip,
			"-j", podFwChainName, "\n"}
		npc.filterTableRules.WriteString(strings.Join(args, " "))

		err = dropUnmarkedTrafficRules(pod.name, pod.namespace, podFwChainName)
		if err != nil {
			return nil, err
		}
	}

	return activePodFwChains, nil
}

func (npc *NetworkPolicyController) getIngressNetworkPolicyEnabledPods(networkPoliciesInfo []networkPolicyInfo, nodeIP string) (*map[string]podInfo, error) {
	nodePods := make(map[string]podInfo)

	for _, obj := range npc.podLister.List() {
		pod := obj.(*api.Pod)

		// ignore the pods running on the different node and pods that are not actionable
		if strings.Compare(pod.Status.HostIP, nodeIP) != 0 || !isNetPolActionable(pod) {
			continue
		}

		for _, policy := range networkPoliciesInfo {
			if policy.namespace != pod.ObjectMeta.Namespace {
				continue
			}
			_, ok := policy.targetPods[pod.Status.PodIP]
			if ok && (policy.policyType == "both" || policy.policyType == "ingress") {
				klog.V(2).Infof("Found pod name: " + pod.ObjectMeta.Name + " namespace: " + pod.ObjectMeta.Namespace + " for which network policies need to be applied.")
				nodePods[pod.Status.PodIP] = podInfo{ip: pod.Status.PodIP,
					name:      pod.ObjectMeta.Name,
					namespace: pod.ObjectMeta.Namespace,
					labels:    pod.ObjectMeta.Labels}
				break
			}
		}
	}

	return &nodePods, nil
}

func (npc *NetworkPolicyController) getEgressNetworkPolicyEnabledPods(networkPoliciesInfo []networkPolicyInfo, nodeIP string) (*map[string]podInfo, error) {

	nodePods := make(map[string]podInfo)

	for _, obj := range npc.podLister.List() {
		pod := obj.(*api.Pod)

		// ignore the pods running on the different node and pods that are not actionable
		if strings.Compare(pod.Status.HostIP, nodeIP) != 0 || !isNetPolActionable(pod) {
			continue
		}

		for _, policy := range networkPoliciesInfo {
			if policy.namespace != pod.ObjectMeta.Namespace {
				continue
			}
			_, ok := policy.targetPods[pod.Status.PodIP]
			if ok && (policy.policyType == "both" || policy.policyType == "egress") {
				klog.V(2).Infof("Found pod name: " + pod.ObjectMeta.Name + " namespace: " + pod.ObjectMeta.Namespace + " for which network policies need to be applied.")
				nodePods[pod.Status.PodIP] = podInfo{ip: pod.Status.PodIP,
					name:      pod.ObjectMeta.Name,
					namespace: pod.ObjectMeta.Namespace,
					labels:    pod.ObjectMeta.Labels}
				break
			}
		}
	}

	return &nodePods, nil
}

func podFirewallChainName(namespace, podName string, version string) string {
	hash := sha256.Sum256([]byte(namespace + podName + version))
	encoded := base32.StdEncoding.EncodeToString(hash[:])
	return kubePodFirewallChainPrefix + encoded[:16]
}
<|endoftext|>


<commit_before>package udp

import (
	"errors"
	"net"
	"sync"
	"time"
)

const receiveMTU = 8192

var ErrClosedListener = errors.New("udp: listener closed")
var ErrClosedConn = errors.New("udp: conn closed")

// Listener augments a connection-oriented Listener over a UDP PacketConn
type Listener struct {
	pConn *net.UDPConn

	lock      sync.RWMutex
	accepting bool
	acceptCh  chan *Conn
	doneCh    chan struct{}
	doneOnce  sync.Once

	conns map[string]*Conn
}

// Accept waits for and returns the next connection to the listener.
// You have to either close or read on all connection that are created.
func (l *Listener) Accept() (*Conn, error) {
	select {
	case c := <-l.acceptCh:
		return c, nil

	case <-l.doneCh:
		return nil, ErrClosedListener
	}
}

// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (l *Listener) Close() error {
	l.lock.Lock()
	defer l.lock.Unlock()

	var err error
	l.doneOnce.Do(func() {
		l.accepting = false
		close(l.doneCh)
		err = l.cleanup()
	})

	return err
}

// cleanup closes the packet conn if it is no longer used
// The caller should hold the read lock.
func (l *Listener) cleanup() error {
	if !l.accepting && len(l.conns) == 0 {
		return l.pConn.Close()
	}
	return nil
}

// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
	return l.pConn.LocalAddr()
}

// Listen creates a new listener
func Listen(network string, laddr *net.UDPAddr) (*Listener, error) {
	conn, err := net.ListenUDP(network, laddr)
	if err != nil {
		return nil, err
	}

	l := &Listener{
		pConn:     conn,
		acceptCh:  make(chan *Conn),
		conns:     make(map[string]*Conn),
		accepting: true,
		doneCh:    make(chan struct{}),
	}

	go l.readLoop()

	return l, nil
}

func (l *Listener) readLoop() {
	buf := make([]byte, receiveMTU)

readLoop:
	for {
		n, raddr, err := l.pConn.ReadFrom(buf)
		if err != nil {
			return
		}
		conn, err := l.getConn(raddr)
		if err != nil {
			continue
		}
		select {
		case cBuf := <-conn.readCh:
			n = copy(cBuf, buf[:n])
			conn.sizeCh <- n
		case <-conn.doneCh:
			continue readLoop
		}
	}
}

func (l *Listener) getConn(raddr net.Addr) (*Conn, error) {
	l.lock.Lock()
	defer l.lock.Unlock()
	if !l.accepting {
		return nil, ErrClosedListener
	}

	conn, ok := l.conns[raddr.String()]
	if !ok {
		conn = l.newConn(raddr)
		l.conns[raddr.String()] = conn
		l.acceptCh <- conn
	}
	return conn, nil
}

// Conn augments a connection-oriented connection over a UDP PacketConn
type Conn struct {
	listener *Listener

	rAddr net.Addr

	readCh chan []byte
	sizeCh chan int

	lock     sync.RWMutex
	doneCh   chan struct{}
	doneOnce sync.Once
	doneErr  error
}

func (l *Listener) newConn(rAddr net.Addr) *Conn {
	return &Conn{
		listener: l,
		rAddr:    rAddr,
		readCh:   make(chan []byte),
		sizeCh:   make(chan int),
		doneCh:   make(chan struct{}),
	}
}

// err returns the closed error
func (c *Conn) err() error {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.doneErr
}

// Read
func (c *Conn) Read(p []byte) (int, error) {
	select {
	case c.readCh <- p:
		n := <-c.sizeCh
		return n, nil
	case <-c.doneCh:
		return 0, c.err()
	}
}

// Write writes len(p) bytes from p to the DTLS connection
func (c *Conn) Write(p []byte) (n int, err error) {
	c.lock.Lock()
	l := c.listener
	c.lock.Unlock()

	if l == nil {
		return 0, ErrClosedConn
	}

	return l.pConn.WriteTo(p, c.rAddr)
}

// Close closes the conn and releases any Read calls
func (c *Conn) Close() error {
	c.lock.Lock()
	defer c.lock.Unlock()

	var err error
	c.doneOnce.Do(func() {
		close(c.doneCh)
		c.listener.lock.RLock()
		err = c.listener.cleanup()
		c.listener.lock.RUnlock()
		c.listener = nil
	})

	return err
}

// LocalAddr is a stub
func (c *Conn) LocalAddr() net.Addr {
	c.lock.Lock()
	l := c.listener
	c.lock.Unlock()

	if l == nil {
		return nil
	}

	return l.pConn.LocalAddr()
}

// RemoteAddr is a stub
func (c *Conn) RemoteAddr() net.Addr {
	return c.rAddr
}

// SetDeadline is a stub
func (c *Conn) SetDeadline(t time.Time) error {
	return nil
}

// SetReadDeadline is a stub
func (c *Conn) SetReadDeadline(t time.Time) error {
	return nil
}

// SetWriteDeadline is a stub
func (c *Conn) SetWriteDeadline(t time.Time) error {
	return nil
}
<commit_msg>UDP: Simplify close error<commit_after>package udp

import (
	"errors"
	"net"
	"sync"
	"time"
)

const receiveMTU = 8192

var ErrClosedListener = errors.New("udp: listener closed")
var ErrClosedConn = errors.New("udp: conn closed")

// Listener augments a connection-oriented Listener over a UDP PacketConn
type Listener struct {
	pConn *net.UDPConn

	lock      sync.RWMutex
	accepting bool
	acceptCh  chan *Conn
	doneCh    chan struct{}
	doneOnce  sync.Once

	conns map[string]*Conn
}

// Accept waits for and returns the next connection to the listener.
// You have to either close or read on all connection that are created.
func (l *Listener) Accept() (*Conn, error) {
	select {
	case c := <-l.acceptCh:
		return c, nil

	case <-l.doneCh:
		return nil, ErrClosedListener
	}
}

// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (l *Listener) Close() error {
	l.lock.Lock()
	defer l.lock.Unlock()

	var err error
	l.doneOnce.Do(func() {
		l.accepting = false
		close(l.doneCh)
		err = l.cleanup()
	})

	return err
}

// cleanup closes the packet conn if it is no longer used
// The caller should hold the read lock.
func (l *Listener) cleanup() error {
	if !l.accepting && len(l.conns) == 0 {
		return l.pConn.Close()
	}
	return nil
}

// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
	return l.pConn.LocalAddr()
}

// Listen creates a new listener
func Listen(network string, laddr *net.UDPAddr) (*Listener, error) {
	conn, err := net.ListenUDP(network, laddr)
	if err != nil {
		return nil, err
	}

	l := &Listener{
		pConn:     conn,
		acceptCh:  make(chan *Conn),
		conns:     make(map[string]*Conn),
		accepting: true,
		doneCh:    make(chan struct{}),
	}

	go l.readLoop()

	return l, nil
}

func (l *Listener) readLoop() {
	buf := make([]byte, receiveMTU)

readLoop:
	for {
		n, raddr, err := l.pConn.ReadFrom(buf)
		if err != nil {
			return
		}
		conn, err := l.getConn(raddr)
		if err != nil {
			continue
		}
		select {
		case cBuf := <-conn.readCh:
			n = copy(cBuf, buf[:n])
			conn.sizeCh <- n
		case <-conn.doneCh:
			continue readLoop
		}
	}
}

func (l *Listener) getConn(raddr net.Addr) (*Conn, error) {
	l.lock.Lock()
	defer l.lock.Unlock()
	if !l.accepting {
		return nil, ErrClosedListener
	}

	conn, ok := l.conns[raddr.String()]
	if !ok {
		conn = l.newConn(raddr)
		l.conns[raddr.String()] = conn
		l.acceptCh <- conn
	}
	return conn, nil
}

// Conn augments a connection-oriented connection over a UDP PacketConn
type Conn struct {
	listener *Listener

	rAddr net.Addr

	readCh chan []byte
	sizeCh chan int

	lock     sync.RWMutex
	doneCh   chan struct{}
	doneOnce sync.Once
}

func (l *Listener) newConn(rAddr net.Addr) *Conn {
	return &Conn{
		listener: l,
		rAddr:    rAddr,
		readCh:   make(chan []byte),
		sizeCh:   make(chan int),
		doneCh:   make(chan struct{}),
	}
}

// Read
func (c *Conn) Read(p []byte) (int, error) {
	select {
	case c.readCh <- p:
		n := <-c.sizeCh
		return n, nil
	case <-c.doneCh:
		return 0, ErrClosedConn
	}
}

// Write writes len(p) bytes from p to the DTLS connection
func (c *Conn) Write(p []byte) (n int, err error) {
	c.lock.Lock()
	l := c.listener
	c.lock.Unlock()

	if l == nil {
		return 0, ErrClosedConn
	}

	return l.pConn.WriteTo(p, c.rAddr)
}

// Close closes the conn and releases any Read calls
func (c *Conn) Close() error {
	c.lock.Lock()
	defer c.lock.Unlock()

	var err error
	c.doneOnce.Do(func() {
		close(c.doneCh)
		c.listener.lock.RLock()
		err = c.listener.cleanup()
		c.listener.lock.RUnlock()
		c.listener = nil
	})

	return err
}

// LocalAddr is a stub
func (c *Conn) LocalAddr() net.Addr {
	c.lock.Lock()
	l := c.listener
	c.lock.Unlock()

	if l == nil {
		return nil
	}

	return l.pConn.LocalAddr()
}

// RemoteAddr is a stub
func (c *Conn) RemoteAddr() net.Addr {
	return c.rAddr
}

// SetDeadline is a stub
func (c *Conn) SetDeadline(t time.Time) error {
	return nil
}

// SetReadDeadline is a stub
func (c *Conn) SetReadDeadline(t time.Time) error {
	return nil
}

// SetWriteDeadline is a stub
func (c *Conn) SetWriteDeadline(t time.Time) error {
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package cmd

import (
	"fmt"
	"io"

	"github.com/spf13/cobra"

	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	jobLong = templates.LongDesc(i18n.T(`
		Create a job with the specified name.`))

	jobExample = templates.Examples(i18n.T(`
		# Create a job from a CronJob named "a-cronjob"
		kubectl create job test-job --from=cronjob/a-cronjob`))
)

type CreateJobOptions struct {
	Name string
	From string

	Namespace    string
	OutputFormat string
	Client       clientbatchv1.BatchV1Interface
	Out          io.Writer
	DryRun       bool
	Builder      *resource.Builder
	Cmd          *cobra.Command
}

// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
	c := &CreateJobOptions{
		Out: cmdOut,
	}
	cmd := &cobra.Command{
		Use:     "job NAME [--from-cronjob=CRONJOB]",
		Short:   jobLong,
		Long:    jobLong,
		Example: jobExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(c.Complete(f, cmd, args))
			cmdutil.CheckErr(c.RunCreateJob())
		},
	}
	cmdutil.AddApplyAnnotationFlags(cmd)
	cmdutil.AddValidateFlags(cmd)
	cmdutil.AddPrinterFlags(cmd)
	cmdutil.AddDryRunFlag(cmd)
	cmd.Flags().String("from", "", "The name of the resource to create a Job from (only cronjob is supported).")

	return cmd
}

func (c *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
	if len(args) == 0 {
		return cmdutil.UsageErrorf(cmd, "NAME is required")
	}
	c.Name = args[0]

	c.From = cmdutil.GetFlagString(cmd, "from")
	c.Namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	clientset, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	c.Client = clientset.BatchV1()
	c.Builder = f.NewBuilder()
	c.DryRun = cmdutil.GetDryRunFlag(cmd)
	c.Cmd = cmd
	c.OutputFormat = cmdutil.GetFlagString(cmd, "output")

	return nil
}

func (c *CreateJobOptions) RunCreateJob() error {
	infos, err := c.Builder.
		Unstructured().
		NamespaceParam(c.Namespace).DefaultNamespace().
		ResourceTypeOrNameArgs(false, c.From).
		Flatten().
		Latest().
		Do().
		Infos()
	if err != nil {
		return err
	}
	if len(infos) != 1 {
		return fmt.Errorf("from must be an existing cronjob")
	}
	cronJob, ok := infos[0].AsVersioned().(*batchv1beta1.CronJob)
	if !ok {
		return fmt.Errorf("from must be an existing cronjob")
	}

	return c.createJob(cronJob)
}

func (c *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
	annotations := make(map[string]string)
	annotations["cronjob.kubernetes.io/instantiate"] = "manual"
	for k, v := range cronJob.Spec.JobTemplate.Annotations {
		annotations[k] = v
	}
	job := &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:        c.Name,
			Namespace:   c.Namespace,
			Annotations: annotations,
			Labels:      cronJob.Spec.JobTemplate.Labels,
		},
		Spec: cronJob.Spec.JobTemplate.Spec,
	}

	if !c.DryRun {
		var err error
		job, err = c.Client.Jobs(c.Namespace).Create(job)
		if err != nil {
			return fmt.Errorf("failed to create job: %v", err)
		}
	}

	if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
		cmdutil.PrintSuccess(useShortOutput, c.Out, job, c.DryRun, "created")
		return nil
	}

	return cmdutil.PrintObject(c.Cmd, job, c.Out)
}
<commit_msg>Fix create job usage<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package cmd

import (
	"fmt"
	"io"

	"github.com/spf13/cobra"

	batchv1 "k8s.io/api/batch/v1"
	batchv1beta1 "k8s.io/api/batch/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	jobLong = templates.LongDesc(i18n.T(`
		Create a job with the specified name.`))

	jobExample = templates.Examples(i18n.T(`
		# Create a job from a CronJob named "a-cronjob"
		kubectl create job test-job --from=cronjob/a-cronjob`))
)

type CreateJobOptions struct {
	Name string
	From string

	Namespace    string
	OutputFormat string
	Client       clientbatchv1.BatchV1Interface
	Out          io.Writer
	DryRun       bool
	Builder      *resource.Builder
	Cmd          *cobra.Command
}

// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
	c := &CreateJobOptions{
		Out: cmdOut,
	}
	cmd := &cobra.Command{
		Use:     "job NAME [--from=CRONJOB]",
		Short:   jobLong,
		Long:    jobLong,
		Example: jobExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(c.Complete(f, cmd, args))
			cmdutil.CheckErr(c.RunCreateJob())
		},
	}
	cmdutil.AddApplyAnnotationFlags(cmd)
	cmdutil.AddValidateFlags(cmd)
	cmdutil.AddPrinterFlags(cmd)
	cmdutil.AddDryRunFlag(cmd)
	cmd.Flags().String("from", "", "The name of the resource to create a Job from (only cronjob is supported).")

	return cmd
}

func (c *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
	if len(args) == 0 {
		return cmdutil.UsageErrorf(cmd, "NAME is required")
	}
	c.Name = args[0]

	c.From = cmdutil.GetFlagString(cmd, "from")
	c.Namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	clientset, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	c.Client = clientset.BatchV1()
	c.Builder = f.NewBuilder()
	c.DryRun = cmdutil.GetDryRunFlag(cmd)
	c.Cmd = cmd
	c.OutputFormat = cmdutil.GetFlagString(cmd, "output")

	return nil
}

func (c *CreateJobOptions) RunCreateJob() error {
	infos, err := c.Builder.
		Unstructured().
		NamespaceParam(c.Namespace).DefaultNamespace().
		ResourceTypeOrNameArgs(false, c.From).
		Flatten().
		Latest().
		Do().
		Infos()
	if err != nil {
		return err
	}
	if len(infos) != 1 {
		return fmt.Errorf("from must be an existing cronjob")
	}
	cronJob, ok := infos[0].AsVersioned().(*batchv1beta1.CronJob)
	if !ok {
		return fmt.Errorf("from must be an existing cronjob")
	}

	return c.createJob(cronJob)
}

func (c *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error {
	annotations := make(map[string]string)
	annotations["cronjob.kubernetes.io/instantiate"] = "manual"
	for k, v := range cronJob.Spec.JobTemplate.Annotations {
		annotations[k] = v
	}
	job := &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:        c.Name,
			Namespace:   c.Namespace,
			Annotations: annotations,
			Labels:      cronJob.Spec.JobTemplate.Labels,
		},
		Spec: cronJob.Spec.JobTemplate.Spec,
	}

	if !c.DryRun {
		var err error
		job, err = c.Client.Jobs(c.Namespace).Create(job)
		if err != nil {
			return fmt.Errorf("failed to create job: %v", err)
		}
	}

	if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
		cmdutil.PrintSuccess(useShortOutput, c.Out, job, c.DryRun, "created")
		return nil
	}

	return cmdutil.PrintObject(c.Cmd, job, c.Out)
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"io"
	"os"
	"runtime"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/filepathfilter"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/localstorage"
	"github.com/git-lfs/git-lfs/tools"
	"github.com/git-lfs/git-lfs/tools/humanize"
	"github.com/spf13/cobra"
)

var (
	// smudgeSkip is a command-line flag belonging to the "git-lfs smudge"
	// command specifying whether to skip the smudge process.
	smudgeSkip = false
)

// smudge smudges the given `*lfs.Pointer`, "ptr", and writes its objects
// contents to the `io.Writer`, "to".
//
// If the encoded LFS pointer is not parse-able as a pointer, the contents of
// that file will instead be spooled to a temporary location on disk and then
// copied out back to Git. If the pointer file is empty, an empty file will be
// written with no error.
//
// If the smudged object did not "pass" the include and exclude filterset, it
// will not be downloaded, and the object will remain a pointer on disk, as if
// the smudge filter had not been applied at all.
//
// Any errors encountered along the way will be returned immediately if they
// were non-fatal, otherwise execution will halt and the process will be
// terminated by using the `commands.Panic()` func.
func smudge(to io.Writer, from io.Reader, filename string, skip bool, filter *filepathfilter.Filter) (int64, error) {
	ptr, pbuf, perr := lfs.DecodeFrom(from)
	if perr != nil {
		n, err := tools.Spool(to, pbuf, localstorage.Objects().TempDir)
		if err != nil {
			return 0, errors.Wrap(err, perr.Error())
		}

		if n != 0 {
			return 0, errors.NewNotAPointerError(errors.Errorf(
				"Unable to parse pointer at: %q", filename,
			))
		}
		return 0, nil
	}

	lfs.LinkOrCopyFromReference(ptr.Oid, ptr.Size)
	cb, file, err := lfs.CopyCallbackFile("download", filename, 1, 1)
	if err != nil {
		return 0, err
	}

	download := !skip
	if download {
		download = filter.Allows(filename)
	}

	n, err := ptr.Smudge(to, filename, download, getTransferManifest(), cb)
	if file != nil {
		file.Close()
	}

	if err != nil {
		ptr.Encode(to)
		// Download declined error is ok to skip if we weren't requesting download
		if !(errors.IsDownloadDeclinedError(err) && !download) {
			var oid string = ptr.Oid
			if len(oid) >= 7 {
				oid = oid[:7]
			}

			LoggedError(err, "Error downloading object: %s (%s): %s", filename, oid, err)
			if !cfg.SkipDownloadErrors() {
				os.Exit(2)
			}
		}
	}

	return n, nil
}

func smudgeCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'smudge' filter")
	lfs.InstallHooks(false)

	if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
		smudgeSkip = true
	}
	filter := filepathfilter.New(cfg.FetchIncludePaths(), cfg.FetchExcludePaths())

	if n, err := smudge(os.Stdout, os.Stdin, smudgeFilename(args), smudgeSkip, filter); err != nil {
		if errors.IsNotAPointerError(err) {
			fmt.Fprintln(os.Stderr, err.Error())
		} else {
			Error(err.Error())
		}
	} else if possiblyMalformedSmudge(n) {
		fmt.Fprintln(os.Stderr, "Possibly malformed smudge on Windows: see `git lfs help smudge` for more info.")
	}
}

func smudgeFilename(args []string) string {
	if len(args) > 0 {
		return args[0]
	}
	return "<unknown file>"
}

func possiblyMalformedSmudge(n int64) bool {
	return n > 4*humanize.Gigabyte && runtime.GOOS == "windows"
}

func init() {
	RegisterCommand("smudge", smudgeCommand, func(cmd *cobra.Command) {
		cmd.Flags().BoolVarP(&smudgeSkip, "skip", "s", false, "")
	})
}
<commit_msg>commands: make 4gb smudge warning show on all platforms<commit_after>package commands

import (
	"fmt"
	"io"
	"os"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/filepathfilter"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/localstorage"
	"github.com/git-lfs/git-lfs/tools"
	"github.com/git-lfs/git-lfs/tools/humanize"
	"github.com/spf13/cobra"
)

var (
	// smudgeSkip is a command-line flag belonging to the "git-lfs smudge"
	// command specifying whether to skip the smudge process.
	smudgeSkip = false
)

// smudge smudges the given `*lfs.Pointer`, "ptr", and writes its objects
// contents to the `io.Writer`, "to".
//
// If the encoded LFS pointer is not parse-able as a pointer, the contents of
// that file will instead be spooled to a temporary location on disk and then
// copied out back to Git. If the pointer file is empty, an empty file will be
// written with no error.
//
// If the smudged object did not "pass" the include and exclude filterset, it
// will not be downloaded, and the object will remain a pointer on disk, as if
// the smudge filter had not been applied at all.
//
// Any errors encountered along the way will be returned immediately if they
// were non-fatal, otherwise execution will halt and the process will be
// terminated by using the `commands.Panic()` func.
func smudge(to io.Writer, from io.Reader, filename string, skip bool, filter *filepathfilter.Filter) (int64, error) {
	ptr, pbuf, perr := lfs.DecodeFrom(from)
	if perr != nil {
		n, err := tools.Spool(to, pbuf, localstorage.Objects().TempDir)
		if err != nil {
			return 0, errors.Wrap(err, perr.Error())
		}

		if n != 0 {
			return 0, errors.NewNotAPointerError(errors.Errorf(
				"Unable to parse pointer at: %q", filename,
			))
		}
		return 0, nil
	}

	lfs.LinkOrCopyFromReference(ptr.Oid, ptr.Size)
	cb, file, err := lfs.CopyCallbackFile("download", filename, 1, 1)
	if err != nil {
		return 0, err
	}

	download := !skip
	if download {
		download = filter.Allows(filename)
	}

	n, err := ptr.Smudge(to, filename, download, getTransferManifest(), cb)
	if file != nil {
		file.Close()
	}

	if err != nil {
		ptr.Encode(to)
		// Download declined error is ok to skip if we weren't requesting download
		if !(errors.IsDownloadDeclinedError(err) && !download) {
			var oid string = ptr.Oid
			if len(oid) >= 7 {
				oid = oid[:7]
			}

			LoggedError(err, "Error downloading object: %s (%s): %s", filename, oid, err)
			if !cfg.SkipDownloadErrors() {
				os.Exit(2)
			}
		}
	}

	return n, nil
}

func smudgeCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'smudge' filter")
	lfs.InstallHooks(false)

	if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
		smudgeSkip = true
	}
	filter := filepathfilter.New(cfg.FetchIncludePaths(), cfg.FetchExcludePaths())

	if n, err := smudge(os.Stdout, os.Stdin, smudgeFilename(args), smudgeSkip, filter); err != nil {
		if errors.IsNotAPointerError(err) {
			fmt.Fprintln(os.Stderr, err.Error())
		} else {
			Error(err.Error())
		}
	} else if possiblyMalformedSmudge(n) {
		fmt.Fprintln(os.Stderr, "Possibly malformed smudge on Windows: see `git lfs help smudge` for more info.")
	}
}

func smudgeFilename(args []string) string {
	if len(args) > 0 {
		return args[0]
	}
	return "<unknown file>"
}

func possiblyMalformedSmudge(n int64) bool {
	return n > 4*humanize.Gigabyte
}

func init() {
	RegisterCommand("smudge", smudgeCommand, func(cmd *cobra.Command) {
		cmd.Flags().BoolVarP(&smudgeSkip, "skip", "s", false, "")
	})
}
<|endoftext|>


<commit_before>package operator

import (
	"fmt"

	"github.com/blang/semver"
	"github.com/coreos/container-linux-update-operator/pkg/constants"
	"github.com/coreos/container-linux-update-operator/pkg/version"
	"github.com/golang/glog"

	v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
)

var (
	managedByOperatorLabels = map[string]string{
		"managed-by": "container-linux-update-operator",
		"app":        agentDefaultAppName,
	}

	daemonsetName = "container-linux-update-agent-ds"
)

// updateAgent updates the agent on nodes if necessary.
//
// NOTE: the version for the agent is assumed to match the versioning scheme
// for the operator, thus our version is used to figure out the appropriate
// agent version.
// Furthermore, it's assumed that all future agent versions will be backwards
// compatible, so if the agent's version is greater than ours, it's okay.
func (k *Kontroller) runDaemonsetUpdate(agentImageRepo string) error {
	agentDaemonsets, err := k.kc.DaemonSets(k.namespace).List(v1meta.ListOptions{
		LabelSelector: labels.SelectorFromSet(labels.Set(managedByOperatorLabels)).String(),
	})
	if err != nil {
		return err
	}

	if len(agentDaemonsets.Items) == 0 {
		// No daemonset, create it
		runErr := k.createAgentDamonset(agentImageRepo)
		if runErr != nil {
			return runErr
		}
		// runAgent succeeded, all should be well and converging now
		return nil
	}

	// There should only be one daemonset since we use a well-known name and
	// patch it each time rather than creating new ones.
	if len(agentDaemonsets.Items) > 1 {
		glog.Errorf("only expected one daemonset managed by operator; found %+v", agentDaemonsets.Items)
		return fmt.Errorf("only expected one daemonset managed by operator; found %v", len(agentDaemonsets.Items))
	}

	agentDS := agentDaemonsets.Items[0]

	var dsSemver semver.Version
	if dsVersion, ok := agentDS.Annotations[constants.AgentVersion]; ok {
		ver, err := semver.Parse(dsVersion)
		if err != nil {
			return fmt.Errorf("agent daemonset had version annotation, but it was not valid semver: %v[%v] = %v", agentDS.Name, constants.AgentVersion, dsVersion)
		}
		dsSemver = ver
	} else {
		glog.Errorf("managed daemonset did not have a version annotation: %+v", agentDS)
		return fmt.Errorf("managed daemonset did not have a version annotation")
	}

	if dsSemver.LT(version.Semver) {
		// daemonset is too old, update it
		// TODO: perform a proper rolling update rather than delete-then-recreate
		// Right now, daemonset rolling updates aren't upstream and are thus fairly
		// painful to do correctly. In addition, doing it correctly doesn't add too
		// much value unless we have corresponding detection/rollback logic.
		falseVal := false
		err := k.kc.DaemonSets(k.namespace).Delete(agentDS.Name, &v1meta.DeleteOptions{
			OrphanDependents: &falseVal, // Cascading delete
		})
		// TODO: this requires delete to be blocking to work. Is it?
		if err != nil {
			glog.Errorf("could not delete old daemonset %+v: %v", agentDS, err)
			return err
		}

		err = k.createAgentDamonset(agentImageRepo)
		if err != nil {
			glog.Errorf("could not create new daemonset: %v", err)
			return err
		}
	}

	return nil
}

func (k *Kontroller) createAgentDamonset(agentImageRepo string) error {
	_, err := k.kc.DaemonSets(k.namespace).Create(agentDaemonsetSpec(agentImageRepo))
	return err
}

func agentDaemonsetSpec(repo string) *v1beta1.DaemonSet {
	// Each agent daemonset includes the version of the agent in the selector.
	// This ensures that the 'orphan adoption' logic doesn't kick in for these
	// daemonsets.
	versionedSelector := make(map[string]string)
	for k, v := range managedByOperatorLabels {
		versionedSelector[k] = v
	}
	versionedSelector[constants.AgentVersion] = version.Version

	return &v1beta1.DaemonSet{
		ObjectMeta: v1meta.ObjectMeta{
			Name:   daemonsetName,
			Labels: managedByOperatorLabels,
			Annotations: map[string]string{
				constants.AgentVersion: version.Version,
			},
		},
		Spec: v1beta1.DaemonSetSpec{
			Selector: &v1meta.LabelSelector{MatchLabels: versionedSelector},
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1meta.ObjectMeta{
					Name:   agentDefaultAppName,
					Labels: versionedSelector,
					Annotations: map[string]string{
						constants.AgentVersion: version.Version,
					},
				},
				Spec: v1.PodSpec{
					// Update the master nodes too
					Tolerations: []v1.Toleration{
						{
							Key:      "node-role.kubernetes.io/master",
							Operator: v1.TolerationOpExists,
							Effect:   v1.TaintEffectNoSchedule,
						},
					},
					Containers: []v1.Container{
						{
							Name:    "update-agent",
							Image:   agentImageName(repo),
							Command: agentCommand(),
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "var-run-dbus",
									MountPath: "/var/run/dbus",
								},
								{
									Name:      "etc-coreos",
									MountPath: "/etc/coreos",
								},
								{
									Name:      "usr-share-coreos",
									MountPath: "/usr/share/coreos",
								},
								{
									Name:      "etc-os-release",
									MountPath: "/etc/os-release",
								},
							},
							Env: []v1.EnvVar{
								{
									Name: "UPDATE_AGENT_NODE",
									ValueFrom: &v1.EnvVarSource{
										FieldRef: &v1.ObjectFieldSelector{
											FieldPath: "spec.nodeName",
										},
									},
								},
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &v1.EnvVarSource{
										FieldRef: &v1.ObjectFieldSelector{
											FieldPath: "metadata.namespace",
										},
									},
								},
							},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: "var-run-dbus",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/var/run/dbus",
								},
							},
						},
						{
							Name: "etc-coreos",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/etc/coreos",
								},
							},
						},
						{
							Name: "usr-share-coreos",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/usr/share/coreos",
								},
							},
						},
						{
							Name: "etc-os-release",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/etc/os-release",
								},
							},
						},
					},
				},
			},
		},
	}
}

func agentImageName(repo string) string {
	return fmt.Sprintf("%s:%s", repo, version.Version)
}

func agentCommand() []string {
	return []string{"/bin/update-agent"}
}
<commit_msg>operator: remove stale todo<commit_after>package operator

import (
	"fmt"

	"github.com/blang/semver"
	"github.com/coreos/container-linux-update-operator/pkg/constants"
	"github.com/coreos/container-linux-update-operator/pkg/version"
	"github.com/golang/glog"

	v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
)

var (
	managedByOperatorLabels = map[string]string{
		"managed-by": "container-linux-update-operator",
		"app":        agentDefaultAppName,
	}

	daemonsetName = "container-linux-update-agent-ds"
)

// updateAgent updates the agent on nodes if necessary.
//
// NOTE: the version for the agent is assumed to match the versioning scheme
// for the operator, thus our version is used to figure out the appropriate
// agent version.
// Furthermore, it's assumed that all future agent versions will be backwards
// compatible, so if the agent's version is greater than ours, it's okay.
func (k *Kontroller) runDaemonsetUpdate(agentImageRepo string) error {
	agentDaemonsets, err := k.kc.DaemonSets(k.namespace).List(v1meta.ListOptions{
		LabelSelector: labels.SelectorFromSet(labels.Set(managedByOperatorLabels)).String(),
	})
	if err != nil {
		return err
	}

	if len(agentDaemonsets.Items) == 0 {
		// No daemonset, create it
		runErr := k.createAgentDamonset(agentImageRepo)
		if runErr != nil {
			return runErr
		}
		// runAgent succeeded, all should be well and converging now
		return nil
	}

	// There should only be one daemonset since we use a well-known name and
	// patch it each time rather than creating new ones.
	if len(agentDaemonsets.Items) > 1 {
		glog.Errorf("only expected one daemonset managed by operator; found %+v", agentDaemonsets.Items)
		return fmt.Errorf("only expected one daemonset managed by operator; found %v", len(agentDaemonsets.Items))
	}

	agentDS := agentDaemonsets.Items[0]

	var dsSemver semver.Version
	if dsVersion, ok := agentDS.Annotations[constants.AgentVersion]; ok {
		ver, err := semver.Parse(dsVersion)
		if err != nil {
			return fmt.Errorf("agent daemonset had version annotation, but it was not valid semver: %v[%v] = %v", agentDS.Name, constants.AgentVersion, dsVersion)
		}
		dsSemver = ver
	} else {
		glog.Errorf("managed daemonset did not have a version annotation: %+v", agentDS)
		return fmt.Errorf("managed daemonset did not have a version annotation")
	}

	if dsSemver.LT(version.Semver) {
		// daemonset is too old, update it
		// TODO: perform a proper rolling update rather than delete-then-recreate
		// Right now, daemonset rolling updates aren't upstream and are thus fairly
		// painful to do correctly. In addition, doing it correctly doesn't add too
		// much value unless we have corresponding detection/rollback logic.
		falseVal := false
		err := k.kc.DaemonSets(k.namespace).Delete(agentDS.Name, &v1meta.DeleteOptions{
			OrphanDependents: &falseVal, // Cascading delete
		})
		if err != nil {
			glog.Errorf("could not delete old daemonset %+v: %v", agentDS, err)
			return err
		}

		err = k.createAgentDamonset(agentImageRepo)
		if err != nil {
			glog.Errorf("could not create new daemonset: %v", err)
			return err
		}
	}

	return nil
}

func (k *Kontroller) createAgentDamonset(agentImageRepo string) error {
	_, err := k.kc.DaemonSets(k.namespace).Create(agentDaemonsetSpec(agentImageRepo))
	return err
}

func agentDaemonsetSpec(repo string) *v1beta1.DaemonSet {
	// Each agent daemonset includes the version of the agent in the selector.
	// This ensures that the 'orphan adoption' logic doesn't kick in for these
	// daemonsets.
	versionedSelector := make(map[string]string)
	for k, v := range managedByOperatorLabels {
		versionedSelector[k] = v
	}
	versionedSelector[constants.AgentVersion] = version.Version

	return &v1beta1.DaemonSet{
		ObjectMeta: v1meta.ObjectMeta{
			Name:   daemonsetName,
			Labels: managedByOperatorLabels,
			Annotations: map[string]string{
				constants.AgentVersion: version.Version,
			},
		},
		Spec: v1beta1.DaemonSetSpec{
			Selector: &v1meta.LabelSelector{MatchLabels: versionedSelector},
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1meta.ObjectMeta{
					Name:   agentDefaultAppName,
					Labels: versionedSelector,
					Annotations: map[string]string{
						constants.AgentVersion: version.Version,
					},
				},
				Spec: v1.PodSpec{
					// Update the master nodes too
					Tolerations: []v1.Toleration{
						{
							Key:      "node-role.kubernetes.io/master",
							Operator: v1.TolerationOpExists,
							Effect:   v1.TaintEffectNoSchedule,
						},
					},
					Containers: []v1.Container{
						{
							Name:    "update-agent",
							Image:   agentImageName(repo),
							Command: agentCommand(),
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "var-run-dbus",
									MountPath: "/var/run/dbus",
								},
								{
									Name:      "etc-coreos",
									MountPath: "/etc/coreos",
								},
								{
									Name:      "usr-share-coreos",
									MountPath: "/usr/share/coreos",
								},
								{
									Name:      "etc-os-release",
									MountPath: "/etc/os-release",
								},
							},
							Env: []v1.EnvVar{
								{
									Name: "UPDATE_AGENT_NODE",
									ValueFrom: &v1.EnvVarSource{
										FieldRef: &v1.ObjectFieldSelector{
											FieldPath: "spec.nodeName",
										},
									},
								},
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &v1.EnvVarSource{
										FieldRef: &v1.ObjectFieldSelector{
											FieldPath: "metadata.namespace",
										},
									},
								},
							},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: "var-run-dbus",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/var/run/dbus",
								},
							},
						},
						{
							Name: "etc-coreos",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/etc/coreos",
								},
							},
						},
						{
							Name: "usr-share-coreos",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/usr/share/coreos",
								},
							},
						},
						{
							Name: "etc-os-release",
							VolumeSource: v1.VolumeSource{
								HostPath: &v1.HostPathVolumeSource{
									Path: "/etc/os-release",
								},
							},
						},
					},
				},
			},
		},
	}
}

func agentImageName(repo string) string {
	return fmt.Sprintf("%s:%s", repo, version.Version)
}

func agentCommand() []string {
	return []string{"/bin/update-agent"}
}
<|endoftext|>