<commit_before>// Package ql exposes implementations and functions that enables ngorm to work
// with ql database.
//
// ql is an embedded sql database. This database doesn't conform 100% qith the
// SQL standard. The link to the project is https://github.com/cznic/ql
package ql

import (
	"fmt"
	"math/big"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gernest/ngorm/dialects"
	"github.com/gernest/ngorm/model"
	"github.com/gernest/ngorm/regexes"
)

//QL implements the dialects.Dialect interface that uses ql database as the SQl
//backend.
//
// For some reason the ql database doesn't support multiple databases, as
// databases are file based. So, the name of the file is the name of the
// database.. Which doesn't affect the querries, since the database name is
// irrelevant assuming the SQLCommon interface is the handle over the open
// database.
type QL struct {
	name string
	db   model.SQLCommon
}

// Memory returns the dialect for in memory ql database. This is not persistent
// everything will be lost when the process exits.
func Memory() *QL {
	return &QL{name: "ql-mem"}
}

//File returns the dialcet for file backed ql database. This is the recommended
//way use the Memory only for testing else you might lose all of your data.
func File() *QL {
	return &QL{name: "ql"}
}

// GetName get dialect's name
func (q *QL) GetName() string {
	return q.name
}

// SetDB set db for dialect
func (q *QL) SetDB(db model.SQLCommon) {
	q.db = db
}

// BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1
func (q QL) BindVar(i int) string {
	return fmt.Sprintf("$%d", i)
}

// Quote quotes field name to avoid SQL parsing exceptions by using a reserved word as a field name
func (q *QL) Quote(key string) string {
	//return fmt.Sprintf(`"%s"`, key)
	return key
}

func (q *QL) PrimaryKey(keys []string) string {
	return ""
}

// DataTypeOf return data's sql type
func (q *QL) DataTypeOf(field *model.StructField) (string, error) {
	var dataValue, sqlType, _, additionalType = dialects.ParseFieldStructForDialect(field)

	if sqlType == "" {
		switch dataValue.Kind() {
		case reflect.Bool:
			sqlType = "boolean"
		case reflect.Int,
			reflect.Int8,
			reflect.Int16,
			reflect.Int32,
			reflect.Int64,
			reflect.Uint,
			reflect.Uint8,
			reflect.Uint16,
			reflect.Uint32,
			reflect.Uint64,
			reflect.Float32,
			reflect.Float64,
			reflect.String:
			sqlType = dataValue.Kind().String()
		case reflect.Struct:
			switch dataValue.Interface().(type) {
			case time.Time:
				sqlType = "time"
			case big.Int:
				sqlType = "bigint"
			case big.Rat:
				sqlType = "bigrat"
			}
		default:
			if _, ok := dataValue.Interface().([]byte); ok {
				sqlType = "blob"
			}
		}
	}

	if sqlType == "" {
		return "", fmt.Errorf("invalid sql type %s (%s) for ql", dataValue.Type().Name(), dataValue.Kind().String())
	}

	if strings.TrimSpace(additionalType) == "" {
		return sqlType, nil
	}
	return fmt.Sprintf("%v %v", sqlType, additionalType), nil
}

