<commit_before>//go:build cgo
// +build cgo

package sqlite

import (
	"database/sql"
	"os"
	"testing"

	"github.com/amacneil/dbmate/pkg/dbmate"
	"github.com/amacneil/dbmate/pkg/dbutil"

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

func testSQLiteDriver(t *testing.T) *Driver {
	u := dbutil.MustParseURL(os.Getenv("SQLITE_TEST_URL"))
	drv, err := dbmate.New(u).GetDriver()
	require.NoError(t, err)

	return drv.(*Driver)
}

func prepTestSQLiteDB(t *testing.T) *sql.DB {
	drv := testSQLiteDriver(t)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// connect database
	db, err := drv.Open()
	require.NoError(t, err)

	return db
}

func TestGetDriver(t *testing.T) {
	db := dbmate.New(dbutil.MustParseURL("sqlite://"))
	drvInterface, err := db.GetDriver()
	require.NoError(t, err)

	// driver should have URL and default migrations table set
	drv, ok := drvInterface.(*Driver)
	require.True(t, ok)
	require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String())
	require.Equal(t, "schema_migrations", drv.migrationsTableName)
}

func TestConnectionString(t *testing.T) {
	t.Run("relative", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:foo/bar.sqlite3?mode=ro")
		require.Equal(t, "foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:./foo/bar.sqlite3?mode=ro")
		require.Equal(t, "./foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with double dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:../foo/bar.sqlite3?mode=ro")
		require.Equal(t, "../foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("absolute", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:/tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("four slashes", func(t *testing.T) {
		// interpreted as absolute path
		// supported for backwards compatibility
		u := dbutil.MustParseURL("sqlite:////tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:foo bar.sqlite3?mode=ro")
		require.Equal(t, "foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space and dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:./foo bar.sqlite3?mode=ro")
		require.Equal(t, "./foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space and double dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:../foo bar.sqlite3?mode=ro")
		require.Equal(t, "../foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("absolute with space", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes with space in path", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tmp/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes with space in path (1st dir)", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tm p/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tm p/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("four slashes with space", func(t *testing.T) {
		// interpreted as absolute path
		// supported for backwards compatibility
		u := dbutil.MustParseURL("sqlite:////tmp/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})
}

func TestSQLiteCreateDropDatabase(t *testing.T) {
	drv := testSQLiteDriver(t)
	path := ConnectionString(drv.databaseURL)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// check that database exists
	_, err = os.Stat(path)
	require.NoError(t, err)

	// drop the database
	err = drv.DropDatabase()
	require.NoError(t, err)

	// check that database no longer exists
	_, err = os.Stat(path)
	require.NotNil(t, err)
	require.Equal(t, true, os.IsNotExist(err))
}

func TestSQLiteDumpSchema(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	// prepare database
	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)
	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	// insert migration
	err = drv.InsertMigration(db, "abc1")
	require.NoError(t, err)
	err = drv.InsertMigration(db, "abc2")
	require.NoError(t, err)

	// DumpSchema should return schema
	schema, err := drv.DumpSchema(db)
	require.NoError(t, err)
	require.Contains(t, string(schema), "CREATE TABLE IF NOT EXISTS \"test_migrations\"")
	require.Contains(t, string(schema), ");\n-- Dbmate schema migrations\n"+
		"INSERT INTO \"test_migrations\" (version) VALUES\n"+
		"  ('abc1'),\n"+
		"  ('abc2');\n")

	// DumpSchema should return error if command fails
	drv.databaseURL = dbutil.MustParseURL(".")
	schema, err = drv.DumpSchema(db)
	require.Nil(t, schema)
	require.Error(t, err)
	require.EqualError(t, err, "Error: unable to open database \"/.\": "+
		"unable to open database file")
}

func TestSQLiteDatabaseExists(t *testing.T) {
	drv := testSQLiteDriver(t)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// DatabaseExists should return false
	exists, err := drv.DatabaseExists()
	require.NoError(t, err)
	require.Equal(t, false, exists)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// DatabaseExists should return true
	exists, err = drv.DatabaseExists()
	require.NoError(t, err)
	require.Equal(t, true, exists)
}

func TestSQLiteCreateMigrationsTable(t *testing.T) {
	t.Run("default table", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		db := prepTestSQLiteDB(t)
		defer dbutil.MustClose(db)

		// migrations table should not exist
		count := 0
		err := db.QueryRow("select count(*) from schema_migrations").Scan(&count)
		require.Error(t, err)
		require.Regexp(t, "no such table: schema_migrations", err.Error())

		// create table
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)

		// migrations table should exist
		err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
		require.NoError(t, err)

		// create table should be idempotent
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)
	})

	t.Run("custom table", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		drv.migrationsTableName = "test_migrations"

		db := prepTestSQLiteDB(t)
		defer dbutil.MustClose(db)

		// migrations table should not exist
		count := 0
		err := db.QueryRow("select count(*) from test_migrations").Scan(&count)
		require.Error(t, err)
		require.Regexp(t, "no such table: test_migrations", err.Error())

		// create table
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)

		// migrations table should exist
		err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
		require.NoError(t, err)

		// create table should be idempotent
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)
	})
}

func TestSQLiteSelectMigrations(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	_, err = db.Exec(`insert into test_migrations (version)
		values ('abc2'), ('abc1'), ('abc3')`)
	require.NoError(t, err)

	migrations, err := drv.SelectMigrations(db, -1)
	require.NoError(t, err)
	require.Equal(t, true, migrations["abc1"])
	require.Equal(t, true, migrations["abc2"])
	require.Equal(t, true, migrations["abc2"])

	// test limit param
	migrations, err = drv.SelectMigrations(db, 1)
	require.NoError(t, err)
	require.Equal(t, true, migrations["abc3"])
	require.Equal(t, false, migrations["abc1"])
	require.Equal(t, false, migrations["abc2"])
}

func TestSQLiteInsertMigration(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	count := 0
	err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 0, count)

	// insert migration
	err = drv.InsertMigration(db, "abc1")
	require.NoError(t, err)

	err = db.QueryRow("select count(*) from test_migrations where version = 'abc1'").
		Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 1, count)
}

func TestSQLiteDeleteMigration(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	_, err = db.Exec(`insert into test_migrations (version)
		values ('abc1'), ('abc2')`)
	require.NoError(t, err)

	err = drv.DeleteMigration(db, "abc2")
	require.NoError(t, err)

	count := 0
	err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 1, count)
}

func TestSQLitePing(t *testing.T) {
	drv := testSQLiteDriver(t)
	path := ConnectionString(drv.databaseURL)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// ping database
	err = drv.Ping()
	require.NoError(t, err)

	// check that the database was created (sqlite-only behavior)
	_, err = os.Stat(path)
	require.NoError(t, err)

	// drop the database
	err = drv.DropDatabase()
	require.NoError(t, err)

	// create directory where database file is expected
	err = os.Mkdir(path, 0755)
	require.NoError(t, err)
	defer func() {
		err = os.RemoveAll(path)
		require.NoError(t, err)
	}()

	// ping database should fail
	err = drv.Ping()
	require.EqualError(t, err, "unable to open database file: is a directory")
}

