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

add support for github app auth #287

Closed
wants to merge 10 commits into from
Closed

Conversation

djeebus
Copy link
Collaborator

@djeebus djeebus commented Oct 1, 2024

Closes #247. Builds on top of #249. Also gracefully handles missing comment permissions

@zapier-sre-bot
Copy link
Collaborator

Mergecat's Review

Click to read mergecats review!

😼 Mergecat review of pkg/vcs/types.go

@@ -17,7 +17,7 @@ type WebHookConfig struct {
 // Client represents a VCS client
 type Client interface {
 	// PostMessage takes in project name in form "owner/repo" (ie zapier/kubechecks), the PR/MR id, and the actual message
-	PostMessage(context.Context, PullRequest, string) *msg.Message
+	PostMessage(context.Context, PullRequest, string) (*msg.Message, error)
 	// UpdateMessage update a message with new content
 	UpdateMessage(context.Context, *msg.Message, string) error
 	// VerifyHook validates a webhook secret and return the body; must be called even if no secret

Feedback & Suggestions:

  1. Error Handling: The change to return an error along with *msg.Message in PostMessage is a good improvement for error handling. Ensure that all implementations of this interface are updated to handle the new return type correctly.
  2. Documentation Update: The comment above PostMessage should be updated to reflect the new return type. This helps maintain clarity for future developers.

Suggested change to the comment:

 	// PostMessage takes in project name in form "owner/repo" (ie zapier/kubechecks), the PR/MR id, and the actual message
-	PostMessage(context.Context, PullRequest, string) *msg.Message
+	// and returns the created message and an error if any occurred
+	PostMessage(context.Context, PullRequest, string) (*msg.Message, error)

😼 Mergecat review of go.mod

@@ -12,6 +12,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/eks v1.46.0
 	github.com/aws/aws-sdk-go-v2/service/sts v1.30.1
 	github.com/aws/smithy-go v1.20.3
+	github.com/bradleyfalzon/ghinstallation/v2 v2.6.0
 	github.com/cenkalti/backoff/v4 v4.3.0
 	github.com/chainguard-dev/git-urls v1.0.2
 	github.com/creasty/defaults v1.7.0
@@ -105,7 +106,6 @@ require (
 	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
 	github.com/bombsimon/logrusr/v2 v2.0.1 // indirect
-	github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 // indirect
 	github.com/bufbuild/protocompile v0.6.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/chai2010/gettext-go v1.0.2 // indirect

Feedback & Suggestions:

  1. Dependency Management: The change moves github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 from an indirect dependency to a direct dependency. Ensure that this change is intentional and necessary. If this package is now being directly used in your code, this change is appropriate. Otherwise, it might be better to keep it as an indirect dependency to avoid unnecessary clutter in the direct dependencies list.

  2. Version Consistency: Verify that the version v2.6.0 is the correct and intended version for your project. If there are newer versions available, consider updating to benefit from potential bug fixes and improvements.

  3. Documentation: If this change is significant, update your project documentation to reflect the new direct dependency. This helps other developers understand why this dependency is now directly required.


😼 Mergecat review of pkg/vcs/github_client/message.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/google/go-github/v62/github"
+	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
 	"github.com/shurcooL/githubv4"
 
@@ -17,7 +18,7 @@ import (
 
 const MaxCommentLength = 64 * 1024
 
-func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) *msg.Message {
+func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) (*msg.Message, error) {
 	_, span := tracer.Start(ctx, "PostMessageToMergeRequest")
 	defer span.End()
 
@@ -37,10 +38,10 @@ func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message st
 
 	if err != nil {
 		telemetry.SetError(span, err, "Create Pull Request comment")
-		log.Error().Err(err).Msg("could not post message to PR")
+		return nil, errors.Wrap(err, "could not post message to PR")
 	}
 
-	return msg.NewMessage(pr.FullName, pr.CheckID, int(*comment.ID), c)
+	return msg.NewMessage(pr.FullName, pr.CheckID, int(*comment.ID), c), nil
 }
 
 func (c *Client) UpdateMessage(ctx context.Context, m *msg.Message, msg string) error {

Feedback & Suggestions:

  1. Error Handling Consistency: The change to return an error from PostMessage is a good improvement for error handling. However, ensure that all places where PostMessage is called are updated to handle the new error return value.
  2. Error Wrapping: Using errors.Wrap is a good practice for adding context to errors. Ensure this is consistently applied across the codebase for better error traceability.
  3. Logging and Telemetry: The logging and telemetry calls are good, but consider adding more context to the logs and telemetry to make debugging easier. For example, include the PR number and repository name in the error message.

Example:

if err != nil {
    telemetry.SetError(span, err, fmt.Sprintf("Create Pull Request comment for PR %d in repo %s", pr.CheckID, pr.FullName))
    return nil, errors.Wrapf(err, "could not post message to PR %d in repo %s", pr.CheckID, pr.FullName)
}
  1. Function Signature Change: The function signature change for PostMessage to return an error is a breaking change. Ensure that all dependent code is updated accordingly to handle the new return type.

😼 Mergecat review of cmd/root.go

@@ -45,7 +45,7 @@ func init() {
 				zerolog.LevelDebugValue,
 				zerolog.LevelTraceValue,
 			).
-			withDefault("info").
+			withDefault(zerolog.LevelInfoValue).
 			withShortHand("l"),
 	)
 	boolFlag(flags, "persist-log-level", "Persists the set log level down to other module loggers.")
@@ -55,6 +55,11 @@ func init() {
 			withChoices("github", "gitlab").
 			withDefault("gitlab"))
 	stringFlag(flags, "vcs-token", "VCS API token.")
+	stringFlag(flags, "vcs-username", "VCS Username.")
+	stringFlag(flags, "vcs-email", "VCS Email.")
+	stringFlag(flags, "github-private-key", "Github App Private Key.")
+	int64Flag(flags, "github-app-id", "Github App ID.")
+	int64Flag(flags, "github-installation-id", "Github Installation ID.")
 	stringFlag(flags, "argocd-api-token", "ArgoCD API token.")
 	stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.",
 		newStringOpts().
@@ -120,7 +125,10 @@ func setupLogOutput() {
 
 	// Default level is info, unless debug flag is present
 	levelFlag := viper.GetString("log-level")
-	level, _ := zerolog.ParseLevel(levelFlag)
+	level, err := zerolog.ParseLevel(levelFlag)
+	if err != nil {
+		log.Error().Err(err).Msg("Invalid log level")
+	}
 
 	zerolog.SetGlobalLevel(level)
 	log.Debug().Msg("Debug level logging enabled.")

Feedback & Suggestions:

  1. Log Level Default Value:

    • The change from "info" to zerolog.LevelInfoValue is good for consistency. However, ensure that zerolog.LevelInfoValue is correctly imported and used throughout the codebase.
  2. New Flags:

    • The addition of new flags (vcs-username, vcs-email, github-private-key, github-app-id, github-installation-id) is useful. Ensure that these flags are documented and validated properly.
    • Consider adding validation for sensitive information like github-private-key to ensure it meets security requirements.
  3. Error Handling for Log Level Parsing:

    • Adding error handling for zerolog.ParseLevel is a good improvement. However, it would be better to provide a fallback mechanism in case of an invalid log level. For example:
      level, err := zerolog.ParseLevel(levelFlag)
      if err != nil {
          log.Error().Err(err).Msg("Invalid log level, defaulting to info")
          level = zerolog.InfoLevel
      }
  4. Security Considerations:

    • Ensure that sensitive flags like github-private-key are not logged or exposed in any way. Consider masking or encrypting these values if they need to be logged for debugging purposes.
  5. Performance Considerations:

    • No significant performance issues are introduced by the diff. However, ensure that the new flags do not introduce any unnecessary overhead or complexity in the configuration parsing.

😼 Mergecat review of pkg/config/config.go

@@ -33,11 +33,18 @@ type ServerConfig struct {
 	OtelCollectorPort string `mapstructure:"otel-collector-port"`
 
 	// vcs
+	VcsUsername  string `mapstructure:"vcs-username"`
+	VcsEmail     string `mapstructure:"vcs-email"`
 	VcsBaseUrl   string `mapstructure:"vcs-base-url"`
 	VcsUploadUrl string `mapstructure:"vcs-upload-url"` // github enterprise upload URL
 	VcsToken     string `mapstructure:"vcs-token"`
 	VcsType      string `mapstructure:"vcs-type"`
 
+	//github
+	GithubPrivateKey     string `mapstructure:"github-private-key"`
+	GithubAppID          int64  `mapstructure:"github-app-id"`
+	GithubInstallationID int64  `mapstructure:"github-installation-id"`
+
 	// webhooks
 	EnsureWebhooks bool   `mapstructure:"ensure-webhooks"`
 	WebhookSecret  string `mapstructure:"webhook-secret"`

Feedback & Suggestions:

  1. Sensitive Data Handling:

    • The addition of GithubPrivateKey and VcsToken fields introduces sensitive data into the configuration. Ensure these values are securely managed and not logged or exposed inadvertently.
    • Consider using environment variables or a secrets management service to handle these sensitive values.
  2. Validation:

    • Add validation for the new fields to ensure they are correctly populated. For example, GithubAppID and GithubInstallationID should be positive integers, and VcsEmail should be a valid email format.
  3. Documentation:

    • Update the documentation to reflect the new configuration options. This will help other developers understand the purpose and usage of these new fields.
  4. Security:

    • Ensure that the GithubPrivateKey is stored securely and consider encrypting it if it needs to be stored in a configuration file.
  5. Consistency:

    • Ensure that the naming conventions are consistent. For example, GithubPrivateKey uses camel case, while other fields like VcsBaseUrl use snake case. Stick to one convention for consistency.
  6. Logging:

    • Be cautious about logging configuration values, especially sensitive ones. Review the logging statements to ensure no sensitive information is being logged.

By addressing these points, you can improve the security, maintainability, and clarity of your configuration code. 🛡️📈

😼 Mergecat review of pkg/vcs/gitlab_client/message.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
 	"github.com/xanzy/go-gitlab"
 
@@ -16,8 +17,8 @@ import (
 
 const MaxCommentLength = 1_000_000
 
-func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) *msg.Message {
-	_, span := tracer.Start(ctx, "PostMessageToMergeRequest")
+func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) (*msg.Message, error) {
+	_, span := tracer.Start(ctx, "PostMessage")
 	defer span.End()
 
 	if len(message) > MaxCommentLength {
@@ -32,10 +33,10 @@ func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message st
 		})
 	if err != nil {
 		telemetry.SetError(span, err, "Create Merge Request Note")
-		log.Error().Err(err).Msg("could not post message to MR")
+		return nil, errors.Wrap(err, "could not post message to MR")
 	}
 
-	return msg.NewMessage(pr.FullName, pr.CheckID, n.ID, c)
+	return msg.NewMessage(pr.FullName, pr.CheckID, n.ID, c), nil
 }
 
 func (c *Client) hideOutdatedMessages(ctx context.Context, projectName string, mergeRequestID int, notes []*gitlab.Note) error {

Feedback & Suggestions:

  1. Error Handling Consistency: The change to return an error from PostMessage is a good improvement for error handling. However, ensure that all calling functions are updated to handle this new error return value.
  2. Error Wrapping: Using errors.Wrap is a good practice for adding context to errors. Ensure this is consistently applied across other functions for better error traceability.
  3. Span Naming: The change from "PostMessageToMergeRequest" to "PostMessage" in the span name is a minor improvement for brevity. Ensure this change is reflected in any monitoring or logging systems that rely on the span name.
  4. Logging: The original logging of the error with log.Error().Err(err).Msg("could not post message to MR") was removed. Consider keeping the log statement for better visibility in logs, even though the error is returned.

Suggested improvement:

@@ -32,10 +33,12 @@ func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message st
 		telemetry.SetError(span, err, "Create Merge Request Note")
-		log.Error().Err(err).Msg("could not post message to MR")
+		log.Error().Err(err).Msg("could not post message to MR")
+		return nil, errors.Wrap(err, "could not post message to MR")
 	}
 
-	return msg.NewMessage(pr.FullName, pr.CheckID, n.ID, c)
+	return msg.NewMessage(pr.FullName, pr.CheckID, n.ID, c), nil
 }

This way, you maintain the logging while also returning the wrapped error.


😼 Mergecat review of docs/usage.md

@@ -47,6 +47,9 @@ The full list of supported environment variables is described below:
 |`KUBECHECKS_ENABLE_PREUPGRADE`|Enable preupgrade checks.|`true`|
 |`KUBECHECKS_ENSURE_WEBHOOKS`|Ensure that webhooks are created in repositories referenced by argo.|`false`|
 |`KUBECHECKS_FALLBACK_K8S_VERSION`|Fallback target Kubernetes version for schema / upgrade checks.|`1.23.0`|
+|`KUBECHECKS_GITHUB_APP_ID`|Github App ID.|`0`|
+|`KUBECHECKS_GITHUB_INSTALLATION_ID`|Github Installation ID.|`0`|
+|`KUBECHECKS_GITHUB_PRIVATE_KEY`|Github App Private Key.||
 |`KUBECHECKS_KUBERNETES_CLUSTERID`|Kubernetes Cluster ID, must be specified if kubernetes-type is eks.||
 |`KUBECHECKS_KUBERNETES_CONFIG`|Path to your kubernetes config file, used to monitor applications.||
 |`KUBECHECKS_KUBERNETES_TYPE`|Kubernetes Type One of eks, or local.|`local`|
@@ -66,8 +69,10 @@ The full list of supported environment variables is described below:
 |`KUBECHECKS_SHOW_DEBUG_INFO`|Set to true to print debug info to the footer of MR comments.|`false`|
 |`KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE`|Sets the mode to use when tidying outdated comments. One of hide, delete.|`hide`|
 |`KUBECHECKS_VCS_BASE_URL`|VCS base url, useful if self hosting gitlab, enterprise github, etc.||
+|`KUBECHECKS_VCS_EMAIL`|VCS Email.||
 |`KUBECHECKS_VCS_TOKEN`|VCS API token.||
 |`KUBECHECKS_VCS_TYPE`|VCS type. One of gitlab or github.|`gitlab`|
+|`KUBECHECKS_VCS_USERNAME`|VCS Username.||
 |`KUBECHECKS_WEBHOOK_SECRET`|Optional secret key for validating the source of incoming webhooks.||
 |`KUBECHECKS_WEBHOOK_URL_BASE`|The endpoint to listen on for incoming PR/MR event webhooks. For example, 'https://checker.mycompany.com'.||
 |`KUBECHECKS_WEBHOOK_URL_PREFIX`|If your application is running behind a proxy that uses path based routing, set this value to match the path prefix. For example, '/hello/world'.||

Feedback & Suggestions:

  1. Security Considerations:

    • For KUBECHECKS_GITHUB_PRIVATE_KEY, it would be beneficial to mention that this should be stored securely and not hardcoded in the values.yaml file. Consider using Kubernetes secrets for this purpose.
    • Similarly, for KUBECHECKS_VCS_TOKEN and KUBECHECKS_WEBHOOK_SECRET, emphasize the importance of secure storage.
  2. Default Values:

    • For KUBECHECKS_GITHUB_APP_ID and KUBECHECKS_GITHUB_INSTALLATION_ID, the default value is set to 0. It might be helpful to clarify that these values need to be replaced with actual IDs.
  3. Consistency:

    • Ensure that the descriptions for new variables are consistent in style and detail with the existing ones. For example, adding a note for KUBECHECKS_GITHUB_PRIVATE_KEY to indicate that it is required for GitHub App authentication.
  4. Documentation:

    • Consider adding a brief section or a link to documentation on how to obtain the Github App ID, Github Installation ID, and Github Private Key for users who might not be familiar with GitHub Apps.

😼 Mergecat review of pkg/events/check.go

@@ -201,7 +201,7 @@ func (ce *CheckEvent) getRepo(ctx context.Context, vcsClient hasUsername, cloneU
 	ce.clonedRepos[reposKey] = repo
 
 	// if we cloned 'HEAD', figure out its original branch and store a copy of the repo there
-	if branchName == "" || branchName == "HEAD" {
+	if branchName == "HEAD" {
 		remoteHeadBranchName, err := repo.GetRemoteHead()
 		if err != nil {
 			return repo, errors.Wrap(err, "failed to determine remote head")
@@ -260,12 +260,17 @@ func (ce *CheckEvent) Process(ctx context.Context) error {
 
 	if len(ce.affectedItems.Applications) <= 0 && len(ce.affectedItems.ApplicationSets) <= 0 {
 		ce.logger.Info().Msg("No affected apps or appsets, skipping")
-		ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, "No changes")
+		if _, err := ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, "No changes"); err != nil {
+			return errors.Wrap(err, "failed to post changes")
+		}
 		return nil
 	}
 
 	// We make one comment per run, containing output for all the apps
-	ce.vcsNote = ce.createNote(ctx)
+	ce.vcsNote, err = ce.createNote(ctx)
+	if err != nil {
+		return errors.Wrap(err, "failed to create note")
+	}
 
 	for num := 0; num <= ce.ctr.Config.MaxConcurrenctChecks; num++ {
 
@@ -375,8 +380,8 @@ Check kubechecks application logs for more information.
 `
 )
 
-// Creates a generic Note struct that we can write into across all worker threads
-func (ce *CheckEvent) createNote(ctx context.Context) *msg.Message {
+// createNote creates a generic Note struct that we can write into across all worker threads
+func (ce *CheckEvent) createNote(ctx context.Context) (*msg.Message, error) {
 	ctx, span := otel.Tracer("check").Start(ctx, "createNote")
 	defer span.End()
 

Feedback & Suggestions:

  1. Error Handling Consistency: The changes introduce error handling for createNote and PostMessage methods, which is a good improvement. However, ensure that all error messages are consistent and informative.
  2. Logging Errors: When returning errors, it might be helpful to log them before returning. This ensures that the error is captured in the logs for debugging purposes.
  3. Branch Name Check: The removal of the empty string check for branchName in the getRepo function might lead to issues if branchName is empty. Consider adding a separate check for an empty branchName before the if branchName == "HEAD" condition.

Suggested Diff:

@@ -201,7 +201,10 @@ func (ce *CheckEvent) getRepo(ctx context.Context, vcsClient hasUsername, cloneU
 	ce.clonedRepos[reposKey] = repo
 
 	// if we cloned 'HEAD', figure out its original branch and store a copy of the repo there
-	if branchName == "HEAD" {
+	if branchName == "" {
+		branchName = "HEAD"
+	}
+	if branchName == "HEAD" {
 		remoteHeadBranchName, err := repo.GetRemoteHead()
 		if err != nil {
 			return repo, errors.Wrap(err, "failed to determine remote head")
@@ -260,12 +263,17 @@ func (ce *CheckEvent) Process(ctx context.Context) error {
 
 	if len(ce.affectedItems.Applications) <= 0 && len(ce.affectedItems.ApplicationSets) <= 0 {
 		ce.logger.Info().Msg("No affected apps or appsets, skipping")
-		ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, "No changes")
+		if _, err := ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, "No changes"); err != nil {
+			ce.logger.Error().Err(err).Msg("Failed to post 'No changes' message")
+			return errors.Wrap(err, "failed to post changes")
+		}
 		return nil
 	}
 
 	// We make one comment per run, containing output for all the apps
-	ce.vcsNote = ce.createNote(ctx)
+	ce.vcsNote, err = ce.createNote(ctx)
+	if err != nil {
+		ce.logger.Error().Err(err).Msg("Failed to create note")
+		return errors.Wrap(err, "failed to create note")
+	}
 
 	for num := 0; num <= ce.ctr.Config.MaxConcurrenctChecks; num++ {
 
@@ -375,8 +383,8 @@ Check kubechecks application logs for more information.
 `
 )
 
-// Creates a generic Note struct that we can write into across all worker threads
-func (ce *CheckEvent) createNote(ctx context.Context) *msg.Message {
+// createNote creates a generic Note struct that we can write into across all worker threads
+func (ce *CheckEvent) createNote(ctx context.Context) (*msg.Message, error) {
 	ctx, span := otel.Tracer("check").Start(ctx, "createNote")
 	defer span.End()
 
 	ce.logger.Info().Msgf("Creating note")
 
-	return ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, ":hourglass: kubechecks running ... ")
+	return ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, ":hourglass: kubechecks running ... "), nil
 }

😼 Mergecat review of pkg/vcs/github_client/client.go

@@ -9,7 +9,8 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/chainguard-dev/git-urls"
+	"github.com/bradleyfalzon/ghinstallation/v2"
+	giturls "github.com/chainguard-dev/git-urls"
 	"github.com/google/go-github/v62/github"
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
@@ -48,37 +49,26 @@ func CreateGithubClient(cfg config.ServerConfig) (*Client, error) {
 		shurcoolClient *githubv4.Client
 	)
 
-	// Initialize the GitLab client with access token
-	t := cfg.VcsToken
-	if t == "" {
-		log.Fatal().Msg("github token needs to be set")
-	}
-	log.Debug().Msgf("Token Length - %d", len(t))
 	ctx := context.Background()
-	ts := oauth2.StaticTokenSource(
-		&oauth2.Token{AccessToken: t},
-	)
-	tc := oauth2.NewClient(ctx, ts)
+
+	githubClient, err := createHttpClient(ctx, cfg)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create github http client")
+	}
 
 	githubUrl := cfg.VcsBaseUrl
 	githubUploadUrl := cfg.VcsUploadUrl
 	// we need both urls to be set for github enterprise
 	if githubUrl == "" || githubUploadUrl == "" {
-		googleClient = github.NewClient(tc) // If this has failed, we'll catch it on first call
+		googleClient = github.NewClient(githubClient) // If this has failed, we'll catch it on first call
 
-		// Use the client from shurcooL's githubv4 library for queries.
-		shurcoolClient = githubv4.NewClient(tc)
+		shurcoolClient = githubv4.NewClient(githubClient)
 	} else {
-		googleClient, err = github.NewClient(tc).WithEnterpriseURLs(githubUrl, githubUploadUrl)
+		googleClient, err = github.NewClient(githubClient).WithEnterpriseURLs(githubUrl, githubUploadUrl)
 		if err != nil {
 			log.Fatal().Err(err).Msg("failed to create github enterprise client")
 		}
-		shurcoolClient = githubv4.NewEnterpriseClient(githubUrl, tc)
-	}
-
-	user, _, err := googleClient.Users.Get(ctx, "")
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to get user")
+		shurcoolClient = githubv4.NewEnterpriseClient(githubUrl, githubClient)
 	}
 
 	client := &Client{
@@ -89,13 +79,20 @@ func CreateGithubClient(cfg config.ServerConfig) (*Client, error) {
 			Issues:       IssuesService{googleClient.Issues},
 		},
 		shurcoolClient: shurcoolClient,
+		username:       cfg.VcsUsername,
+		email:          cfg.VcsEmail,
 	}
-	if user != nil {
-		if user.Login != nil {
-			client.username = *user.Login
-		}
-		if user.Email != nil {
-			client.email = *user.Email
+
+	if client.username == "" || client.email == "" {
+		user, _, err := googleClient.Users.Get(ctx, "")
+		if err == nil {
+			if user.Login != nil {
+				client.username = *user.Login
+			}
+
+			if user.Email != nil {
+				client.email = *user.Email
+			}
 		}
 	}
 
@@ -108,6 +105,32 @@ func CreateGithubClient(cfg config.ServerConfig) (*Client, error) {
 	return client, nil
 }
 
+func createHttpClient(ctx context.Context, cfg config.ServerConfig) (*http.Client, error) {
+	// Initialize the GitHub client with app key if provided
+	if cfg.GithubAppID != 0 && cfg.GithubInstallationID != 0 && cfg.GithubPrivateKey != "" {
+		appTransport, err := ghinstallation.New(
+			http.DefaultTransport, cfg.GithubAppID, cfg.GithubInstallationID, []byte(cfg.GithubPrivateKey),
+		)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to create github app transport")
+		}
+
+		return &http.Client{Transport: appTransport}, nil
+	}
+
+	// Initialize the GitHub client with access token if app key is not provided
+	vcsToken := cfg.VcsToken
+	if vcsToken != "" {
+		log.Debug().Msgf("Token Length - %d", len(vcsToken))
+		ts := oauth2.StaticTokenSource(
+			&oauth2.Token{AccessToken: vcsToken},
+		)
+		return oauth2.NewClient(ctx, ts), nil
+	}
+
+	return nil, errors.New("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")
+}
+
 func (c *Client) Username() string { return c.username }
 func (c *Client) Email() string    { return c.email }
 func (c *Client) GetName() string {

Feedback & Suggestions:

  1. Security Improvement: The createHttpClient function should ensure that the private key is securely handled. Consider using a secure storage mechanism for the private key instead of directly passing it as a byte slice.

    appTransport, err := ghinstallation.New(
        http.DefaultTransport, cfg.GithubAppID, cfg.GithubInstallationID, []byte(cfg.GithubPrivateKey),
    )

    Suggestion: Use a secure vault or environment variable management system to handle sensitive data.

  2. Error Handling: The createHttpClient function should log the error details when returning an error. This will help in debugging issues related to client creation.

    return nil, errors.New("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")

    Suggestion: Add logging before returning the error.

    log.Error().Msg("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")
    return nil, errors.New("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")
  3. Performance Improvement: The createHttpClient function creates a new HTTP client for each call. Consider reusing the HTTP client if possible to avoid unnecessary overhead.

    return &http.Client{Transport: appTransport}, nil

    Suggestion: Implement a caching mechanism to reuse the HTTP client.

  4. Code Readability: The createHttpClient function has nested if-else blocks which can be simplified for better readability.

    if cfg.GithubAppID != 0 && cfg.GithubInstallationID != 0 && cfg.GithubPrivateKey != "" {
        // ...
    } else if vcsToken != "" {
        // ...
    } else {
        // ...
    }

    Suggestion: Refactor the nested if-else blocks to improve readability.

    if cfg.GithubAppID != 0 && cfg.GithubInstallationID != 0 && cfg.GithubPrivateKey != "" {
        // ...
        return &http.Client{Transport: appTransport}, nil
    }
    
    if vcsToken != "" {
        // ...
        return oauth2.NewClient(ctx, ts), nil
    }
    
    log.Error().Msg("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")
    return nil, errors.New("Either GitHub token or GitHub App credentials (App ID, Installation ID, Private Key) must be set")
  5. Logging Improvement: The createHttpClient function logs the token length, which might be unnecessary and could be considered sensitive information.

    log.Debug().Msgf("Token Length - %d", len(vcsToken))

    Suggestion: Remove or modify the log statement to avoid logging sensitive information.

    log.Debug().Msg("Using provided VCS token for authentication")


Dependency Review

Click to read mergecats review!

No suggestions found

@djeebus
Copy link
Collaborator Author

djeebus commented Oct 1, 2024

Can't push images from forks =/ opened #288 locally instead

@djeebus djeebus closed this Oct 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use KubeChecks with GitHub app
3 participants