// HasIndex check has index or not
func (q *QL) HasIndex(tableName string, indexName string) bool {
	querry := "select count() from __Index where Name=$1  && TableName=$2"
	var count int
	err := q.db.QueryRow(querry, indexName, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// HasForeignKey check has foreign key or not
func (q *QL) HasForeignKey(tableName string, foreignKeyName string) bool {
	return false
}

// RemoveIndex remove index
func (q *QL) RemoveIndex(tableName string, indexName string) error {
	tx, err := q.db.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec(fmt.Sprintf("DROP INDEX %v", indexName))
	if err != nil {
		return err
	}
	return tx.Commit()
}

// HasTable check has table or not
func (q *QL) HasTable(tableName string) bool {
	querry := "select count() from __Table where Name=$1"
	var count int
	err := q.db.QueryRow(querry, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// HasColumn check has column or not
func (q *QL) HasColumn(tableName string, columnName string) bool {
	querry := "select count() from __Column where Name=$1  && TableName=$2"
	var count int
	err := q.db.QueryRow(querry, columnName, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case
func (q *QL) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
	if limit != nil {
		if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit > 0 {
			sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
		}
	}
	if offset != nil {
		if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset > 0 {
			sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
		}
	}
	return
}

// SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL`
func (q *QL) SelectFromDummyTable() string {
	return ""
}

// LastInsertIDReturningSuffix ost dbs support LastInsertId, but postgres needs to use `RETURNING`
func (q *QL) LastInsertIDReturningSuffix(tableName, columnName string) string {
	return ""
}

// BuildForeignKeyName returns a foreign key name for the given table, field and reference
func (q *QL) BuildForeignKeyName(tableName, field, dest string) string {
	keyName := fmt.Sprintf("%s_%s_%s_foreign", tableName, field, dest)
	keyName = regexes.KeyName.ReplaceAllString(keyName, "_")
	return keyName
}

// CurrentDatabase return current database name
func (q *QL) CurrentDatabase() string {
	return ""
}
<commit_msg>[dialects] Add godoc<commit_after>// Package ql exposes implementations and functions that enables ngorm to work
// with ql database.
//
// ql is an embedded sql database. This database doesn't conform 100% qith the
// SQL standard. The link to the project is https://github.com/cznic/ql
package ql

import (
	"fmt"
	"math/big"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/gernest/ngorm/dialects"
	"github.com/gernest/ngorm/model"
	"github.com/gernest/ngorm/regexes"
)

//QL implements the dialects.Dialect interface that uses ql database as the SQl
//backend.
//
// For some reason the ql database doesn't support multiple databases, as
// databases are file based. So, the name of the file is the name of the
// database.. Which doesn't affect the querries, since the database name is
// irrelevant assuming the SQLCommon interface is the handle over the open
// database.
type QL struct {
	name string
	db   model.SQLCommon
}

// Memory returns the dialect for in memory ql database. This is not persistent
// everything will be lost when the process exits.
func Memory() *QL {
	return &QL{name: "ql-mem"}
}

//File returns the dialcet for file backed ql database. This is the recommended
//way use the Memory only for testing else you might lose all of your data.
func File() *QL {
	return &QL{name: "ql"}
}

// GetName get dialect's name
func (q *QL) GetName() string {
	return q.name
}

// SetDB set db for dialect
func (q *QL) SetDB(db model.SQLCommon) {
	q.db = db
}

// BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1
func (q QL) BindVar(i int) string {
	return fmt.Sprintf("$%d", i)
}

// Quote quotes field name to avoid SQL parsing exceptions by using a reserved word as a field name
func (q *QL) Quote(key string) string {
	//return fmt.Sprintf(`"%s"`, key)
	return key
}

//PrimaryKey implements dialects.Dialect interface. This is supposed to return a
//comma separated string of primary keys.
//
// ql does not support PRIMARY KEY so no matter how many keys are passed this
// method will return an empty string.
func (q *QL) PrimaryKey(keys []string) string {
	return ""
}

// DataTypeOf return data's sql type
func (q *QL) DataTypeOf(field *model.StructField) (string, error) {
	var dataValue, sqlType, _, additionalType = dialects.ParseFieldStructForDialect(field)

	if sqlType == "" {
		switch dataValue.Kind() {
		case reflect.Bool:
			sqlType = "boolean"
		case reflect.Int,
			reflect.Int8,
			reflect.Int16,
			reflect.Int32,
			reflect.Int64,
			reflect.Uint,
			reflect.Uint8,
			reflect.Uint16,
			reflect.Uint32,
			reflect.Uint64,
			reflect.Float32,
			reflect.Float64,
			reflect.String:
			sqlType = dataValue.Kind().String()
		case reflect.Struct:
			switch dataValue.Interface().(type) {
			case time.Time:
				sqlType = "time"
			case big.Int:
				sqlType = "bigint"
			case big.Rat:
				sqlType = "bigrat"
			}
		default:
			if _, ok := dataValue.Interface().([]byte); ok {
				sqlType = "blob"
			}
		}
	}

	if sqlType == "" {
		return "", fmt.Errorf("invalid sql type %s (%s) for ql", dataValue.Type().Name(), dataValue.Kind().String())
	}

	if strings.TrimSpace(additionalType) == "" {
		return sqlType, nil
	}
	return fmt.Sprintf("%v %v", sqlType, additionalType), nil
}

// HasIndex check has index or not
func (q *QL) HasIndex(tableName string, indexName string) bool {
	querry := "select count() from __Index where Name=$1  && TableName=$2"
	var count int
	err := q.db.QueryRow(querry, indexName, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// HasForeignKey check has foreign key or not
func (q *QL) HasForeignKey(tableName string, foreignKeyName string) bool {
	return false
}

// RemoveIndex remove index
func (q *QL) RemoveIndex(tableName string, indexName string) error {
	tx, err := q.db.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec(fmt.Sprintf("DROP INDEX %v", indexName))
	if err != nil {
		return err
	}
	return tx.Commit()
}

// HasTable check has table or not
func (q *QL) HasTable(tableName string) bool {
	querry := "select count() from __Table where Name=$1"
	var count int
	err := q.db.QueryRow(querry, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// HasColumn check has column or not
func (q *QL) HasColumn(tableName string, columnName string) bool {
	querry := "select count() from __Column where Name=$1  && TableName=$2"
	var count int
	err := q.db.QueryRow(querry, columnName, tableName).Scan(&count)
	if err != nil {
		//TODO; Propery log or return this error?
	}
	return count > 0
}

// LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case
func (q *QL) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
	if limit != nil {
		if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit > 0 {
			sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
		}
	}
	if offset != nil {
		if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset > 0 {
			sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
		}
	}
	return
}

// SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL`
func (q *QL) SelectFromDummyTable() string {
	return ""
}

// LastInsertIDReturningSuffix ost dbs support LastInsertId, but postgres needs to use `RETURNING`
func (q *QL) LastInsertIDReturningSuffix(tableName, columnName string) string {
	return ""
}

// BuildForeignKeyName returns a foreign key name for the given table, field and reference
func (q *QL) BuildForeignKeyName(tableName, field, dest string) string {
	keyName := fmt.Sprintf("%s_%s_%s_foreign", tableName, field, dest)
	keyName = regexes.KeyName.ReplaceAllString(keyName, "_")
	return keyName
}

// CurrentDatabase return current database name
func (q *QL) CurrentDatabase() string {
	return ""
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/google/go-github/github"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"golang.org/x/net/context"
)

func TestNewGithubRepositories_WithToken(t *testing.T) {
	ctx := context.WithValue(context.Background(), GithubToken, "secret_token")

	r, err := NewGithubRepositories(ctx)
	require.NoError(t, err, "Expected NewGithubRepositories to succeed")
	assert.NotNil(t, r, "Expected NewGithubRepositories to return new instance")
}

func TestNewGithubRepositories_NoToken(t *testing.T) {
	_, err := NewGithubRepositories(context.Background())
	assert.Error(t, err, "Expected NewGithubRepositories to return an error")
}

func TestNewGithubRepositories_EmptyToken(t *testing.T) {
	ctx := context.WithValue(context.Background(), GithubToken, "")

	_, err := NewGithubRepositories(ctx)
	assert.Error(t, err, "Expected NewGithubRepositories to return an error")
}

func TestGithubRepositoriesAll_SingleRepository_DefaultFields(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "git@github.com:user1/repo1.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1) {
		repo := repos[0]
		assert.Equal(t, repo.FullName, "user1/repo1")
		assert.Equal(t, repo.Master, "master")
		assert.Empty(t, repo.Description)
		assert.Empty(t, repo.GitURL)
		assert.Empty(t, repo.HTMLURL)
	}
}

func TestGithubRepositoriesAll_SingleRepository_AllFields(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "description": "Repo1",
                    "default_branch": "production",
                    "git_url": "git@github.com:user1/repo1.git",
                    "html_url": "https://github.com/user1/repo1"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1) {
		repo := repos[0]
		assert.Equal(t, repo.FullName, "user1/repo1")
		assert.Equal(t, repo.Description, "Repo1")
		assert.Equal(t, repo.Master, "production")
		assert.Empty(t, repo.GitURL)
	}
}

func TestGithubRepositoriesAll_MultipleRepositories(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "https://github.com/user1/repo1"
                },{
                    "full_name": "user2/repo2",
                    "git_url": "https://github.com/user2/repo2"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	assert.Len(t, repos, 2)
}

func TestGithubRepositoriesAll_SkipWithoutGitURL(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1"
                },{
                    "full_name": "user2/repo2",
                    "git_url": "git:git@github.com:user2/repo2.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1, "Should exclude one repository without git_url") {
		assert.Equal(t, repos[0].FullName, "user2/repo2")
	}
}

func TestGithubRepositoriesAll_SkipWithoutFullName(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "git:git@github.com:user1/repo1.git"
                },{
                    "git_url": "git:git@github.com:user2/repo2.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1, "Should exclude one repository without full_name") {
		assert.Equal(t, repos[0].FullName, "user1/repo1")
	}
}

func TestGithubRepositoriesAll_HandlePagination(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		if page := r.FormValue("page"); page == "" || page == "1" {
			perPage := r.FormValue("per_page")
			if perPage == "" {
				perPage = "30"
			}

			w.Header().Set("Link", fmt.Sprintf(`<https://api.github.com/user/repos?page=2&per_page=%s>; rel="next"`, perPage))
		}

		fmt.Fprint(w, `[{"full_name": "user1/repo1","git_url": "git:git@github.com:user1/repo1.git"}]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	assert.Len(t, repos, 2)
}

func setup() (ctx context.Context, mux *http.ServeMux, teardownFn func()) {
	mux = http.NewServeMux()
	server := httptest.NewServer(mux)

	client := github.NewClient(nil)
	url, _ := url.Parse(server.URL)
	client.BaseURL = url

	ctx = context.Background()
	ctx = context.WithValue(ctx, GithubToken, "secret_token")
	ctx = context.WithValue(ctx, httpClient, client)

	return ctx, mux, server.Close
}
<commit_msg>Add tests for git.ParseRepositoryName<commit_after>package git

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/google/go-github/github"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"golang.org/x/net/context"
)

func TestParseRepositoryName(t *testing.T) {
	owner, repo := ParseRepositoryName("test/me")
	assert.Equal(t, owner, "test")
	assert.Equal(t, repo, "me")
}

func TestNewGithubRepositories_WithToken(t *testing.T) {
	ctx := context.WithValue(context.Background(), GithubToken, "secret_token")

	r, err := NewGithubRepositories(ctx)
	require.NoError(t, err, "Expected NewGithubRepositories to succeed")
	assert.NotNil(t, r, "Expected NewGithubRepositories to return new instance")
}

func TestNewGithubRepositories_NoToken(t *testing.T) {
	_, err := NewGithubRepositories(context.Background())
	assert.Error(t, err, "Expected NewGithubRepositories to return an error")
}

func TestNewGithubRepositories_EmptyToken(t *testing.T) {
	ctx := context.WithValue(context.Background(), GithubToken, "")

	_, err := NewGithubRepositories(ctx)
	assert.Error(t, err, "Expected NewGithubRepositories to return an error")
}

func TestGithubRepositoriesAll_SingleRepository_DefaultFields(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "git@github.com:user1/repo1.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1) {
		repo := repos[0]
		assert.Equal(t, repo.FullName, "user1/repo1")
		assert.Equal(t, repo.Master, "master")
		assert.Empty(t, repo.Description)
		assert.Empty(t, repo.GitURL)
		assert.Empty(t, repo.HTMLURL)
	}
}

func TestGithubRepositoriesAll_SingleRepository_AllFields(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "description": "Repo1",
                    "default_branch": "production",
                    "git_url": "git@github.com:user1/repo1.git",
                    "html_url": "https://github.com/user1/repo1"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1) {
		repo := repos[0]
		assert.Equal(t, repo.FullName, "user1/repo1")
		assert.Equal(t, repo.Description, "Repo1")
		assert.Equal(t, repo.Master, "production")
		assert.Empty(t, repo.GitURL)
	}
}

func TestGithubRepositoriesAll_MultipleRepositories(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "https://github.com/user1/repo1"
                },{
                    "full_name": "user2/repo2",
                    "git_url": "https://github.com/user2/repo2"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	assert.Len(t, repos, 2)
}

func TestGithubRepositoriesAll_SkipWithoutGitURL(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1"
                },{
                    "full_name": "user2/repo2",
                    "git_url": "git:git@github.com:user2/repo2.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1, "Should exclude one repository without git_url") {
		assert.Equal(t, repos[0].FullName, "user2/repo2")
	}
}

func TestGithubRepositoriesAll_SkipWithoutFullName(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `[{
                    "full_name": "user1/repo1",
                    "git_url": "git:git@github.com:user1/repo1.git"
                },{
                    "git_url": "git:git@github.com:user2/repo2.git"
                }]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	if assert.Len(t, repos, 1, "Should exclude one repository without full_name") {
		assert.Equal(t, repos[0].FullName, "user1/repo1")
	}
}

func TestGithubRepositoriesAll_HandlePagination(t *testing.T) {
	ctx, mux, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
		if page := r.FormValue("page"); page == "" || page == "1" {
			perPage := r.FormValue("per_page")
			if perPage == "" {
				perPage = "30"
			}

			w.Header().Set("Link", fmt.Sprintf(`<https://api.github.com/user/repos?page=2&per_page=%s>; rel="next"`, perPage))
		}

		fmt.Fprint(w, `[{"full_name": "user1/repo1","git_url": "git:git@github.com:user1/repo1.git"}]`)
	})

	githubRepos, err := NewGithubRepositories(ctx)
	require.NoError(t, err)

	repos, err := githubRepos.All()
	require.NoError(t, err)

	assert.Len(t, repos, 2)
}

func setup() (ctx context.Context, mux *http.ServeMux, teardownFn func()) {
	mux = http.NewServeMux()
	server := httptest.NewServer(mux)

	client := github.NewClient(nil)
	url, _ := url.Parse(server.URL)
	client.BaseURL = url

	ctx = context.Background()
	ctx = context.WithValue(ctx, GithubToken, "secret_token")
	ctx = context.WithValue(ctx, httpClient, client)

	return ctx, mux, server.Close
}
<|endoftext|>


<commit_before>// Copyright 2019 Istio 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 helmreconciler

import (
	"context"
	"fmt"
	"sync"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"

	"k8s.io/apimachinery/pkg/runtime"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"istio.io/api/operator/v1alpha1"
	v1alpha12 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/object"
	"istio.io/istio/operator/pkg/translate"
	"istio.io/istio/operator/pkg/util"
	binversion "istio.io/istio/operator/version"
	"istio.io/pkg/log"
)

const (
	pollTimeout  = 100 * time.Second
	pollInterval = 2 * time.Second
)

// HelmReconciler reconciles resources rendered by a set of helm charts for a specific instances of a custom resource,
// or deletes all resources associated with a specific instance of a custom resource.
type HelmReconciler struct {
	client             client.Client
	customizer         RenderingCustomizer
	instance           runtime.Object
	needUpdateAndPrune bool
}

// NewHelmReconciler creates a HelmReconciler and returns a ptr to it
func NewHelmReconciler(instance runtime.Object, customizer RenderingCustomizer, client client.Client) *HelmReconciler {
	return &HelmReconciler{
		instance:   instance,
		client:     client,
		customizer: customizer,
	}
}

// Factory is a factory for creating HelmReconciler objects using the specified CustomizerFactory.
type Factory struct {
	// CustomizerFactory is a factory for creating the Customizer object for the HelmReconciler.
	CustomizerFactory RenderingCustomizerFactory
}

// New Returns a new HelmReconciler for the custom resource.
// instance is the custom resource to be reconciled/deleted.
// client is the kubernetes client
// logger is the logger
func (f *Factory) New(instance runtime.Object, client client.Client) (*HelmReconciler, error) {
	delegate, err := f.CustomizerFactory.NewCustomizer(instance)
	if err != nil {
		return nil, err
	}
	wrappedcustomizer, err := wrapCustomizer(instance, delegate)
	if err != nil {
		return nil, err
	}
	reconciler := &HelmReconciler{client: client, customizer: wrappedcustomizer, instance: instance, needUpdateAndPrune: true}
	wrappedcustomizer.RegisterReconciler(reconciler)
	return reconciler, nil
}

// wrapCustomizer creates a new internalCustomizer object wrapping the delegate, by inject a LoggingRenderingListener,
// an OwnerReferenceDecorator, and a PruningDetailsDecorator into a CompositeRenderingListener that includes the listener
// from the delegate.  This ensures the HelmReconciler can properly implement pruning, etc.
// instance is the custom resource to be processed by the HelmReconciler
// delegate is the delegate
func wrapCustomizer(instance runtime.Object, delegate RenderingCustomizer) (*SimpleRenderingCustomizer, error) {
	ownerReferenceDecorator, err := NewOwnerReferenceDecorator(instance)
	if err != nil {
		return nil, err
	}
	return &SimpleRenderingCustomizer{
		InputValue:          delegate.Input(),
		PruningDetailsValue: delegate.PruningDetails(),
		ListenerValue: &CompositeRenderingListener{
			Listeners: []RenderingListener{
				&LoggingRenderingListener{Level: 1},
				ownerReferenceDecorator,
				NewPruningMarkingsDecorator(delegate.PruningDetails()),
				delegate.Listener(),
			},
		},
	}, nil
}

// Reconcile the resources associated with the custom resource instance.
func (h *HelmReconciler) Reconcile() error {
	// any processing required before processing the charts
	err := h.customizer.Listener().BeginReconcile(h.instance)
	if err != nil {
		return err
	}

	// render charts
	manifestMap, err := h.RenderCharts(h.customizer.Input())
	if err != nil {
		// TODO: this needs to update status to RECONCILING.
		return err
	}

	status := h.processRecursive(manifestMap)

	// Delete any resources not in the manifest but managed by operator.
	var errs util.Errors
	if h.needUpdateAndPrune {
		errs = util.AppendErr(errs, h.Prune(allObjectHashes(manifestMap), false))
	}
	errs = util.AppendErr(errs, h.customizer.Listener().EndReconcile(h.instance, status))

	return errs.ToError()
}

// processRecursive processes the given manifests in an order of dependencies defined in h. Dependencies are a tree,
// where a child must wait for the parent to complete before starting.
func (h *HelmReconciler) processRecursive(manifests ChartManifestsMap) *v1alpha1.InstallStatus {
	deps, dch := h.customizer.Input().GetProcessingOrder(manifests)
	componentStatus := make(map[string]*v1alpha1.InstallStatus_VersionStatus)

	// mu protects the shared InstallStatus componentStatus across goroutines
	var mu sync.Mutex
	// wg waits for all manifest processing goroutines to finish
	var wg sync.WaitGroup

	for c, m := range manifests {
		c, m := c, m
		wg.Add(1)
		go func() {
			defer wg.Done()
			cn := name.ComponentName(c)
			if s := dch[cn]; s != nil {
				log.Infof("%s is waiting on dependency...", c)
				<-s
				log.Infof("Dependency for %s has completed, proceeding.", c)
			}

			// Set status when reconciling starts
			status := v1alpha1.InstallStatus_RECONCILING
			mu.Lock()
			if _, ok := componentStatus[c]; !ok {
				componentStatus[c] = &v1alpha1.InstallStatus_VersionStatus{}
				componentStatus[c].Status = status
			}
			mu.Unlock()

			// Process manifests and get the status result
			errString := ""
			if len(m) == 0 {
				status = v1alpha1.InstallStatus_NONE
			} else {
				status = v1alpha1.InstallStatus_HEALTHY
				if cnt, err := h.ProcessManifest(m[0]); err != nil {
					errString = err.Error()
					status = v1alpha1.InstallStatus_ERROR
				} else if cnt == 0 {
					status = v1alpha1.InstallStatus_NONE
				}
			}

			// Update status based on the result
			mu.Lock()
			if status == v1alpha1.InstallStatus_NONE {
				delete(componentStatus, c)
			} else {
				componentStatus[c].Status = status
				if errString != "" {
					componentStatus[c].Error = errString
				}
			}
			mu.Unlock()

			// Signal all the components that depend on us.
			for _, ch := range deps[cn] {
				log.Infof("Unblocking dependency %s.", ch)
				dch[ch] <- struct{}{}
			}
		}()
	}
	wg.Wait()

	// Update overall status
	// - If all components are HEALTHY, overall status is HEALTHY.
	// - If one or more components are RECONCILING and others are HEALTHY, overall status is RECONCILING.
	// - If one or more components are UPDATING and others are HEALTHY, overall status is UPDATING.
	// - If components are a mix of RECONCILING, UPDATING and HEALTHY, overall status is UPDATING.
	// - If any component is in ERROR state, overall status is ERROR.
	overallStatus := v1alpha1.InstallStatus_HEALTHY
	for _, cs := range componentStatus {
		if cs.Status == v1alpha1.InstallStatus_ERROR {
			overallStatus = v1alpha1.InstallStatus_ERROR
			break
		} else if cs.Status == v1alpha1.InstallStatus_UPDATING {
			overallStatus = v1alpha1.InstallStatus_UPDATING
			break
		} else if cs.Status == v1alpha1.InstallStatus_RECONCILING {
			overallStatus = v1alpha1.InstallStatus_RECONCILING
			break
		}
	}
	// update status further based on the in cluster resources status if manifests processed successfully,
	// otherwise just use status obtained from processing manifests.
	if overallStatus == v1alpha1.InstallStatus_HEALTHY {
		err := h.checkResourceStatus(&componentStatus, &overallStatus)
		if err != nil {
			log.Errorf("failed to check resource status %v", err)
		}
	}
	out := &v1alpha1.InstallStatus{
		Status:          overallStatus,
		ComponentStatus: componentStatus,
	}

	return out
}

// checkResourceStatus check and wait for resource to be ready,
// update overallStatus and componentStatus correspondingly
func (h *HelmReconciler) checkResourceStatus(componentStatus *map[string]*v1alpha1.InstallStatus_VersionStatus,
	overallStatus *v1alpha1.InstallStatus_Status) error {
	cs := h.client
	iop := h.GetInstance().(*v1alpha12.IstioOperator)
	if iop == nil {
		return fmt.Errorf("failed to get IstioOperator instance")
	}
	t, err := translate.NewTranslator(binversion.OperatorBinaryVersion.MinorVersion)
	if err != nil {
		return err
	}
	errPoll := wait.Poll(pollInterval, pollTimeout, func() (bool, error) {
		for cn := range *componentStatus {
			cnMap, ok := t.ComponentMaps[name.ComponentName(cn)]
			if ok && cnMap.ResourceName != "" {
				dp := &appsv1.Deployment{}
				key := client.ObjectKey{Namespace: iop.Namespace, Name: cnMap.ResourceName}
				err := cs.Get(context.TODO(), key, dp)
				if err != nil {
					log.Warnf("deployment: %v not found", cnMap.ResourceName)
					continue
				}

				if dp.Status.ReadyReplicas != dp.Status.UnavailableReplicas+dp.Status.AvailableReplicas {
					(*componentStatus)[cn].Status = v1alpha1.InstallStatus_UPDATING
					return false, nil
				}
			}
			(*componentStatus)[cn].Status = v1alpha1.InstallStatus_HEALTHY
		}

		podList := &v1.PodList{}
		err := cs.List(context.TODO(), podList, client.InNamespace(iop.Namespace))
		if err != nil {
			return false, err
		}
		for _, pod := range podList.Items {
			if len(pod.Status.Conditions) > 0 {
				for _, condition := range pod.Status.Conditions {
					if condition.Type == v1.PodReady && condition.Status != v1.ConditionTrue {
						return false, nil
					}
				}
			}
		}
		return true, nil
	})
	if errPoll != nil {
		*overallStatus = v1alpha1.InstallStatus_UPDATING
	}
	return nil
}

// Delete resources associated with the custom resource instance
func (h *HelmReconciler) Delete() error {
	h.needUpdateAndPrune = true
	allErrors := []error{}

	// any processing required before processing the charts
	err := h.customizer.Listener().BeginDelete(h.instance)
	if err != nil {
		allErrors = append(allErrors, err)
	}

	err = h.customizer.Listener().BeginPrune(true)
	if err != nil {
		allErrors = append(allErrors, err)
	}
	err = h.Prune(nil, true)
	if err != nil {
		allErrors = append(allErrors, err)
	}
	err = h.customizer.Listener().EndPrune()
	if err != nil {
		allErrors = append(allErrors, err)
	}

	// any post processing required after deleting
	err = utilerrors.NewAggregate(allErrors)
	if listenerErr := h.customizer.Listener().EndDelete(h.instance, err); listenerErr != nil {
		log.Errorf("error calling listener: %s", listenerErr)
	}

	// return any errors
	return err
}

// allObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
func allObjectHashes(cmm ChartManifestsMap) map[string]bool {
	ret := make(map[string]bool)
	for _, mm := range cmm {
		for _, m := range mm {
			objs, err := object.ParseK8sObjectsFromYAMLManifest(m.Content)
			if err != nil {
				log.Error(err.Error())
			}
			for _, o := range objs {
				ret[o.Hash()] = true
			}
		}
	}
	return ret
}

// GetClient returns the kubernetes client associated with this HelmReconciler
func (h *HelmReconciler) GetClient() client.Client {
	return h.client
}

// GetInstance returns the instance associated with this HelmReconciler
func (h *HelmReconciler) GetInstance() runtime.Object {
	return h.instance
}

// SetInstance set the instance associated with this HelmReconciler
func (h *HelmReconciler) SetInstance(instance runtime.Object) {
	h.instance = instance
}

// SetNeedUpdateAndPrune set the needUpdateAndPrune flag associated with this HelmReconciler
func (h *HelmReconciler) SetNeedUpdateAndPrune(u bool) {
	h.needUpdateAndPrune = u
}
<commit_msg>add container status check (#22924)<commit_after>// Copyright 2019 Istio 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 helmreconciler

import (
	"context"
	"fmt"
	"sync"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"

	"k8s.io/apimachinery/pkg/runtime"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"istio.io/api/operator/v1alpha1"
	v1alpha12 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/object"
	"istio.io/istio/operator/pkg/translate"
	"istio.io/istio/operator/pkg/util"
	binversion "istio.io/istio/operator/version"
	"istio.io/pkg/log"
)

const (
	pollTimeout  = 100 * time.Second
	pollInterval = 2 * time.Second
)

// HelmReconciler reconciles resources rendered by a set of helm charts for a specific instances of a custom resource,
// or deletes all resources associated with a specific instance of a custom resource.
type HelmReconciler struct {
	client             client.Client
	customizer         RenderingCustomizer
	instance           runtime.Object
	needUpdateAndPrune bool
}

// NewHelmReconciler creates a HelmReconciler and returns a ptr to it
func NewHelmReconciler(instance runtime.Object, customizer RenderingCustomizer, client client.Client) *HelmReconciler {
	return &HelmReconciler{
		instance:   instance,
		client:     client,
		customizer: customizer,
	}
}

// Factory is a factory for creating HelmReconciler objects using the specified CustomizerFactory.
type Factory struct {
	// CustomizerFactory is a factory for creating the Customizer object for the HelmReconciler.
	CustomizerFactory RenderingCustomizerFactory
}

// New Returns a new HelmReconciler for the custom resource.
// instance is the custom resource to be reconciled/deleted.
// client is the kubernetes client
// logger is the logger
func (f *Factory) New(instance runtime.Object, client client.Client) (*HelmReconciler, error) {
	delegate, err := f.CustomizerFactory.NewCustomizer(instance)
	if err != nil {
		return nil, err
	}
	wrappedcustomizer, err := wrapCustomizer(instance, delegate)
	if err != nil {
		return nil, err
	}
	reconciler := &HelmReconciler{client: client, customizer: wrappedcustomizer, instance: instance, needUpdateAndPrune: true}
	wrappedcustomizer.RegisterReconciler(reconciler)
	return reconciler, nil
}

// wrapCustomizer creates a new internalCustomizer object wrapping the delegate, by inject a LoggingRenderingListener,
// an OwnerReferenceDecorator, and a PruningDetailsDecorator into a CompositeRenderingListener that includes the listener
// from the delegate.  This ensures the HelmReconciler can properly implement pruning, etc.
// instance is the custom resource to be processed by the HelmReconciler
// delegate is the delegate
func wrapCustomizer(instance runtime.Object, delegate RenderingCustomizer) (*SimpleRenderingCustomizer, error) {
	ownerReferenceDecorator, err := NewOwnerReferenceDecorator(instance)
	if err != nil {
		return nil, err
	}
	return &SimpleRenderingCustomizer{
		InputValue:          delegate.Input(),
		PruningDetailsValue: delegate.PruningDetails(),
		ListenerValue: &CompositeRenderingListener{
			Listeners: []RenderingListener{
				&LoggingRenderingListener{Level: 1},
				ownerReferenceDecorator,
				NewPruningMarkingsDecorator(delegate.PruningDetails()),
				delegate.Listener(),
			},
		},
	}, nil
}

// Reconcile the resources associated with the custom resource instance.
func (h *HelmReconciler) Reconcile() error {
	// any processing required before processing the charts
	err := h.customizer.Listener().BeginReconcile(h.instance)
	if err != nil {
		return err
	}

	// render charts
	manifestMap, err := h.RenderCharts(h.customizer.Input())
	if err != nil {
		// TODO: this needs to update status to RECONCILING.
		return err
	}

	status := h.processRecursive(manifestMap)

	// Delete any resources not in the manifest but managed by operator.
	var errs util.Errors
	if h.needUpdateAndPrune {
		errs = util.AppendErr(errs, h.Prune(allObjectHashes(manifestMap), false))
	}
	errs = util.AppendErr(errs, h.customizer.Listener().EndReconcile(h.instance, status))

	return errs.ToError()
}

// processRecursive processes the given manifests in an order of dependencies defined in h. Dependencies are a tree,
// where a child must wait for the parent to complete before starting.
func (h *HelmReconciler) processRecursive(manifests ChartManifestsMap) *v1alpha1.InstallStatus {
	deps, dch := h.customizer.Input().GetProcessingOrder(manifests)
	componentStatus := make(map[string]*v1alpha1.InstallStatus_VersionStatus)

	// mu protects the shared InstallStatus componentStatus across goroutines
	var mu sync.Mutex
	// wg waits for all manifest processing goroutines to finish
	var wg sync.WaitGroup

	for c, m := range manifests {
		c, m := c, m
		wg.Add(1)
		go func() {
			defer wg.Done()
			cn := name.ComponentName(c)
			if s := dch[cn]; s != nil {
				log.Infof("%s is waiting on dependency...", c)
				<-s
				log.Infof("Dependency for %s has completed, proceeding.", c)
			}

			// Set status when reconciling starts
			status := v1alpha1.InstallStatus_RECONCILING
			mu.Lock()
			if _, ok := componentStatus[c]; !ok {
				componentStatus[c] = &v1alpha1.InstallStatus_VersionStatus{}
				componentStatus[c].Status = status
			}
			mu.Unlock()

			// Process manifests and get the status result
			errString := ""
			if len(m) == 0 {
				status = v1alpha1.InstallStatus_NONE
			} else {
				status = v1alpha1.InstallStatus_HEALTHY
				if cnt, err := h.ProcessManifest(m[0]); err != nil {
					errString = err.Error()
					status = v1alpha1.InstallStatus_ERROR
				} else if cnt == 0 {
					status = v1alpha1.InstallStatus_NONE
				}
			}

			// Update status based on the result
			mu.Lock()
			if status == v1alpha1.InstallStatus_NONE {
				delete(componentStatus, c)
			} else {
				componentStatus[c].Status = status
				if errString != "" {
					componentStatus[c].Error = errString
				}
			}
			mu.Unlock()

			// Signal all the components that depend on us.
			for _, ch := range deps[cn] {
				log.Infof("Unblocking dependency %s.", ch)
				dch[ch] <- struct{}{}
			}
		}()
	}
	wg.Wait()

	// Update overall status
	// - If all components are HEALTHY, overall status is HEALTHY.
	// - If one or more components are RECONCILING and others are HEALTHY, overall status is RECONCILING.
	// - If one or more components are UPDATING and others are HEALTHY, overall status is UPDATING.
	// - If components are a mix of RECONCILING, UPDATING and HEALTHY, overall status is UPDATING.
	// - If any component is in ERROR state, overall status is ERROR.
	overallStatus := v1alpha1.InstallStatus_HEALTHY
	for _, cs := range componentStatus {
		if cs.Status == v1alpha1.InstallStatus_ERROR {
			overallStatus = v1alpha1.InstallStatus_ERROR
			break
		} else if cs.Status == v1alpha1.InstallStatus_UPDATING {
			overallStatus = v1alpha1.InstallStatus_UPDATING
			break
		} else if cs.Status == v1alpha1.InstallStatus_RECONCILING {
			overallStatus = v1alpha1.InstallStatus_RECONCILING
			break
		}
	}
	// update status further based on the in cluster resources status if manifests processed successfully,
	// otherwise just use status obtained from processing manifests.
	if overallStatus == v1alpha1.InstallStatus_HEALTHY {
		err := h.checkResourceStatus(&componentStatus, &overallStatus)
		if err != nil {
			log.Errorf("failed to check resource status %v", err)
		}
	}
	out := &v1alpha1.InstallStatus{
		Status:          overallStatus,
		ComponentStatus: componentStatus,
	}

	return out
}

// checkResourceStatus check and wait for resource to be ready,
// update overallStatus and componentStatus correspondingly
func (h *HelmReconciler) checkResourceStatus(componentStatus *map[string]*v1alpha1.InstallStatus_VersionStatus,
	overallStatus *v1alpha1.InstallStatus_Status) error {
	cs := h.client
	iop := h.GetInstance().(*v1alpha12.IstioOperator)
	if iop == nil {
		return fmt.Errorf("failed to get IstioOperator instance")
	}
	t, err := translate.NewTranslator(binversion.OperatorBinaryVersion.MinorVersion)
	if err != nil {
		return err
	}
	errPoll := wait.Poll(pollInterval, pollTimeout, func() (bool, error) {
		for cn := range *componentStatus {
			cnMap, ok := t.ComponentMaps[name.ComponentName(cn)]
			if ok && cnMap.ResourceName != "" {
				dp := &appsv1.Deployment{}
				key := client.ObjectKey{Namespace: iop.Namespace, Name: cnMap.ResourceName}
				err := cs.Get(context.TODO(), key, dp)
				if err != nil {
					log.Warnf("deployment: %v not found", cnMap.ResourceName)
					continue
				}

				if dp.Status.ReadyReplicas != dp.Status.UnavailableReplicas+dp.Status.AvailableReplicas {
					(*componentStatus)[cn].Status = v1alpha1.InstallStatus_UPDATING
					return false, nil
				}
			}
			(*componentStatus)[cn].Status = v1alpha1.InstallStatus_HEALTHY
		}

		podList := &v1.PodList{}
		err := cs.List(context.TODO(), podList, client.InNamespace(iop.Namespace))
		if err != nil {
			return false, err
		}
		for _, pod := range podList.Items {
			if len(pod.Status.Conditions) > 0 {
				for _, condition := range pod.Status.Conditions {
					if condition.Type == v1.PodReady && condition.Status != v1.ConditionTrue {
						return false, nil
					}
				}
			}
			if pod.Status.Phase != v1.PodSucceeded && pod.Status.Phase != v1.PodRunning {
				return false, nil
			}
			if pod.Status.Phase == v1.PodRunning {
				for _, containerStatus := range pod.Status.ContainerStatuses {
					if !containerStatus.Ready {
						return false, nil
					}
				}
			}
		}
		return true, nil
	})
	if errPoll != nil {
		*overallStatus = v1alpha1.InstallStatus_UPDATING
	}
	return nil
}

// Delete resources associated with the custom resource instance
func (h *HelmReconciler) Delete() error {
	h.needUpdateAndPrune = true
	allErrors := []error{}

	// any processing required before processing the charts
	err := h.customizer.Listener().BeginDelete(h.instance)
	if err != nil {
		allErrors = append(allErrors, err)
	}

	err = h.customizer.Listener().BeginPrune(true)
	if err != nil {
		allErrors = append(allErrors, err)
	}
	err = h.Prune(nil, true)
	if err != nil {
		allErrors = append(allErrors, err)
	}
	err = h.customizer.Listener().EndPrune()
	if err != nil {
		allErrors = append(allErrors, err)
	}

	// any post processing required after deleting
	err = utilerrors.NewAggregate(allErrors)
	if listenerErr := h.customizer.Listener().EndDelete(h.instance, err); listenerErr != nil {
		log.Errorf("error calling listener: %s", listenerErr)
	}

	// return any errors
	return err
}

// allObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
func allObjectHashes(cmm ChartManifestsMap) map[string]bool {
	ret := make(map[string]bool)
	for _, mm := range cmm {
		for _, m := range mm {
			objs, err := object.ParseK8sObjectsFromYAMLManifest(m.Content)
			if err != nil {
				log.Error(err.Error())
			}
			for _, o := range objs {
				ret[o.Hash()] = true
			}
		}
	}
	return ret
}

// GetClient returns the kubernetes client associated with this HelmReconciler
func (h *HelmReconciler) GetClient() client.Client {
	return h.client
}

// GetInstance returns the instance associated with this HelmReconciler
func (h *HelmReconciler) GetInstance() runtime.Object {
	return h.instance
}

// SetInstance set the instance associated with this HelmReconciler
func (h *HelmReconciler) SetInstance(instance runtime.Object) {
	h.instance = instance
}

// SetNeedUpdateAndPrune set the needUpdateAndPrune flag associated with this HelmReconciler
func (h *HelmReconciler) SetNeedUpdateAndPrune(u bool) {
	h.needUpdateAndPrune = u
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"fmt"
	"net/http"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestMarketplaceService_ListPlans(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/plans", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{
			"page":     "1",
			"per_page": "2",
		})
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	plans, _, err := client.Marketplace.ListPlans(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlans returned error: %v", err)
	}

	want := []*MarketplacePlan{{ID: Int64(1)}}
	if !cmp.Equal(plans, want) {
		t.Errorf("Marketplace.ListPlans returned %+v, want %+v", plans, want)
	}

	const methodName = "ListPlans"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListPlans(ctx, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListPlans(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/plans", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	plans, _, err := client.Marketplace.ListPlans(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlans (Stubbed) returned error: %v", err)
	}

	want := []*MarketplacePlan{{ID: Int64(1)}}
	if !cmp.Equal(plans, want) {
		t.Errorf("Marketplace.ListPlans (Stubbed) returned %+v, want %+v", plans, want)
	}
}

func TestMarketplaceService_ListPlanAccountsForPlan(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlanAccountsForPlan returned error: %v", err)
	}

	want := []*MarketplacePlanAccount{{ID: Int64(1)}}
	if !cmp.Equal(accounts, want) {
		t.Errorf("Marketplace.ListPlanAccountsForPlan returned %+v, want %+v", accounts, want)
	}

	const methodName = "ListPlanAccountsForPlan"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListPlanAccountsForPlan(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned error: %v", err)
	}

	want := []*MarketplacePlanAccount{{ID: Int64(1)}}
	if !cmp.Equal(accounts, want) {
		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned %+v, want %+v", accounts, want)
	}
}

func TestMarketplaceService_GetPlanAccountForAccount(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/accounts/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1, "marketplace_pending_change": {"id": 77}}`)
	})

	client.Marketplace.Stubbed = false
	ctx := context.Background()
	account, _, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
	if err != nil {
		t.Errorf("Marketplace.GetPlanAccountForAccount returned error: %v", err)
	}

	want := &MarketplacePlanAccount{ID: Int64(1), MarketplacePendingChange: &MarketplacePendingChange{ID: Int64(77)}}
	if !cmp.Equal(account, want) {
		t.Errorf("Marketplace.GetPlanAccountForAccount returned %+v, want %+v", account, want)
	}

	const methodName = "GetPlanAccountForAccount"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_GetPlanAccountForAccount(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/accounts/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1}`)
	})

	client.Marketplace.Stubbed = true
	ctx := context.Background()
	account, _, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
	if err != nil {
		t.Errorf("Marketplace.GetPlanAccountForAccount (Stubbed) returned error: %v", err)
	}

	want := &MarketplacePlanAccount{ID: Int64(1)}
	if !cmp.Equal(account, want) {
		t.Errorf("Marketplace.GetPlanAccountForAccount (Stubbed) returned %+v, want %+v", account, want)
	}
}