func TestSQLiteQuotedMigrationsTableName(t *testing.T) {
	t.Run("default name", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		name := drv.quotedMigrationsTableName()
		require.Equal(t, `"schema_migrations"`, name)
	})

	t.Run("custom name", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		drv.migrationsTableName = "fooMigrations"

		name := drv.quotedMigrationsTableName()
		require.Equal(t, `"fooMigrations"`, name)
	})
}
<commit_msg>sqlite: Add test for two slashes in URL (#269)<commit_after>//go:build cgo
// +build cgo

package sqlite

import (
	"database/sql"
	"os"
	"testing"

	"github.com/amacneil/dbmate/pkg/dbmate"
	"github.com/amacneil/dbmate/pkg/dbutil"

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

func testSQLiteDriver(t *testing.T) *Driver {
	u := dbutil.MustParseURL(os.Getenv("SQLITE_TEST_URL"))
	drv, err := dbmate.New(u).GetDriver()
	require.NoError(t, err)

	return drv.(*Driver)
}

func prepTestSQLiteDB(t *testing.T) *sql.DB {
	drv := testSQLiteDriver(t)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// connect database
	db, err := drv.Open()
	require.NoError(t, err)

	return db
}

func TestGetDriver(t *testing.T) {
	db := dbmate.New(dbutil.MustParseURL("sqlite://"))
	drvInterface, err := db.GetDriver()
	require.NoError(t, err)

	// driver should have URL and default migrations table set
	drv, ok := drvInterface.(*Driver)
	require.True(t, ok)
	require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String())
	require.Equal(t, "schema_migrations", drv.migrationsTableName)
}

func TestConnectionString(t *testing.T) {
	t.Run("relative", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:foo/bar.sqlite3?mode=ro")
		require.Equal(t, "foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:./foo/bar.sqlite3?mode=ro")
		require.Equal(t, "./foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with double dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:../foo/bar.sqlite3?mode=ro")
		require.Equal(t, "../foo/bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("absolute", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:/tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("two slashes", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite://tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("four slashes", func(t *testing.T) {
		// interpreted as absolute path
		// supported for backwards compatibility
		u := dbutil.MustParseURL("sqlite:////tmp/foo.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:foo bar.sqlite3?mode=ro")
		require.Equal(t, "foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space and dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:./foo bar.sqlite3?mode=ro")
		require.Equal(t, "./foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("relative with space and double dot", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:../foo bar.sqlite3?mode=ro")
		require.Equal(t, "../foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("absolute with space", func(t *testing.T) {
		u := dbutil.MustParseURL("sqlite:/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("two slashes with space in path", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite://tmp/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes with space in path", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tmp/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("three slashes with space in path (1st dir)", func(t *testing.T) {
		// interpreted as absolute path
		u := dbutil.MustParseURL("sqlite:///tm p/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tm p/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})

	t.Run("four slashes with space", func(t *testing.T) {
		// interpreted as absolute path
		// supported for backwards compatibility
		u := dbutil.MustParseURL("sqlite:////tmp/foo bar.sqlite3?mode=ro")
		require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u))
	})
}

func TestSQLiteCreateDropDatabase(t *testing.T) {
	drv := testSQLiteDriver(t)
	path := ConnectionString(drv.databaseURL)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// check that database exists
	_, err = os.Stat(path)
	require.NoError(t, err)

	// drop the database
	err = drv.DropDatabase()
	require.NoError(t, err)

	// check that database no longer exists
	_, err = os.Stat(path)
	require.NotNil(t, err)
	require.Equal(t, true, os.IsNotExist(err))
}

func TestSQLiteDumpSchema(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	// prepare database
	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)
	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	// insert migration
	err = drv.InsertMigration(db, "abc1")
	require.NoError(t, err)
	err = drv.InsertMigration(db, "abc2")
	require.NoError(t, err)

	// DumpSchema should return schema
	schema, err := drv.DumpSchema(db)
	require.NoError(t, err)
	require.Contains(t, string(schema), "CREATE TABLE IF NOT EXISTS \"test_migrations\"")
	require.Contains(t, string(schema), ");\n-- Dbmate schema migrations\n"+
		"INSERT INTO \"test_migrations\" (version) VALUES\n"+
		"  ('abc1'),\n"+
		"  ('abc2');\n")

	// DumpSchema should return error if command fails
	drv.databaseURL = dbutil.MustParseURL(".")
	schema, err = drv.DumpSchema(db)
	require.Nil(t, schema)
	require.Error(t, err)
	require.EqualError(t, err, "Error: unable to open database \"/.\": "+
		"unable to open database file")
}

func TestSQLiteDatabaseExists(t *testing.T) {
	drv := testSQLiteDriver(t)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// DatabaseExists should return false
	exists, err := drv.DatabaseExists()
	require.NoError(t, err)
	require.Equal(t, false, exists)

	// create database
	err = drv.CreateDatabase()
	require.NoError(t, err)

	// DatabaseExists should return true
	exists, err = drv.DatabaseExists()
	require.NoError(t, err)
	require.Equal(t, true, exists)
}

func TestSQLiteCreateMigrationsTable(t *testing.T) {
	t.Run("default table", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		db := prepTestSQLiteDB(t)
		defer dbutil.MustClose(db)

		// migrations table should not exist
		count := 0
		err := db.QueryRow("select count(*) from schema_migrations").Scan(&count)
		require.Error(t, err)
		require.Regexp(t, "no such table: schema_migrations", err.Error())

		// create table
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)

		// migrations table should exist
		err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
		require.NoError(t, err)

		// create table should be idempotent
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)
	})

	t.Run("custom table", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		drv.migrationsTableName = "test_migrations"

		db := prepTestSQLiteDB(t)
		defer dbutil.MustClose(db)

		// migrations table should not exist
		count := 0
		err := db.QueryRow("select count(*) from test_migrations").Scan(&count)
		require.Error(t, err)
		require.Regexp(t, "no such table: test_migrations", err.Error())

		// create table
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)

		// migrations table should exist
		err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
		require.NoError(t, err)

		// create table should be idempotent
		err = drv.CreateMigrationsTable(db)
		require.NoError(t, err)
	})
}

func TestSQLiteSelectMigrations(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	_, err = db.Exec(`insert into test_migrations (version)
		values ('abc2'), ('abc1'), ('abc3')`)
	require.NoError(t, err)

	migrations, err := drv.SelectMigrations(db, -1)
	require.NoError(t, err)
	require.Equal(t, true, migrations["abc1"])
	require.Equal(t, true, migrations["abc2"])
	require.Equal(t, true, migrations["abc2"])

	// test limit param
	migrations, err = drv.SelectMigrations(db, 1)
	require.NoError(t, err)
	require.Equal(t, true, migrations["abc3"])
	require.Equal(t, false, migrations["abc1"])
	require.Equal(t, false, migrations["abc2"])
}

func TestSQLiteInsertMigration(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	count := 0
	err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 0, count)

	// insert migration
	err = drv.InsertMigration(db, "abc1")
	require.NoError(t, err)

	err = db.QueryRow("select count(*) from test_migrations where version = 'abc1'").
		Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 1, count)
}

func TestSQLiteDeleteMigration(t *testing.T) {
	drv := testSQLiteDriver(t)
	drv.migrationsTableName = "test_migrations"

	db := prepTestSQLiteDB(t)
	defer dbutil.MustClose(db)

	err := drv.CreateMigrationsTable(db)
	require.NoError(t, err)

	_, err = db.Exec(`insert into test_migrations (version)
		values ('abc1'), ('abc2')`)
	require.NoError(t, err)

	err = drv.DeleteMigration(db, "abc2")
	require.NoError(t, err)

	count := 0
	err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
	require.NoError(t, err)
	require.Equal(t, 1, count)
}

func TestSQLitePing(t *testing.T) {
	drv := testSQLiteDriver(t)
	path := ConnectionString(drv.databaseURL)

	// drop any existing database
	err := drv.DropDatabase()
	require.NoError(t, err)

	// ping database
	err = drv.Ping()
	require.NoError(t, err)

	// check that the database was created (sqlite-only behavior)
	_, err = os.Stat(path)
	require.NoError(t, err)

	// drop the database
	err = drv.DropDatabase()
	require.NoError(t, err)

	// create directory where database file is expected
	err = os.Mkdir(path, 0755)
	require.NoError(t, err)
	defer func() {
		err = os.RemoveAll(path)
		require.NoError(t, err)
	}()

	// ping database should fail
	err = drv.Ping()
	require.EqualError(t, err, "unable to open database file: is a directory")
}

func TestSQLiteQuotedMigrationsTableName(t *testing.T) {
	t.Run("default name", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		name := drv.quotedMigrationsTableName()
		require.Equal(t, `"schema_migrations"`, name)
	})

	t.Run("custom name", func(t *testing.T) {
		drv := testSQLiteDriver(t)
		drv.migrationsTableName = "fooMigrations"

		name := drv.quotedMigrationsTableName()
		require.Equal(t, `"fooMigrations"`, name)
	})
}
<|endoftext|>


<commit_before>//  Copyright (c) 2013 Couchbase, Inc.
//  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 auth

import (
	"crypto/sha1"
	"fmt"
	"sync"

	"github.com/couchbase/sync_gateway/base"
	"github.com/pkg/errors"
	"golang.org/x/crypto/bcrypt"
)

var (
	// bcryptDefaultCost is the default bcrypt cost to use
	bcryptDefaultCost = bcrypt.DefaultCost
	// bcryptCost is used when hashing new passwords
	// either via password reset, or rehashPassword
	bcryptCost = bcryptDefaultCost
	// Used to exit-early for unchanged values
	bcryptCostChanged = false
)

var ErrInvalidBcryptCost = fmt.Errorf("invalid bcrypt cost")

// Set of known-to-be-valid {password, bcryt-hash} pairs.
// Keys are of the form SHA1 digest of password + bcrypt'ed hash of password
var cachedHashes = map[string]struct{}{}
var cacheLock sync.Mutex

// The maximum number of pairs to keep in the above cache
const kMaxCacheSize = 25000

// Optimized wrapper around bcrypt.CompareHashAndPassword that caches successful results in
// memory to avoid the _very_ high overhead of calling bcrypt.
func compareHashAndPassword(hash []byte, password []byte) bool {
	// Actually we cache the SHA1 digest of the password to avoid keeping passwords in RAM.
	s := sha1.New()
	s.Write(password)
	digest := string(s.Sum(nil))
	key := digest + string(hash)

	cacheLock.Lock()
	_, valid := cachedHashes[key]
	cacheLock.Unlock()
	if valid {
		return true
	}

	// Cache missed; now we make the very slow (~100ms) bcrypt call:
	if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
		// Note: It's important to only cache successful matches, not failures.
		// Failure is supposed to be slow, to make online attacks impractical.
		return false
	}

	cacheLock.Lock()
	// TODO: Replace this with an LRU cache so the whole map doesn't get wiped
	if len(cachedHashes) >= kMaxCacheSize {
		cachedHashes = map[string]struct{}{}
	}
	cachedHashes[key] = struct{}{}
	cacheLock.Unlock()

	return true
}

