Skip to content

Commit

Permalink
Merge pull request #1360 from weaveworks/add-profile
Browse files Browse the repository at this point in the history
Implement `add profile` to install a profile to a cluster
  • Loading branch information
Niki authored Feb 3, 2022
2 parents 0d6fd9c + 011bfdd commit c0353c3
Show file tree
Hide file tree
Showing 35 changed files with 1,886 additions and 92 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ ginkgo.report
.deps
test/library/wego-library-test
tilt_modules
.envrc
2 changes: 2 additions & 0 deletions cmd/gitops/add/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"
"github.com/weaveworks/weave-gitops/cmd/gitops/add/app"
"github.com/weaveworks/weave-gitops/cmd/gitops/add/clusters"
"github.com/weaveworks/weave-gitops/cmd/gitops/add/profiles"
)

func GetCommand(endpoint *string, client *resty.Client) *cobra.Command {
Expand All @@ -21,6 +22,7 @@ gitops add cluster`,

cmd.AddCommand(clusters.ClusterCommand(endpoint, client))
cmd.AddCommand(app.Cmd)
cmd.AddCommand(profiles.AddCommand())

return cmd
}
122 changes: 122 additions & 0 deletions cmd/gitops/add/profiles/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package profiles

import (
"context"
"fmt"
"math/rand"
"os"
"path/filepath"
"time"

"github.com/Masterminds/semver/v3"
"github.com/spf13/cobra"
"github.com/weaveworks/weave-gitops/cmd/internal"
"github.com/weaveworks/weave-gitops/pkg/flux"
"github.com/weaveworks/weave-gitops/pkg/kube"
"github.com/weaveworks/weave-gitops/pkg/models"
"github.com/weaveworks/weave-gitops/pkg/osys"
"github.com/weaveworks/weave-gitops/pkg/runner"
"github.com/weaveworks/weave-gitops/pkg/server"
"github.com/weaveworks/weave-gitops/pkg/services"
"github.com/weaveworks/weave-gitops/pkg/services/auth"
"github.com/weaveworks/weave-gitops/pkg/services/profiles"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)

var opts profiles.AddOptions

// AddCommand provides support for adding a profile to a cluster.
func AddCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "profile",
Short: "Add a profile to a cluster",
SilenceUsage: true,
SilenceErrors: true,
Example: `
# Add a profile to a cluster
gitops add profile --name=podinfo --cluster=prod --version=1.0.0 --config-repo=ssh://[email protected]/owner/config-repo.git
`,
RunE: addProfileCmdRunE(),
}

cmd.Flags().StringVar(&opts.Name, "name", "", "Name of the profile")
cmd.Flags().StringVar(&opts.Version, "version", "latest", "Version of the profile specified as semver (e.g.: 0.1.0) or as 'latest'")
cmd.Flags().StringVar(&opts.ConfigRepo, "config-repo", "", "URL of external repository (if any) which will hold automation manifests")
cmd.Flags().StringVar(&opts.Cluster, "cluster", "", "Name of the cluster to add the profile to")
cmd.Flags().StringVar(&opts.ProfilesPort, "profiles-port", server.DefaultPort, "Port the Profiles API is running on")
cmd.Flags().BoolVar(&opts.AutoMerge, "auto-merge", false, "If set, 'gitops add profile' will merge automatically into the repository's default branch")
cmd.Flags().StringVar(&opts.Kubeconfig, "kubeconfig", filepath.Join(homedir.HomeDir(), ".kube", "config"), "Absolute path to the kubeconfig file")

requiredFlags := []string{"name", "config-repo", "cluster"}
for _, f := range requiredFlags {
if err := cobra.MarkFlagRequired(cmd.Flags(), f); err != nil {
panic(fmt.Errorf("unexpected error: %w", err))
}
}

return cmd
}

func addProfileCmdRunE() func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
rand.Seed(time.Now().UnixNano())

log := internal.NewCLILogger(os.Stdout)
fluxClient := flux.New(osys.New(), &runner.CLIRunner{})
factory := services.NewFactory(fluxClient, log)
providerClient := internal.NewGitProviderClient(os.Stdout, os.LookupEnv, auth.NewAuthCLIHandler, log)

if err := validateAddOptions(opts); err != nil {
return err
}

var err error
if opts.Namespace, err = cmd.Flags().GetString("namespace"); err != nil {
return err
}

config, err := clientcmd.BuildConfigFromFlags("", opts.Kubeconfig)
if err != nil {
return fmt.Errorf("error initializing kubernetes config: %w", err)
}

clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("error initializing kubernetes client: %w", err)
}

kubeClient, _, err := kube.NewKubeHTTPClient()
if err != nil {
return fmt.Errorf("failed to create kube client: %w", err)
}

_, gitProvider, err := factory.GetGitClients(context.Background(), kubeClient, providerClient, services.GitConfigParams{
ConfigRepo: opts.ConfigRepo,
Namespace: opts.Namespace,
IsHelmRepository: true,
DryRun: false,
})
if err != nil {
return fmt.Errorf("failed to get git clients: %w", err)
}

return profiles.NewService(clientSet, log).Add(context.Background(), gitProvider, opts)
}
}

func validateAddOptions(opts profiles.AddOptions) error {
if models.ApplicationNameTooLong(opts.Name) {
return fmt.Errorf("--name value is too long: %s; must be <= %d characters",
opts.Name, models.MaxKubernetesResourceNameLength)
}

if opts.Version != "latest" {
if _, err := semver.StrictNewVersion(opts.Version); err != nil {
return fmt.Errorf("error parsing --version=%s: %w", opts.Version, err)
}
}

return nil
}
90 changes: 90 additions & 0 deletions cmd/gitops/add/profiles/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package profiles_test

import (
"github.com/go-resty/resty/v2"
"github.com/jarcoal/httpmock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
"github.com/weaveworks/weave-gitops/cmd/gitops/root"
)

var _ = Describe("Add Profiles", func() {
var (
cmd *cobra.Command
)

BeforeEach(func() {
client := resty.New()
httpmock.ActivateNonDefault(client.GetClient())
cmd = root.RootCmd(client)
})

AfterEach(func() {
httpmock.DeactivateAndReset()
})

When("the flags are valid", func() {
It("accepts all known flags for adding a profile", func() {
cmd.SetArgs([]string{
"add", "profile",
"--name", "podinfo",
"--version", "0.0.1",
"--cluster", "prod",
"--namespace", "test-namespace",
"--config-repo", "https://ssh@github:test/test.git",
"--auto-merge", "true",
})

err := cmd.Execute()
Expect(err.Error()).NotTo(ContainSubstring("unknown flag"))
})
})

When("flags are not valid", func() {
It("fails if --name, --cluster, and --config-repo are not provided", func() {
cmd.SetArgs([]string{
"add", "profile",
})

err := cmd.Execute()
Expect(err).To(MatchError("required flag(s) \"cluster\", \"config-repo\", \"name\" not set"))
})

It("fails if --name value is <= 63 characters in length", func() {
cmd.SetArgs([]string{
"add", "profile",
"--name", "a234567890123456789012345678901234567890123456789012345678901234",
"--cluster", "cluster",
"--config-repo", "config-repo",
})
err := cmd.Execute()
Expect(err).To(MatchError("--name value is too long: a234567890123456789012345678901234567890123456789012345678901234; must be <= 63 characters"))
})

It("fails if given version is not valid semver", func() {
cmd.SetArgs([]string{
"add", "profile",
"--name", "podinfo",
"--config-repo", "ssh://[email protected]/owner/config-repo.git",
"--cluster", "prod",
"--version", "&%*/v",
})

err := cmd.Execute()
Expect(err).To(MatchError("error parsing --version=&%*/v: Invalid Semantic Version"))
})
})

When("a flag is unknown", func() {
It("fails", func() {
cmd.SetArgs([]string{
"add", "profile",
"--unknown", "param",
})

err := cmd.Execute()
Expect(err).To(MatchError("unknown flag: --unknown"))
})
})
})
13 changes: 13 additions & 0 deletions cmd/gitops/add/profiles/profile_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package profiles_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestProfile(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Profiles Suite")
}
6 changes: 3 additions & 3 deletions cmd/gitops/get/profiles/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"

"github.com/spf13/cobra"
"github.com/weaveworks/weave-gitops/cmd/internal"
"github.com/weaveworks/weave-gitops/pkg/server"
"github.com/weaveworks/weave-gitops/pkg/services/profiles"
"k8s.io/client-go/kubernetes"
Expand All @@ -33,7 +34,7 @@ gitops get profiles
}

func init() {
Cmd.Flags().StringVar(&port, "port", server.DefaultPort, "port the profiles API is running on")
Cmd.Flags().StringVar(&port, "port", server.DefaultPort, "Port the profiles API is running on")
}

func runCmd(cmd *cobra.Command, args []string) error {
Expand All @@ -52,9 +53,8 @@ func runCmd(cmd *cobra.Command, args []string) error {
return err
}

return profiles.GetProfiles(context.Background(), profiles.GetOptions{
return profiles.NewService(clientSet, internal.NewCLILogger(os.Stdout)).Get(context.Background(), profiles.GetOptions{
Namespace: ns,
ClientSet: clientSet,
Writer: os.Stdout,
Port: port,
})
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/benbjohnson/clock v1.3.0
github.com/coreos/go-oidc/v3 v3.1.0
github.com/deepmap/oapi-codegen v1.8.1
github.com/fluxcd/go-git-providers v0.5.2
github.com/fluxcd/go-git-providers v0.5.3
github.com/fluxcd/helm-controller/api v0.14.1
github.com/fluxcd/kustomize-controller/api v0.18.2
github.com/fluxcd/pkg/apis/meta v0.10.1
Expand Down Expand Up @@ -69,6 +69,7 @@ require (

require (
github.com/ghodss/yaml v1.0.0
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gofrs/flock v0.8.1
github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671
gopkg.in/square/go-jose.v2 v2.5.1
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/go-git-providers v0.5.2 h1:TA+1q6w2KgSugaZ9VXODQcYbtZyn6JH+V7g2MZtzTPA=
github.com/fluxcd/go-git-providers v0.5.2/go.mod h1:4jTHTmSx3rFGnG78KUVgFYeG6vWFnKwUSr2mi31tvp8=
github.com/fluxcd/go-git-providers v0.5.3 h1:Sg5XH+aXb6mYwITtdE4yc4yLoyFW3aVZx33qWuQb9/Q=
github.com/fluxcd/go-git-providers v0.5.3/go.mod h1:4jTHTmSx3rFGnG78KUVgFYeG6vWFnKwUSr2mi31tvp8=
github.com/fluxcd/helm-controller/api v0.14.1 h1:aAWaYZxTI68SD1R2SpNJh8+hm9oBeIOa9nW4YX5qYjM=
github.com/fluxcd/helm-controller/api v0.14.1/go.mod h1:NkfZ5ugs9EUUPSGHfAnNs295mf8sVKG0842aL6cFzMM=
github.com/fluxcd/kustomize-controller/api v0.18.2 h1:rGu9R6PMFw3x0S6tVj/ZS54sJWW6/cdWe0Gga09e1AY=
Expand Down Expand Up @@ -511,6 +511,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A=
Expand Down
6 changes: 6 additions & 0 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ func GetSystemQualifiedPath(clusterName string, relativePath string) string {
return filepath.Join(GetSystemPath(clusterName), relativePath)
}

// GetProfilesPath returns the path of the file containing the manifests of installed Profiles
// joined with the cluster's system directory
func GetProfilesPath(clusterName, profilesManifestPath string) string {
return filepath.Join(GetSystemPath(clusterName), profilesManifestPath)
}

// Git is an interface for basic Git operations on a single branch of a
// remote repository.
//counterfeiter:generate . Git
Expand Down
8 changes: 8 additions & 0 deletions pkg/gitproviders/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ func (p *dryrunProvider) GetCommits(_ context.Context, repoUrl RepoURL, targetBr
func (p *dryrunProvider) GetProviderDomain() string {
return p.provider.GetProviderDomain()
}

func (p *dryrunProvider) GetRepoDirFiles(_ context.Context, repoUrl RepoURL, dirPath, targetBranch string) ([]*gitprovider.CommitFile, error) {
return nil, nil
}

func (p *dryrunProvider) MergePullRequest(ctx context.Context, repoUrl RepoURL, pullRequestNumber int, commitMesage string) error {
return nil
}
Loading

0 comments on commit c0353c3

Please sign in to comment.