func TestMarketplaceService_ListMarketplacePurchasesForUser(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/marketplace_purchases", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
	}

	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
	if !cmp.Equal(purchases, want) {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
	}

	const methodName = "ListMarketplacePurchasesForUser"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListMarketplacePurchasesForUser(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/marketplace_purchases/stubbed", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
	}

	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
	if !cmp.Equal(purchases, want) {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
	}
}
<commit_msg>Add test cases for JSON resource marshaling (#1927)<commit_after>// Copyright 2017 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"fmt"
	"net/http"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestMarketplaceService_ListPlans(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/plans", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{
			"page":     "1",
			"per_page": "2",
		})
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	plans, _, err := client.Marketplace.ListPlans(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlans returned error: %v", err)
	}

	want := []*MarketplacePlan{{ID: Int64(1)}}
	if !cmp.Equal(plans, want) {
		t.Errorf("Marketplace.ListPlans returned %+v, want %+v", plans, want)
	}

	const methodName = "ListPlans"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListPlans(ctx, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListPlans(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/plans", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	plans, _, err := client.Marketplace.ListPlans(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlans (Stubbed) returned error: %v", err)
	}

	want := []*MarketplacePlan{{ID: Int64(1)}}
	if !cmp.Equal(plans, want) {
		t.Errorf("Marketplace.ListPlans (Stubbed) returned %+v, want %+v", plans, want)
	}
}