// SetBcryptCost will set the bcrypt cost for Sync Gateway to use
// Values of zero or less will use bcryptDefaultCost instead
// An error is returned if the cost is not between bcryptDefaultCost and bcrypt.MaxCost
func SetBcryptCost(cost int) error {
	if cost <= 0 {
		bcryptCost = bcryptDefaultCost
		base.Debugf(base.KeyAuth, "bcrypt cost set to default: %d", cost)
		return nil
	}

	if cost < bcryptDefaultCost || cost > bcrypt.MaxCost {
		return errors.Wrapf(ErrInvalidBcryptCost,
			"%d outside allowed range: %d-%d",
			cost, bcryptDefaultCost, bcrypt.MaxCost)
	}

	base.Infof(base.KeyAuth, "bcrypt cost set to: %d", cost)
	bcryptCost = cost
	bcryptCostChanged = true

	return nil
}
<commit_msg>Change Mutex to RWMutex (#4246)<commit_after>//  Copyright (c) 2013 Couchbase, Inc.
//  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 auth

import (
	"crypto/sha1"
	"fmt"
	"sync"

	"github.com/couchbase/sync_gateway/base"
	"github.com/pkg/errors"
	"golang.org/x/crypto/bcrypt"
)

var (
	// bcryptDefaultCost is the default bcrypt cost to use
	bcryptDefaultCost = bcrypt.DefaultCost
	// bcryptCost is used when hashing new passwords
	// either via password reset, or rehashPassword
	bcryptCost = bcryptDefaultCost
	// Used to exit-early for unchanged values
	bcryptCostChanged = false
)

var ErrInvalidBcryptCost = fmt.Errorf("invalid bcrypt cost")

// Set of known-to-be-valid {password, bcryt-hash} pairs.
// Keys are of the form SHA1 digest of password + bcrypt'ed hash of password
var cachedHashes = map[string]struct{}{}
var cacheLock sync.RWMutex

// The maximum number of pairs to keep in the above cache
const kMaxCacheSize = 25000

// Optimized wrapper around bcrypt.CompareHashAndPassword that caches successful results in
// memory to avoid the _very_ high overhead of calling bcrypt.
func compareHashAndPassword(hash []byte, password []byte) bool {
	// Actually we cache the SHA1 digest of the password to avoid keeping passwords in RAM.
	s := sha1.New()
	s.Write(password)
	digest := string(s.Sum(nil))
	key := digest + string(hash)

	cacheLock.RLock()
	_, valid := cachedHashes[key]
	cacheLock.RUnlock()
	if valid {
		return true
	}

	// Cache missed; now we make the very slow (~100ms) bcrypt call:
	if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
		// Note: It's important to only cache successful matches, not failures.
		// Failure is supposed to be slow, to make online attacks impractical.
		return false
	}

	cacheLock.Lock()
	// TODO: Replace this with an LRU cache so the whole map doesn't get wiped
	if len(cachedHashes) >= kMaxCacheSize {
		cachedHashes = map[string]struct{}{}
	}
	cachedHashes[key] = struct{}{}
	cacheLock.Unlock()

	return true
}

// SetBcryptCost will set the bcrypt cost for Sync Gateway to use
// Values of zero or less will use bcryptDefaultCost instead
// An error is returned if the cost is not between bcryptDefaultCost and bcrypt.MaxCost
func SetBcryptCost(cost int) error {
	if cost <= 0 {
		bcryptCost = bcryptDefaultCost
		base.Debugf(base.KeyAuth, "bcrypt cost set to default: %d", cost)
		return nil
	}

	if cost < bcryptDefaultCost || cost > bcrypt.MaxCost {
		return errors.Wrapf(ErrInvalidBcryptCost,
			"%d outside allowed range: %d-%d",
			cost, bcryptDefaultCost, bcrypt.MaxCost)
	}

	base.Infof(base.KeyAuth, "bcrypt cost set to: %d", cost)
	bcryptCost = cost
	bcryptCostChanged = true

	return nil
}
<|endoftext|>


<commit_before>package stringfy_test

import (
	"testing"

	"github.com/hgsigner/stringfy"
)

func Test_WordWrap(t *testing.T) {

	text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil."

	tests := []struct {
		in           string
		addLineWidth bool
		lineWidth    int
		out          string
	}{
		{
			in:  text,
			out: text,
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    1,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    2,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    3,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    4,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    10,
			out:          "Lorem\nipsum\ndolor sit\namet,\nconsectetur\nadipiscing\nelit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    20,
			out:          "Lorem ipsum dolor\nsit amet,\nconsectetur\nadipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    35,
			out:          "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    47,
			out:          "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    80,
			out:          "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    100,
			out:          "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil.",
		},
	}

	for _, test := range tests {
		wwr := stringfy.NewWordWrap()
		if test.addLineWidth {
			wwr.Options(stringfy.AddLineWidth(test.lineWidth))
		}
		wwrf := wwr.Perform(test.in)
		if wwrf != test.out {
			t.Fatalf("\nExpected: %s\nGot:      %s", test.out, wwrf)
		} else {
			t.Logf("Pass: %s", wwrf)
		}
	}
}
<commit_msg>removes log from word_wrap test<commit_after>package stringfy_test

import (
	"testing"

	"github.com/hgsigner/stringfy"
)

func Test_WordWrap(t *testing.T) {

	text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil."

	tests := []struct {
		in           string
		addLineWidth bool
		lineWidth    int
		out          string
	}{
		{
			in:  text,
			out: text,
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    1,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    2,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    3,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    4,
			out:          "Lorem\nipsum\ndolor\nsit\namet,\nconsectetur\nadipiscing\nelit\nsil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    10,
			out:          "Lorem\nipsum\ndolor sit\namet,\nconsectetur\nadipiscing\nelit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    20,
			out:          "Lorem ipsum dolor\nsit amet,\nconsectetur\nadipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    35,
			out:          "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    47,
			out:          "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    80,
			out:          "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil.",
		},
		{
			in:           text,
			addLineWidth: true,
			lineWidth:    100,
			out:          "Lorem ipsum dolor sit amet, consectetur adipiscing elit sil.",
		},
	}

	for _, test := range tests {
		wwr := stringfy.NewWordWrap()
		if test.addLineWidth {
			wwr.Options(stringfy.AddLineWidth(test.lineWidth))
		}
		wwrf := wwr.Perform(test.in)
		if wwrf != test.out {
			t.Errorf("\nExpected: %s\nGot:      %s", test.out, wwrf)
		}
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io"
	"strings"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/errors"

	"github.com/openshift/origin/pkg/api/latest"
	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/generate/app"
	"github.com/openshift/origin/pkg/template"
	templateapi "github.com/openshift/origin/pkg/template/api"
)

// TransformTemplate processes a template with the provided parameters, returning an error if transformation fails.
func TransformTemplate(tpl *templateapi.Template, client client.TemplateConfigsNamespacer, namespace string, parameters map[string]string) (*templateapi.Template, error) {
	// only set values that match what's expected by the template.
	for k, value := range parameters {
		v := template.GetParameterByName(tpl, k)
		if v == nil {
			return nil, fmt.Errorf("unexpected parameter name %q", k)
		}
		v.Value = value
		v.Generate = ""
	}

	name := localOrRemoteName(tpl.ObjectMeta, namespace)

	// transform the template
	result, err := client.TemplateConfigs(namespace).Create(tpl)
	if err != nil {
		return nil, fmt.Errorf("error processing template %q: %v", name, err)
	}

	// ensure the template objects are decoded
	// TODO: in the future, this should be more automatic
	// TODO: this should use the UniversalDecoder() once we deprecate legacy group. Until
	// then we have to maintain the backward compatibility with old servers so this will
	// force to decode the list as legacy/core API.
	if errs := runtime.DecodeList(result.Objects, kapi.Codecs.LegacyCodec(latest.Version)); len(errs) > 0 {
		err = errors.NewAggregate(errs)
		return nil, fmt.Errorf("error processing template %q: %v", name, err)
	}

	return result, nil
}

func formatString(out io.Writer, tab, s string) {
	labelVals := strings.Split(s, "\n")

	for _, lval := range labelVals {
		fmt.Fprintf(out, fmt.Sprintf("%s%s\n", tab, lval))
	}
}

// DescribeGeneratedTemplate writes a description of the provided template to out.
func DescribeGeneratedTemplate(out io.Writer, input string, result *templateapi.Template, baseNamespace string) {
	qualifiedName := localOrRemoteName(result.ObjectMeta, baseNamespace)
	if len(input) > 0 && result.ObjectMeta.Name != input {
		fmt.Fprintf(out, "--> Deploying template %q for %q to project %s\n", qualifiedName, input, baseNamespace)
	} else {
		fmt.Fprintf(out, "--> Deploying template %q to project %s\n", qualifiedName, baseNamespace)
	}
	fmt.Fprintln(out)

	name := displayName(result.ObjectMeta)
	message := result.Message
	description := result.Annotations["description"]

	// If there is a message or description
	if len(message) > 0 || len(description) > 0 {
		fmt.Fprintf(out, "     %s\n", name)
		fmt.Fprintf(out, "     ---------\n")
		if len(description) > 0 {
			formatString(out, "     ", description)
			fmt.Fprintln(out)
		}
		if len(message) > 0 {
			formatString(out, "     ", message)
			fmt.Fprintln(out)
		}
		fmt.Fprintln(out)
	}

	if warnings := result.Annotations[app.GenerationWarningAnnotation]; len(warnings) > 0 {
		delete(result.Annotations, app.GenerationWarningAnnotation)
		fmt.Fprintln(out)
		lines := strings.Split("warning: "+warnings, "\n")
		for _, line := range lines {
			fmt.Fprintf(out, "    %s\n", line)
		}
		fmt.Fprintln(out)
	}

	if len(result.Parameters) > 0 {
		fmt.Fprintf(out, "     * With parameters:\n")
		for _, p := range result.Parameters {
			name := p.DisplayName
			if len(name) == 0 {
				name = p.Name
			}
			var generated string
			if len(p.Generate) > 0 {
				generated = " # generated"
			}
			fmt.Fprintf(out, "        * %s=%s%s\n", name, p.Value, generated)
		}
		fmt.Fprintln(out)
	}
}
<commit_msg>fix extra lines in new-app output<commit_after>package cmd

import (
	"fmt"
	"io"
	"strings"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/errors"

	"github.com/openshift/origin/pkg/api/latest"
	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/generate/app"
	"github.com/openshift/origin/pkg/template"
	templateapi "github.com/openshift/origin/pkg/template/api"
)

// TransformTemplate processes a template with the provided parameters, returning an error if transformation fails.
func TransformTemplate(tpl *templateapi.Template, client client.TemplateConfigsNamespacer, namespace string, parameters map[string]string) (*templateapi.Template, error) {
	// only set values that match what's expected by the template.
	for k, value := range parameters {
		v := template.GetParameterByName(tpl, k)
		if v == nil {
			return nil, fmt.Errorf("unexpected parameter name %q", k)
		}
		v.Value = value
		v.Generate = ""
	}

	name := localOrRemoteName(tpl.ObjectMeta, namespace)

	// transform the template
	result, err := client.TemplateConfigs(namespace).Create(tpl)
	if err != nil {
		return nil, fmt.Errorf("error processing template %q: %v", name, err)
	}

	// ensure the template objects are decoded
	// TODO: in the future, this should be more automatic
	// TODO: this should use the UniversalDecoder() once we deprecate legacy group. Until
	// then we have to maintain the backward compatibility with old servers so this will
	// force to decode the list as legacy/core API.
	if errs := runtime.DecodeList(result.Objects, kapi.Codecs.LegacyCodec(latest.Version)); len(errs) > 0 {
		err = errors.NewAggregate(errs)
		return nil, fmt.Errorf("error processing template %q: %v", name, err)
	}

	return result, nil
}

func formatString(out io.Writer, tab, s string) {
	labelVals := strings.Split(strings.TrimSuffix(s, "\n"), "\n")

	for _, lval := range labelVals {
		fmt.Fprintf(out, fmt.Sprintf("%s%s\n", tab, lval))
	}
}

// DescribeGeneratedTemplate writes a description of the provided template to out.
func DescribeGeneratedTemplate(out io.Writer, input string, result *templateapi.Template, baseNamespace string) {
	qualifiedName := localOrRemoteName(result.ObjectMeta, baseNamespace)
	if len(input) > 0 && result.ObjectMeta.Name != input {
		fmt.Fprintf(out, "--> Deploying template %q for %q to project %s\n", qualifiedName, input, baseNamespace)
	} else {
		fmt.Fprintf(out, "--> Deploying template %q to project %s\n", qualifiedName, baseNamespace)
	}
	fmt.Fprintln(out)

	name := displayName(result.ObjectMeta)
	message := result.Message
	description := result.Annotations["description"]

	// If there is a message or description
	if len(message) > 0 || len(description) > 0 {
		fmt.Fprintf(out, "     %s\n", name)
		fmt.Fprintf(out, "     ---------\n")
		if len(description) > 0 {
			formatString(out, "     ", description)
			fmt.Fprintln(out)
		}
		if len(message) > 0 {
			formatString(out, "     ", message)
			fmt.Fprintln(out)
		}
	}

	if warnings := result.Annotations[app.GenerationWarningAnnotation]; len(warnings) > 0 {
		delete(result.Annotations, app.GenerationWarningAnnotation)
		fmt.Fprintln(out)
		lines := strings.Split("warning: "+warnings, "\n")
		for _, line := range lines {
			fmt.Fprintf(out, "    %s\n", line)
		}
		fmt.Fprintln(out)
	}

	if len(result.Parameters) > 0 {
		fmt.Fprintf(out, "     * With parameters:\n")
		for _, p := range result.Parameters {
			name := p.DisplayName
			if len(name) == 0 {
				name = p.Name
			}
			var generated string
			if len(p.Generate) > 0 {
				generated = " # generated"
			}
			fmt.Fprintf(out, "        * %s=%s%s\n", name, p.Value, generated)
		}
		fmt.Fprintln(out)
	}
}
<|endoftext|>


<commit_before>package worker

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"time"

	"github.com/iron-io/iron_go/api"
)

