mirror of
https://github.com/fluxcd/flux2.git
synced 2026-02-08 00:37:27 +00:00
Merge pull request #3299 from aryan9600/use-pkg-git
Refactor bootstrap process to use `fluxcd/pkg/git`
This commit is contained in:
commit
d4ba6c4f44
15 changed files with 262 additions and 596 deletions
|
|
@ -22,13 +22,13 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
|
|
@ -171,10 +171,16 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
|||
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,
|
||||
|
||||
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Username: user,
|
||||
Password: bitbucketToken,
|
||||
CAFile: caBundle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install manifest config
|
||||
installOptions := install.Options{
|
||||
|
|
@ -253,13 +259,12 @@ func bootstrapBServerCmdRun(cmd *cobra.Command, args []string) error {
|
|||
bootstrap.WithProviderRepository(bServerArgs.owner, bServerArgs.repository, bServerArgs.personal),
|
||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||
bootstrap.WithBootstrapTransportType("https"),
|
||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||
bootstrap.WithProviderTeamPermissions(mapTeamSlice(bServerArgs.teams, bServerDefaultPermission)),
|
||||
bootstrap.WithReadWriteKeyPermissions(bServerArgs.readWriteKey),
|
||||
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||
bootstrap.WithLogger(logger),
|
||||
bootstrap.WithCABundle(caBundle),
|
||||
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||
}
|
||||
if bootstrapArgs.sshHostname != "" {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
|
@ -34,11 +31,12 @@ import (
|
|||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
)
|
||||
|
||||
var bootstrapGitCmd = &cobra.Command{
|
||||
|
|
@ -62,6 +60,12 @@ command will perform an upgrade if needed.`,
|
|||
|
||||
# Run bootstrap for a Git repository with a private key and password
|
||||
flux bootstrap git --url=ssh://git@example.com/repository.git --private-key-file=<path/to/private.key> --password=<password>
|
||||
|
||||
# Run bootstrap for a Git repository on AWS CodeCommit
|
||||
flux bootstrap git --url=ssh://<SSH-Key-ID>@git-codecommit.<region>.amazonaws.com/v1/repos/<repository> --private-key-file=<path/to/private.key> --password=<SSH-passphrase>
|
||||
|
||||
# Run bootstrap for a Git repository on Azure Devops
|
||||
flux bootstrap git --url=ssh://git@ssh.dev.azure.com/v3/<org>/<project>/<repository> --ssh-key-algorithm=rsa --ssh-rsa-bits=4096
|
||||
`,
|
||||
RunE: bootstrapGitCmdRun,
|
||||
}
|
||||
|
|
@ -116,9 +120,22 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gitAuth, err := transportForURL(repositoryURL)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if strings.Contains(repositoryURL.Hostname(), "git-codecommit") && strings.Contains(repositoryURL.Hostname(), "amazonaws.com") {
|
||||
if repositoryURL.Scheme == string(git.SSH) {
|
||||
if repositoryURL.User == nil {
|
||||
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be specified in the url")
|
||||
}
|
||||
if repositoryURL.User.Username() == git.DefaultPublicKeyAuthUser {
|
||||
return fmt.Errorf("invalid AWS CodeCommit url: ssh username should be the SSH key ID for the provided private key")
|
||||
}
|
||||
if bootstrapArgs.privateKeyFile == "" {
|
||||
return fmt.Errorf("private key file is required for bootstrapping against AWS CodeCommit using ssh")
|
||||
}
|
||||
}
|
||||
if repositoryURL.Scheme == string(git.HTTPS) && !bootstrapArgs.tokenAuth {
|
||||
return fmt.Errorf("--token-auth=true must be specified for using a HTTPS AWS CodeCommit url")
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
|
|
@ -145,7 +162,28 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
gitClient := gogit.New(tmpDir, gitAuth)
|
||||
|
||||
var caBundle []byte
|
||||
if bootstrapArgs.caFile != "" {
|
||||
var err error
|
||||
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
}
|
||||
authOpts, err := getAuthOpts(repositoryURL, caBundle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create authentication options for %s: %w", repositoryURL.String(), err)
|
||||
}
|
||||
|
||||
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
|
||||
if gitArgs.insecureHttpAllowed {
|
||||
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
|
||||
}
|
||||
gitClient, err := gogit.NewClient(tmpDir, authOpts, clientOpts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create a Git client: %w", err)
|
||||
}
|
||||
|
||||
// Install manifest config
|
||||
installOptions := install.Options{
|
||||
|
|
@ -169,15 +207,6 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||
installOptions.BaseURL = customBaseURL
|
||||
}
|
||||
|
||||
var caBundle []byte
|
||||
if bootstrapArgs.caFile != "" {
|
||||
var err error
|
||||
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read TLS CA file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Source generation and secret config
|
||||
secretOpts := sourcesecret.Options{
|
||||
Name: bootstrapArgs.secretName,
|
||||
|
|
@ -253,12 +282,11 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||
bootstrapOpts := []bootstrap.GitOption{
|
||||
bootstrap.WithRepositoryURL(gitArgs.url),
|
||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
|
||||
bootstrap.WithLogger(logger),
|
||||
bootstrap.WithCABundle(caBundle),
|
||||
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||
}
|
||||
|
||||
|
|
@ -272,28 +300,45 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
|
||||
}
|
||||
|
||||
// transportForURL constructs a transport.AuthMethod based on the scheme
|
||||
// getAuthOpts retruns a AuthOptions based on the scheme
|
||||
// of the given URL and the configured flags. If the protocol equals
|
||||
// "ssh" but no private key is configured, authentication using the local
|
||||
// SSH-agent is attempted.
|
||||
func transportForURL(u *url.URL) (transport.AuthMethod, error) {
|
||||
func getAuthOpts(u *url.URL, caBundle []byte) (*git.AuthOptions, error) {
|
||||
switch u.Scheme {
|
||||
case "http":
|
||||
if !gitArgs.insecureHttpAllowed {
|
||||
return nil, fmt.Errorf("scheme http is insecure, pass --allow-insecure-http=true to allow it")
|
||||
}
|
||||
return &http.BasicAuth{
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
return &git.AuthOptions{
|
||||
Transport: git.HTTP,
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
}, nil
|
||||
case "https":
|
||||
return &http.BasicAuth{
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
return &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Username: gitArgs.username,
|
||||
Password: gitArgs.password,
|
||||
CAFile: caBundle,
|
||||
}, nil
|
||||
case "ssh":
|
||||
if bootstrapArgs.privateKeyFile != "" {
|
||||
return ssh.NewPublicKeysFromFile(u.User.Username(), bootstrapArgs.privateKeyFile, gitArgs.password)
|
||||
pk, err := os.ReadFile(bootstrapArgs.privateKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kh, err := sourcesecret.ScanHostKey(u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &git.AuthOptions{
|
||||
Transport: git.SSH,
|
||||
Username: u.User.Username(),
|
||||
Password: gitArgs.password,
|
||||
Identity: pk,
|
||||
KnownHosts: kh,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
|
|
@ -161,16 +161,21 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Lazy go-git repository
|
||||
tmpDir, err := manifestgen.MkdirTempAbs("", "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: githubArgs.owner,
|
||||
Password: ghToken,
|
||||
|
||||
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Username: githubArgs.owner,
|
||||
Password: ghToken,
|
||||
CAFile: caBundle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install manifest config
|
||||
installOptions := install.Options{
|
||||
|
|
@ -239,13 +244,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||
bootstrap.WithProviderRepository(githubArgs.owner, githubArgs.repository, githubArgs.personal),
|
||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||
bootstrap.WithBootstrapTransportType("https"),
|
||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||
bootstrap.WithProviderTeamPermissions(mapTeamSlice(githubArgs.teams, ghDefaultPermission)),
|
||||
bootstrap.WithReadWriteKeyPermissions(githubArgs.readWriteKey),
|
||||
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||
bootstrap.WithLogger(logger),
|
||||
bootstrap.WithCABundle(caBundle),
|
||||
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||
}
|
||||
if bootstrapArgs.sshHostname != "" {
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git/gogit"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
|
|
@ -178,10 +178,16 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to create temporary working dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
gitClient := gogit.New(tmpDir, &http.BasicAuth{
|
||||
Username: gitlabArgs.owner,
|
||||
Password: glToken,
|
||||
|
||||
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Username: gitlabArgs.owner,
|
||||
Password: glToken,
|
||||
CAFile: caBundle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install manifest config
|
||||
installOptions := install.Options{
|
||||
|
|
@ -255,13 +261,12 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||
bootstrap.WithProviderRepository(gitlabArgs.owner, gitlabArgs.repository, gitlabArgs.personal),
|
||||
bootstrap.WithBranch(bootstrapArgs.branch),
|
||||
bootstrap.WithBootstrapTransportType("https"),
|
||||
bootstrap.WithAuthor(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
|
||||
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
|
||||
bootstrap.WithProviderTeamPermissions(mapTeamSlice(gitlabArgs.teams, glDefaultPermission)),
|
||||
bootstrap.WithReadWriteKeyPermissions(gitlabArgs.readWriteKey),
|
||||
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
|
||||
bootstrap.WithLogger(logger),
|
||||
bootstrap.WithCABundle(caBundle),
|
||||
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
|
||||
}
|
||||
if bootstrapArgs.sshHostname != "" {
|
||||
|
|
|
|||
9
go.mod
9
go.mod
|
|
@ -8,12 +8,15 @@ require (
|
|||
github.com/cyphar/filepath-securejoin v0.2.3
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221111170714-3b8fbf975279
|
||||
github.com/fluxcd/go-git-providers v0.9.0
|
||||
github.com/fluxcd/go-git/v5 v5.0.0-20221104190732-329fd6659b10
|
||||
github.com/fluxcd/helm-controller/api v0.26.0
|
||||
github.com/fluxcd/image-automation-controller/api v0.26.1
|
||||
github.com/fluxcd/image-reflector-controller/api v0.22.1
|
||||
github.com/fluxcd/kustomize-controller/api v0.30.0
|
||||
github.com/fluxcd/notification-controller/api v0.28.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.18.0
|
||||
github.com/fluxcd/pkg/git v0.7.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.2.0
|
||||
github.com/fluxcd/pkg/kustomize v0.10.0
|
||||
github.com/fluxcd/pkg/oci v0.15.0
|
||||
github.com/fluxcd/pkg/runtime v0.24.0
|
||||
|
|
@ -23,7 +26,6 @@ require (
|
|||
github.com/fluxcd/pkg/untar v0.2.0
|
||||
github.com/fluxcd/pkg/version v0.2.0
|
||||
github.com/fluxcd/source-controller/api v0.31.0
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/gonvenience/bunt v1.3.4
|
||||
github.com/gonvenience/ytbx v1.4.4
|
||||
github.com/google/go-cmp v0.5.9
|
||||
|
|
@ -87,7 +89,7 @@ require (
|
|||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
|
@ -106,7 +108,6 @@ require (
|
|||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fluxcd/go-git/v5 v5.0.0-20221104190732-329fd6659b10 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/kustomize v0.7.0 // indirect
|
||||
github.com/fluxcd/pkg/tar v0.2.0 // indirect
|
||||
|
|
@ -114,6 +115,7 @@ require (
|
|||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
|
|
@ -181,6 +183,7 @@ require (
|
|||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect
|
||||
|
|
|
|||
11
go.sum
11
go.sum
|
|
@ -136,8 +136,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
|||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/circl v1.3.0 h1:Anq00jxDtoyX3+aCaYUZ0vXC5r4k4epberfWGDXV1zE=
|
||||
github.com/cloudflare/circl v1.3.0/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
|
|
@ -200,6 +201,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
|||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
|
||||
github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
|
||||
github.com/fluxcd/go-git-providers v0.9.0 h1:iiiKe3dzRCgVkjEi8rfpvaWTZwuGRfb3RJFhjSNz+Uc=
|
||||
github.com/fluxcd/go-git-providers v0.9.0/go.mod h1:GcaDVP9RlamLKJp25Nwi7X5t0MyZV3FY+qy1GiRdbtk=
|
||||
github.com/fluxcd/go-git/v5 v5.0.0-20221104190732-329fd6659b10 h1:au798417R1iWtgcgKe3Dg495mexQmuxelL+NebAtexE=
|
||||
|
|
@ -220,6 +222,11 @@ github.com/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwU
|
|||
github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg=
|
||||
github.com/fluxcd/pkg/apis/meta v0.18.0 h1:s0LeulWcQ4DxVX6805vgDTxlA6bAYk+Lq1QHSnNdqLM=
|
||||
github.com/fluxcd/pkg/apis/meta v0.18.0/go.mod h1:pYvXRFi1UKNNrGR34jw3uqOnMXw9X6dTkML8j5Z7tis=
|
||||
github.com/fluxcd/pkg/git v0.7.0 h1:sQHRpFMcOzEdqlyGMjFv2LKMdcoE5xeUr2UcRrsLRG8=
|
||||
github.com/fluxcd/pkg/git v0.7.0/go.mod h1:3deiLPws4DSQ3hqwtQd7Dt66GXTN/4RcT/yHAljXaHo=
|
||||
github.com/fluxcd/pkg/git/gogit v0.2.0 h1:vhFzk2Pky4tDZwisx8+26YZumRDPxERnkV8l2dbLSoo=
|
||||
github.com/fluxcd/pkg/git/gogit v0.2.0/go.mod h1:d1RIwl6DVdU8/2dBIhw6n7GNokIKqs+b9cKc/8tz7ew=
|
||||
github.com/fluxcd/pkg/gittestserver v0.8.0 h1:YrYe63KScKlLxx0GAiQthx2XqHDx0vKitIIx4JnDtIo=
|
||||
github.com/fluxcd/pkg/kustomize v0.10.0 h1:EG5MbYrLtxeCiZxeFUgvyBhFZaXnKfeqqpg7O+J7o3s=
|
||||
github.com/fluxcd/pkg/kustomize v0.10.0/go.mod h1:awHID4OKe2/WAfTFg4u0fURXZPUkrIslSZNSPX9MEFQ=
|
||||
github.com/fluxcd/pkg/oci v0.15.0 h1:M8fiWveUPoUxZqvHc6om1/5tDYMOEdbJAURfKK7mGAA=
|
||||
|
|
@ -283,6 +290,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
|||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
|
@ -597,6 +605,7 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
|
|||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
||||
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ package bootstrap
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
gogit "github.com/fluxcd/go-git/v5"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
|
@ -40,21 +41,21 @@ import (
|
|||
runclient "github.com/fluxcd/pkg/runtime/client"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git"
|
||||
"github.com/fluxcd/flux2/pkg/log"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||
"github.com/fluxcd/flux2/pkg/status"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/repository"
|
||||
)
|
||||
|
||||
type PlainGitBootstrapper struct {
|
||||
url string
|
||||
branch string
|
||||
caBundle []byte
|
||||
url string
|
||||
branch string
|
||||
|
||||
author git.Author
|
||||
signature git.Signature
|
||||
commitMessageAppendix string
|
||||
|
||||
gpgKeyRing openpgp.EntityList
|
||||
|
|
@ -66,9 +67,9 @@ type PlainGitBootstrapper struct {
|
|||
|
||||
postGenerateSecret []PostGenerateSecretFunc
|
||||
|
||||
git git.Git
|
||||
kube client.Client
|
||||
logger log.Logger
|
||||
gitClient repository.Client
|
||||
kube client.Client
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type GitOption interface {
|
||||
|
|
@ -95,10 +96,10 @@ func (o postGenerateSecret) applyGit(b *PlainGitBootstrapper) {
|
|||
b.postGenerateSecret = append(b.postGenerateSecret, PostGenerateSecretFunc(o))
|
||||
}
|
||||
|
||||
func NewPlainGitProvider(git git.Git, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) {
|
||||
func NewPlainGitProvider(git repository.Client, kube client.Client, opts ...GitOption) (*PlainGitBootstrapper, error) {
|
||||
b := &PlainGitBootstrapper{
|
||||
git: git,
|
||||
kube: kube,
|
||||
gitClient: git,
|
||||
kube: kube,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.applyGit(b)
|
||||
|
|
@ -108,7 +109,7 @@ func NewPlainGitProvider(git git.Git, kube client.Client, opts ...GitOption) (*P
|
|||
|
||||
func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifestsBase string, options install.Options, _ sourcesecret.Options) error {
|
||||
// Clone if not already
|
||||
if _, err := b.git.Status(); err != nil {
|
||||
if _, err := b.gitClient.Head(); err != nil {
|
||||
if err != git.ErrNoGitRepository {
|
||||
return err
|
||||
}
|
||||
|
|
@ -116,7 +117,17 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
|
|||
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
|
||||
var cloned bool
|
||||
if err = retry(1, 2*time.Second, func() (err error) {
|
||||
cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
|
||||
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{
|
||||
CheckoutStrategy: repository.CheckoutStrategy{
|
||||
Branch: b.branch,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
b.logger.Warningf(" clone failure: %s", err)
|
||||
}
|
||||
if err == nil {
|
||||
cloned = true
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
|
|
@ -134,28 +145,33 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
|
|||
}
|
||||
b.logger.Successf("generated component manifests")
|
||||
|
||||
// Write manifest to Git repository
|
||||
if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
|
||||
return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
|
||||
// Write generated files and make a commit
|
||||
var signer *openpgp.Entity
|
||||
if b.gpgKeyRing != nil {
|
||||
signer, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate OpenPGP entity: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Git commit generated
|
||||
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
|
||||
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
|
||||
if b.commitMessageAppendix != "" {
|
||||
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
|
||||
}
|
||||
commit, err := b.git.Commit(git.Commit{
|
||||
Author: b.author,
|
||||
|
||||
commit, err := b.gitClient.Commit(git.Commit{
|
||||
Author: b.signature,
|
||||
Message: commitMsg,
|
||||
}, gpgOpts)
|
||||
}, repository.WithFiles(map[string]io.Reader{
|
||||
manifests.Path: strings.NewReader(manifests.Content),
|
||||
}), repository.WithSigner(signer))
|
||||
if err != nil && err != git.ErrNoStagedFiles {
|
||||
return fmt.Errorf("failed to commit sync manifests: %w", err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
|
||||
b.logger.Actionf("pushing component manifests to %q", b.url)
|
||||
if err = b.git.Push(ctx, b.caBundle); err != nil {
|
||||
if err = b.gitClient.Push(ctx); err != nil {
|
||||
return fmt.Errorf("failed to push manifests: %w", err)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -166,16 +182,16 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
|
|||
if mustInstallManifests(ctx, b.kube, options.Namespace) {
|
||||
b.logger.Actionf("installing components in %q namespace", options.Namespace)
|
||||
|
||||
componentsYAML := filepath.Join(b.git.Path(), manifests.Path)
|
||||
componentsYAML := filepath.Join(b.gitClient.Path(), manifests.Path)
|
||||
kfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName())
|
||||
if _, err := os.Stat(kfile); err == nil {
|
||||
// Apply the components and their patches
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), kfile); err != nil {
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), kfile); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Apply the CRDs and controllers
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), componentsYAML); err != nil {
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), componentsYAML); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -237,12 +253,19 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
|
|||
}
|
||||
|
||||
// Clone if not already
|
||||
if _, err := b.git.Status(); err != nil {
|
||||
if _, err := b.gitClient.Head(); err != nil {
|
||||
if err == git.ErrNoGitRepository {
|
||||
b.logger.Actionf("cloning branch %q from Git repository %q", b.branch, b.url)
|
||||
var cloned bool
|
||||
if err = retry(1, 2*time.Second, func() (err error) {
|
||||
cloned, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
|
||||
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{
|
||||
CheckoutStrategy: repository.CheckoutStrategy{
|
||||
Branch: b.branch,
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
cloned = true
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
|
|
@ -260,41 +283,47 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
|
|||
if err != nil {
|
||||
return fmt.Errorf("sync manifests generation failed: %w", err)
|
||||
}
|
||||
if err = b.git.Write(manifests.Path, strings.NewReader(manifests.Content)); err != nil {
|
||||
return fmt.Errorf("failed to write manifest %q: %w", manifests.Path, err)
|
||||
}
|
||||
|
||||
// Create secure Kustomize FS
|
||||
fs, err := filesys.MakeFsOnDiskSecureBuild(b.git.Path())
|
||||
fs, err := filesys.MakeFsOnDiskSecureBuild(b.gitClient.Path())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize Kustomize file system: %w", err)
|
||||
}
|
||||
|
||||
if err = fs.WriteFile(filepath.Join(b.gitClient.Path(), manifests.Path), []byte(manifests.Content)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate Kustomization
|
||||
kusManifests, err := kustomization.Generate(kustomization.Options{
|
||||
FileSystem: fs,
|
||||
BaseDir: b.git.Path(),
|
||||
BaseDir: b.gitClient.Path(),
|
||||
TargetPath: filepath.Dir(manifests.Path),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s generation failed: %w", konfig.DefaultKustomizationFileName(), err)
|
||||
}
|
||||
if err = b.git.Write(kusManifests.Path, strings.NewReader(kusManifests.Content)); err != nil {
|
||||
return fmt.Errorf("failed to write manifest %q: %w", kusManifests.Path, err)
|
||||
}
|
||||
b.logger.Successf("generated sync manifests")
|
||||
|
||||
// Git commit generated
|
||||
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
|
||||
// Write generated files and make a commit
|
||||
var signer *openpgp.Entity
|
||||
if b.gpgKeyRing != nil {
|
||||
signer, err = getOpenPgpEntity(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate OpenPGP entity: %w", err)
|
||||
}
|
||||
}
|
||||
commitMsg := fmt.Sprintf("Add Flux sync manifests")
|
||||
if b.commitMessageAppendix != "" {
|
||||
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
|
||||
}
|
||||
commit, err := b.git.Commit(git.Commit{
|
||||
Author: b.author,
|
||||
Message: commitMsg,
|
||||
}, gpgOpts)
|
||||
|
||||
commit, err := b.gitClient.Commit(git.Commit{
|
||||
Author: b.signature,
|
||||
Message: commitMsg,
|
||||
}, repository.WithFiles(map[string]io.Reader{
|
||||
kusManifests.Path: strings.NewReader(kusManifests.Content),
|
||||
}), repository.WithSigner(signer))
|
||||
if err != nil && err != git.ErrNoStagedFiles {
|
||||
return fmt.Errorf("failed to commit sync manifests: %w", err)
|
||||
}
|
||||
|
|
@ -302,18 +331,22 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
|
|||
if err == nil {
|
||||
b.logger.Successf("committed sync manifests to %q (%q)", b.branch, commit)
|
||||
b.logger.Actionf("pushing sync manifests to %q", b.url)
|
||||
err = b.git.Push(ctx, b.caBundle)
|
||||
err = b.gitClient.Push(ctx)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), gogit.ErrNonFastForwardUpdate.Error()) {
|
||||
b.logger.Waitingf("git conflict detected, retrying with a fresh clone")
|
||||
if err := os.RemoveAll(b.git.Path()); err != nil {
|
||||
if err := os.RemoveAll(b.gitClient.Path()); err != nil {
|
||||
return fmt.Errorf("failed to remove tmp dir: %w", err)
|
||||
}
|
||||
if err := os.Mkdir(b.git.Path(), 0o700); err != nil {
|
||||
if err := os.Mkdir(b.gitClient.Path(), 0o700); err != nil {
|
||||
return fmt.Errorf("failed to recreate tmp dir: %w", err)
|
||||
}
|
||||
if err = retry(1, 2*time.Second, func() (err error) {
|
||||
_, err = b.git.Clone(ctx, b.url, b.branch, b.caBundle)
|
||||
_, err = b.gitClient.Clone(ctx, b.url, repository.CloneOptions{
|
||||
CheckoutStrategy: repository.CheckoutStrategy{
|
||||
Branch: b.branch,
|
||||
},
|
||||
})
|
||||
return
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
|
|
@ -328,7 +361,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
|
|||
|
||||
// Apply to cluster
|
||||
b.logger.Actionf("applying sync manifests")
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), filepath.Join(b.git.Path(), kusManifests.Path)); err != nil {
|
||||
if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.gitClient.Path(), filepath.Join(b.gitClient.Path(), kusManifests.Path)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -338,7 +371,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
|
|||
}
|
||||
|
||||
func (b *PlainGitBootstrapper) ReportKustomizationHealth(ctx context.Context, options sync.Options, pollInterval, timeout time.Duration) error {
|
||||
head, err := b.git.Head()
|
||||
head, err := b.gitClient.Head()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -390,3 +423,42 @@ func (b *PlainGitBootstrapper) ReportComponentsHealth(ctx context.Context, insta
|
|||
b.logger.Successf("all components are healthy")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOpenPgpEntity(keyRing openpgp.EntityList, passphrase, keyID string) (*openpgp.Entity, error) {
|
||||
if len(keyRing) == 0 {
|
||||
return nil, fmt.Errorf("empty GPG key ring")
|
||||
}
|
||||
|
||||
var entity *openpgp.Entity
|
||||
if keyID != "" {
|
||||
if strings.HasPrefix(keyID, "0x") {
|
||||
keyID = strings.TrimPrefix(keyID, "0x")
|
||||
}
|
||||
if len(keyID) != 16 {
|
||||
return nil, fmt.Errorf("invalid GPG key id length; expected %d, got %d", 16, len(keyID))
|
||||
}
|
||||
keyID = strings.ToUpper(keyID)
|
||||
|
||||
for _, ent := range keyRing {
|
||||
if ent.PrimaryKey.KeyIdString() == keyID {
|
||||
entity = ent
|
||||
}
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
return nil, fmt.Errorf("no GPG keyring matching key id '%s' found", keyID)
|
||||
}
|
||||
if entity.PrivateKey == nil {
|
||||
return nil, fmt.Errorf("keyring does not contain private key for key id '%s'", keyID)
|
||||
}
|
||||
} else {
|
||||
entity = keyRing[0]
|
||||
}
|
||||
|
||||
err := entity.PrivateKey.Decrypt([]byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ import (
|
|||
|
||||
"github.com/fluxcd/go-git-providers/gitprovider"
|
||||
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/provider"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||
"github.com/fluxcd/pkg/git/repository"
|
||||
)
|
||||
|
||||
type GitProviderBootstrapper struct {
|
||||
|
|
@ -62,11 +62,12 @@ type GitProviderBootstrapper struct {
|
|||
provider gitprovider.Client
|
||||
}
|
||||
|
||||
func NewGitProviderBootstrapper(git git.Git, provider gitprovider.Client, kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
|
||||
func NewGitProviderBootstrapper(git repository.Client, provider gitprovider.Client,
|
||||
kube client.Client, opts ...GitProviderOption) (*GitProviderBootstrapper, error) {
|
||||
b := &GitProviderBootstrapper{
|
||||
PlainGitBootstrapper: &PlainGitBootstrapper{
|
||||
git: git,
|
||||
kube: kube,
|
||||
gitClient: git,
|
||||
kube: kube,
|
||||
},
|
||||
bootstrapTransportType: "https",
|
||||
syncTransportType: "ssh",
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
)
|
||||
|
||||
// Option is a some configuration that modifies options for a commit.
|
||||
type Option interface {
|
||||
// ApplyToCommit applies this configuration to a given commit option.
|
||||
ApplyToCommit(*CommitOptions)
|
||||
}
|
||||
|
||||
// CommitOptions contains options for making a commit.
|
||||
type CommitOptions struct {
|
||||
*GPGSigningInfo
|
||||
}
|
||||
|
||||
// GPGSigningInfo contains information for signing a commit.
|
||||
type GPGSigningInfo struct {
|
||||
KeyRing openpgp.EntityList
|
||||
Passphrase string
|
||||
KeyID string
|
||||
}
|
||||
|
||||
type GpgSigningOption struct {
|
||||
*GPGSigningInfo
|
||||
}
|
||||
|
||||
func (w GpgSigningOption) ApplyToCommit(in *CommitOptions) {
|
||||
in.GPGSigningInfo = w.GPGSigningInfo
|
||||
}
|
||||
|
||||
func WithGpgSigningOption(keyRing openpgp.EntityList, passphrase, keyID string) Option {
|
||||
// Return nil if no path is set, even if other options are configured.
|
||||
if len(keyRing) == 0 {
|
||||
return GpgSigningOption{}
|
||||
}
|
||||
|
||||
return GpgSigningOption{
|
||||
GPGSigningInfo: &GPGSigningInfo{
|
||||
KeyRing: keyRing,
|
||||
Passphrase: passphrase,
|
||||
KeyID: keyID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
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 git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoGitRepository = errors.New("no git repository")
|
||||
ErrNoStagedFiles = errors.New("no staged files")
|
||||
)
|
||||
|
||||
type Author struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Author
|
||||
Hash string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Git is an interface for basic Git operations on a single branch of a
|
||||
// remote repository.
|
||||
type Git interface {
|
||||
Init(url, branch string) (bool, error)
|
||||
Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error)
|
||||
Write(path string, reader io.Reader) error
|
||||
Commit(message Commit, options ...Option) (string, error)
|
||||
Push(ctx context.Context, caBundle []byte) error
|
||||
Status() (bool, error)
|
||||
Head() (string, error)
|
||||
Path() string
|
||||
}
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
/*
|
||||
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 gogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git"
|
||||
)
|
||||
|
||||
type GoGit struct {
|
||||
path string
|
||||
auth transport.AuthMethod
|
||||
repository *gogit.Repository
|
||||
}
|
||||
|
||||
type CommitOptions struct {
|
||||
GpgKeyPath string
|
||||
GpgKeyPassphrase string
|
||||
KeyID string
|
||||
}
|
||||
|
||||
func New(path string, auth transport.AuthMethod) *GoGit {
|
||||
return &GoGit{
|
||||
path: path,
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GoGit) Init(url, branch string) (bool, error) {
|
||||
if g.repository != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
r, err := gogit.PlainInit(g.path, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if _, err = r.CreateRemote(&config.RemoteConfig{
|
||||
Name: gogit.DefaultRemoteName,
|
||||
URLs: []string{url},
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
branchRef := plumbing.NewBranchReferenceName(branch)
|
||||
if err = r.CreateBranch(&config.Branch{
|
||||
Name: branch,
|
||||
Remote: gogit.DefaultRemoteName,
|
||||
Merge: branchRef,
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// PlainInit assumes the initial branch to always be master, we can
|
||||
// overwrite this by setting the reference of the Storer to a new
|
||||
// symbolic reference (as there are no commits yet) that points
|
||||
// the HEAD to our new branch.
|
||||
if err = r.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, branchRef)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
g.repository = r
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (g *GoGit) Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error) {
|
||||
branchRef := plumbing.NewBranchReferenceName(branch)
|
||||
r, err := gogit.PlainCloneContext(ctx, g.path, false, &gogit.CloneOptions{
|
||||
URL: url,
|
||||
Auth: g.auth,
|
||||
RemoteName: gogit.DefaultRemoteName,
|
||||
ReferenceName: branchRef,
|
||||
SingleBranch: true,
|
||||
|
||||
NoCheckout: false,
|
||||
Progress: nil,
|
||||
Tags: gogit.NoTags,
|
||||
CABundle: caBundle,
|
||||
})
|
||||
if err != nil {
|
||||
if err == transport.ErrEmptyRemoteRepository || isRemoteBranchNotFoundErr(err, branchRef.String()) {
|
||||
return g.Init(url, branch)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
g.repository = r
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (g *GoGit) Write(path string, reader io.Reader) error {
|
||||
if g.repository == nil {
|
||||
return git.ErrNoGitRepository
|
||||
}
|
||||
|
||||
wt, err := g.repository.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := wt.Filesystem.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *GoGit) Commit(message git.Commit, opts ...git.Option) (string, error) {
|
||||
if g.repository == nil {
|
||||
return "", git.ErrNoGitRepository
|
||||
}
|
||||
|
||||
wt, err := g.repository.Worktree()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// apply the options
|
||||
options := &git.CommitOptions{}
|
||||
for _, opt := range opts {
|
||||
opt.ApplyToCommit(options)
|
||||
}
|
||||
|
||||
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
|
||||
// whereby it thinks broken symlinks to absolute paths are
|
||||
// modified. There's no circumstance in which we want to commit a
|
||||
// change to a broken symlink: so, detect and skip those.
|
||||
var changed bool
|
||||
for file, _ := range status {
|
||||
abspath := filepath.Join(g.path, file)
|
||||
info, err := os.Lstat(abspath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking if %s is a symlink: %w", file, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink > 0 {
|
||||
// symlinks are OK; broken symlinks are probably a result
|
||||
// of the bug mentioned above, but not of interest in any
|
||||
// case.
|
||||
if _, err := os.Stat(abspath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
_, _ = wt.Add(file)
|
||||
changed = true
|
||||
}
|
||||
|
||||
if !changed {
|
||||
head, err := g.repository.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return head.Hash().String(), git.ErrNoStagedFiles
|
||||
}
|
||||
|
||||
commitOpts := &gogit.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: message.Name,
|
||||
Email: message.Email,
|
||||
When: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
if options.GPGSigningInfo != nil {
|
||||
entity, err := getOpenPgpEntity(*options.GPGSigningInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
commitOpts.SignKey = entity
|
||||
}
|
||||
|
||||
commit, err := wt.Commit(message.Message, commitOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return commit.String(), nil
|
||||
}
|
||||
|
||||
func (g *GoGit) Push(ctx context.Context, caBundle []byte) error {
|
||||
if g.repository == nil {
|
||||
return git.ErrNoGitRepository
|
||||
}
|
||||
|
||||
return g.repository.PushContext(ctx, &gogit.PushOptions{
|
||||
RemoteName: gogit.DefaultRemoteName,
|
||||
Auth: g.auth,
|
||||
Progress: nil,
|
||||
CABundle: caBundle,
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GoGit) Status() (bool, error) {
|
||||
if g.repository == nil {
|
||||
return false, git.ErrNoGitRepository
|
||||
}
|
||||
wt, err := g.repository.Worktree()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return status.IsClean(), nil
|
||||
}
|
||||
|
||||
func (g *GoGit) Head() (string, error) {
|
||||
if g.repository == nil {
|
||||
return "", git.ErrNoGitRepository
|
||||
}
|
||||
head, err := g.repository.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return head.Hash().String(), nil
|
||||
}
|
||||
|
||||
func (g *GoGit) Path() string {
|
||||
return g.path
|
||||
}
|
||||
|
||||
func isRemoteBranchNotFoundErr(err error, ref string) bool {
|
||||
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
|
||||
}
|
||||
|
||||
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
|
||||
if len(info.KeyRing) == 0 {
|
||||
return nil, fmt.Errorf("empty GPG key ring")
|
||||
}
|
||||
|
||||
var entity *openpgp.Entity
|
||||
if info.KeyID != "" {
|
||||
for _, ent := range info.KeyRing {
|
||||
if ent.PrimaryKey.KeyIdString() == info.KeyID {
|
||||
entity = ent
|
||||
}
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
return nil, fmt.Errorf("no GPG private key matching key id '%s' found", info.KeyID)
|
||||
}
|
||||
} else {
|
||||
entity = info.KeyRing[0]
|
||||
}
|
||||
|
||||
err := entity.PrivateKey.Decrypt([]byte(info.Passphrase))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
|
||||
}
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git"
|
||||
)
|
||||
|
||||
func TestGetOpenPgpEntity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyPath string
|
||||
passphrase string
|
||||
id string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "no default key id given",
|
||||
keyPath: "testdata/private.key",
|
||||
passphrase: "flux",
|
||||
id: "",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "key id given",
|
||||
keyPath: "testdata/private.key",
|
||||
passphrase: "flux",
|
||||
id: "0619327DBD777415",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "wrong key id",
|
||||
keyPath: "testdata/private.key",
|
||||
passphrase: "flux",
|
||||
id: "0619327DBD777416",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong password",
|
||||
keyPath: "testdata/private.key",
|
||||
passphrase: "fluxe",
|
||||
id: "0619327DBD777415",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var entityList openpgp.EntityList
|
||||
if tt.keyPath != "" {
|
||||
r, err := os.Open(tt.keyPath)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
entityList, err = openpgp.ReadKeyRing(r)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
gpgInfo := git.GPGSigningInfo{
|
||||
KeyRing: entityList,
|
||||
Passphrase: tt.passphrase,
|
||||
KeyID: tt.id,
|
||||
}
|
||||
|
||||
_, err := getOpenPgpEntity(gpgInfo)
|
||||
if err != nil && !tt.expectErr {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if err == nil && tt.expectErr {
|
||||
t.Errorf("expected error when %s", tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
BIN
pkg/bootstrap/git/gogit/testdata/private.key
vendored
BIN
pkg/bootstrap/git/gogit/testdata/private.key
vendored
Binary file not shown.
|
|
@ -23,9 +23,9 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
runclient "github.com/fluxcd/pkg/runtime/client"
|
||||
|
||||
"github.com/fluxcd/flux2/pkg/bootstrap/git"
|
||||
"github.com/fluxcd/flux2/pkg/log"
|
||||
)
|
||||
|
||||
|
|
@ -48,42 +48,28 @@ func (o branchOption) applyGitProvider(b *GitProviderBootstrapper) {
|
|||
o.applyGit(b.PlainGitBootstrapper)
|
||||
}
|
||||
|
||||
func WithAuthor(name, email string) Option {
|
||||
return authorOption{
|
||||
func WithSignature(name, email string) Option {
|
||||
return signatureOption{
|
||||
Name: name,
|
||||
Email: email,
|
||||
}
|
||||
}
|
||||
|
||||
type authorOption git.Author
|
||||
type signatureOption git.Signature
|
||||
|
||||
func (o authorOption) applyGit(b *PlainGitBootstrapper) {
|
||||
func (o signatureOption) applyGit(b *PlainGitBootstrapper) {
|
||||
if o.Name != "" {
|
||||
b.author.Name = o.Name
|
||||
b.signature.Name = o.Name
|
||||
}
|
||||
if o.Email != "" {
|
||||
b.author.Email = o.Email
|
||||
b.signature.Email = o.Email
|
||||
}
|
||||
}
|
||||
|
||||
func (o authorOption) applyGitProvider(b *GitProviderBootstrapper) {
|
||||
func (o signatureOption) applyGitProvider(b *GitProviderBootstrapper) {
|
||||
o.applyGit(b.PlainGitBootstrapper)
|
||||
}
|
||||
|
||||
func WithCABundle(b []byte) Option {
|
||||
return caBundleOption(b)
|
||||
}
|
||||
|
||||
type caBundleOption []byte
|
||||
|
||||
func (o caBundleOption) applyGit(b *PlainGitBootstrapper) {
|
||||
b.caBundle = o
|
||||
}
|
||||
|
||||
func (o caBundleOption) applyGitProvider(b *GitProviderBootstrapper) {
|
||||
b.caBundle = o
|
||||
}
|
||||
|
||||
func WithCommitMessageAppendix(appendix string) Option {
|
||||
return commitMessageAppendixOption(appendix)
|
||||
}
|
||||
|
|
@ -169,7 +155,7 @@ func LoadEntityListFromPath(path string) (openpgp.EntityList, error) {
|
|||
}
|
||||
entityList, err := openpgp.ReadKeyRing(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to read GPG key ring: %w", err)
|
||||
}
|
||||
return entityList, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
|
|||
|
||||
var hostKey []byte
|
||||
if keypair != nil {
|
||||
if hostKey, err = scanHostKey(options.SSHHostname); err != nil {
|
||||
if hostKey, err = ScanHostKey(options.SSHHostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ func generateKeyPair(options Options) (*ssh.KeyPair, error) {
|
|||
return pair, nil
|
||||
}
|
||||
|
||||
func scanHostKey(host string) ([]byte, error) {
|
||||
func ScanHostKey(host string) ([]byte, error) {
|
||||
if _, _, err := net.SplitHostPort(host); err != nil {
|
||||
// Assume we are dealing with a hostname without a port,
|
||||
// append the default SSH port as this is required for
|
||||
|
|
|
|||
Loading…
Reference in a new issue