func TestMarketplaceService_ListPlanAccountsForPlan(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlanAccountsForPlan returned error: %v", err)
	}

	want := []*MarketplacePlanAccount{{ID: Int64(1)}}
	if !cmp.Equal(accounts, want) {
		t.Errorf("Marketplace.ListPlanAccountsForPlan returned %+v, want %+v", accounts, want)
	}

	const methodName = "ListPlanAccountsForPlan"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListPlanAccountsForPlan(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id":1}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(ctx, 1, opt)
	if err != nil {
		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned error: %v", err)
	}

	want := []*MarketplacePlanAccount{{ID: Int64(1)}}
	if !cmp.Equal(accounts, want) {
		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned %+v, want %+v", accounts, want)
	}
}

func TestMarketplaceService_GetPlanAccountForAccount(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/accounts/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1, "marketplace_pending_change": {"id": 77}}`)
	})

	client.Marketplace.Stubbed = false
	ctx := context.Background()
	account, _, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
	if err != nil {
		t.Errorf("Marketplace.GetPlanAccountForAccount returned error: %v", err)
	}

	want := &MarketplacePlanAccount{ID: Int64(1), MarketplacePendingChange: &MarketplacePendingChange{ID: Int64(77)}}
	if !cmp.Equal(account, want) {
		t.Errorf("Marketplace.GetPlanAccountForAccount returned %+v, want %+v", account, want)
	}

	const methodName = "GetPlanAccountForAccount"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_GetPlanAccountForAccount(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/marketplace_listing/stubbed/accounts/1", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"id":1}`)
	})

	client.Marketplace.Stubbed = true
	ctx := context.Background()
	account, _, err := client.Marketplace.GetPlanAccountForAccount(ctx, 1)
	if err != nil {
		t.Errorf("Marketplace.GetPlanAccountForAccount (Stubbed) returned error: %v", err)
	}

	want := &MarketplacePlanAccount{ID: Int64(1)}
	if !cmp.Equal(account, want) {
		t.Errorf("Marketplace.GetPlanAccountForAccount (Stubbed) returned %+v, want %+v", account, want)
	}
}