type Schedule struct {
	CodeName       string         `json:"code_name"`
	Delay          *time.Duration `json:"delay"`
	EndAt          *time.Time     `json:"end_at"`
	MaxConcurrency *int           `json:"max_concurrency"`
	Name           string         `json:"name"`
	Payload        string         `json:"payload"`
	Priority       *int           `json:"priority"`
	RunEvery       *int           `json:"run_every"`
	RunTimes       *int           `json:"run_times"`
	StartAt        *time.Time     `json:"start_at"`
	Cluster        string         `json:"cluster"`
	Label          string         `json:"label"`
}

type ScheduleInfo struct {
	CodeName       string    `json:"code_name"`
	CreatedAt      time.Time `json:"created_at"`
	EndAt          time.Time `json:"end_at"`
	Id             string    `json:"id"`
	LastRunTime    time.Time `json:"last_run_time"`
	MaxConcurrency int       `json:"max_concurrency"`
	Msg            string    `json:"msg"`
	NextStart      time.Time `json:"next_start"`
	ProjectId      string    `json:"project_id"`
	RunCount       int       `json:"run_count"`
	RunTimes       int       `json:"run_times"`
	StartAt        time.Time `json:"start_at"`
	Status         string    `json:"status"`
	UpdatedAt      time.Time `json:"updated_at"`
}

type Task struct {
	CodeName string         `json:"code_name"`
	Payload  string         `json:"payload"`
	Priority int            `json:"priority"`
	Timeout  *time.Duration `json:"timeout"`
	Delay    *time.Duration `json:"delay"`
	Cluster  string         `json:"cluster"`
	Label    string         `json:"label"`
}

type TaskInfo struct {
	CodeHistoryId string    `json:"code_history_id"`
	CodeId        string    `json:"code_id"`
	CodeName      string    `json:"code_name"`
	CodeRev       string    `json:"code_rev"`
	Id            string    `json:"id"`
	Payload       string    `json:"payload"`
	ProjectId     string    `json:"project_id"`
	Status        string    `json:"status"`
	Msg           string    `json:"msg,omitempty"`
	Duration      int       `json:"duration"`
	RunTimes      int       `json:"run_times"`
	Timeout       int       `json:"timeout"`
	Percent       int       `json:"percent,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
	StartTime     time.Time `json:"start_time"`
	EndTime       time.Time `json:"end_time"`
}

type CodeSource map[string][]byte // map[pathInZip]code

type Code struct {
	Name           string        `json:"name"`
	Runtime        string        `json:"runtime"`
	FileName       string        `json:"file_name"`
	Config         string        `json:"config,omitempty"`
	MaxConcurrency int           `json:"max_concurrency,omitempty"`
	Retries        int           `json:"retries,omitempty"`
	Stack          string        `json:"stack"`
	RetriesDelay   time.Duration `json:"-"`
	Source         CodeSource    `json:"-"`
}

type CodeInfo struct {
	Id              string    `json:"id"`
	LatestChecksum  string    `json:"latest_checksum"`
	LatestHistoryId string    `json:"latest_history_id"`
	Name            string    `json:"name"`
	ProjectId       string    `json:"project_id"`
	Runtime         string    `json:"runtime"`
	Rev             int       `json:"rev"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
	LatestChange    time.Time `json:"latest_change"`
}

// CodePackageList lists code packages.
//
// The page argument decides the page of code packages you want to retrieve, starting from 0, maximum is 100.
//
// The perPage argument determines the number of code packages to return. Note
// this is a maximum value, so there may be fewer packages returned if there
// aren’t enough results. If this is < 1, 1 will be the default. Maximum is 100.
func (w *Worker) CodePackageList(page, perPage int) (codes []CodeInfo, err error) {
	out := map[string][]CodeInfo{}

	err = w.codes().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	return out["codes"], nil
}

