Skip to content

Commit

Permalink
fix: code refactoring and cleanup (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucaLanziani authored Nov 30, 2023
1 parent 90aa6d8 commit b08bdd7
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 92 deletions.
16 changes: 15 additions & 1 deletion src/cli/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"os"

"github.com/nearform/initium-cli/src/services/git"
knative "github.com/nearform/initium-cli/src/services/k8s"
Expand Down Expand Up @@ -47,7 +48,20 @@ func (c *icli) Deploy(cCtx *cli.Context) error {
return err
}

return knative.Apply(serviceManifest, config)
url, err := knative.Apply(serviceManifest, config)

if err != nil {
return err
}

_, isPR := os.LookupEnv("GITHUB_HEAD_REF")
if os.Getenv("CI") == "true" && os.Getenv("GITHUB_ACTIONS") == "true" && isPR {
return git.PublishCommentPRGithub(url)
}

fmt.Fprintf(c.Writer, "You can reach the app via %s\n", url)

return nil
}

func (c icli) DeployCMD() *cli.Command {
Expand Down
19 changes: 1 addition & 18 deletions src/cli/onbranch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package cli

import (
"fmt"
"net/url"
"os"

"github.com/nearform/initium-cli/src/services/git"
"github.com/nearform/initium-cli/src/utils"
Expand Down Expand Up @@ -32,22 +30,7 @@ func (c icli) buildPushDeploy(cCtx *cli.Context) error {
return err
}

err = c.Deploy(cCtx)
appUrl, urlErr := url.Parse(err.Error()) // Check if it contains the app URL or it's a legit error
if urlErr != nil {
fmt.Println("No app URL available")
return err
}

// Check if the CI environment variable is set to GitHub Actions
if os.Getenv("CI") == "true" && os.Getenv("GITHUB_ACTIONS") == "true" {
err = git.PublishCommentPRGithub(appUrl.String())
} else {
fmt.Printf("You can reach the app via %s\n", appUrl.String())
err = nil
}

return err
return c.Deploy(cCtx)
}

func (c icli) OnBranchCMD() *cli.Command {
Expand Down
143 changes: 80 additions & 63 deletions src/services/git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import (
"strings"
"time"

"github.com/charmbracelet/log"
git "github.com/go-git/go-git/v5"
github "github.com/google/go-github/v56/github"
oauth2 "golang.org/x/oauth2"
)

const (
httpgithubprefix = "https://github.com/"
gitgithubprefix = "git@github.com:"
httpgithubprefix = "https://github.com/"
gitgithubprefix = "git@github.com:"
githubRefPattern = `refs/pull/(\d+)/merge`
githubCommentIdentifier = "Deployed by [Initium](https://initium.nearform.com)" // used to find and update existing comments
)

func initRepo() (*git.Repository, error) {
Expand Down Expand Up @@ -115,95 +118,109 @@ func GetGithubOrg() (string, error) {
return splitRemote[0], nil
}

func PublishCommentPRGithub (url string) error {
var message, owner, repo string
var prNumber int
func buildMarkdownMessage(url string) (string, error) {
commitSha, err := GetHash()
if err != nil {
return "", err
}

// Build message
message = fmt.Sprintf("Application URL: %s\n", url) + fmt.Sprintf("Commit hash: %s\n", commitSha) + fmt.Sprintf("Timestamp: %v\n", time.Now())
message := fmt.Sprintf(githubCommentIdentifier+`
|Application URL | %s |
|:-----------------|:----|
|Commit hash | %s |
|Timestamp | %s |
`, url, commitSha, time.Now().UTC())
return message, nil
}

// Check GITHUB_TOKEN
func PublishCommentPRGithub(url string) error {
token := os.Getenv("GITHUB_TOKEN")
prRef := os.Getenv("GITHUB_REF")
repoInfo := os.Getenv("GITHUB_REPOSITORY")

if token == "" {
return fmt.Errorf("Please set up the GITHUB_TOKEN environment variable")
return fmt.Errorf("GITHUB_TOKEN environment variable not set")
}

// Create an authenticated GitHub client
ctx := context.Background()
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tc := oauth2.NewClient(ctx, ts)
client := github.NewClient(tc)
// Extract pull request number
prNumber, err := extractPullRequestNumber(prRef)
if err != nil {
return err
}

// Get required data to publish a comment
repoInfo := os.Getenv("GITHUB_REPOSITORY")
repoParts := strings.Split(repoInfo, "/")
if len(repoParts) == 2 {
owner = repoParts[0]
repo = repoParts[1]
} else {
return fmt.Errorf("Invalid repository information")
}

// Check if the workflow was triggered by a pull request event
eventName := os.Getenv("GITHUB_EVENT_NAME")
if eventName == "pull_request" {
// Get the pull request ref
prRef := os.Getenv("GITHUB_REF")

// Extract the pull request number using a regular expression
re := regexp.MustCompile(`refs/pull/(\d+)/merge`)
matches := re.FindStringSubmatch(prRef)

if len(matches) == 2 {
prNumber, err = strconv.Atoi(matches[1])
if err != nil {
return fmt.Errorf("Error converting string to int: %v", err)
}
} else {
return fmt.Errorf("Unable to extract pull request number from GITHUB_REF")
}
} else {
return fmt.Errorf("This workflow was not triggered by a pull request event")
message, err := buildMarkdownMessage(url)
if err != nil {
return fmt.Errorf("cannot build the message: %v", err)
}

// Create comment with body
comment := &github.IssueComment{
Body: github.String(message),
}

// List comments on the PR
// Get required data to publish a comment
repoParts := strings.Split(repoInfo, "/")
if len(repoParts) != 2 {
return fmt.Errorf("invalid repository information %s", repoInfo)
}
owner := repoParts[0]
repo := repoParts[1]

// Create an authenticated GitHub client
ctx := context.Background()
client := createGithubClient(ctx, token)

// Check if we have to update an existing comment
comments, _, err := client.Issues.ListComments(ctx, owner, repo, prNumber, nil)
if err != nil {
return err
}

commentID := findExistingCommentIDPRGithub(comments, "Application URL:") // Search for app URL comment

if commentID != 0 {
// Update existing comment
updatedComment, _, err := client.Issues.EditComment(ctx, owner, repo, commentID, comment)
matchingComments := findExistingGithubComments(comments, githubCommentIdentifier) // Search for app URL comment
if n := len(matchingComments); n != 0 {
log.Infof("%d matching comment[s] found %v, will always update the last one", n, matchingComments)
updatedComment, _, err := client.Issues.EditComment(ctx, owner, repo, matchingComments[n-1], comment)
if err != nil {
return err
}
fmt.Printf("Comment updated successfully: %s\n", updatedComment.GetHTMLURL())
} else {
// Publish a new comment
newComment, _, err := client.Issues.CreateComment(ctx, owner, repo, prNumber, comment)
if err != nil {
return err
}
fmt.Printf("Comment published: %s\n", newComment.GetHTMLURL())
log.Infof("Comment updated successfully: %s\n", updatedComment.GetHTMLURL())
return nil
}

// Publish a new comment
newComment, _, err := client.Issues.CreateComment(ctx, owner, repo, prNumber, comment)
if err != nil {
return err
}
log.Infof("Comment published: %s\n", newComment.GetHTMLURL())
return nil
}

func findExistingCommentIDPRGithub(comments []*github.IssueComment, targetBody string) int64 {
func createGithubClient(ctx context.Context, token string) *github.Client {
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tc := oauth2.NewClient(ctx, ts)
return github.NewClient(tc)
}

func extractPullRequestNumber(prRef string) (int, error) {
matches := regexp.MustCompile(githubRefPattern).FindStringSubmatch(prRef)
if len(matches) != 2 {
return 0, fmt.Errorf("unable to extract pull request number from GITHUB_REF %s", prRef)
}

prNumber, err := strconv.Atoi(matches[1])
if err != nil {
return 0, fmt.Errorf("error converting string to int: %v", err)
}
return prNumber, nil
}

func findExistingGithubComments(comments []*github.IssueComment, targetString string) []int64 {
matchingComments := []int64{}
for _, comment := range comments {
if strings.Contains(comment.GetBody(), targetBody) {
return comment.GetID()
body := comment.GetBody()
if strings.Contains(body, targetString) && strings.Contains(body, "initium") {
matchingComments = append(matchingComments, comment.GetID())
}
}
return 0
return matchingComments
}
20 changes: 10 additions & 10 deletions src/services/k8s/knative.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,11 @@ func setEnv(manifest *servingv1.Service, envFile string, manifestEnvVars map[str

func loadEnvFile(envFile string, manifestEnvVars map[string]string) ([]corev1.EnvVar, error) {
var envVarList []corev1.EnvVar

if _, err := os.Stat(envFile); errors.Is(err, os.ErrNotExist) {
return nil, nil
}

envVariables, err := godotenv.Read(envFile)
if err != nil {
return nil, fmt.Errorf("Error loading .env file. '%s' already set", err)
Expand Down Expand Up @@ -204,20 +204,20 @@ func ToYaml(serviceManifest *servingv1.Service) ([]byte, error) {
return yaml.JSONToYAML(jsonBytes)
}

func Apply(serviceManifest *servingv1.Service, config *rest.Config) error {
func Apply(serviceManifest *servingv1.Service, config *rest.Config) (string, error) {
log.Info("Deploying Knative service", "host", config.Host, "name", serviceManifest.ObjectMeta.Name, "namespace", serviceManifest.ObjectMeta.Namespace)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
defer cancel()

// Create a new Knative Serving client
servingClient, err := servingv1client.NewForConfig(config)
if err != nil {
return fmt.Errorf("Error creating the knative client %v", err)
return "", fmt.Errorf("Error creating the knative client %v", err)
}

client, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("Creating Kubernetes client %v", err)
return "", fmt.Errorf("Creating Kubernetes client %v", err)
}

_, err = client.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{
Expand All @@ -227,21 +227,21 @@ func Apply(serviceManifest *servingv1.Service, config *rest.Config) error {
}, metav1.CreateOptions{})

if err != nil && !apimachineryErrors.IsAlreadyExists(err) {
return fmt.Errorf("cannot create namespace %s, failed with %v", serviceManifest.ObjectMeta.Namespace, err)
return "", fmt.Errorf("cannot create namespace %s, failed with %v", serviceManifest.ObjectMeta.Namespace, err)
}

service, err := servingClient.Services(serviceManifest.ObjectMeta.Namespace).Get(ctx, serviceManifest.ObjectMeta.Name, metav1.GetOptions{})
var deployedService *servingv1.Service
if err != nil {
deployedService, err = servingClient.Services(serviceManifest.ObjectMeta.Namespace).Create(ctx, serviceManifest, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("Creating Knative service %v", err)
return "", fmt.Errorf("Creating Knative service %v", err)
}
} else {
service.Spec = serviceManifest.Spec
deployedService, err = servingClient.Services(serviceManifest.ObjectMeta.Namespace).Update(ctx, service, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("Updating Knative service %v", err)
return "", fmt.Errorf("Updating Knative service %v", err)
}
}

Expand All @@ -250,10 +250,10 @@ func Apply(serviceManifest *servingv1.Service, config *rest.Config) error {
for {
service, err = servingClient.Services(serviceManifest.ObjectMeta.Namespace).Get(ctx, serviceManifest.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
return err
return "", err
}
if service.Status.URL != nil {
return fmt.Errorf("%s", service.Status.URL) // Overload error return variable with URL string
return service.Status.URL.String(), nil
}

time.Sleep(time.Millisecond * 500)
Expand Down

0 comments on commit b08bdd7

Please sign in to comment.