func TestMarketplaceService_ListMarketplacePurchasesForUser(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/marketplace_purchases", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = false
	ctx := context.Background()
	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
	}

	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
	if !cmp.Equal(purchases, want) {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
	}

	const methodName = "ListMarketplacePurchasesForUser"
	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestMarketplaceService_Stubbed_ListMarketplacePurchasesForUser(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/user/marketplace_purchases/stubbed", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
	})

	opt := &ListOptions{Page: 1, PerPage: 2}
	client.Marketplace.Stubbed = true
	ctx := context.Background()
	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(ctx, opt)
	if err != nil {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
	}

	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
	if !cmp.Equal(purchases, want) {
		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
	}
}

func TestMarketplacePlan_Marshal(t *testing.T) {
	testJSONMarshal(t, &MarketplacePlan{}, "{}")

	u := &MarketplacePlan{
		URL:                 String("u"),
		AccountsURL:         String("au"),
		ID:                  Int64(1),
		Number:              Int(1),
		Name:                String("n"),
		Description:         String("d"),
		MonthlyPriceInCents: Int(1),
		YearlyPriceInCents:  Int(1),
		PriceModel:          String("pm"),
		UnitName:            String("un"),
		Bullets:             &[]string{"b"},
		State:               String("s"),
		HasFreeTrial:        Bool(false),
	}

	want := `{
		"url": "u",
		"accounts_url": "au",
		"id": 1,
		"number": 1,
		"name": "n",
		"description": "d",
		"monthly_price_in_cents": 1,
		"yearly_price_in_cents": 1,
		"price_model": "pm",
		"unit_name": "un",
		"bullets": ["b"],
		"state": "s",
		"has_free_trial": false
	}`

	testJSONMarshal(t, u, want)
}