// CodePackageUpload uploads a code package
func (w *Worker) CodePackageUpload(code Code) (id string, err error) {
	client := http.Client{}

	body := &bytes.Buffer{}
	mWriter := multipart.NewWriter(body)

	// write meta-data
	mMetaWriter, err := mWriter.CreateFormField("data")
	if err != nil {
		return
	}
	jEncoder := json.NewEncoder(mMetaWriter)
	err = jEncoder.Encode(map[string]interface{}{
		"name":            code.Name,
		"runtime":         code.Runtime,
		"file_name":       code.FileName,
		"config":          code.Config,
		"max_concurrency": code.MaxConcurrency,
		"retries":         code.Retries,
		"retries_delay":   code.RetriesDelay.Seconds(),
	})
	if err != nil {
		return
	}

	// write the zip
	mFileWriter, err := mWriter.CreateFormFile("file", "worker.zip")
	if err != nil {
		return
	}
	zWriter := zip.NewWriter(mFileWriter)

	for sourcePath, sourceText := range code.Source {
		fWriter, err := zWriter.Create(sourcePath)
		if err != nil {
			return "", err
		}
		fWriter.Write([]byte(sourceText))
	}

	zWriter.Close()

	// done with multipart
	mWriter.Close()

	req, err := http.NewRequest("POST", w.codes().URL.String(), body)
	if err != nil {
		return
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("Accept-Encoding", "gzip/deflate")
	req.Header.Set("Authorization", "OAuth "+w.Settings.Token)
	req.Header.Set("Content-Type", mWriter.FormDataContentType())
	req.Header.Set("User-Agent", w.Settings.UserAgent)

	// dumpRequest(req) NOTE: never do this here, it breaks stuff
	response, err := client.Do(req)
	if err != nil {
		return
	}
	if err = api.ResponseAsError(response); err != nil {
		return
	}

	// dumpResponse(response)

	data := struct {
		Id         string `json:"id"`
		Msg        string `json:"msg"`
		StatusCode int    `json:"status_code"`
	}{}
	err = json.NewDecoder(response.Body).Decode(&data)
	if err != nil {
		return
	}

	return data.Id, err
}

// CodePackageInfo gets info about a code package
func (w *Worker) CodePackageInfo(codeId string) (code CodeInfo, err error) {
	out := CodeInfo{}
	err = w.codes(codeId).Req("GET", nil, &out)
	return out, err
}

// CodePackageDelete deletes a code package
func (w *Worker) CodePackageDelete(codeId string) (err error) {
	return w.codes(codeId).Req("DELETE", nil, nil)
}

// CodePackageDownload downloads a code package
func (w *Worker) CodePackageDownload(codeId string) (code Code, err error) {
	out := Code{}
	err = w.codes(codeId, "download").Req("GET", nil, &out)
	return out, err
}

// CodePackageRevisions lists the revisions of a code pacakge
func (w *Worker) CodePackageRevisions(codeId string) (code Code, err error) {
	out := Code{}
	err = w.codes(codeId, "revisions").Req("GET", nil, &out)
	return out, err
}

func (w *Worker) TaskList() (tasks []TaskInfo, err error) {
	out := map[string][]TaskInfo{}
	err = w.tasks().Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["tasks"], nil
}

type TaskListParams struct {
	CodeName string
	Page     int
	PerPage  int
	FromTime time.Time
	ToTime   time.Time
	Statuses []string
}

func (w *Worker) FilteredTaskList(params TaskListParams) (tasks []TaskInfo, err error) {
	out := map[string][]TaskInfo{}
	url := w.tasks()

	url.QueryAdd("code_name", "%s", params.CodeName)

	if params.Page > 0 {
		url.QueryAdd("page", "%d", params.Page)
	}

	if params.PerPage > 0 {
		url.QueryAdd("per_page", "%d", params.PerPage)
	}

	if fromTimeSeconds := params.FromTime.Unix(); fromTimeSeconds > 0 {
		url.QueryAdd("from_time", "%d", fromTimeSeconds)
	}

	if toTimeSeconds := params.ToTime.Unix(); toTimeSeconds > 0 {
		url.QueryAdd("to_time", "%d", toTimeSeconds)
	}

	for _, status := range params.Statuses {
		url.QueryAdd(status, "%d", true)
	}

	err = url.Req("GET", nil, &out)

	if err != nil {
		return
	}

	return out["tasks"], nil
}

// TaskQueue queues a task
func (w *Worker) TaskQueue(tasks ...Task) (taskIds []string, err error) {
	outTasks := make([]map[string]interface{}, 0, len(tasks))

	for _, task := range tasks {
		thisTask := map[string]interface{}{
			"code_name": task.CodeName,
			"payload":   task.Payload,
			"priority":  task.Priority,
			"cluster":   task.Cluster,
			"label":     task.Label,
		}
		if task.Timeout != nil {
			thisTask["timeout"] = (*task.Timeout).Seconds()
		}
		if task.Delay != nil {
			thisTask["delay"] = int64((*task.Delay).Seconds())
		}

		outTasks = append(outTasks, thisTask)
	}

	in := map[string][]map[string]interface{}{"tasks": outTasks}
	out := struct {
		Tasks []struct {
			Id string `json:"id"`
		} `json:"tasks"`
		Msg string `json:"msg"`
	}{}

	err = w.tasks().Req("POST", &in, &out)
	if err != nil {
		return
	}

	taskIds = make([]string, 0, len(out.Tasks))
	for _, task := range out.Tasks {
		taskIds = append(taskIds, task.Id)
	}

	return
}

// TaskInfo gives info about a given task
func (w *Worker) TaskInfo(taskId string) (task TaskInfo, err error) {
	out := TaskInfo{}
	err = w.tasks(taskId).Req("GET", nil, &out)
	return out, err
}

func (w *Worker) TaskLog(taskId string) (log []byte, err error) {
	response, err := w.tasks(taskId, "log").Request("GET", nil)
	if err != nil {
		return
	}

	log, err = ioutil.ReadAll(response.Body)
	return
}

// TaskCancel cancels a Task
func (w *Worker) TaskCancel(taskId string) (err error) {
	_, err = w.tasks(taskId, "cancel").Request("POST", nil)
	return err
}

// TaskProgress sets a Task's Progress
func (w *Worker) TaskProgress(taskId string, progress int, msg string) (err error) {
	payload := map[string]interface{}{
		"msg":     msg,
		"percent": progress,
	}

	err = w.tasks(taskId, "progress").Req("POST", payload, nil)
	return
}

// TaskQueueWebhook queues a Task from a Webhook
func (w *Worker) TaskQueueWebhook() (err error) { return }

// ScheduleList lists Scheduled Tasks
func (w *Worker) ScheduleList() (schedules []ScheduleInfo, err error) {
	out := map[string][]ScheduleInfo{}
	err = w.schedules().Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["schedules"], nil
}

// Schedule a Task
func (w *Worker) Schedule(schedules ...Schedule) (scheduleIds []string, err error) {
	outSchedules := make([]map[string]interface{}, 0, len(schedules))

	for _, schedule := range schedules {
		sm := map[string]interface{}{
			"code_name": schedule.CodeName,
			"name":      schedule.Name,
			"payload":   schedule.Payload,
			"label":     schedule.Label,
			"cluster":   schedule.Cluster,
		}
		if schedule.Delay != nil {
			sm["delay"] = (*schedule.Delay).Seconds()
		}
		if schedule.EndAt != nil {
			sm["end_at"] = *schedule.EndAt
		}
		if schedule.MaxConcurrency != nil {
			sm["max_concurrency"] = *schedule.MaxConcurrency
		}
		if schedule.Priority != nil {
			sm["priority"] = *schedule.Priority
		}
		if schedule.RunEvery != nil {
			sm["run_every"] = *schedule.RunEvery
		}
		if schedule.RunTimes != nil {
			sm["run_times"] = *schedule.RunTimes
		}
		if schedule.StartAt != nil {
			sm["start_at"] = *schedule.StartAt
		}
		outSchedules = append(outSchedules, sm)
	}

	in := map[string][]map[string]interface{}{"schedules": outSchedules}
	out := struct {
		Schedules []struct {
			Id string `json:"id"`
		} `json:"schedules"`
		Msg string `json:"msg"`
	}{}

	err = w.schedules().Req("POST", &in, &out)
	if err != nil {
		return
	}

	scheduleIds = make([]string, 0, len(out.Schedules))

	for _, schedule := range out.Schedules {
		scheduleIds = append(scheduleIds, schedule.Id)
	}

	return
}

// ScheduleInfo gets info about a scheduled task
func (w *Worker) ScheduleInfo(scheduleId string) (info ScheduleInfo, err error) {
	info = ScheduleInfo{}
	err = w.schedules(scheduleId).Req("GET", nil, &info)
	return info, nil
}

// ScheduleCancel cancels a scheduled task
func (w *Worker) ScheduleCancel(scheduleId string) (err error) {
	_, err = w.schedules(scheduleId, "cancel").Request("POST", nil)
	return
}
<commit_msg>add ScheduleId (schedule_id) to TaskInfo<commit_after>package worker

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"time"

	"github.com/iron-io/iron_go/api"
)

type Schedule struct {
	CodeName       string         `json:"code_name"`
	Delay          *time.Duration `json:"delay"`
	EndAt          *time.Time     `json:"end_at"`
	MaxConcurrency *int           `json:"max_concurrency"`
	Name           string         `json:"name"`
	Payload        string         `json:"payload"`
	Priority       *int           `json:"priority"`
	RunEvery       *int           `json:"run_every"`
	RunTimes       *int           `json:"run_times"`
	StartAt        *time.Time     `json:"start_at"`
	Cluster        string         `json:"cluster"`
	Label          string         `json:"label"`
}

type ScheduleInfo struct {
	CodeName       string    `json:"code_name"`
	CreatedAt      time.Time `json:"created_at"`
	EndAt          time.Time `json:"end_at"`
	Id             string    `json:"id"`
	LastRunTime    time.Time `json:"last_run_time"`
	MaxConcurrency int       `json:"max_concurrency"`
	Msg            string    `json:"msg"`
	NextStart      time.Time `json:"next_start"`
	ProjectId      string    `json:"project_id"`
	RunCount       int       `json:"run_count"`
	RunTimes       int       `json:"run_times"`
	StartAt        time.Time `json:"start_at"`
	Status         string    `json:"status"`
	UpdatedAt      time.Time `json:"updated_at"`
}

type Task struct {
	CodeName string         `json:"code_name"`
	Payload  string         `json:"payload"`
	Priority int            `json:"priority"`
	Timeout  *time.Duration `json:"timeout"`
	Delay    *time.Duration `json:"delay"`
	Cluster  string         `json:"cluster"`
	Label    string         `json:"label"`
}

type TaskInfo struct {
	CodeHistoryId string    `json:"code_history_id"`
	CodeId        string    `json:"code_id"`
	CodeName      string    `json:"code_name"`
	CodeRev       string    `json:"code_rev"`
	Id            string    `json:"id"`
	Payload       string    `json:"payload"`
	ProjectId     string    `json:"project_id"`
	Status        string    `json:"status"`
	Msg           string    `json:"msg,omitempty"`
	ScheduleId    string    `json:"schedule_id"`
	Duration      int       `json:"duration"`
	RunTimes      int       `json:"run_times"`
	Timeout       int       `json:"timeout"`
	Percent       int       `json:"percent,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
	StartTime     time.Time `json:"start_time"`
	EndTime       time.Time `json:"end_time"`
}

type CodeSource map[string][]byte // map[pathInZip]code

type Code struct {
	Name           string        `json:"name"`
	Runtime        string        `json:"runtime"`
	FileName       string        `json:"file_name"`
	Config         string        `json:"config,omitempty"`
	MaxConcurrency int           `json:"max_concurrency,omitempty"`
	Retries        int           `json:"retries,omitempty"`
	Stack          string        `json:"stack"`
	RetriesDelay   time.Duration `json:"-"`
	Source         CodeSource    `json:"-"`
}

type CodeInfo struct {
	Id              string    `json:"id"`
	LatestChecksum  string    `json:"latest_checksum"`
	LatestHistoryId string    `json:"latest_history_id"`
	Name            string    `json:"name"`
	ProjectId       string    `json:"project_id"`
	Runtime         string    `json:"runtime"`
	Rev             int       `json:"rev"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
	LatestChange    time.Time `json:"latest_change"`
}

