Skip to content
This repository was archived by the owner on May 9, 2025. It is now read-only.

Commit 1c0be8c

Browse files
committed
updated tiltfile and added github repositroy support
1 parent f971d50 commit 1c0be8c

File tree

9 files changed

+196
-34
lines changed

9 files changed

+196
-34
lines changed

Tiltfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ local_resource(
3939
"main.go",
4040
"go.mod",
4141
"go.sum",
42-
"api",
42+
"apis",
4343
"controllers",
4444
"pkg",
4545
],
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package v1alpha1
6+
7+
const (
8+
// RepositoryCreateFailedReason is used when we fail to create a Repository.
9+
RepositoryCreateFailedReason = "RepositoryCreateFailed"
10+
)

apis/mpas/v1alpha1/repository_types.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,24 @@ type Credentials struct {
2222

2323
// RepositorySpec defines the desired state of Repository
2424
type RepositorySpec struct {
25-
Provider string `json:"provider"`
26-
Owner string `json:"owner"`
27-
Repository string `json:"repository"`
28-
Credentials Credentials `json:"credentials"`
29-
Interval metav1.Duration `json:"interval"`
25+
Provider string `json:"provider"`
26+
Owner string `json:"owner"`
27+
RepositoryName string `json:"repositoryName"`
28+
Credentials Credentials `json:"credentials"`
3029

30+
//+optional
31+
Interval metav1.Duration `json:"interval,omitempty"`
32+
//+optional
33+
//+kubebuilder:default:=internal
34+
Visibility string `json:"visibility,omitempty"`
35+
//+kubebuilder:default:=true
36+
IsOrganization bool `json:"isOrganization,omitempty"`
37+
//+optional
38+
Domain string `json:"domain,omitempty"`
3139
//+optional
3240
Maintainers []string `json:"maintainers,omitempty"`
3341
//+optional
34-
//+kubebuilder:default=true;
42+
//+kubebuilder:default:=true
3543
AutomaticPullRequestCreation bool `json:"automaticPullRequestCreation,omitempty"`
3644
}
3745

config/crd/bases/mpas.ocm.software_repositories.yaml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ spec:
3636
description: RepositorySpec defines the desired state of Repository
3737
properties:
3838
automaticPullRequestCreation:
39-
default:
40-
- true
39+
default: true
4140
type: boolean
4241
credentials:
4342
description: Credentials contains ways of authenticating the creation
@@ -55,8 +54,13 @@ spec:
5554
required:
5655
- secretRef
5756
type: object
57+
domain:
58+
type: string
5859
interval:
5960
type: string
61+
isOrganization:
62+
default: true
63+
type: boolean
6064
maintainers:
6165
items:
6266
type: string
@@ -65,14 +69,17 @@ spec:
6569
type: string
6670
provider:
6771
type: string
68-
repository:
72+
repositoryName:
73+
type: string
74+
visibility:
75+
default: internal
6976
type: string
7077
required:
7178
- credentials
7279
- interval
7380
- owner
7481
- provider
75-
- repository
82+
- repositoryName
7683
type: object
7784
status:
7885
description: RepositoryStatus defines the observed state of Repository
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
apiVersion: mpas.ocm.software/v1alpha1
22
kind: Repository
33
metadata:
4-
labels:
5-
app.kubernetes.io/name: repository
6-
app.kubernetes.io/instance: repository-sample
7-
app.kubernetes.io/part-of: git-controller
8-
app.kubernetes.io/managed-by: kustomize
9-
app.kubernetes.io/created-by: git-controller
104
name: repository-sample
115
spec:
12-
# TODO(user): Add fields here
6+
credentials:
7+
secretRef:
8+
name: github-creds
9+
interval: 10m
10+
owner: Skarlso
11+
provider: github
12+
repositoryName: new-repository-1

controllers/mpas/repository_controller.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ import (
1616
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1717
"k8s.io/apimachinery/pkg/runtime"
1818
ctrl "sigs.k8s.io/controller-runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/builder"
1920
"sigs.k8s.io/controller-runtime/pkg/client"
2021
"sigs.k8s.io/controller-runtime/pkg/log"
22+
"sigs.k8s.io/controller-runtime/pkg/predicate"
2123

2224
mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
25+
"github.com/open-component-model/git-controller/pkg/providers"
2326
)
2427

2528
// RepositoryReconciler reconciles a Repository object
2629
type RepositoryReconciler struct {
2730
client.Client
28-
Scheme *runtime.Scheme
31+
Scheme *runtime.Scheme
32+
Provider providers.Provider
2933
}
3034

3135
//+kubebuilder:rbac:groups=mpas.ocm.software,resources=repositories,verbs=get;list;watch;create;update;patch;delete
@@ -41,7 +45,6 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
4145
)
4246