func TestMarketplacePurchase_Marshal(t *testing.T) {
	testJSONMarshal(t, &MarketplacePurchase{}, "{}")

	u := &MarketplacePurchase{
		BillingCycle:    String("bc"),
		NextBillingDate: &Timestamp{referenceTime},
		UnitCount:       Int(1),
		Plan: &MarketplacePlan{
			URL:                 String("u"),
			AccountsURL:         String("au"),
			ID:                  Int64(1),
			Number:              Int(1),
			Name:                String("n"),
			Description:         String("d"),
			MonthlyPriceInCents: Int(1),
			YearlyPriceInCents:  Int(1),
			PriceModel:          String("pm"),
			UnitName:            String("un"),
			Bullets:             &[]string{"b"},
			State:               String("s"),
			HasFreeTrial:        Bool(false),
		},
		OnFreeTrial:     Bool(false),
		FreeTrialEndsOn: &Timestamp{referenceTime},
		UpdatedAt:       &Timestamp{referenceTime},
	}

	want := `{
		"billing_cycle": "bc",
		"next_billing_date": ` + referenceTimeStr + `,
		"unit_count": 1,
		"plan": {
			"url": "u",
			"accounts_url": "au",
			"id": 1,
			"number": 1,
			"name": "n",
			"description": "d",
			"monthly_price_in_cents": 1,
			"yearly_price_in_cents": 1,
			"price_model": "pm",
			"unit_name": "un",
			"bullets": ["b"],
			"state": "s",
			"has_free_trial": false
			},
		"on_free_trial": false,
		"free_trial_ends_on": ` + referenceTimeStr + `,
		"updated_at": ` + referenceTimeStr + `
	}`

	testJSONMarshal(t, u, want)
}

func TestMarketplacePendingChange_Marshal(t *testing.T) {
	testJSONMarshal(t, &MarketplacePendingChange{}, "{}")

	u := &MarketplacePendingChange{
		EffectiveDate: &Timestamp{referenceTime},
		UnitCount:     Int(1),
		ID:            Int64(1),
		Plan: &MarketplacePlan{
			URL:                 String("u"),
			AccountsURL:         String("au"),
			ID:                  Int64(1),
			Number:              Int(1),
			Name:                String("n"),
			Description:         String("d"),
			MonthlyPriceInCents: Int(1),
			YearlyPriceInCents:  Int(1),
			PriceModel:          String("pm"),
			UnitName:            String("un"),
			Bullets:             &[]string{"b"},
			State:               String("s"),
			HasFreeTrial:        Bool(false),
		},
	}

	want := `{
		"effective_date": ` + referenceTimeStr + `,
		"unit_count": 1,
		"id": 1,
		"plan": {
			"url": "u",
			"accounts_url": "au",
			"id": 1,
			"number": 1,
			"name": "n",
			"description": "d",
			"monthly_price_in_cents": 1,
			"yearly_price_in_cents": 1,
			"price_model": "pm",
			"unit_name": "un",
			"bullets": ["b"],
			"state": "s",
			"has_free_trial": false
			}
	}`

	testJSONMarshal(t, u, want)
}

func TestMarketplacePlanAccount_Marshal(t *testing.T) {
	testJSONMarshal(t, &MarketplacePlanAccount{}, "{}")

	u := &MarketplacePlanAccount{
		URL:                      String("u"),
		Type:                     String("t"),
		ID:                       Int64(1),
		Login:                    String("l"),
		OrganizationBillingEmail: String("obe"),
		MarketplacePurchase: &MarketplacePurchase{
			BillingCycle:    String("bc"),
			NextBillingDate: &Timestamp{referenceTime},
			UnitCount:       Int(1),
			Plan: &MarketplacePlan{
				URL:                 String("u"),
				AccountsURL:         String("au"),
				ID:                  Int64(1),
				Number:              Int(1),
				Name:                String("n"),
				Description:         String("d"),
				MonthlyPriceInCents: Int(1),
				YearlyPriceInCents:  Int(1),
				PriceModel:          String("pm"),
				UnitName:            String("un"),
				Bullets:             &[]string{"b"},
				State:               String("s"),
				HasFreeTrial:        Bool(false),
			},
			OnFreeTrial:     Bool(false),
			FreeTrialEndsOn: &Timestamp{referenceTime},
			UpdatedAt:       &Timestamp{referenceTime},
		},
		MarketplacePendingChange: &MarketplacePendingChange{
			EffectiveDate: &Timestamp{referenceTime},
			UnitCount:     Int(1),
			ID:            Int64(1),
			Plan: &MarketplacePlan{
				URL:                 String("u"),
				AccountsURL:         String("au"),
				ID:                  Int64(1),
				Number:              Int(1),
				Name:                String("n"),
				Description:         String("d"),
				MonthlyPriceInCents: Int(1),
				YearlyPriceInCents:  Int(1),
				PriceModel:          String("pm"),
				UnitName:            String("un"),
				Bullets:             &[]string{"b"},
				State:               String("s"),
				HasFreeTrial:        Bool(false),
			},
		},
	}

	want := `{
		"url": "u",
		"type": "t",
		"id": 1,
		"login": "l",
		"organization_billing_email": "obe",
		"marketplace_purchase": {
			"billing_cycle": "bc",
			"next_billing_date": ` + referenceTimeStr + `,
			"unit_count": 1,
			"plan": {
				"url": "u",
				"accounts_url": "au",
				"id": 1,
				"number": 1,
				"name": "n",
				"description": "d",
				"monthly_price_in_cents": 1,
				"yearly_price_in_cents": 1,
				"price_model": "pm",
				"unit_name": "un",
				"bullets": ["b"],
				"state": "s",
				"has_free_trial": false
				},
			"on_free_trial": false,
			"free_trial_ends_on": ` + referenceTimeStr + `,
			"updated_at": ` + referenceTimeStr + `
		},
		"marketplace_pending_change": {
			"effective_date": ` + referenceTimeStr + `,
			"unit_count": 1,
			"id": 1,
			"plan": {
				"url": "u",
				"accounts_url": "au",
				"id": 1,
				"number": 1,
				"name": "n",
				"description": "d",
				"monthly_price_in_cents": 1,
				"yearly_price_in_cents": 1,
				"price_model": "pm",
				"unit_name": "un",
				"bullets": ["b"],
				"state": "s",
				"has_free_trial": false
			}
		}
	}`

	testJSONMarshal(t, u, want)
}
<|endoftext|>


<commit_before>package app

import (
	"reflect"
	"sync"

	"github.com/google/uuid"
	"github.com/pkg/errors"
)

// Event is a string that identifies an app event.
type Event string

// Subscriber is a struct to subscribe to events emitted by a event registry.
type Subscriber struct {
	events      *eventRegistry
	unsuscribes []func()
}

// Subscribe subscribes a function to the given event. Emit fails if the
// subscribbed func have more arguments than the emitted event.
//
// Panics if f is not a func.
func (s *Subscriber) Subscribe(e Event, f interface{}) *Subscriber {
	unsubscribe := s.events.subscribe(e, f)
	s.unsuscribes = append(s.unsuscribes, unsubscribe)
	return s
}

// Close unsubscribes all the subscriptions.
func (s *Subscriber) Close() {
	for _, unsuscribe := range s.unsuscribes {
		unsuscribe()
	}
}

type eventHandler struct {
	ID         string
	MsgHandler interface{}
}

type eventRegistry struct {
	mutex    sync.RWMutex
	handlers map[Event][]eventHandler
	ui       chan func()
}

func newEventRegistry(ui chan func()) *eventRegistry {
	return &eventRegistry{
		handlers: make(map[Event][]eventHandler),
		ui:       ui,
	}
}

