func splitSubOrganizationsFromRepositoryName(name string) ([]string, string) {
	elements := strings.Split(name, "/")
	i := len(elements)
	switch i {
	case 1:
		return nil, name
	default:
		return elements[:i-1], elements[i-1]
	}
}
splitSubOrganizationsFromRepositoryName removes any prefixed sub
organizations from the given repository name by splitting the
string into a slice by '/'.
The last (or only) item of the slice result is assumed to be the
repository name, other items (nested) sub organizations.
package bootstrap
import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"github.com/fluxcd/go-git-providers/gitprovider"
	"github.com/fluxcd/flux2/internal/bootstrap/git"
	"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
	"github.com/fluxcd/flux2/pkg/manifestgen/sync"
)
type GitProviderBootstrapper struct {
	*PlainGitBootstrapper
	owner      string
	repository string
	personal   bool
	description   string
	defaultBranch string
	visibility    string
	reconcile bool
	teams map[string]string
	readWriteKey bool
	bootstrapTransportType string
	syncTransportType      string
	sshHostname string
	provider gitprovider.Client
}
func NewGitProviderBootstrapper(git git.Git, provider gitprovider.Client, kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
	b := &GitProviderBootstrapper{
		PlainGitBootstrapper: &PlainGitBootstrapper{
			git:  git,
			kube: kube,
		},
		bootstrapTransportType: "https",
		syncTransportType:      "ssh",
		provider:               provider,
	}
	b.PlainGitBootstrapper.postGenerateSecret = append(b.PlainGitBootstrapper.postGenerateSecret, b.reconcileDeployKey)
	for _, opt := range opts {
		opt.applyGitProvider(b)
	}
	return b, nil
}
type GitProviderOption interface {
	applyGitProvider(b *GitProviderBootstrapper)
}
func WithProviderRepository(owner, repository string, personal bool) GitProviderOption {
	return providerRepositoryOption{
		owner:      owner,
		repository: repository,
		personal:   personal,
	}
}
type providerRepositoryOption struct {
	owner      string
	repository string
	personal   bool
}
func (o providerRepositoryOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.owner = o.owner
	b.repository = o.repository
	b.personal = o.personal
}
func WithProviderRepositoryConfig(description, defaultBranch, visibility string) GitProviderOption {
	return providerRepositoryConfigOption{
		description:   description,
		defaultBranch: defaultBranch,
		visibility:    visibility,
	}
}
type providerRepositoryConfigOption struct {
	description   string
	defaultBranch string
	visibility    string
}
func (o providerRepositoryConfigOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.description = o.description
	b.defaultBranch = o.defaultBranch
	b.visibility = o.visibility
}
func WithProviderTeamPermissions(teams map[string]string) GitProviderOption {
	return providerRepositoryTeamPermissionsOption(teams)
}
type providerRepositoryTeamPermissionsOption map[string]string
func (o providerRepositoryTeamPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.teams = o
}
func WithReadWriteKeyPermissions(b bool) GitProviderOption {
	return withReadWriteKeyPermissionsOption(b)
}
type withReadWriteKeyPermissionsOption bool
func (o withReadWriteKeyPermissionsOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.readWriteKey = bool(o)
}
func WithBootstrapTransportType(protocol string) GitProviderOption {
	return bootstrapTransportTypeOption(protocol)
}
type bootstrapTransportTypeOption string
func (o bootstrapTransportTypeOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.bootstrapTransportType = string(o)
}
func WithSyncTransportType(protocol string) GitProviderOption {
	return syncProtocolOption(protocol)
}
type syncProtocolOption string
func (o syncProtocolOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.syncTransportType = string(o)
}
func WithSSHHostname(hostname string) GitProviderOption {
	return sshHostnameOption(hostname)
}
type sshHostnameOption string
func (o sshHostnameOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.sshHostname = string(o)
}
func WithReconcile() GitProviderOption {
	return reconcileOption(true)
}
type reconcileOption bool
func (o reconcileOption) applyGitProvider(b *GitProviderBootstrapper) {
	b.reconcile = true
}
func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {
	repo, err := b.getRepository(ctx)
	if err != nil {
		return err
	}
	if b.url == "" {
		bootstrapURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.bootstrapTransportType))
		if err != nil {
			return err
		}
		WithRepositoryURL(bootstrapURL).applyGit(b.PlainGitBootstrapper)
	}
	if options.URL == "" {
		syncURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.syncTransportType))
		if err != nil {
			return err
		}
		options.URL = syncURL
	}
	return b.PlainGitBootstrapper.ReconcileSyncConfig(ctx, options)
}
func (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error {
	var repo gitprovider.UserRepository
	var err error
	if b.personal {
		repo, err = b.reconcileUserRepository(ctx)
	} else {
		repo, err = b.reconcileOrgRepository(ctx)
	}
	if err != nil && !errors.Is(err, ErrReconciledWithWarning) {
		return err
	}
	cloneURL := repo.Repository().GetCloneURL(gitprovider.TransportType(b.bootstrapTransportType))
	if strings.HasPrefix(cloneURL, "https://https://") {
		cloneURL = strings.TrimPrefix(cloneURL, "https://")
	}
	WithRepositoryURL(cloneURL).applyGit(b.PlainGitBootstrapper)
	return err
}
func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error {
	ppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]
	if !ok {
		return nil
	}
	b.logger.Successf("public key: %s", strings.TrimSpace(ppk))
	repo, err := b.getRepository(ctx)
	if err != nil {
		return err
	}
	name := deployKeyName(options.Namespace, b.branch, options.Name, options.TargetPath)
	deployKeyInfo := newDeployKeyInfo(name, ppk, b.readWriteKey)
	var changed bool
	if _, changed, err = repo.DeployKeys().Reconcile(ctx, deployKeyInfo); err != nil {
		return err
	}
	if changed {
		b.logger.Successf("configured deploy key %q for %q", deployKeyInfo.Name, repo.Repository().String())
	}
	return nil
}
func (b *GitProviderBootstrapper) reconcileOrgRepository(ctx context.Context) (gitprovider.UserRepository, error) {
	b.logger.Actionf("connecting to %s", b.provider.SupportedDomain())
	subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
	orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)
	repoRef := newOrgRepositoryRef(orgRef, repoName)
	repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
	repo, err := b.provider.OrgRepositories().Get(ctx, repoRef)
	if err != nil {
		if !errors.Is(err, gitprovider.ErrNotFound) {
			return nil, fmt.Errorf("failed to get Git repository %q: %w", repoRef.String(), err)
		}
		repo, err = b.provider.OrgRepositories().Create(ctx, repoRef, repoInfo)
		if err != nil {
			return nil, fmt.Errorf("failed to create new Git repository %q: %w", repoRef.String(), err)
		}
		b.logger.Successf("repository %q created", repoRef.String())
	}
	var changed bool
	if b.reconcile {
		repoInfo.DefaultBranch = repo.Get().DefaultBranch
		if err = retry(1, 2*time.Second, func() (err error) {
			repo, changed, err = b.provider.OrgRepositories().Reconcile(ctx, repoRef, repoInfo)
			return
		}); err != nil {
			return nil, fmt.Errorf("failed to reconcile Git repository %q: %w", repoRef.String(), err)
		}
		if changed {
			b.logger.Successf("repository %q reconciled", repoRef.String())
		}
	}
	teamAccessInfo, err := buildTeamAccessInfo(b.teams, gitprovider.RepositoryPermissionVar(gitprovider.RepositoryPermissionMaintain))
	if err != nil {
		return nil, fmt.Errorf("failed to reconcile repository team access: %w", err)
	}
	var warning error
	if count := len(teamAccessInfo); count > 0 {
		b.logger.Actionf("reconciling repository permissions")
		for _, i := range teamAccessInfo {
			var err error
			if team, err := repo.TeamAccess().Get(ctx, i.Name); err == nil && !b.reconcile && team != nil {
				continue
			}
			_, changed, err = repo.TeamAccess().Reconcile(ctx, i)
			if err != nil {
				warning = fmt.Errorf("failed to grant permissions to team: %w", ErrReconciledWithWarning)
				b.logger.Failuref("failed to grant %q permissions to %q: %s", *i.Permission, i.Name, err.Error())
			} else if changed {
				b.logger.Successf("granted %q permissions to %q", *i.Permission, i.Name)
			}
		}
		b.logger.Successf("reconciled repository permissions")
	}
	return repo, warning
}
func (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) (gitprovider.UserRepository, error) {
	b.logger.Actionf("connecting to %s", b.provider.SupportedDomain())
	_, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
	userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
	repoRef := newUserRepositoryRef(userRef, repoName)
	repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
	repo, err := b.provider.UserRepositories().Get(ctx, repoRef)
	if err != nil {
		if !errors.Is(err, gitprovider.ErrNotFound) {
			return nil, fmt.Errorf("failed to get Git repository %q: %w", repoRef.String(), err)
		}
		repo, err = b.provider.UserRepositories().Create(ctx, repoRef, repoInfo)
		if err != nil {
			return nil, fmt.Errorf("failed to create new Git repository %q: %w", repoRef.String(), err)
		}
		b.logger.Successf("repository %q created", repoRef.String())
	}
	if b.reconcile {
		repoInfo.DefaultBranch = repo.Get().DefaultBranch
		var changed bool
		if err = retry(1, 2*time.Second, func() (err error) {
			repo, changed, err = b.provider.UserRepositories().Reconcile(ctx, repoRef, repoInfo)
			return
		}); err != nil {
			return nil, fmt.Errorf("failed to reconcile Git repository %q: %w", repoRef.String(), err)
		}
		if changed {
			b.logger.Successf("repository %q reconciled", repoRef.String())
		}
	}
	return repo, nil
}
func (b *GitProviderBootstrapper) getRepository(ctx context.Context) (gitprovider.UserRepository, error) {
	subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository)
	if b.personal {
		userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
		return b.provider.UserRepositories().Get(ctx, newUserRepositoryRef(userRef, repoName))
	}
	orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)
	return b.provider.OrgRepositories().Get(ctx, newOrgRepositoryRef(orgRef, repoName))
}
func (b *GitProviderBootstrapper) getCloneURL(repository gitprovider.UserRepository, transport gitprovider.TransportType) (string, error) {
	u := repository.Repository().GetCloneURL(transport)
	if strings.HasPrefix(u, "https://https://") {
		u = strings.TrimPrefix(u, "https://")
	}
	var err error
	if transport == gitprovider.TransportTypeSSH && b.sshHostname != "" {
		if u, err = setHostname(u, b.sshHostname); err != nil {
			err = fmt.Errorf("failed to set SSH hostname for URL %q: %w", u, err)
		}
	}
	return u, err
}