4347
logger := log.FromContext(ctx).WithName("repository")
44-
4548
logger.V(4).Info("entering repository loop...")
4649

4750
obj := &mpasv1alpha1.Repository{}
@@ -94,10 +97,10 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9497
// If not reconciling or stalled than mark Ready=True
9598
if !conditions.IsReconciling(obj) &&
9699
!conditions.IsStalled(obj) &&
97-
retErr == nil &&
98-
result.RequeueAfter == obj.GetRequeueAfter() {
100+
retErr == nil {
99101
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")
100102
}
103+
101104
// Set status observed generation option if the component is stalled or ready.
102105
if conditions.IsStalled(obj) || conditions.IsReady(obj) {
103106
obj.Status.ObservedGeneration = obj.Generation
@@ -109,17 +112,27 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
109112
}
110113
}()
111114

115+
// Remove any stale Ready condition, most likely False, set above. Its value
116+
// is derived from the overall result of the reconciliation in the deferred
117+
// block at the very end.
118+
conditions.Delete(obj, meta.ReadyCondition)
119+
112120
result, retErr = r.reconcile(ctx, obj)
113121
return result, retErr
114122
}
115123

116124
// SetupWithManager sets up the controller with the Manager.
117125
func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
118126
return ctrl.NewControllerManagedBy(mgr).
119-
For(&mpasv1alpha1.Repository{}).
127+
For(&mpasv1alpha1.Repository{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
120128
Complete(r)
121129
}
122130

123131
func (r *RepositoryReconciler) reconcile(ctx context.Context, obj *mpasv1alpha1.Repository) (ctrl.Result, error) {
132+
if err := r.Provider.CreateRepository(ctx, *obj); err != nil {
133+
conditions.MarkFalse(obj, meta.ReadyCondition, mpasv1alpha1.RepositoryCreateFailedReason, err.Error())
134+
return ctrl.Result{}, fmt.Errorf("failed to create repository: %w", err)
135+
}
136+
124137
return ctrl.Result{}, nil
125138
}

main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010

1111
"github.com/open-component-model/git-controller/pkg/gogit"
12+
"github.com/open-component-model/git-controller/pkg/providers/github"
1213
"k8s.io/apimachinery/pkg/runtime"
1314
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1415
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -30,7 +31,6 @@ import (
3031
var (
3132
scheme = runtime.NewScheme()
3233
setupLog = ctrl.Log.WithName("setup")
33-
ociAgent = "git-controller/v1alpha1"
3434
)
3535

3636
func init() {
@@ -100,9 +100,12 @@ func main() {
100100
setupLog.Error(err, "unable to create controller", "controller", "Sync")
101101
os.Exit(1)
102102
}
103+
104+
githubProvider := github.NewClient(mgr.GetClient(), nil)
103105
if err = (&mpascontrollers.RepositoryReconciler{
104-
Client: mgr.GetClient(),
105-
Scheme: mgr.GetScheme(),
106+
Client: mgr.GetClient(),
107+
Scheme: mgr.GetScheme(),
108+
Provider: githubProvider,
106109
}).SetupWithManager(mgr); err != nil {
107110
setupLog.Error(err, "unable to create controller", "controller", "Repository")
108111
os.Exit(1)

pkg/providers/github/github.go

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,142 @@ import (
55
"fmt"
66

77
"github.com/fluxcd/go-git-providers/github"
8+
"github.com/fluxcd/go-git-providers/gitprovider"
9+
v1 "k8s.io/api/core/v1"
10+
"k8s.io/apimachinery/pkg/types"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
"sigs.k8s.io/controller-runtime/pkg/log"
13+
14+
mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
815
"github.com/open-component-model/git-controller/pkg/providers"
916
)
1017

18+
const (
19+
tokenKey = "token"
20+
providerType = "github"
21+
defaultDomain = "github.com"
22+
)
23+
24+
// Client github.
1125
type Client struct {
12-
// TODO: Figure out how to get this.
13-
BaseURL string
26+
client client.Client
27+
next providers.Provider
28+
}
29+
30+
// TODO: Use this instead and somehow abstract the two clients.
31+
type RepositoryOpts struct {
32+
Owner string
33+
Domain string
34+
Visibility gitprovider.RepositoryVisibility
1435
}
1536

16-
func NewClient() *Client {
17-
return &Client{}
37+
// NewClient creates a new GitHub client.
38+
func NewClient(client client.Client, next providers.Provider) *Client {
39+
return &Client{
40+
client: client,
41+
next: next,
42+
}
1843
}
1944

2045
var _ providers.Provider = &Client{}
2146

22-
func (c *Client) CreateRepository(ctx context.Context, owner, repo string) error {
23-
_, err := github.NewClient()
47+
func (c *Client) CreateRepository(ctx context.Context, obj mpasv1alpha1.Repository) error {
48+
if obj.Spec.Provider != providerType {
49+
if c.next == nil {
50+
return fmt.Errorf("can't handle provider type '%s' and no next provider is configured", obj.Spec.Provider)
51+
}
52+
53+
return c.next.CreateRepository(ctx, obj)
54+
}
55+
56+
authenticationOption, err := c.constructAuthenticationOption(ctx, obj)
57+
if err != nil {
58+
return err
59+
}
60+
61+
gc, err := github.NewClient(authenticationOption)
2462
if err != nil {
2563
return fmt.Errorf("failed to create github client: %w", err)
2664
}
2765

66+
visibility := gitprovider.RepositoryVisibility(obj.Spec.Visibility)
67+
68+
if err := gitprovider.ValidateRepositoryVisibility(visibility); err != nil {
69+
return fmt.Errorf("failed to validate visibility: %w", err)
70+
}
71+
72+
domain := defaultDomain
73+
if obj.Spec.Domain != "" {
74+
domain = obj.Spec.Domain
75+
}
76+
77+
if obj.Spec.IsOrganization {
78+
return c.createOrganizationRepository(ctx, gc, domain, visibility, obj.Spec)
79+
}
80+
81+
return c.createUserRepository(ctx, gc, domain, visibility, obj.Spec)
82+
}
83+
84+
// constructAuthenticationOption will take the object and construct an authentication option.
85+
// For now, only token secret is supported, this will be extended in the future.
86+
func (c *Client) constructAuthenticationOption(ctx context.Context, obj mpasv1alpha1.Repository) (gitprovider.ClientOption, error) {
87+
secret := &v1.Secret{}
88+
if err := c.client.Get(ctx, types.NamespacedName{
89+
Name: obj.Spec.Credentials.SecretRef.Name,
90+
Namespace: obj.Namespace,
91+
}, secret); err != nil {
92+
return nil, fmt.Errorf("failed to get secret: %w", err)
93+
}
94+
95+
token, ok := secret.Data[tokenKey]
96+
if !ok {
97+
return nil, fmt.Errorf("token '%s' not found in secret", tokenKey)
98+
}
99+
100+
return gitprovider.WithOAuth2Token(string(token)), nil
101+
}
102+
103+
func (c *Client) createOrganizationRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error {
104+
logger := log.FromContext(ctx)
105+
106+
repo, err := gc.OrgRepositories().Create(ctx, gitprovider.OrgRepositoryRef{
107+
OrganizationRef: gitprovider.OrganizationRef{
108+
Domain: domain,
109+
Organization: spec.Owner,
110+
},
111+
RepositoryName: spec.RepositoryName,
112+
}, gitprovider.RepositoryInfo{
113+
DefaultBranch: gitprovider.StringVar("main"),
114+
Visibility: &visibility,
115+
})
116+
if err != nil {
117+
return fmt.Errorf("failed to create repository: %w", err)
118+
}
119+
120+
logger.Info("organization repository successfully created", "name", repo.Repository().String())
121+
122+
return nil
123+
}
124+
125+
func (c *Client) createUserRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error {
126+
logger := log.FromContext(ctx)
127+
128+
repo, err := gc.UserRepositories().Create(ctx, gitprovider.UserRepositoryRef{
129+
UserRef: gitprovider.UserRef{
130+
Domain: domain,
131+
UserLogin: spec.Owner,
132+
},
133+
RepositoryName: spec.RepositoryName,
134+
}, gitprovider.RepositoryInfo{
135+
DefaultBranch: gitprovider.StringVar("main"),
136+
Visibility: &visibility,
137+
})
138+
if err != nil {
139+
return fmt.Errorf("failed to create repository: %w", err)
140+
}
141+
142+
logger.Info("user repository successfully created", "name", repo.Repository().String())
143+
28144
return nil
29145
}
30146

pkg/providers/providers.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package providers
22

3-
import "context"
3+
import (
4+
"context"
45

6+
mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
7+
)
8+
9+
// Provider adds the ability to create repositories and pull requests.
510
type Provider interface {
6-
CreateRepository(ctx context.Context, owner, repo string) error
11+
CreateRepository(ctx context.Context, spec mpasv1alpha1.Repository) error
712
CreatePullRequest(ctx context.Context, owner, repo, title, branch, description string) error
813
}

0 commit comments

Comments
 (0)