func (r *eventRegistry) subscribe(e Event, handler interface{}) func() {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if reflect.ValueOf(handler).Kind() != reflect.Func {
		Panic(errors.Errorf("can't subscribe to event %s: handler is not a func: %T",
			e,
			handler,
		))
	}

	id := uuid.New().String()
	handlers := r.handlers[e]

	handlers = append(handlers, eventHandler{
		ID:         id,
		MsgHandler: handler,
	})

	r.handlers[e] = handlers

	return func() {
		r.unsubscribe(e, id)
	}
}

func (r *eventRegistry) unsubscribe(e Event, id string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	handlers := r.handlers[e]

	for i, h := range handlers {
		if h.ID == id {
			end := len(handlers) - 1
			handlers[i] = handlers[end]
			handlers[end] = eventHandler{}
			handlers = handlers[:end]

			r.handlers[e] = handlers
			return
		}
	}
}

// Emit emits the event with the given arguments.
func (r *eventRegistry) Emit(e Event, args ...interface{}) {
	r.mutex.RLock()
	defer r.mutex.RUnlock()

	for _, h := range r.handlers[e] {
		if err := r.callHandler(h.MsgHandler, args...); err != nil {
			Logf("emitting %s failed: %s", e, err)
		}
	}
}

func (r *eventRegistry) callHandler(h interface{}, args ...interface{}) error {
	v := reflect.ValueOf(h)
	t := v.Type()

	argsv := make([]reflect.Value, t.NumIn())

	for i := 0; i < t.NumIn(); i++ {
		argt := t.In(i)

		if i >= len(args) {
			return errors.Errorf("missing %v at index %v", argt, i)
		}

		argv := reflect.ValueOf(args[i])
		if !argv.Type().ConvertibleTo(argt) {
			return errors.Errorf("arg at index %v is not a %v: %v", i, argt, argv.Type())
		}

		argsv[i] = argv.Convert(argt)
	}

	r.ui <- func() {
		v.Call(argsv)
	}

	return nil
}
<commit_msg>misspell<commit_after>package app

import (
	"reflect"
	"sync"

	"github.com/google/uuid"
	"github.com/pkg/errors"
)

// Event is a string that identifies an app event.
type Event string

// Subscriber is a struct to subscribe to events emitted by a event registry.
type Subscriber struct {
	events      *eventRegistry
	unsuscribes []func()
}

// Subscribe subscribes a function to the given event. Emit fails if the
// subscribed func have more arguments than the emitted event.
//
// Panics if f is not a func.
func (s *Subscriber) Subscribe(e Event, f interface{}) *Subscriber {
	unsubscribe := s.events.subscribe(e, f)
	s.unsuscribes = append(s.unsuscribes, unsubscribe)
	return s
}

// Close unsubscribes all the subscriptions.
func (s *Subscriber) Close() {
	for _, unsuscribe := range s.unsuscribes {
		unsuscribe()
	}
}

type eventHandler struct {
	ID         string
	MsgHandler interface{}
}

type eventRegistry struct {
	mutex    sync.RWMutex
	handlers map[Event][]eventHandler
	ui       chan func()
}

func newEventRegistry(ui chan func()) *eventRegistry {
	return &eventRegistry{
		handlers: make(map[Event][]eventHandler),
		ui:       ui,
	}
}

func (r *eventRegistry) subscribe(e Event, handler interface{}) func() {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if reflect.ValueOf(handler).Kind() != reflect.Func {
		Panic(errors.Errorf("can't subscribe to event %s: handler is not a func: %T",
			e,
			handler,
		))
	}

	id := uuid.New().String()
	handlers := r.handlers[e]

	handlers = append(handlers, eventHandler{
		ID:         id,
		MsgHandler: handler,
	})

	r.handlers[e] = handlers

	return func() {
		r.unsubscribe(e, id)
	}
}

func (r *eventRegistry) unsubscribe(e Event, id string) {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	handlers := r.handlers[e]

	for i, h := range handlers {
		if h.ID == id {
			end := len(handlers) - 1
			handlers[i] = handlers[end]
			handlers[end] = eventHandler{}
			handlers = handlers[:end]

			r.handlers[e] = handlers
			return
		}
	}
}

// Emit emits the event with the given arguments.
func (r *eventRegistry) Emit(e Event, args ...interface{}) {
	r.mutex.RLock()
	defer r.mutex.RUnlock()

	for _, h := range r.handlers[e] {
		if err := r.callHandler(h.MsgHandler, args...); err != nil {
			Logf("emitting %s failed: %s", e, err)
		}
	}
}

func (r *eventRegistry) callHandler(h interface{}, args ...interface{}) error {
	v := reflect.ValueOf(h)
	t := v.Type()

	argsv := make([]reflect.Value, t.NumIn())

	for i := 0; i < t.NumIn(); i++ {
		argt := t.In(i)

		if i >= len(args) {
			return errors.Errorf("missing %v at index %v", argt, i)
		}

		argv := reflect.ValueOf(args[i])
		if !argv.Type().ConvertibleTo(argt) {
			return errors.Errorf("arg at index %v is not a %v: %v", i, argt, argv.Type())
		}

		argsv[i] = argv.Convert(argt)
	}

	r.ui <- func() {
		v.Call(argsv)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	//"context"
	"fmt"
	"github.com/google/uuid"
	"io"
	"net/http"
	"net/url"
)

func init() {
	http.HandleFunc("/", handleIndex)
	http.HandleFunc("/githublogin", handleGithubLogin)
	http.HandleFunc("/oauthcallback", handleOauthCallback)

}

// https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/about-authorization-options-for-oauth-apps/

const loginhtml = `<!DOCTYPE html>

LOGIN WITH GITHUB

`

func handleIndex(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, loginhtml)
}

func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
	fmt.Println("&&& handleGithubLogin begin")

	fmt.Println("app url:", r.Host)

	id := uuid.New()
	fmt.Println("id:", id)
	//ctx := r.Context()
	//TODO: get session from context

	//redirect_uri := "http://localhost:8080/callback"
	redirect_uri := "http://" + r.Host + "/callback"
	fmt.Println("redirect_uri:", redirect_uri)

	values := url.Values{}
	values.Add("client_id", "03712bbff7dae4203b4e")
	values.Add("redirect_uri", redirect_uri)
	values.Add("scope", "user:email")
	values.Add("state", id.String())

	redirectRequestUrl := fmt.Sprintf("https://github.com/login/oauth/authorize?%s",
		values.Encode())

	fmt.Println("redirectRequestUrl:", redirectRequestUrl)

	//TODO: save session back to context after saving the id.String()  to a field in session.State

	http.Redirect(w, r, redirectRequestUrl, 302)
}

func handleOauthCallback(w http.ResponseWriter, r *http.Request) {
	/*	state := r.FormValue("state")
		////ctx := context.WithValue(r.Context(), "state", state)

		ctx := r.Context()
		//TODO: get session from context

		//TODO: compare the state from session with the state from request. if no match, red flag
	*/
}
<commit_msg>github oauth callback fix<commit_after>package main

import (
	//"context"
	"fmt"
	"github.com/google/uuid"
	"io"
	"net/http"
	"net/url"
)

func init() {
	http.HandleFunc("/", handleIndex)
	http.HandleFunc("/githublogin", handleGithubLogin)
	http.HandleFunc("/callback", handleOauthCallback)

}

// https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/about-authorization-options-for-oauth-apps/

const loginhtml = `<!DOCTYPE html>

LOGIN WITH GITHUB

`

func handleIndex(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, loginhtml)
}

func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
	fmt.Println("&&& handleGithubLogin begin")

	fmt.Println("app url:", r.Host)

	id := uuid.New()
	fmt.Println("id:", id)
	//ctx := r.Context()
	//TODO: get session from context

	//redirect_uri := "http://localhost:8080/callback"
	redirect_uri := "http://" + r.Host + "/callback"
	fmt.Println("redirect_uri:", redirect_uri)

	values := url.Values{}
	values.Add("client_id", "03712bbff7dae4203b4e")
	values.Add("redirect_uri", redirect_uri)
	values.Add("scope", "user:email")
	values.Add("state", id.String())

	redirectRequestUrl := fmt.Sprintf("https://github.com/login/oauth/authorize?%s",
		values.Encode())

	fmt.Println("redirectRequestUrl:", redirectRequestUrl)

	//TODO: save session back to context after saving the id.String()  to a field in session.State

	http.Redirect(w, r, redirectRequestUrl, 302)
}

func handleOauthCallback(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, r.FormValue("state"))
	/*	state := r.FormValue("state")
		////ctx := context.WithValue(r.Context(), "state", state)

		ctx := r.Context()
		//TODO: get session from context

		//TODO: compare the state from session with the state from request. if no match, red flag
	*/
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Rook 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 controller

import (
	"encoding/json"
	"strings"

	"github.com/banzaicloud/k8s-objectmatcher/patch"
	"github.com/google/go-cmp/cmp"
	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// WatchControllerPredicate is a special update filter for update events
// do not reconcile if the the status changes, this avoids a reconcile storm loop
//
// returning 'true' means triggering a reconciliation
// returning 'false' means do NOT trigger a reconciliation
func WatchControllerPredicate() predicate.Funcs {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			logger.Debug("create event from the parent object")
			return true
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			logger.Debug("delete event from the parent object")
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			logger.Debug("update event from the parent object")
			// resource.Quantity has non-exportable fields, so we use its comparator method
			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })

			switch objOld := e.ObjectOld.(type) {
			case *cephv1.CephObjectStore:
				objNew := e.ObjectNew.(*cephv1.CephObjectStore)
				logger.Debug("update event from the parent object CephObjectStore")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephObjectStoreUser:
				objNew := e.ObjectNew.(*cephv1.CephObjectStoreUser)
				logger.Debug("update event from the parent object CephObjectStoreUser")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephBlockPool:
				objNew := e.ObjectNew.(*cephv1.CephBlockPool)
				logger.Debug("update event from the parent object CephBlockPool")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephFilesystem:
				objNew := e.ObjectNew.(*cephv1.CephFilesystem)
				logger.Debug("update event from the parent object CephFilesystem")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephNFS:
				objNew := e.ObjectNew.(*cephv1.CephNFS)
				logger.Debug("update event from the parent object CephNFS")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			}

			logger.Debug("wont update unknown object")
			return false
		},
		GenericFunc: func(e event.GenericEvent) bool {
			return false
		},
	}
}

