Add stash provider bootstrap support

The new command set is:
  flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth

There is a parity in the capabilities with the other providers.

Signed-off-by: Soule BA <soule@weave.works>
pull/2070/head
Soule BA 3 years ago
parent 6c5f27be02
commit 46f9fc194c

@ -0,0 +1,268 @@
/*
Copyright 2021 The Flux 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 main
import (
"context"
"fmt"
"os"
"time"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/spf13/cobra"
"github.com/fluxcd/flux2/internal/bootstrap"
"github.com/fluxcd/flux2/internal/bootstrap/git/gogit"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/install"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
)
var bootstrapBServerCmd = &cobra.Command{
Use: "bitbucket-server",
Short: "Bootstrap toolkit components in a Bitbucket Server repository",
Long: `The bootstrap bitbucket-server command creates the Bitbucket Server repository if it doesn't exists and
commits the toolkit components manifests to the master branch.
Then it configures the target cluster to synchronize with the repository.
If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a Bitbucket Server API token and export it as an env var
export BITBUCKET_TOKEN=<my-token>
# Run bootstrap for a private repository using HTTPS token authentication
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain> --token-auth
# Run bootstrap for a private repository using SSH authentication
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --hostname=<domain>
# Run bootstrap for a repository path
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --path=dev-cluster --hostname=<domain>
# Run bootstrap for a public repository on a personal account
flux bootstrap bitbucket-server --owner=<user> --repository=<repository name> --private=false --personal --hostname=<domain> --token-auth
# Run bootstrap for a an existing repository with a branch named main
flux bootstrap bitbucket-server --owner=<project> --username=<user> --repository=<repository name> --branch=main --hostname=<domain> --token-auth`,
RunE: bootstrapBServerCmdRun,
}
const (
bServerDefaultPermission = "push"
bServerTokenEnvVar = "BITBUCKET_TOKEN"
)
type bServerFlags struct {
owner string
repository string
interval time.Duration
personal bool
username string
private bool
hostname string
path flags.SafeRelativePath
teams []string
readWriteKey bool
reconcile bool
}
var bServerArgs bServerFlags
func init() {
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.owner, "owner", "", "Bitbucket Server user or project name")
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.repository, "repository", "", "Bitbucket Server repository name")
bootstrapBServerCmd.Flags().StringSliceVar(&bServerArgs.teams, "group", []string{}, "Bitbucket Server groups to be given write access (also accepts comma-separated values)")
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.personal, "personal", false, "if true, the owner is assumed to be a Bitbucket Server user; otherwise a group")
bootstrapBServerCmd.Flags().StringVarP(&bServerArgs.username, "username", "u", "git", "authentication username")
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.private, "private", true, "if true, the repository is setup or configured as private")
bootstrapBServerCmd.Flags().DurationVar(&bServerArgs.interval, "interval", time.Minute, "sync interval")
bootstrapBServerCmd.Flags().StringVar(&bServerArgs.hostname, "hostname", "", "Bitbucket Server hostname")
bootstrapBServerCmd.Flags().Var(&bServerArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
bootstrapBServerCmd.Flags().BoolVar(&bServerArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
bootstrapCmd.AddCommand(bootstrapBServerCmd)
}
func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
bitbucketToken := os.Getenv(bServerTokenEnvVar)
if bitbucketToken == "" {
var err error
bitbucketToken, err = readPasswordFromStdin("Please enter your Bitbucket personal access token (PAT): ")
if err != nil {
return fmt.Errorf("could not read token: %w", err)
}
}
if bServerArgs.hostname == "" {
return fmt.Errorf("invalid hostname %q", bServerArgs.hostname)
}
if err := bootstrapValidate(); err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return err
}
// Manifest base
if ver, err := getVersion(bootstrapArgs.version); err == nil {
bootstrapArgs.version = ver
}
manifestsBase, err := buildEmbeddedManifestBase()
if err != nil {
return err
}
defer os.RemoveAll(manifestsBase)
user := bServerArgs.username
if bServerArgs.personal {
user = bServerArgs.owner
}
// Build Bitbucket Server provider
providerCfg := provider.Config{
Provider: provider.GitProviderStash,
Hostname: bServerArgs.hostname,
Username: user,
Token: bitbucketToken,
}
providerClient, err := provider.BuildGitProvider(providerCfg)
if err != nil {
return err
}
// Lazy go-git repository
tmpDir, err := os.MkdirTemp("", "flux-bootstrap-")
if err != nil {
return fmt.Errorf("failed to create temporary working dir: %w", err)
}
defer os.RemoveAll(tmpDir)
gitClient := gogit.New(tmpDir, &http.BasicAuth{
Username: user,
Password: bitbucketToken,
})
// Install manifest config
installOptions := install.Options{
BaseURL: rootArgs.defaults.BaseURL,
Version: bootstrapArgs.version,
Namespace: rootArgs.namespace,
Components: bootstrapComponents(),
Registry: bootstrapArgs.registry,
ImagePullSecret: bootstrapArgs.imagePullSecret,
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
NetworkPolicy: bootstrapArgs.networkPolicy,
LogLevel: bootstrapArgs.logLevel.String(),
NotificationController: rootArgs.defaults.NotificationController,
ManifestFile: rootArgs.defaults.ManifestFile,
Timeout: rootArgs.timeout,
TargetPath: bServerArgs.path.ToSlash(),
ClusterDomain: bootstrapArgs.clusterDomain,
TolerationKeys: bootstrapArgs.tolerationKeys,
}
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
installOptions.BaseURL = customBaseURL
}
// Source generation and secret config
secretOpts := sourcesecret.Options{
Name: bootstrapArgs.secretName,
Namespace: rootArgs.namespace,
TargetPath: bServerArgs.path.String(),
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
}
if bootstrapArgs.tokenAuth {
if bServerArgs.personal {
secretOpts.Username = bServerArgs.owner
} else {
secretOpts.Username = bServerArgs.username
}
secretOpts.Password = bitbucketToken
if bootstrapArgs.caFile != "" {
secretOpts.CAFilePath = bootstrapArgs.caFile
}
} else {
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
secretOpts.SSHHostname = bServerArgs.hostname
if bootstrapArgs.privateKeyFile != "" {
secretOpts.PrivateKeyPath = bootstrapArgs.privateKeyFile
}
if bootstrapArgs.sshHostname != "" {
secretOpts.SSHHostname = bootstrapArgs.sshHostname
}
}
// Sync manifest config
syncOpts := sync.Options{
Interval: bServerArgs.interval,
Name: rootArgs.namespace,
Namespace: rootArgs.namespace,
Branch: bootstrapArgs.branch,
Secret: bootstrapArgs.secretName,
TargetPath: bServerArgs.path.ToSlash(),
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
GitImplementation: sourceGitArgs.gitImplementation.String(),
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
}
// Bootstrap config
bootstrapOpts := []bootstrap.GitProviderOption{
bootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal),
bootstrap.WithBranch(bootstrapArgs.branch),
bootstrap.WithBootstrapTransportType("https"),
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),
bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),
bootstrap.WithKubeconfig(rootArgs.kubeconfig, rootArgs.kubecontext),
bootstrap.WithLogger(logger),
}
if bootstrapArgs.sshHostname != "" {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
}
if bootstrapArgs.tokenAuth {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
}
if !bServerArgs.private {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
}
if bServerArgs.reconcile {
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
}
// Setup bootstrapper with constructed configs
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
if err != nil {
return err
}
// Run
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
}

@ -6,7 +6,7 @@ require (
github.com/Masterminds/semver/v3 v3.1.0 github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin v0.2.2
github.com/fluxcd/go-git-providers v0.3.1 github.com/fluxcd/go-git-providers v0.3.2
github.com/fluxcd/helm-controller/api v0.13.0 github.com/fluxcd/helm-controller/api v0.13.0
github.com/fluxcd/image-automation-controller/api v0.17.1 github.com/fluxcd/image-automation-controller/api v0.17.1
github.com/fluxcd/image-reflector-controller/api v0.13.2 github.com/fluxcd/image-reflector-controller/api v0.13.2

@ -223,8 +223,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/go-git-providers v0.3.1 h1:9B3b7mK3XmMxZzcbes3xEJTnQlhkNURhmOY1kLijnZA= github.com/fluxcd/go-git-providers v0.3.2 h1:89dzg5SCAwdNsLjD4GvCVWo9zNKUDkea6shjBJEfspg=
github.com/fluxcd/go-git-providers v0.3.1/go.mod h1:enIPrXnSOBxahS6rngohpG3d/QZ3yjjy/w+agbp97ZI= github.com/fluxcd/go-git-providers v0.3.2/go.mod h1:enIPrXnSOBxahS6rngohpG3d/QZ3yjjy/w+agbp97ZI=
github.com/fluxcd/helm-controller/api v0.13.0 h1:f9SwsHjqbWfeHMEtpr9wfdbMm0HQ2dL8bVayp2QyPxs= github.com/fluxcd/helm-controller/api v0.13.0 h1:f9SwsHjqbWfeHMEtpr9wfdbMm0HQ2dL8bVayp2QyPxs=
github.com/fluxcd/helm-controller/api v0.13.0/go.mod h1:zWmzV0s2SU4rEIGLPTt+dsaMs40OsNQgSgOATgJmxB0= github.com/fluxcd/helm-controller/api v0.13.0/go.mod h1:zWmzV0s2SU4rEIGLPTt+dsaMs40OsNQgSgOATgJmxB0=
github.com/fluxcd/image-automation-controller/api v0.17.1 h1:nINAsH6ERKItuWQSH2/Iovjn6a/fu/n7WRFVrloryFE= github.com/fluxcd/image-automation-controller/api v0.17.1 h1:nINAsH6ERKItuWQSH2/Iovjn6a/fu/n7WRFVrloryFE=
@ -472,6 +472,7 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@ -480,6 +481,7 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs=
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=

@ -30,6 +30,7 @@ import (
"github.com/fluxcd/go-git-providers/gitprovider" "github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/flux2/internal/bootstrap/git" "github.com/fluxcd/flux2/internal/bootstrap/git"
"github.com/fluxcd/flux2/internal/bootstrap/provider"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/fluxcd/flux2/pkg/manifestgen/sync" "github.com/fluxcd/flux2/pkg/manifestgen/sync"
) )
@ -38,7 +39,9 @@ type GitProviderBootstrapper struct {
*PlainGitBootstrapper *PlainGitBootstrapper
owner string owner string
repository string repositoryName string
repository gitprovider.UserRepository
personal bool personal bool
description string description string
@ -80,23 +83,23 @@ type GitProviderOption interface {
applyGitProvider(b *GitProviderBootstrapper) applyGitProvider(b *GitProviderBootstrapper)
} }
func WithProviderRepository(owner, repository string, personal bool) GitProviderOption { func WithProviderRepository(owner, repositoryName string, personal bool) GitProviderOption {
return providerRepositoryOption{ return providerRepositoryOption{
owner: owner, owner: owner,
repository: repository, repositoryName: repositoryName,
personal: personal, personal: personal,
} }
} }
type providerRepositoryOption struct { type providerRepositoryOption struct {
owner string owner string
repository string repositoryName string
personal bool personal bool
} }
func (o providerRepositoryOption) applyGitProvider(b *GitProviderBootstrapper) { func (o providerRepositoryOption) applyGitProvider(b *GitProviderBootstrapper) {
b.owner = o.owner b.owner = o.owner
b.repository = o.repository b.repositoryName = o.repositoryName
b.personal = o.personal b.personal = o.personal
} }
@ -181,19 +184,19 @@ func (o reconcileOption) applyGitProvider(b *GitProviderBootstrapper) {
} }
func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error { func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, options sync.Options) error {
repo, err := b.getRepository(ctx) if b.repository == nil {
if err != nil { return errors.New("repository is required")
return err
} }
if b.url == "" { if b.url == "" {
bootstrapURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.bootstrapTransportType)) bootstrapURL, err := b.getCloneURL(b.repository, gitprovider.TransportType(b.bootstrapTransportType))
if err != nil { if err != nil {
return err return err
} }
WithRepositoryURL(bootstrapURL).applyGit(b.PlainGitBootstrapper) WithRepositoryURL(bootstrapURL).applyGit(b.PlainGitBootstrapper)
} }
if options.URL == "" { if options.URL == "" {
syncURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.syncTransportType)) syncURL, err := b.getCloneURL(b.repository, gitprovider.TransportType(b.syncTransportType))
if err != nil { if err != nil {
return err return err
} }
@ -211,7 +214,6 @@ func (b *GitProviderBootstrapper) ReconcileSyncConfig(ctx context.Context, optio
func (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error { func (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error {
var repo gitprovider.UserRepository var repo gitprovider.UserRepository
var err error var err error
if b.personal { if b.personal {
repo, err = b.reconcileUserRepository(ctx) repo, err = b.reconcileUserRepository(ctx)
} else { } else {
@ -221,36 +223,37 @@ func (b *GitProviderBootstrapper) ReconcileRepository(ctx context.Context) error
return err return err
} }
cloneURL := repo.Repository().GetCloneURL(gitprovider.TransportType(b.bootstrapTransportType)) cloneURL, err := b.getCloneURL(repo, gitprovider.TransportType(b.bootstrapTransportType))
// TODO(hidde): https://github.com/fluxcd/go-git-providers/issues/55 if err != nil {
if strings.HasPrefix(cloneURL, "https://https://") { return err
cloneURL = strings.TrimPrefix(cloneURL, "https://")
} }
b.repository = repo
WithRepositoryURL(cloneURL).applyGit(b.PlainGitBootstrapper) WithRepositoryURL(cloneURL).applyGit(b.PlainGitBootstrapper)
return err return err
} }
func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error { func (b *GitProviderBootstrapper) reconcileDeployKey(ctx context.Context, secret corev1.Secret, options sourcesecret.Options) error {
if b.repository == nil {
return errors.New("repository is required")
}
ppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey] ppk, ok := secret.StringData[sourcesecret.PublicKeySecretKey]
if !ok { if !ok {
return nil return nil
} }
b.logger.Successf("public key: %s", strings.TrimSpace(ppk)) 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) name := deployKeyName(options.Namespace, b.branch, options.Name, options.TargetPath)
deployKeyInfo := newDeployKeyInfo(name, ppk, b.readWriteKey) deployKeyInfo := newDeployKeyInfo(name, ppk, b.readWriteKey)
var changed bool
if _, changed, err = repo.DeployKeys().Reconcile(ctx, deployKeyInfo); err != nil { _, changed, err := b.repository.DeployKeys().Reconcile(ctx, deployKeyInfo)
if err != nil {
return err return err
} }
if changed { if changed {
b.logger.Successf("configured deploy key %q for %q", deployKeyInfo.Name, repo.Repository().String()) b.logger.Successf("configured deploy key %q for %q", deployKeyInfo.Name, b.repository.Repository().String())
} }
return nil return nil
} }
@ -267,9 +270,12 @@ func (b *GitProviderBootstrapper) reconcileOrgRepository(ctx context.Context) (g
// Construct the repository and other configuration objects // Construct the repository and other configuration objects
// go-git-provider likes to work with // go-git-provider likes to work with
subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository) subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repositoryName)
orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs) orgRef, err := b.getOrganization(ctx, subOrgs)
repoRef := newOrgRepositoryRef(orgRef, repoName) if err != nil {
return nil, fmt.Errorf("failed to create new Git repository for the organization %q: %w", orgRef.String(), err)
}
repoRef := newOrgRepositoryRef(*orgRef, repoName)
repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility) repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
// Reconcile the organization repository // Reconcile the organization repository
@ -343,7 +349,7 @@ func (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) (
// Construct the repository and other metadata objects // Construct the repository and other metadata objects
// go-git-provider likes to work with. // go-git-provider likes to work with.
_, repoName := splitSubOrganizationsFromRepositoryName(b.repository) _, repoName := splitSubOrganizationsFromRepositoryName(b.repositoryName)
userRef := newUserRef(b.provider.SupportedDomain(), b.owner) userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
repoRef := newUserRepositoryRef(userRef, repoName) repoRef := newUserRepositoryRef(userRef, repoName)
repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility) repoInfo := newRepositoryInfo(b.description, b.defaultBranch, b.visibility)
@ -383,21 +389,22 @@ func (b *GitProviderBootstrapper) reconcileUserRepository(ctx context.Context) (
return repo, nil return repo, nil
} }
// getRepository retrieves and returns the gitprovider.UserRepository // getOrganization retrieves and returns the gitprovider.Organization
// for organization and user repositories using the // using the GitProviderBootstrapper values.
// GitProviderBootstrapper values. func (b *GitProviderBootstrapper) getOrganization(ctx context.Context, subOrgs []string) (*gitprovider.OrganizationRef, error) {
// As gitprovider.OrgRepository is a superset of gitprovider.UserRepository, this orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs)
// type is returned. // With Stash get the organization to be sure to get the correct key
func (b *GitProviderBootstrapper) getRepository(ctx context.Context) (gitprovider.UserRepository, error) { if string(b.provider.ProviderID()) == string(provider.GitProviderStash) {
subOrgs, repoName := splitSubOrganizationsFromRepositoryName(b.repository) org, err := b.provider.Organizations().Get(ctx, orgRef)
if err != nil {
if b.personal { return nil, fmt.Errorf("failed to get Git organization: %w", err)
userRef := newUserRef(b.provider.SupportedDomain(), b.owner)
return b.provider.UserRepositories().Get(ctx, newUserRepositoryRef(userRef, repoName))
} }
orgRef := newOrganizationRef(b.provider.SupportedDomain(), b.owner, subOrgs) orgRef = org.Organization()
return b.provider.OrgRepositories().Get(ctx, newOrgRepositoryRef(orgRef, repoName))
return &orgRef, nil
}
return &orgRef, nil
} }
// getCloneURL returns the Git clone URL for the given // getCloneURL returns the Git clone URL for the given
@ -405,18 +412,23 @@ func (b *GitProviderBootstrapper) getRepository(ctx context.Context) (gitprovide
// gitprovider.TransportTypeSSH and a custom SSH hostname is configured, // gitprovider.TransportTypeSSH and a custom SSH hostname is configured,
// the hostname of the URL will be modified to this hostname. // the hostname of the URL will be modified to this hostname.
func (b *GitProviderBootstrapper) getCloneURL(repository gitprovider.UserRepository, transport gitprovider.TransportType) (string, error) { func (b *GitProviderBootstrapper) getCloneURL(repository gitprovider.UserRepository, transport gitprovider.TransportType) (string, error) {
u := repository.Repository().GetCloneURL(transport) var url string
if cloner, ok := repository.(gitprovider.CloneableURL); ok {
return cloner.GetCloneURL("", transport), nil
}
url = repository.Repository().GetCloneURL(transport)
// TODO(hidde): https://github.com/fluxcd/go-git-providers/issues/55 // TODO(hidde): https://github.com/fluxcd/go-git-providers/issues/55
if strings.HasPrefix(u, "https://https://") { if strings.HasPrefix(url, "https://https://") {
u = strings.TrimPrefix(u, "https://") url = strings.TrimPrefix(url, "https://")
} }
var err error var err error
if transport == gitprovider.TransportTypeSSH && b.sshHostname != "" { if transport == gitprovider.TransportTypeSSH && b.sshHostname != "" {
if u, err = setHostname(u, b.sshHostname); err != nil { if url, err = setHostname(url, b.sshHostname); err != nil {
err = fmt.Errorf("failed to set SSH hostname for URL %q: %w", u, err) err = fmt.Errorf("failed to set SSH hostname for URL %q: %w", url, err)
} }
} }
return u, err return url, err
} }
// splitSubOrganizationsFromRepositoryName removes any prefixed sub // splitSubOrganizationsFromRepositoryName removes any prefixed sub

@ -22,6 +22,7 @@ import (
"github.com/fluxcd/go-git-providers/github" "github.com/fluxcd/go-git-providers/github"
"github.com/fluxcd/go-git-providers/gitlab" "github.com/fluxcd/go-git-providers/gitlab"
"github.com/fluxcd/go-git-providers/gitprovider" "github.com/fluxcd/go-git-providers/gitprovider"
"github.com/fluxcd/go-git-providers/stash"
) )
// BuildGitProvider builds a gitprovider.Client for the provided // BuildGitProvider builds a gitprovider.Client for the provided
@ -51,6 +52,14 @@ func BuildGitProvider(config Config) (gitprovider.Client, error) {
if client, err = gitlab.NewClient(config.Token, "", opts...); err != nil { if client, err = gitlab.NewClient(config.Token, "", opts...); err != nil {
return nil, err return nil, err
} }
case GitProviderStash:
opts := []gitprovider.ClientOption{}
if config.Hostname != "" {
opts = append(opts, gitprovider.WithDomain(config.Hostname))
}
if client, err = stash.NewStashClient(config.Username, config.Token, opts...); err != nil {
return nil, err
}
default: default:
return nil, fmt.Errorf("unsupported Git provider '%s'", config.Provider) return nil, fmt.Errorf("unsupported Git provider '%s'", config.Provider)
} }

@ -22,6 +22,7 @@ type GitProvider string
const ( const (
GitProviderGitHub GitProvider = "github" GitProviderGitHub GitProvider = "github"
GitProviderGitLab GitProvider = "gitlab" GitProviderGitLab GitProvider = "gitlab"
GitProviderStash GitProvider = "stash"
) )
// Config defines the configuration for connecting to a GitProvider. // Config defines the configuration for connecting to a GitProvider.
@ -33,6 +34,10 @@ type Config struct {
// e.g. github.example.com. // e.g. github.example.com.
Hostname string Hostname string
// Username contains the username used to authenticate with
// the Provider.
Username string
// Token contains the token used to authenticate with the // Token contains the token used to authenticate with the
// Provider. // Provider.
Token string Token string

Loading…
Cancel
Save