Skip to content

Commit

Permalink
updated tiltfile and added github repositroy support
Browse files Browse the repository at this point in the history
  • Loading branch information
Skarlso committed Mar 30, 2023
1 parent f971d50 commit 5a5f4d5
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ local_resource(
"main.go",
"go.mod",
"go.sum",
"api",
"apis",
"controllers",
"pkg",
],
Expand Down
10 changes: 10 additions & 0 deletions apis/mpas/v1alpha1/condition_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

const (
// RepositoryCreateFailedReason is used when we fail to create a Repository.
RepositoryCreateFailedReason = "RepositoryCreateFailed"
)
20 changes: 14 additions & 6 deletions apis/mpas/v1alpha1/repository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,24 @@ type Credentials struct {

// RepositorySpec defines the desired state of Repository
type RepositorySpec struct {
Provider string `json:"provider"`
Owner string `json:"owner"`
Repository string `json:"repository"`
Credentials Credentials `json:"credentials"`
Interval metav1.Duration `json:"interval"`
Provider string `json:"provider"`
Owner string `json:"owner"`
RepositoryName string `json:"repositoryName"`
Credentials Credentials `json:"credentials"`

//+optional
Interval metav1.Duration `json:"interval,omitempty"`
//+optional
//+kubebuilder:default:=internal
Visibility string `json:"visibility,omitempty"`
//+kubebuilder:default:=true
IsOrganization bool `json:"isOrganization,omitempty"`
//+optional
Domain string `json:"domain,omitempty"`
//+optional
Maintainers []string `json:"maintainers,omitempty"`
//+optional
//+kubebuilder:default=true;
//+kubebuilder:default:=true
AutomaticPullRequestCreation bool `json:"automaticPullRequestCreation,omitempty"`
}

Expand Down
16 changes: 11 additions & 5 deletions config/crd/bases/mpas.ocm.software_repositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ spec:
description: RepositorySpec defines the desired state of Repository
properties:
automaticPullRequestCreation:
default:
- true
default: true
type: boolean
credentials:
description: Credentials contains ways of authenticating the creation
Expand All @@ -55,8 +54,13 @@ spec:
required:
- secretRef
type: object
domain:
type: string
interval:
type: string
isOrganization:
default: true
type: boolean
maintainers:
items:
type: string
Expand All @@ -65,14 +69,16 @@ spec:
type: string
provider:
type: string
repository:
repositoryName:
type: string
visibility:
default: internal
type: string
required:
- credentials
- interval
- owner
- provider
- repository
- repositoryName
type: object
status:
description: RepositoryStatus defines the observed state of Repository
Expand Down
14 changes: 7 additions & 7 deletions config/samples/mpas_v1alpha1_repository.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
apiVersion: mpas.ocm.software/v1alpha1
kind: Repository
metadata:
labels:
app.kubernetes.io/name: repository
app.kubernetes.io/instance: repository-sample
app.kubernetes.io/part-of: git-controller
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: git-controller
name: repository-sample
spec:
# TODO(user): Add fields here
credentials:
secretRef:
name: github-creds
interval: 10m
owner: Skarlso
provider: github
repositoryName: new-repository-1
23 changes: 18 additions & 5 deletions controllers/mpas/repository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
"github.com/open-component-model/git-controller/pkg/providers"
)

// RepositoryReconciler reconciles a Repository object
type RepositoryReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
Provider providers.Provider
}

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

logger := log.FromContext(ctx).WithName("repository")

logger.V(4).Info("entering repository loop...")

obj := &mpasv1alpha1.Repository{}
Expand Down Expand Up @@ -94,10 +97,10 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// If not reconciling or stalled than mark Ready=True
if !conditions.IsReconciling(obj) &&
!conditions.IsStalled(obj) &&
retErr == nil &&
result.RequeueAfter == obj.GetRequeueAfter() {
retErr == nil {
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")
}

// Set status observed generation option if the component is stalled or ready.
if conditions.IsStalled(obj) || conditions.IsReady(obj) {
obj.Status.ObservedGeneration = obj.Generation
Expand All @@ -109,17 +112,27 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
}()

// Remove any stale Ready condition, most likely False, set above. Its value
// is derived from the overall result of the reconciliation in the deferred
// block at the very end.
conditions.Delete(obj, meta.ReadyCondition)

result, retErr = r.reconcile(ctx, obj)
return result, retErr
}

// SetupWithManager sets up the controller with the Manager.
func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&mpasv1alpha1.Repository{}).
For(&mpasv1alpha1.Repository{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Complete(r)
}

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

return ctrl.Result{}, nil
}
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"

"github.com/open-component-model/git-controller/pkg/gogit"
"github.com/open-component-model/git-controller/pkg/providers/github"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
Expand All @@ -30,7 +31,6 @@ import (
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
ociAgent = "git-controller/v1alpha1"
)

func init() {
Expand Down Expand Up @@ -100,9 +100,12 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Sync")
os.Exit(1)
}