// CodePackageList lists code packages.
//
// The page argument decides the page of code packages you want to retrieve, starting from 0, maximum is 100.
//
// The perPage argument determines the number of code packages to return. Note
// this is a maximum value, so there may be fewer packages returned if there
// aren’t enough results. If this is < 1, 1 will be the default. Maximum is 100.
func (w *Worker) CodePackageList(page, perPage int) (codes []CodeInfo, err error) {
	out := map[string][]CodeInfo{}

	err = w.codes().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	return out["codes"], nil
}

// CodePackageUpload uploads a code package
func (w *Worker) CodePackageUpload(code Code) (id string, err error) {
	client := http.Client{}

	body := &bytes.Buffer{}
	mWriter := multipart.NewWriter(body)

	// write meta-data
	mMetaWriter, err := mWriter.CreateFormField("data")
	if err != nil {
		return
	}
	jEncoder := json.NewEncoder(mMetaWriter)
	err = jEncoder.Encode(map[string]interface{}{
		"name":            code.Name,
		"runtime":         code.Runtime,
		"file_name":       code.FileName,
		"config":          code.Config,
		"max_concurrency": code.MaxConcurrency,
		"retries":         code.Retries,
		"retries_delay":   code.RetriesDelay.Seconds(),
	})
	if err != nil {
		return
	}

	// write the zip
	mFileWriter, err := mWriter.CreateFormFile("file", "worker.zip")
	if err != nil {
		return
	}
	zWriter := zip.NewWriter(mFileWriter)

	for sourcePath, sourceText := range code.Source {
		fWriter, err := zWriter.Create(sourcePath)
		if err != nil {
			return "", err
		}
		fWriter.Write([]byte(sourceText))
	}

	zWriter.Close()

	// done with multipart
	mWriter.Close()

	req, err := http.NewRequest("POST", w.codes().URL.String(), body)
	if err != nil {
		return
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("Accept-Encoding", "gzip/deflate")
	req.Header.Set("Authorization", "OAuth "+w.Settings.Token)
	req.Header.Set("Content-Type", mWriter.FormDataContentType())
	req.Header.Set("User-Agent", w.Settings.UserAgent)

	// dumpRequest(req) NOTE: never do this here, it breaks stuff
	response, err := client.Do(req)
	if err != nil {
		return
	}
	if err = api.ResponseAsError(response); err != nil {
		return
	}

	// dumpResponse(response)

	data := struct {
		Id         string `json:"id"`
		Msg        string `json:"msg"`
		StatusCode int    `json:"status_code"`
	}{}
	err = json.NewDecoder(response.Body).Decode(&data)
	if err != nil {
		return
	}

	return data.Id, err
}

// CodePackageInfo gets info about a code package
func (w *Worker) CodePackageInfo(codeId string) (code CodeInfo, err error) {
	out := CodeInfo{}
	err = w.codes(codeId).Req("GET", nil, &out)
	return out, err
}

// CodePackageDelete deletes a code package
func (w *Worker) CodePackageDelete(codeId string) (err error) {
	return w.codes(codeId).Req("DELETE", nil, nil)
}

// CodePackageDownload downloads a code package
func (w *Worker) CodePackageDownload(codeId string) (code Code, err error) {
	out := Code{}
	err = w.codes(codeId, "download").Req("GET", nil, &out)
	return out, err
}

// CodePackageRevisions lists the revisions of a code pacakge
func (w *Worker) CodePackageRevisions(codeId string) (code Code, err error) {
	out := Code{}
	err = w.codes(codeId, "revisions").Req("GET", nil, &out)
	return out, err
}

func (w *Worker) TaskList() (tasks []TaskInfo, err error) {
	out := map[string][]TaskInfo{}
	err = w.tasks().Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["tasks"], nil
}

type TaskListParams struct {
	CodeName string
	Page     int
	PerPage  int
	FromTime time.Time
	ToTime   time.Time
	Statuses []string
}

func (w *Worker) FilteredTaskList(params TaskListParams) (tasks []TaskInfo, err error) {
	out := map[string][]TaskInfo{}
	url := w.tasks()

	url.QueryAdd("code_name", "%s", params.CodeName)

	if params.Page > 0 {
		url.QueryAdd("page", "%d", params.Page)
	}

	if params.PerPage > 0 {
		url.QueryAdd("per_page", "%d", params.PerPage)
	}

	if fromTimeSeconds := params.FromTime.Unix(); fromTimeSeconds > 0 {
		url.QueryAdd("from_time", "%d", fromTimeSeconds)
	}

	if toTimeSeconds := params.ToTime.Unix(); toTimeSeconds > 0 {
		url.QueryAdd("to_time", "%d", toTimeSeconds)
	}

	for _, status := range params.Statuses {
		url.QueryAdd(status, "%d", true)
	}

	err = url.Req("GET", nil, &out)

	if err != nil {
		return
	}

	return out["tasks"], nil
}

// TaskQueue queues a task
func (w *Worker) TaskQueue(tasks ...Task) (taskIds []string, err error) {
	outTasks := make([]map[string]interface{}, 0, len(tasks))

	for _, task := range tasks {
		thisTask := map[string]interface{}{
			"code_name": task.CodeName,
			"payload":   task.Payload,
			"priority":  task.Priority,
			"cluster":   task.Cluster,
			"label":     task.Label,
		}
		if task.Timeout != nil {
			thisTask["timeout"] = (*task.Timeout).Seconds()
		}
		if task.Delay != nil {
			thisTask["delay"] = int64((*task.Delay).Seconds())
		}

		outTasks = append(outTasks, thisTask)
	}

	in := map[string][]map[string]interface{}{"tasks": outTasks}
	out := struct {
		Tasks []struct {
			Id string `json:"id"`
		} `json:"tasks"`
		Msg string `json:"msg"`
	}{}

	err = w.tasks().Req("POST", &in, &out)
	if err != nil {
		return
	}

	taskIds = make([]string, 0, len(out.Tasks))
	for _, task := range out.Tasks {
		taskIds = append(taskIds, task.Id)
	}

	return
}

// TaskInfo gives info about a given task
func (w *Worker) TaskInfo(taskId string) (task TaskInfo, err error) {
	out := TaskInfo{}
	err = w.tasks(taskId).Req("GET", nil, &out)
	return out, err
}

func (w *Worker) TaskLog(taskId string) (log []byte, err error) {
	response, err := w.tasks(taskId, "log").Request("GET", nil)
	if err != nil {
		return
	}

	log, err = ioutil.ReadAll(response.Body)
	return
}

// TaskCancel cancels a Task
func (w *Worker) TaskCancel(taskId string) (err error) {
	_, err = w.tasks(taskId, "cancel").Request("POST", nil)
	return err
}

// TaskProgress sets a Task's Progress
func (w *Worker) TaskProgress(taskId string, progress int, msg string) (err error) {
	payload := map[string]interface{}{
		"msg":     msg,
		"percent": progress,
	}

	err = w.tasks(taskId, "progress").Req("POST", payload, nil)
	return
}

// TaskQueueWebhook queues a Task from a Webhook
func (w *Worker) TaskQueueWebhook() (err error) { return }

// ScheduleList lists Scheduled Tasks
func (w *Worker) ScheduleList() (schedules []ScheduleInfo, err error) {
	out := map[string][]ScheduleInfo{}
	err = w.schedules().Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["schedules"], nil
}

// Schedule a Task
func (w *Worker) Schedule(schedules ...Schedule) (scheduleIds []string, err error) {
	outSchedules := make([]map[string]interface{}, 0, len(schedules))

	for _, schedule := range schedules {
		sm := map[string]interface{}{
			"code_name": schedule.CodeName,
			"name":      schedule.Name,
			"payload":   schedule.Payload,
			"label":     schedule.Label,
			"cluster":   schedule.Cluster,
		}
		if schedule.Delay != nil {
			sm["delay"] = (*schedule.Delay).Seconds()
		}
		if schedule.EndAt != nil {
			sm["end_at"] = *schedule.EndAt
		}
		if schedule.MaxConcurrency != nil {
			sm["max_concurrency"] = *schedule.MaxConcurrency
		}
		if schedule.Priority != nil {
			sm["priority"] = *schedule.Priority
		}
		if schedule.RunEvery != nil {
			sm["run_every"] = *schedule.RunEvery
		}
		if schedule.RunTimes != nil {
			sm["run_times"] = *schedule.RunTimes
		}
		if schedule.StartAt != nil {
			sm["start_at"] = *schedule.StartAt
		}
		outSchedules = append(outSchedules, sm)
	}

	in := map[string][]map[string]interface{}{"schedules": outSchedules}
	out := struct {
		Schedules []struct {
			Id string `json:"id"`
		} `json:"schedules"`
		Msg string `json:"msg"`
	}{}

	err = w.schedules().Req("POST", &in, &out)
	if err != nil {
		return
	}

	scheduleIds = make([]string, 0, len(out.Schedules))

	for _, schedule := range out.Schedules {
		scheduleIds = append(scheduleIds, schedule.Id)
	}

	return
}

// ScheduleInfo gets info about a scheduled task
func (w *Worker) ScheduleInfo(scheduleId string) (info ScheduleInfo, err error) {
	info = ScheduleInfo{}
	err = w.schedules(scheduleId).Req("GET", nil, &info)
	return info, nil
}

// ScheduleCancel cancels a scheduled task
func (w *Worker) ScheduleCancel(scheduleId string) (err error) {
	_, err = w.schedules(scheduleId, "cancel").Request("POST", nil)
	return
}
<|endoftext|>


<commit_before>package cloudflare

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

var (
	ErrMissingHostname    = errors.New("required hostname missing")
	ErrMissingService     = errors.New("required service missing")
	ErrMissingEnvironment = errors.New("required environment missing")
)

type WorkersDomain struct {
	ID          string `json:"id,omitempty"`
	ZoneID      string `json:"zone_id,omitempty"`
	ZoneName    string `json:"zone_name,omitempty"`
	Hostname    string `json:"hostname,omitempty"`
	Service     string `json:"service,omitempty"`
	Environment string `json:"environment,omitempty"`
}

type WorkersDomainResponse struct {
	Response
	Result WorkersDomain `json:"result"`
}

type WorkersDomainListParams struct {
	ZoneID      string `url:"zone_id,omitempty"`
	ZoneName    string `url:"zone_name,omitempty"`
	Hostname    string `url:"hostname,omitempty"`
	Service     string `url:"service,omitempty"`
	Environment string `url:"environment,omitempty"`
}

type WorkersDomainListResponse struct {
	Response
	Result []WorkersDomain `json:"result"`
}

// WorkersListDomains lists all Worker Domains.
//
// API reference: https://developers.cloudflare.com/api/operations/worker-domain-list-domains
func (api *API) WorkersListDomains(ctx context.Context, rc *ResourceContainer, params WorkersDomainListParams) ([]WorkersDomain, error) {
	if rc.Identifier == "" {
		return []WorkersDomain{}, ErrMissingAccountID
	}
	uri := buildURI(fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier), params)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainListResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return []WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// WorkersAttachDomain attaches a worker to a zone and hostname.
//
// API reference: https://developers.cloudflare.com/api/operations/worker-domain-attach-to-domain
func (api *API) WorkersAttachDomain(ctx context.Context, rc *ResourceContainer, domain WorkersDomain) (WorkersDomain, error) {
	if rc.Identifier == "" {
		return WorkersDomain{}, ErrMissingAccountID
	}
	if domain.ZoneID == "" {
		return WorkersDomain{}, ErrMissingZoneID
	}
	if domain.Hostname == "" {
		return WorkersDomain{}, ErrMissingHostname
	}
	if domain.Service == "" {
		return WorkersDomain{}, ErrMissingService
	}
	if domain.Environment == "" {
		return WorkersDomain{}, ErrMissingEnvironment
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier)
	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, domain)
	if err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// WorkersDetachDomain detaches a worker from a zone and hostname.
//
// API reference: https://developers.cloudflare.com/api/operations/worker-domain-detach-from-domain
func (api *API) WorkersDetachDomain(ctx context.Context, rc *ResourceContainer, domainID string) error {
	if rc.Identifier == "" {
		return ErrMissingAccountID
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	return nil
}

// WorkersGetDomain gets a Worker Domain.
//
// API reference: https://developers.cloudflare.com/api/operations/worker-domain-get-a-domain
func (api *API) WorkersGetDomain(ctx context.Context, rc *ResourceContainer, domainID string) (WorkersDomain, error) {
	if rc.Identifier == "" {
		return WorkersDomain{}, ErrMissingAccountID
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}
<commit_msg>Switch to old API docs<commit_after>package cloudflare

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

var (
	ErrMissingHostname    = errors.New("required hostname missing")
	ErrMissingService     = errors.New("required service missing")
	ErrMissingEnvironment = errors.New("required environment missing")
)

type WorkersDomain struct {
	ID          string `json:"id,omitempty"`
	ZoneID      string `json:"zone_id,omitempty"`
	ZoneName    string `json:"zone_name,omitempty"`
	Hostname    string `json:"hostname,omitempty"`
	Service     string `json:"service,omitempty"`
	Environment string `json:"environment,omitempty"`
}

type WorkersDomainResponse struct {
	Response
	Result WorkersDomain `json:"result"`
}

type WorkersDomainListParams struct {
	ZoneID      string `url:"zone_id,omitempty"`
	ZoneName    string `url:"zone_name,omitempty"`
	Hostname    string `url:"hostname,omitempty"`
	Service     string `url:"service,omitempty"`
	Environment string `url:"environment,omitempty"`
}

type WorkersDomainListResponse struct {
	Response
	Result []WorkersDomain `json:"result"`
}

// WorkersListDomains lists all Worker Domains.
//
// API reference: https://api.cloudflare.com/#worker-domain-list-domains
func (api *API) WorkersListDomains(ctx context.Context, rc *ResourceContainer, params WorkersDomainListParams) ([]WorkersDomain, error) {
	if rc.Identifier == "" {
		return []WorkersDomain{}, ErrMissingAccountID
	}
	uri := buildURI(fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier), params)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainListResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return []WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// WorkersAttachDomain attaches a worker to a zone and hostname.
//
// API reference: https://api.cloudflare.com/#worker-domain-attach-to-domain
func (api *API) WorkersAttachDomain(ctx context.Context, rc *ResourceContainer, domain WorkersDomain) (WorkersDomain, error) {
	if rc.Identifier == "" {
		return WorkersDomain{}, ErrMissingAccountID
	}
	if domain.ZoneID == "" {
		return WorkersDomain{}, ErrMissingZoneID
	}
	if domain.Hostname == "" {
		return WorkersDomain{}, ErrMissingHostname
	}
	if domain.Service == "" {
		return WorkersDomain{}, ErrMissingService
	}
	if domain.Environment == "" {
		return WorkersDomain{}, ErrMissingEnvironment
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier)
	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, domain)
	if err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// WorkersGetDomain gets a Worker Domain.
//
// API reference: https://api.cloudflare.com/#worker-domain-get-a-domain
func (api *API) WorkersGetDomain(ctx context.Context, rc *ResourceContainer, domainID string) (WorkersDomain, error) {
	if rc.Identifier == "" {
		return WorkersDomain{}, ErrMissingAccountID
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	var r WorkersDomainResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return r.Result, nil
}

// WorkersDetachDomain detaches a worker from a zone and hostname.
//
// API reference: https://api.cloudflare.com/#worker-domain-detach-from-domain
func (api *API) WorkersDetachDomain(ctx context.Context, rc *ResourceContainer, domainID string) error {
	if rc.Identifier == "" {
		return ErrMissingAccountID
	}
	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return fmt.Errorf("%s: %w", errMakeRequestError, err)
	}
	return nil
}
<|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 cluster

import (
	"bytes"
	gflag "flag"
	"fmt"
	"net"
	"strings"
	"text/template"

	"k8s.io/minikube/pkg/minikube/constants"
)

// Kill any running instances.
var localkubeStartCmdTemplate = "/usr/local/bin/localkube {{.Flags}} --generate-certs=false --logtostderr=true --enable-dns=false"

var localkubeSystemdTmpl = `[Unit]
Description=Localkube
Documentation=https://github.com/kubernetes/minikube/tree/master/pkg/localkube

[Service]
Type=notify
Restart=always
RestartSec=3

ExecStart={{.LocalkubeStartCmd}}

ExecReload=/bin/kill -s HUP $MAINPID

[Install]
WantedBy=multi-user.target
`

var startCommandTemplate = `
  {{.StartCommandSystemd}}
  sudo systemctl daemon-reload
  sudo systemctl enable localkube.service
  sudo systemctl restart localkube.service || true
`

func GetStartCommand(kubernetesConfig KubernetesConfig) (string, error) {
	localkubeStartCommand, err := GenLocalkubeStartCmd(kubernetesConfig)
	if err != nil {
		return "", err
	}
	startCommandSystemd, err := GetStartCommandSystemd(kubernetesConfig, localkubeStartCommand)
	if err != nil {
		return "", err
	}
	t := template.Must(template.New("startCommand").Parse(startCommandTemplate))
	buf := bytes.Buffer{}
	data := struct {
		StartCommandSystemd string
	}{
		StartCommandSystemd: startCommandSystemd,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

func GetStartCommandSystemd(kubernetesConfig KubernetesConfig, localkubeStartCmd string) (string, error) {
	t, err := template.New("localkubeConfig").Parse(localkubeSystemdTmpl)
	if err != nil {
		return "", err
	}
	buf := bytes.Buffer{}
	data := struct {
		LocalkubeStartCmd string
	}{
		LocalkubeStartCmd: localkubeStartCmd,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return fmt.Sprintf("printf %%s \"%s\" | sudo tee %s", buf.String(),
		constants.LocalkubeServicePath), nil
}

func GenLocalkubeStartCmd(kubernetesConfig KubernetesConfig) (string, error) {
	flagVals := make([]string, len(constants.LogFlags))
	for _, logFlag := range constants.LogFlags {
		if logVal := gflag.Lookup(logFlag); logVal != nil && logVal.Value.String() != logVal.DefValue {
			flagVals = append(flagVals, fmt.Sprintf("--%s %s", logFlag, logVal.Value.String()))
		}
	}

	if kubernetesConfig.ContainerRuntime != "" {
		flagVals = append(flagVals, "--container-runtime="+kubernetesConfig.ContainerRuntime)
	}

	if kubernetesConfig.NetworkPlugin != "" {
		flagVals = append(flagVals, "--network-plugin="+kubernetesConfig.NetworkPlugin)
	}

	if kubernetesConfig.FeatureGates != "" {
		flagVals = append(flagVals, "--feature-gates="+kubernetesConfig.FeatureGates)
	}

	if kubernetesConfig.APIServerName != constants.APIServerName {
		flagVals = append(flagVals, "--apiserver-name="+kubernetesConfig.APIServerName)
	}

	if kubernetesConfig.DNSDomain != "" {
		flagVals = append(flagVals, "--dns-domain="+kubernetesConfig.DNSDomain)
	}

	if kubernetesConfig.NodeIP != "127.0.0.1" {
		flagVals = append(flagVals, "--node-ip="+kubernetesConfig.NodeIP)
	}

	for _, e := range kubernetesConfig.ExtraOptions {
		flagVals = append(flagVals, fmt.Sprintf("--extra-config=%s", e.String()))
	}
	flags := strings.Join(flagVals, " ")
	t := template.Must(template.New("localkubeStartCmd").Parse(localkubeStartCmdTemplate))
	buf := bytes.Buffer{}
	data := struct {
		Flags         string
		APIServerName string
	}{
		Flags:         flags,
		APIServerName: kubernetesConfig.APIServerName,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

const logsTemplate = "sudo journalctl {{.Flags}} -u localkube"

func GetLogsCommand(follow bool) (string, error) {
	t, err := template.New("logsTemplate").Parse(logsTemplate)
	if err != nil {
		return "", err
	}
	var flags []string
	if follow {
		flags = append(flags, "-f")
	}

	buf := bytes.Buffer{}
	data := struct {
		RemoteLocalkubeErrPath string
		RemoteLocalkubeOutPath string
		Flags                  string
	}{
		RemoteLocalkubeErrPath: constants.RemoteLocalKubeErrPath,
		RemoteLocalkubeOutPath: constants.RemoteLocalKubeOutPath,
		Flags: strings.Join(flags, " "),
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

var localkubeStatusCommand = `sudo systemctl is-active localkube 2>&1 1>/dev/null && echo "Running" || echo "Stopped"`

func GetMountCleanupCommand(path string) string {
	return fmt.Sprintf("sudo umount %s;", path)
}

var mountTemplate = `
sudo mkdir -p {{.Path}} || true;
sudo mount -t 9p -o trans=tcp -o port={{.Port}} -o uid=1001 -o gid=1001 {{.IP}} {{.Path}};
sudo chmod 775 {{.Path}};`

func GetMountCommand(ip net.IP, path string, port string) (string, error) {
	t := template.Must(template.New("mountCommand").Parse(mountTemplate))
	buf := bytes.Buffer{}
	data := struct {
		IP   string
		Path string
		Port string
	}{
		IP:   ip.String(),
		Path: path,
		Port: port,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}
<commit_msg>Force create /usr/lib/systemd/system<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 cluster

import (
	"bytes"
	gflag "flag"
	"fmt"
	"net"
	"strings"
	"text/template"

	"k8s.io/minikube/pkg/minikube/constants"
)

// Kill any running instances.
var localkubeStartCmdTemplate = "/usr/local/bin/localkube {{.Flags}} --generate-certs=false --logtostderr=true --enable-dns=false"

var localkubeSystemdTmpl = `[Unit]
Description=Localkube
Documentation=https://github.com/kubernetes/minikube/tree/master/pkg/localkube

[Service]
Type=notify
Restart=always
RestartSec=3

ExecStart={{.LocalkubeStartCmd}}

ExecReload=/bin/kill -s HUP $MAINPID

[Install]
WantedBy=multi-user.target
`

var startCommandTemplate = `
  {{.StartCommandSystemd}}
  sudo systemctl daemon-reload
  sudo systemctl enable localkube.service
  sudo systemctl restart localkube.service || true
`

func GetStartCommand(kubernetesConfig KubernetesConfig) (string, error) {
	localkubeStartCommand, err := GenLocalkubeStartCmd(kubernetesConfig)
	if err != nil {
		return "", err
	}
	startCommandSystemd, err := GetStartCommandSystemd(kubernetesConfig, localkubeStartCommand)
	if err != nil {
		return "", err
	}
	t := template.Must(template.New("startCommand").Parse(startCommandTemplate))
	buf := bytes.Buffer{}
	data := struct {
		StartCommandSystemd string
	}{
		StartCommandSystemd: startCommandSystemd,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

func GetStartCommandSystemd(kubernetesConfig KubernetesConfig, localkubeStartCmd string) (string, error) {
	t, err := template.New("localkubeConfig").Parse(localkubeSystemdTmpl)
	if err != nil {
		return "", err
	}
	buf := bytes.Buffer{}
	data := struct {
		LocalkubeStartCmd string
	}{
		LocalkubeStartCmd: localkubeStartCmd,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
    os.Mkdir("/usr/lib/systemd/system/",os.FileMode(0755))
	return fmt.Sprintf("printf %%s \"%s\" | sudo tee %s", buf.String(),
		constants.LocalkubeServicePath), nil
}

func GenLocalkubeStartCmd(kubernetesConfig KubernetesConfig) (string, error) {
	flagVals := make([]string, len(constants.LogFlags))
	for _, logFlag := range constants.LogFlags {
		if logVal := gflag.Lookup(logFlag); logVal != nil && logVal.Value.String() != logVal.DefValue {
			flagVals = append(flagVals, fmt.Sprintf("--%s %s", logFlag, logVal.Value.String()))
		}
	}

	if kubernetesConfig.ContainerRuntime != "" {
		flagVals = append(flagVals, "--container-runtime="+kubernetesConfig.ContainerRuntime)
	}

	if kubernetesConfig.NetworkPlugin != "" {
		flagVals = append(flagVals, "--network-plugin="+kubernetesConfig.NetworkPlugin)
	}

	if kubernetesConfig.FeatureGates != "" {
		flagVals = append(flagVals, "--feature-gates="+kubernetesConfig.FeatureGates)
	}

	if kubernetesConfig.APIServerName != constants.APIServerName {
		flagVals = append(flagVals, "--apiserver-name="+kubernetesConfig.APIServerName)
	}

	if kubernetesConfig.DNSDomain != "" {
		flagVals = append(flagVals, "--dns-domain="+kubernetesConfig.DNSDomain)
	}

	if kubernetesConfig.NodeIP != "127.0.0.1" {
		flagVals = append(flagVals, "--node-ip="+kubernetesConfig.NodeIP)
	}

	for _, e := range kubernetesConfig.ExtraOptions {
		flagVals = append(flagVals, fmt.Sprintf("--extra-config=%s", e.String()))
	}
	flags := strings.Join(flagVals, " ")
	t := template.Must(template.New("localkubeStartCmd").Parse(localkubeStartCmdTemplate))
	buf := bytes.Buffer{}
	data := struct {
		Flags         string
		APIServerName string
	}{
		Flags:         flags,
		APIServerName: kubernetesConfig.APIServerName,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

const logsTemplate = "sudo journalctl {{.Flags}} -u localkube"

func GetLogsCommand(follow bool) (string, error) {
	t, err := template.New("logsTemplate").Parse(logsTemplate)
	if err != nil {
		return "", err
	}
	var flags []string
	if follow {
		flags = append(flags, "-f")
	}

	buf := bytes.Buffer{}
	data := struct {
		RemoteLocalkubeErrPath string
		RemoteLocalkubeOutPath string
		Flags                  string
	}{
		RemoteLocalkubeErrPath: constants.RemoteLocalKubeErrPath,
		RemoteLocalkubeOutPath: constants.RemoteLocalKubeOutPath,
		Flags: strings.Join(flags, " "),
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

var localkubeStatusCommand = `sudo systemctl is-active localkube 2>&1 1>/dev/null && echo "Running" || echo "Stopped"`

func GetMountCleanupCommand(path string) string {
	return fmt.Sprintf("sudo umount %s;", path)
}

var mountTemplate = `
sudo mkdir -p {{.Path}} || true;
sudo mount -t 9p -o trans=tcp -o port={{.Port}} -o uid=1001 -o gid=1001 {{.IP}} {{.Path}};
sudo chmod 775 {{.Path}};`

func GetMountCommand(ip net.IP, path string, port string) (string, error) {
	t := template.Must(template.New("mountCommand").Parse(mountTemplate))
	buf := bytes.Buffer{}
	data := struct {
		IP   string
		Path string
		Port string
	}{
		IP:   ip.String(),
		Path: path,
		Port: port,
	}
	if err := t.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}
<|endoftext|>


<commit_before>package container

import (
	"fmt"
	"strings"

	kubeModels "git.containerum.net/ch/kube-client/pkg/model"
)

type Container struct {
	kubeModels.Container
}

func (container *Container) String() string {
	return strings.Join([]string{
		container.Name,
		fmt.Sprintf("CPU %s", container.Limits.CPU),
		fmt.Sprintf("MEM %s", container.Limits.Memory),
	}, ", ")
}
<commit_msg>fix container formatting<commit_after>package container

import (
	"fmt"
	"strings"

	kubeModels "git.containerum.net/ch/kube-client/pkg/model"
)

type Container struct {
	kubeModels.Container
}

func (container *Container) String() string {
	return strings.Join([]string{
		container.Name,
		fmt.Sprintf("CPU %s", container.Limits.CPU),
		fmt.Sprintf("MEM %s", container.Limits.Memory),
	}, " ") + "; "
}
<|endoftext|>