Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional dynamic subscriptions #69

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ limitations under the License.
*/

// Package v1alpha1 contains API Schema definitions for the gitops v1alpha1 API group
//+kubebuilder:object:generate=true
//+groupName=gitops.hybrid-cloud-patterns.io
// +kubebuilder:object:generate=true
// +groupName=gitops.hybrid-cloud-patterns.io
package v1alpha1

import (
Expand Down
12 changes: 9 additions & 3 deletions api/v1alpha1/pattern_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,19 @@ type PatternSpec struct {

// Look for external changes every N minutes
// ReconcileMinutes int `json:"reconcileMinutes,omitempty"`

// Disable pre-existing subscriptions
DynamicSubscriptions bool `json:"dynamicSubscriptions"`
}

type GitConfig struct {
// Optional. FQDN of the git server if automatic parsing from TargetRepo is broken
Hostname string `json:"hostname,omitempty"`

//Account string `json:"account,omitempty"`
//TokenSecret string `json:"tokenSecret,omitempty"`
//TokenSecretNamespace string `json:"tokenSecretNamespace,omitempty"`
//TokenSecretKey string `json:"tokenSecretKey,omitempty"`
TokenSecret string `json:"tokenSecret,omitempty"`
TokenSecretNamespace string `json:"tokenSecretNamespace,omitempty"`
TokenSecretKey string `json:"tokenSecretKey,omitempty"`

// Upstream git repo containing the pattern to deploy. Used when in-cluster fork to point to the upstream pattern repository
//+operator-sdk:csv:customresourcedefinitions:type=spec
Expand Down Expand Up @@ -149,6 +152,9 @@ type PatternStatus struct {
//+operator-sdk:csv:customresourcedefinitions:type=status
Version int `json:"version,omitempty"`

Path string `json:"path,omitempty"`
Revision string `json:"revision,omitempty"`

//+operator-sdk:csv:customresourcedefinitions:type=status
ClusterName string `json:"clusterName,omitempty"`
//+operator-sdk:csv:customresourcedefinitions:type=status
Expand Down
15 changes: 15 additions & 0 deletions config/crd/bases/gitops.hybrid-cloud-patterns.io_patterns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ spec:
properties:
clusterGroupName:
type: string
dynamicSubscriptions:
description: Disable pre-existing subscriptions
type: boolean
extraParameters:
description: '.Name is dot separated per the helm --set syntax, such
as: global.something.field'
Expand Down Expand Up @@ -121,11 +124,19 @@ spec:
description: 'Branch, tag, or commit to deploy. Does not support
short-sha''s. Default: HEAD'
type: string
tokenSecret:
description: Account string `json:"account,omitempty"`
type: string
tokenSecretKey:
type: string
tokenSecretNamespace:
type: string
required:
- targetRepo
type: object
required:
- clusterGroupName
- dynamicSubscriptions
- gitSpec
type: object
status:
Expand Down Expand Up @@ -177,6 +188,10 @@ spec:
lastStep:
description: Last action related to the pattern
type: string
path:
type: string
revision:
type: string
version:
description: Number of updates to the pattern
type: integer
Expand Down
21 changes: 17 additions & 4 deletions controllers/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import (

argoapi "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoclient "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
olmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"

api "github.com/hybrid-cloud-patterns/patterns-operator/api/v1alpha1"
)

func newApplicationParameters(p api.Pattern) []argoapi.HelmParameter {
func newApplicationParameters(p api.Pattern, valueFiles []string, client olmclient.Interface) []argoapi.HelmParameter {

parameters := []argoapi.HelmParameter{
{
Expand Down Expand Up @@ -76,6 +77,17 @@ func newApplicationParameters(p api.Pattern) []argoapi.HelmParameter {
},
}

if len(valueFiles) > 0 && client != nil && p.Spec.DynamicSubscriptions {
log.Println("Spec: ", p.Spec)
excludeList := buildLiveSubscriptionExclusions(p, valueFiles, client)
for _, excludeParam := range excludeList {
parameters = append(parameters, argoapi.HelmParameter{
Name: excludeParam,
Value: "1",
})
}
}

for _, extra := range p.Spec.ExtraParameters {
if !updateHelmParameter(extra, parameters) {
log.Printf("Parameter %q = %q added", extra.Name, extra.Value)
Expand Down Expand Up @@ -114,12 +126,13 @@ func newApplicationValueFiles(p api.Pattern) []string {
return files
}

func newApplication(p api.Pattern) *argoapi.Application {
func newApplication(p api.Pattern, client olmclient.Interface) *argoapi.Application {

// Argo uses...
// r := regexp.MustCompile("(/|:)")
// root := filepath.Join(os.TempDir(), r.ReplaceAllString(NormalizeGitURL(rawRepoURL), "_"))

valueFiles := newApplicationValueFiles(p)
spec := argoapi.ApplicationSpec{

// Source is a reference to the location of the application's manifests or chart
Expand All @@ -128,10 +141,10 @@ func newApplication(p api.Pattern) *argoapi.Application {
Path: "common/clustergroup",
TargetRevision: p.Spec.GitConfig.TargetRevision,
Helm: &argoapi.ApplicationSourceHelm{
ValueFiles: newApplicationValueFiles(p),
ValueFiles: valueFiles,

// Parameters is a list of Helm parameters which are passed to the helm template command upon manifest generation
Parameters: newApplicationParameters(p),
Parameters: newApplicationParameters(p, valueFiles, client),

// ReleaseName is the Helm release name to use. If omitted it will use the application name
// ReleaseName string `json:"releaseName,omitempty" protobuf:"bytes,3,opt,name=releaseName"`
Expand Down
236 changes: 236 additions & 0 deletions controllers/checkout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
Copyright 2022.

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 controllers

import (
// "log"
"fmt"
"os"

"path/filepath"

"github.com/go-git/go-git/v5"
// . "github.com/go-git/go-git/v5/_examples"
"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/http"
)

// https://github.com/go-git/go-git/blob/master/_examples/commit/main.go

func checkout(url, directory, token, branch, commit string) error {

if err := cloneRepo(url, directory, token); err != nil {
return err
}

if len(commit) == 0 {
// Nothing more to do
return nil
}

if err := checkoutRevision(directory, token, commit); err != nil {
return err
}

return nil
}

func getHashFromReference(repo *git.Repository, name plumbing.ReferenceName) (plumbing.Hash, error) {
b, err := repo.Reference(name, true)
if err != nil {
return plumbing.ZeroHash, err
}

if !b.Name().IsTag() {
return b.Hash(), nil
}

o, err := repo.Object(plumbing.AnyObject, b.Hash())
if err != nil {
return plumbing.ZeroHash, err
}

switch o := o.(type) {
case *object.Tag:
if o.TargetType != plumbing.CommitObject {
return plumbing.ZeroHash, fmt.Errorf("unsupported tag object target %q", o.TargetType)
}

return o.Target, nil
case *object.Commit:
return o.Hash, nil
}

return plumbing.ZeroHash, fmt.Errorf("unsupported tag target %q", o.Type())
}

func getCommitFromTarget(repo *git.Repository, name string) (plumbing.Hash, error) {
if len(name) == 0 {
return getHashFromReference(repo, plumbing.NewBranchReferenceName("main"))
}

// Try as commit hash
h := plumbing.NewHash(name)
_, err := repo.Object(plumbing.AnyObject, h)
if err == nil {
return h, err
}

// Try various reference types...

if h, err := getHashFromReference(repo, plumbing.NewBranchReferenceName(name)); err == nil {
return h, err
}

if h, err := getHashFromReference(repo, plumbing.NewTagReferenceName(name)); err == nil {
return h, err
}

if h, err := getHashFromReference(repo, plumbing.NewRemoteHEADReferenceName(name)); err == nil {
return h, err
}

return plumbing.ZeroHash, fmt.Errorf("unknown target %q", name)
}

func checkoutRevision(directory, token, commit string) error {

fmt.Printf("Accessing %s\n", directory)
repo, err := git.PlainOpen(directory)
if err != nil {
return err
}

var foptions = &git.FetchOptions{
Force: true,
InsecureSkipTLS: true,
Tags: git.AllTags,
}

if len(token) > 0 {
// The intended use of a GitHub personal access token is in replace of your password
// because access tokens can easily be revoked.
// https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
foptions.Auth = &http.BasicAuth{
Username: "abc123", // yes, this can be anything except an empty string
Password: token,
}
}

if err := repo.Fetch(foptions); err != nil && err != git.NoErrAlreadyUpToDate {
fmt.Println("Error fetching")
return err
}

w, err := repo.Worktree()
if err != nil {
fmt.Println("Error obtaining worktree")
return err
}

h, err := getCommitFromTarget(repo, commit)
coptions := git.CheckoutOptions{
Force: true,
Hash: h,
}

if err != nil {
return err
}

fmt.Printf("git checkout %s (%s)\n", h, commit)

if err := w.Checkout(&coptions); err != nil && err != git.NoErrAlreadyUpToDate {
fmt.Printf("Error during checkout")
return err
}

// ... retrieving the commit being pointed by HEAD, it shows that the
// repository is pointing to the giving commit in detached mode
fmt.Println("git show-ref --head HEAD")
ref, err := repo.Head()
if err != nil {
fmt.Println("Error obtaining HEAD")
return err
}

fmt.Printf("%s\n", ref.Hash())
return err

}

func cloneRepo(url string, directory string, token string) error {

gitDir := filepath.Join(directory, ".git")
if _, err := os.Stat(gitDir); err == nil {
fmt.Printf("%s already exists\n", gitDir)
return nil
}
fmt.Printf("git clone %s into %s\n", url, directory)

// Clone the given repository to the given directory
var options = &git.CloneOptions{
URL: url,
Progress: os.Stdout,
Depth: 0,
// ReferenceName: plumbing.ReferenceName,
}

if len(token) > 0 {
// The intended use of a GitHub personal access token is in replace of your password
// because access tokens can easily be revoked.
// https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
options.Auth = &http.BasicAuth{
Username: "abc123", // yes, this can be anything except an empty string
Password: token,
}
}

if err := os.MkdirAll(directory, os.ModePerm); err != nil {
return err
}

repo, err := git.PlainClone(directory, false, options)
if err != nil {
return err
}

// ... retrieving the commit being pointed by HEAD
fmt.Println("git show-ref --head HEAD")
if ref, err := repo.Head(); err != nil {
return err
} else {
fmt.Printf("%s\n", ref.Hash())
}
return nil
}

func repoHash(directory string) (error, string) {
repo, err := git.PlainOpen(directory)
if err != nil {
return err, ""
}

// ... checking out to commit
ref, err := repo.Head()
if err != nil {
return err, ""
}

return nil, ref.Hash().String()
}
File renamed without changes.
Loading