// objectChanged checks whether the object has been updated
func objectChanged(oldObj, newObj runtime.Object) (bool, error) {
	var doReconcile bool
	old := oldObj.DeepCopyObject()
	new := newObj.DeepCopyObject()

	// Set resource version
	accessor := meta.NewAccessor()
	currentResourceVersion, err := accessor.ResourceVersion(old)
	if err == nil {
		accessor.SetResourceVersion(new, currentResourceVersion)
	}

	// Calculate diff between old and new object
	diff, err := patch.DefaultPatchMaker.Calculate(old, new)
	if err != nil {
		doReconcile = true
		return doReconcile, errors.Wrap(err, "failed to calculate object diff")
	} else if diff.IsEmpty() {
		return doReconcile, nil
	}

	return isValidEvent(diff.Patch), nil
}

// WatchPredicateForNonCRDObject is a special filter for create events
// It only applies to non-CRD objects, meaning, for instance a cephv1.CephBlockPool{}
// object will not have this filter
// Only for objects like &v1.Secret{} etc...
//
// We return 'false' on a create event so we don't overstep with the main watcher on cephv1.CephBlockPool{}
// This avoids a double reconcile when the secret gets deleted.
func WatchPredicateForNonCRDObject(owner runtime.Object, scheme *runtime.Scheme) predicate.Funcs {
	// Initialize the Owner Matcher, which is the main controller object: e.g. cephv1.CephBlockPool{}
	ownerMatcher := NewOwnerReferenceMatcher(owner, scheme)

	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			match, object, err := ownerMatcher.Match(e.Object)
			if err != nil {
				logger.Errorf("failed to check if object kind %q matched. %v", e.Object.GetObjectKind(), err)
			}
			if match {
				logger.Debugf("object %q matched on delete", object.GetName())
				return true
			}

			logger.Debugf("object %q did not match on delete", object.GetName())
			return false
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			match, object, err := ownerMatcher.Match(e.ObjectNew)
			if err != nil {
				logger.Errorf("failed to check if object matched. %v", err)
			}
			if match {
				logger.Debugf("object %q matched on update", object.GetName())
				objectChanged, err := objectChanged(e.ObjectOld, e.ObjectNew)
				if err != nil {
					logger.Errorf("failed to check if object %q changed. %v", object.GetName(), err)
				}
				return objectChanged
			}

			return false
		},
		GenericFunc: func(e event.GenericEvent) bool {
			return false
		},
	}
}

// isValidEvent analyses the diff between two objects events and determines
// if we should reconcile that event or not
// The goal is to avoid double-reconcile as much as possible
func isValidEvent(patch []byte) bool {
	patchString := string(patch)

	// Seem a bit 'weak' but since we can't get a real struct of the object
	// (unless we use the unstructured package, but that over complicates things)
	// That's probably the most straightforward approach for now...
	//
	// The downscale only shows a "deletionTimestamp" which is not appropriate to catch
	if strings.Contains(patchString, "Created new replica set") {
		logger.Debug("don't reconcile on replicaset addition")
		return false
	}

	// It looks like there is a diff
	// if the status changed, we do nothing
	var p map[string]interface{}
	json.Unmarshal(patch, &p)
	delete(p, "status")
	if len(p) == 0 {
		return false
	}

	logger.Infof("will reconcile based on patch %s", patchString)
	return true
}
<commit_msg>ceph: change verbose info log to debug in controller<commit_after>/*
Copyright 2020 The Rook 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 controller

import (
	"encoding/json"
	"strings"

	"github.com/banzaicloud/k8s-objectmatcher/patch"
	"github.com/google/go-cmp/cmp"
	"github.com/pkg/errors"
	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// WatchControllerPredicate is a special update filter for update events
// do not reconcile if the the status changes, this avoids a reconcile storm loop
//
// returning 'true' means triggering a reconciliation
// returning 'false' means do NOT trigger a reconciliation
func WatchControllerPredicate() predicate.Funcs {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			logger.Debug("create event from the parent object")
			return true
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			logger.Debug("delete event from the parent object")
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			logger.Debug("update event from the parent object")
			// resource.Quantity has non-exportable fields, so we use its comparator method
			resourceQtyComparer := cmp.Comparer(func(x, y resource.Quantity) bool { return x.Cmp(y) == 0 })

			switch objOld := e.ObjectOld.(type) {
			case *cephv1.CephObjectStore:
				objNew := e.ObjectNew.(*cephv1.CephObjectStore)
				logger.Debug("update event from the parent object CephObjectStore")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephObjectStoreUser:
				objNew := e.ObjectNew.(*cephv1.CephObjectStoreUser)
				logger.Debug("update event from the parent object CephObjectStoreUser")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephBlockPool:
				objNew := e.ObjectNew.(*cephv1.CephBlockPool)
				logger.Debug("update event from the parent object CephBlockPool")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephFilesystem:
				objNew := e.ObjectNew.(*cephv1.CephFilesystem)
				logger.Debug("update event from the parent object CephFilesystem")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			case *cephv1.CephNFS:
				objNew := e.ObjectNew.(*cephv1.CephNFS)
				logger.Debug("update event from the parent object CephNFS")
				diff := cmp.Diff(objOld.Spec, objNew.Spec, resourceQtyComparer)
				if diff != "" ||
					objOld.GetDeletionTimestamp() != objNew.GetDeletionTimestamp() ||
					objOld.GetGeneration() != objNew.GetGeneration() {
					// Checking if diff is not empty so we don't print it when the CR gets deleted
					if diff != "" {
						logger.Infof("CR has changed for %q. diff=%s", objNew.Name, diff)
					}
					return true
				}

			}

			logger.Debug("wont update unknown object")
			return false
		},
		GenericFunc: func(e event.GenericEvent) bool {
			return false
		},
	}
}

// objectChanged checks whether the object has been updated
func objectChanged(oldObj, newObj runtime.Object) (bool, error) {
	var doReconcile bool
	old := oldObj.DeepCopyObject()
	new := newObj.DeepCopyObject()

	// Set resource version
	accessor := meta.NewAccessor()
	currentResourceVersion, err := accessor.ResourceVersion(old)
	if err == nil {
		accessor.SetResourceVersion(new, currentResourceVersion)
	}

	// Calculate diff between old and new object
	diff, err := patch.DefaultPatchMaker.Calculate(old, new)
	if err != nil {
		doReconcile = true
		return doReconcile, errors.Wrap(err, "failed to calculate object diff")
	} else if diff.IsEmpty() {
		return doReconcile, nil
	}

	return isValidEvent(diff.Patch), nil
}

// WatchPredicateForNonCRDObject is a special filter for create events
// It only applies to non-CRD objects, meaning, for instance a cephv1.CephBlockPool{}
// object will not have this filter
// Only for objects like &v1.Secret{} etc...
//
// We return 'false' on a create event so we don't overstep with the main watcher on cephv1.CephBlockPool{}
// This avoids a double reconcile when the secret gets deleted.
func WatchPredicateForNonCRDObject(owner runtime.Object, scheme *runtime.Scheme) predicate.Funcs {
	// Initialize the Owner Matcher, which is the main controller object: e.g. cephv1.CephBlockPool{}
	ownerMatcher := NewOwnerReferenceMatcher(owner, scheme)

	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			match, object, err := ownerMatcher.Match(e.Object)
			if err != nil {
				logger.Errorf("failed to check if object kind %q matched. %v", e.Object.GetObjectKind(), err)
			}
			if match {
				logger.Debugf("object %q matched on delete", object.GetName())
				return true
			}

			logger.Debugf("object %q did not match on delete", object.GetName())
			return false
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			match, object, err := ownerMatcher.Match(e.ObjectNew)
			if err != nil {
				logger.Errorf("failed to check if object matched. %v", err)
			}
			if match {
				logger.Debugf("object %q matched on update", object.GetName())
				objectChanged, err := objectChanged(e.ObjectOld, e.ObjectNew)
				if err != nil {
					logger.Errorf("failed to check if object %q changed. %v", object.GetName(), err)
				}
				return objectChanged
			}

			return false
		},
		GenericFunc: func(e event.GenericEvent) bool {
			return false
		},
	}
}

// isValidEvent analyses the diff between two objects events and determines
// if we should reconcile that event or not
// The goal is to avoid double-reconcile as much as possible
func isValidEvent(patch []byte) bool {
	patchString := string(patch)

	// Seem a bit 'weak' but since we can't get a real struct of the object
	// (unless we use the unstructured package, but that over complicates things)
	// That's probably the most straightforward approach for now...
	//
	// The downscale only shows a "deletionTimestamp" which is not appropriate to catch
	if strings.Contains(patchString, "Created new replica set") {
		logger.Debug("don't reconcile on replicaset addition")
		return false
	}

	// It looks like there is a diff
	// if the status changed, we do nothing
	var p map[string]interface{}
	json.Unmarshal(patch, &p)
	delete(p, "status")
	if len(p) == 0 {
		return false
	}

	logger.Debugf("will reconcile based on patch %s", patchString)
	return true
}
<|endoftext|>