githubProvider := github.NewClient(mgr.GetClient(), nil)
if err = (&mpascontrollers.RepositoryReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Provider: githubProvider,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Repository")
os.Exit(1)
Expand Down
128 changes: 122 additions & 6 deletions pkg/providers/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,142 @@ import (
"fmt"

"github.com/fluxcd/go-git-providers/github"
"github.com/fluxcd/go-git-providers/gitprovider"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
"github.com/open-component-model/git-controller/pkg/providers"
)

const (
tokenKey = "token"
providerType = "github"
defaultDomain = "github.com"
)

// Client github.
type Client struct {
// TODO: Figure out how to get this.
BaseURL string
client client.Client
next providers.Provider
}

// TODO: Use this instead and somehow abstract the two clients.
type RepositoryOpts struct {
Owner string
Domain string
Visibility gitprovider.RepositoryVisibility
}

func NewClient() *Client {
return &Client{}
// NewClient creates a new GitHub client.
func NewClient(client client.Client, next providers.Provider) *Client {
return &Client{
client: client,
next: next,
}
}

var _ providers.Provider = &Client{}

func (c *Client) CreateRepository(ctx context.Context, owner, repo string) error {
_, err := github.NewClient()
func (c *Client) CreateRepository(ctx context.Context, obj mpasv1alpha1.Repository) error {
if obj.Spec.Provider != providerType {
if c.next == nil {
return fmt.Errorf("can't handle provider type '%s' and no next provider is configured", obj.Spec.Provider)
}

return c.next.CreateRepository(ctx, obj)
}

authenticationOption, err := c.constructAuthenticationOption(ctx, obj)
if err != nil {
return err
}

gc, err := github.NewClient(authenticationOption)
if err != nil {
return fmt.Errorf("failed to create github client: %w", err)
}

visibility := gitprovider.RepositoryVisibility(obj.Spec.Visibility)

if err := gitprovider.ValidateRepositoryVisibility(visibility); err != nil {
return fmt.Errorf("failed to validate visibility: %w", err)
}

domain := defaultDomain
if obj.Spec.Domain != "" {
domain = obj.Spec.Domain
}

if obj.Spec.IsOrganization {
return c.createOrganizationRepository(ctx, gc, domain, visibility, obj.Spec)
}

return c.createUserRepository(ctx, gc, domain, visibility, obj.Spec)
}

// constructAuthenticationOption will take the object and construct an authentication option.
// For now, only token secret is supported, this will be extended in the future.
func (c *Client) constructAuthenticationOption(ctx context.Context, obj mpasv1alpha1.Repository) (gitprovider.ClientOption, error) {
secret := &v1.Secret{}
if err := c.client.Get(ctx, types.NamespacedName{
Name: obj.Spec.Credentials.SecretRef.Name,
Namespace: obj.Namespace,
}, secret); err != nil {
return nil, fmt.Errorf("failed to get secret: %w", err)
}

token, ok := secret.Data[tokenKey]
if !ok {
return nil, fmt.Errorf("token '%s' not found in secret", tokenKey)
}

return gitprovider.WithOAuth2Token(string(token)), nil
}

func (c *Client) createOrganizationRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error {
logger := log.FromContext(ctx)

repo, err := gc.OrgRepositories().Create(ctx, gitprovider.OrgRepositoryRef{
OrganizationRef: gitprovider.OrganizationRef{
Domain: domain,
Organization: spec.Owner,
},
RepositoryName: spec.RepositoryName,
}, gitprovider.RepositoryInfo{
DefaultBranch: gitprovider.StringVar("main"),
Visibility: &visibility,
})
if err != nil {
return fmt.Errorf("failed to create repository: %w", err)
}

logger.Info("organization repository successfully created", "name", repo.Repository().String())

return nil
}

func (c *Client) createUserRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error {
logger := log.FromContext(ctx)

repo, err := gc.UserRepositories().Create(ctx, gitprovider.UserRepositoryRef{
UserRef: gitprovider.UserRef{
Domain: domain,
UserLogin: spec.Owner,
},
RepositoryName: spec.RepositoryName,
}, gitprovider.RepositoryInfo{
DefaultBranch: gitprovider.StringVar("main"),
Visibility: &visibility,
})
if err != nil {
return fmt.Errorf("failed to create repository: %w", err)
}

logger.Info("user repository successfully created", "name", repo.Repository().String())

return nil
}

Expand Down
9 changes: 7 additions & 2 deletions pkg/providers/providers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package providers

import "context"
import (
"context"

mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
)

// Provider adds the ability to create repositories and pull requests.
type Provider interface {
CreateRepository(ctx context.Context, owner, repo string) error
CreateRepository(ctx context.Context, spec mpasv1alpha1.Repository) error
CreatePullRequest(ctx context.Context, owner, repo, title, branch, description string) error
}

0 comments on commit 5a5f4d5

Please sign in to comment.