Skip to content

Commit 29a39fc

Browse files
committed
feat: add gitea bootstrapper
1 parent d3eacd4 commit 29a39fc

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

cmd/flux/bootstrap_gitea.go

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/*
2+
Copyright 2020 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"time"
24+
25+
"github.com/fluxcd/pkg/git"
26+
"github.com/fluxcd/pkg/git/gogit"
27+
"github.com/spf13/cobra"
28+
29+
"github.com/fluxcd/flux2/v2/internal/flags"
30+
"github.com/fluxcd/flux2/v2/internal/utils"
31+
"github.com/fluxcd/flux2/v2/pkg/bootstrap"
32+
"github.com/fluxcd/flux2/v2/pkg/bootstrap/provider"
33+
"github.com/fluxcd/flux2/v2/pkg/manifestgen"
34+
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install"
35+
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
36+
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sync"
37+
)
38+
39+
var bootstrapGiteaCmd = &cobra.Command{
40+
Use: "gitea",
41+
Short: "Deploy Flux on a cluster connected to a Gitea repository",
42+
Long: `The bootstrap gitea command creates the Gitea repository if it doesn't exists and
43+
commits the Flux manifests to the specified branch.
44+
Then it configures the target cluster to synchronize with that repository.
45+
If the Flux components are present on the cluster,
46+
the bootstrap command will perform an upgrade if needed.`,
47+
Example: ` # Create a Gitea personal access token and export it as an env var
48+
export GITEA_TOKEN=<my-token>
49+
50+
# Run bootstrap for a private repository owned by a Gitea organization
51+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --path=clusters/my-cluster
52+
53+
# Run bootstrap for a private repository and assign organization teams to it
54+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug> --team=<team2 slug> --path=clusters/my-cluster
55+
56+
# Run bootstrap for a private repository and assign organization teams with their access level(e.g maintain, admin) to it
57+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --team=<team1 slug>:<access-level> --path=clusters/my-cluster
58+
59+
# Run bootstrap for a public repository on a personal account
60+
flux bootstrap gitea --owner=<user> --repository=<repository name> --private=false --personal=true --path=clusters/my-cluster
61+
62+
# Run bootstrap for a private repository hosted on Gitea Enterprise using SSH auth
63+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --ssh-hostname=<domain> --path=clusters/my-cluster
64+
65+
# Run bootstrap for a private repository hosted on Gitea Enterprise using HTTPS auth
66+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --hostname=<domain> --token-auth --path=clusters/my-cluster
67+
68+
# Run bootstrap for an existing repository with a branch named main
69+
flux bootstrap gitea --owner=<organization> --repository=<repository name> --branch=main --path=clusters/my-cluster`,
70+
RunE: bootstrapGiteaCmdRun,
71+
}
72+
73+
type giteaFlags struct {
74+
owner string
75+
repository string
76+
interval time.Duration
77+
personal bool
78+
private bool
79+
hostname string
80+
path flags.SafeRelativePath
81+
teams []string
82+
readWriteKey bool
83+
reconcile bool
84+
}
85+
86+
const (
87+
gtDefaultPermission = "maintain"
88+
gtDefaultDomain = "gitea.com"
89+
gtTokenEnvVar = "GITEA_TOKEN"
90+
)
91+
92+
var giteaArgs giteaFlags
93+
94+
func init() {
95+
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.owner, "owner", "", "Gitea user or organization name")
96+
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.repository, "repository", "", "Gitea repository name")
97+
bootstrapGiteaCmd.Flags().StringSliceVar(&giteaArgs.teams, "team", []string{}, "Gitea team and the access to be given to it(team:maintain). Defaults to maintainer access if no access level is specified (also accepts comma-separated values)")
98+
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.personal, "personal", false, "if true, the owner is assumed to be a Gitea user; otherwise an org")
99+
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.private, "private", true, "if true, the repository is setup or configured as private")
100+
bootstrapGiteaCmd.Flags().DurationVar(&giteaArgs.interval, "interval", time.Minute, "sync interval")
101+
bootstrapGiteaCmd.Flags().StringVar(&giteaArgs.hostname, "hostname", gtDefaultDomain, "Gitea hostname")
102+
bootstrapGiteaCmd.Flags().Var(&giteaArgs.path, "path", "path relative to the repository root, when specified the cluster sync will be scoped to this path")
103+
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions")
104+
bootstrapGiteaCmd.Flags().BoolVar(&giteaArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists")
105+
106+
bootstrapCmd.AddCommand(bootstrapGiteaCmd)
107+
}
108+
109+
func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error {
110+
gtToken := os.Getenv(gtTokenEnvVar)
111+
if gtToken == "" {
112+
var err error
113+
gtToken, err = readPasswordFromStdin("Please enter your Gitea personal access token (PAT): ")
114+
if err != nil {
115+
return fmt.Errorf("could not read token: %w", err)
116+
}
117+
}
118+
119+
if err := bootstrapValidate(); err != nil {
120+
return err
121+
}
122+
123+
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
124+
defer cancel()
125+
126+
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
127+
if err != nil {
128+
return err
129+
}
130+
131+
// Manifest base
132+
if ver, err := getVersion(bootstrapArgs.version); err != nil {
133+
return err
134+
} else {
135+
bootstrapArgs.version = ver
136+
}
137+
manifestsBase, err := buildEmbeddedManifestBase()
138+
if err != nil {
139+
return err
140+
}
141+
defer os.RemoveAll(manifestsBase)
142+
143+
var caBundle []byte
144+
if bootstrapArgs.caFile != "" {
145+
var err error
146+
caBundle, err = os.ReadFile(bootstrapArgs.caFile)
147+
if err != nil {
148+
return fmt.Errorf("unable to read TLS CA file: %w", err)
149+
}
150+
}
151+
// Build Gitea provider
152+
providerCfg := provider.Config{
153+
Provider: provider.GitProviderGitea,
154+
Hostname: giteaArgs.hostname,
155+
Token: gtToken,
156+
CaBundle: caBundle,
157+
}
158+
providerClient, err := provider.BuildGitProvider(providerCfg)
159+
if err != nil {
160+
return err
161+
}
162+
163+
tmpDir, err := manifestgen.MkdirTempAbs("", "flux-bootstrap-")
164+
if err != nil {
165+
return fmt.Errorf("failed to create temporary working dir: %w", err)
166+
}
167+
defer os.RemoveAll(tmpDir)
168+
169+
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage(), gogit.WithFallbackToDefaultKnownHosts()}
170+
gitClient, err := gogit.NewClient(tmpDir, &git.AuthOptions{
171+
Transport: git.HTTPS,
172+
Username: giteaArgs.owner,
173+
Password: gtToken,
174+
CAFile: caBundle,
175+
}, clientOpts...)
176+
if err != nil {
177+
return fmt.Errorf("failed to create a Git client: %w", err)
178+
}
179+
180+
// Install manifest config
181+
installOptions := install.Options{
182+
BaseURL: rootArgs.defaults.BaseURL,
183+
Version: bootstrapArgs.version,
184+
Namespace: *kubeconfigArgs.Namespace,
185+
Components: bootstrapComponents(),
186+
Registry: bootstrapArgs.registry,
187+
ImagePullSecret: bootstrapArgs.imagePullSecret,
188+
WatchAllNamespaces: bootstrapArgs.watchAllNamespaces,
189+
NetworkPolicy: bootstrapArgs.networkPolicy,
190+
LogLevel: bootstrapArgs.logLevel.String(),
191+
NotificationController: rootArgs.defaults.NotificationController,
192+
ManifestFile: rootArgs.defaults.ManifestFile,
193+
Timeout: rootArgs.timeout,
194+
TargetPath: giteaArgs.path.ToSlash(),
195+
ClusterDomain: bootstrapArgs.clusterDomain,
196+
TolerationKeys: bootstrapArgs.tolerationKeys,
197+
}
198+
if customBaseURL := bootstrapArgs.manifestsPath; customBaseURL != "" {
199+
installOptions.BaseURL = customBaseURL
200+
}
201+
202+
// Source generation and secret config
203+
secretOpts := sourcesecret.Options{
204+
Name: bootstrapArgs.secretName,
205+
Namespace: *kubeconfigArgs.Namespace,
206+
TargetPath: giteaArgs.path.ToSlash(),
207+
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
208+
}
209+
if bootstrapArgs.tokenAuth {
210+
secretOpts.Username = "git"
211+
secretOpts.Password = gtToken
212+
secretOpts.CAFile = caBundle
213+
} else {
214+
secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(bootstrapArgs.keyAlgorithm)
215+
secretOpts.RSAKeyBits = int(bootstrapArgs.keyRSABits)
216+
secretOpts.ECDSACurve = bootstrapArgs.keyECDSACurve.Curve
217+
218+
secretOpts.SSHHostname = giteaArgs.hostname
219+
if bootstrapArgs.sshHostname != "" {
220+
secretOpts.SSHHostname = bootstrapArgs.sshHostname
221+
}
222+
}
223+
224+
// Sync manifest config
225+
syncOpts := sync.Options{
226+
Interval: giteaArgs.interval,
227+
Name: *kubeconfigArgs.Namespace,
228+
Namespace: *kubeconfigArgs.Namespace,
229+
Branch: bootstrapArgs.branch,
230+
Secret: bootstrapArgs.secretName,
231+
TargetPath: giteaArgs.path.ToSlash(),
232+
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
233+
RecurseSubmodules: bootstrapArgs.recurseSubmodules,
234+
}
235+
236+
entityList, err := bootstrap.LoadEntityListFromPath(bootstrapArgs.gpgKeyRingPath)
237+
if err != nil {
238+
return err
239+
}
240+
241+
// Bootstrap config
242+
bootstrapOpts := []bootstrap.GitProviderOption{
243+
bootstrap.WithProviderRepository(giteaArgs.owner, giteaArgs.repository, giteaArgs.personal),
244+
bootstrap.WithBranch(bootstrapArgs.branch),
245+
bootstrap.WithBootstrapTransportType("https"),
246+
bootstrap.WithSignature(bootstrapArgs.authorName, bootstrapArgs.authorEmail),
247+
bootstrap.WithCommitMessageAppendix(bootstrapArgs.commitMessageAppendix),
248+
bootstrap.WithProviderTeamPermissions(mapTeamSlice(giteaArgs.teams, gtDefaultPermission)),
249+
bootstrap.WithReadWriteKeyPermissions(giteaArgs.readWriteKey),
250+
bootstrap.WithKubeconfig(kubeconfigArgs, kubeclientOptions),
251+
bootstrap.WithLogger(logger),
252+
bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
253+
}
254+
if bootstrapArgs.sshHostname != "" {
255+
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname))
256+
}
257+
if bootstrapArgs.tokenAuth {
258+
bootstrapOpts = append(bootstrapOpts, bootstrap.WithSyncTransportType("https"))
259+
}
260+
if !giteaArgs.private {
261+
bootstrapOpts = append(bootstrapOpts, bootstrap.WithProviderRepositoryConfig("", "", "public"))
262+
}
263+
if giteaArgs.reconcile {
264+
bootstrapOpts = append(bootstrapOpts, bootstrap.WithReconcile())
265+
}
266+
267+
// Setup bootstrapper with constructed configs
268+
b, err := bootstrap.NewGitProviderBootstrapper(gitClient, providerClient, kubeClient, bootstrapOpts...)
269+
if err != nil {
270+
return err
271+
}
272+
273+
// Run
274+
return bootstrap.Run(ctx, b, manifestsBase, installOptions, secretOpts, syncOpts, rootArgs.pollInterval, rootArgs.timeout)
275+
}

pkg/bootstrap/provider/factory.go

+14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package provider
1919
import (
2020
"fmt"
2121

22+
"github.com/fluxcd/go-git-providers/gitea"
2223
"github.com/fluxcd/go-git-providers/github"
2324
"github.com/fluxcd/go-git-providers/gitlab"
2425
"github.com/fluxcd/go-git-providers/gitprovider"
@@ -45,6 +46,19 @@ func BuildGitProvider(config Config) (gitprovider.Client, error) {
4546
if client, err = github.NewClient(opts...); err != nil {
4647
return nil, err
4748
}
49+
case GitProviderGitea:
50+
opts := []gitprovider.ClientOption{
51+
gitprovider.WithDomain(config.Hostname),
52+
}
53+
if config.Hostname != "" {
54+
opts = append(opts, gitprovider.WithDomain(config.Hostname))
55+
}
56+
if config.CaBundle != nil {
57+
opts = append(opts, gitprovider.WithCustomCAPostChainTransportHook(config.CaBundle))
58+
}
59+
if client, err = gitea.NewClient(config.Token); err != nil {
60+
return nil, err
61+
}
4862
case GitProviderGitLab:
4963
opts := []gitprovider.ClientOption{
5064
gitprovider.WithConditionalRequests(true),

pkg/bootstrap/provider/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type GitProvider string
2121

2222
const (
2323
GitProviderGitHub GitProvider = "github"
24+
GitProviderGitea GitProvider = "gitea"
2425
GitProviderGitLab GitProvider = "gitlab"
2526
GitProviderStash GitProvider = "stash"
2627
)

0 commit comments

Comments
 (0)