Feat: Remove Requirement for Dotfile (#84)
This MR removes the requirement for a dotfile (the dotfile is now optional and will override the configuration provided via environment variables). The requirement for providing a project ID is also eliminated, by parsing the namespace and project name from the SSH or HTTPS remote, and then using that to query Gitlab for a matching project.
This commit is contained in:
committed by
GitHub
parent
38df51bfbc
commit
80b597e56a
164
cmd/client.go
164
cmd/client.go
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
@@ -18,7 +19,6 @@ type Client struct {
|
||||
mergeId int
|
||||
gitlabInstance string
|
||||
authToken string
|
||||
logPath string
|
||||
git *gitlab.Client
|
||||
}
|
||||
|
||||
@@ -27,85 +27,32 @@ type DebugSettings struct {
|
||||
GoResponse bool `json:"go_response"`
|
||||
}
|
||||
|
||||
var requestLogger retryablehttp.RequestLogHook = func(l retryablehttp.Logger, r *http.Request, i int) {
|
||||
logPath := os.Args[len(os.Args)-1]
|
||||
/* This will parse and validate the project settings and then initialize the Gitlab client */
|
||||
func (c *Client) initGitlabClient() error {
|
||||
|
||||
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
token := r.Header.Get("Private-Token")
|
||||
r.Header.Set("Private-Token", "REDACTED")
|
||||
res, err := httputil.DumpRequest(r, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.Header.Set("Private-Token", token)
|
||||
|
||||
_, err = file.Write([]byte("\n-- REQUEST --\n")) //nolint:all
|
||||
_, err = file.Write(res) //nolint:all
|
||||
_, err = file.Write([]byte("\n")) //nolint:all
|
||||
}
|
||||
|
||||
var responseLogger retryablehttp.ResponseLogHook = func(l retryablehttp.Logger, response *http.Response) {
|
||||
logPath := os.Args[len(os.Args)-1]
|
||||
|
||||
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
res, err := httputil.DumpResponse(response, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if len(os.Args) < 6 {
|
||||
return errors.New("Must provide gitlab url, port, auth token, debug settings, and log path")
|
||||
}
|
||||
|
||||
_, err = file.Write([]byte("\n-- RESPONSE --\n")) //nolint:all
|
||||
_, err = file.Write(res) //nolint:all
|
||||
_, err = file.Write([]byte("\n")) //nolint:all
|
||||
}
|
||||
|
||||
/* This will initialize the client with the token and check for the basic project ID and command arguments */
|
||||
func (c *Client) init(branchName string) error {
|
||||
|
||||
if len(os.Args) < 5 {
|
||||
return errors.New("Must provide project ID, gitlab instance, port, and auth token!")
|
||||
gitlabInstance := os.Args[1]
|
||||
if gitlabInstance == "" {
|
||||
return errors.New("GitLab instance URL cannot be empty")
|
||||
}
|
||||
|
||||
projectId := os.Args[1]
|
||||
gitlabInstance := os.Args[2]
|
||||
authToken := os.Args[4]
|
||||
debugSettings := os.Args[5]
|
||||
authToken := os.Args[3]
|
||||
if authToken == "" {
|
||||
return errors.New("Auth token cannot be empty")
|
||||
}
|
||||
|
||||
/* Parse debug settings and initialize logger handlers */
|
||||
debugSettings := os.Args[4]
|
||||
var debugObject DebugSettings
|
||||
err := json.Unmarshal([]byte(debugSettings), &debugObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse debug settings: %w, %s", err, debugSettings)
|
||||
}
|
||||
|
||||
logPath := os.Args[len(os.Args)-1]
|
||||
|
||||
if projectId == "" {
|
||||
return errors.New("Project ID cannot be empty")
|
||||
}
|
||||
|
||||
if gitlabInstance == "" {
|
||||
return errors.New("GitLab instance URL cannot be empty")
|
||||
}
|
||||
|
||||
if authToken == "" {
|
||||
return errors.New("Auth token cannot be empty")
|
||||
}
|
||||
|
||||
c.gitlabInstance = gitlabInstance
|
||||
c.projectId = projectId
|
||||
c.authToken = authToken
|
||||
c.logPath = logPath
|
||||
|
||||
var apiCustUrl = fmt.Sprintf(c.gitlabInstance + "/api/v4")
|
||||
var apiCustUrl = fmt.Sprintf(gitlabInstance + "/api/v4")
|
||||
|
||||
gitlabOptions := []gitlab.ClientOptionFunc{
|
||||
gitlab.WithBaseURL(apiCustUrl),
|
||||
@@ -125,13 +72,40 @@ func (c *Client) init(branchName string) error {
|
||||
return fmt.Errorf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
c.gitlabInstance = gitlabInstance
|
||||
c.authToken = authToken
|
||||
c.git = git
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/* This will fetch the project ID and merge request ID using the client */
|
||||
func (c *Client) initProjectSettings(remoteUrl string, namespace string, projectName string, branchName string) error {
|
||||
|
||||
idStr := namespace + "/" + projectName
|
||||
opt := gitlab.GetProjectOptions{}
|
||||
project, _, err := c.git.Projects.GetProject(idStr, &opt)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf(fmt.Sprintf("Error getting project at %s", remoteUrl), err)
|
||||
}
|
||||
if project == nil {
|
||||
return fmt.Errorf(fmt.Sprintf("Could not find project at %s", remoteUrl), err)
|
||||
}
|
||||
|
||||
if project == nil {
|
||||
return fmt.Errorf("No projects you are a member of contained remote URL %s", remoteUrl)
|
||||
}
|
||||
|
||||
c.projectId = fmt.Sprint(project.ID)
|
||||
|
||||
options := gitlab.ListProjectMergeRequestsOptions{
|
||||
Scope: gitlab.String("all"),
|
||||
State: gitlab.String("opened"),
|
||||
SourceBranch: &branchName,
|
||||
}
|
||||
|
||||
mergeRequests, _, err := git.MergeRequests.ListProjectMergeRequests(c.projectId, &options)
|
||||
mergeRequests, _, err := c.git.MergeRequests.ListProjectMergeRequests(c.projectId, &options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to list merge requests: %w", err)
|
||||
}
|
||||
@@ -147,7 +121,6 @@ func (c *Client) init(branchName string) error {
|
||||
}
|
||||
|
||||
c.mergeId = mergeIdInt
|
||||
c.git = git
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -165,3 +138,54 @@ func (c *Client) handleError(w http.ResponseWriter, err error, message string, s
|
||||
c.handleError(w, err, "Could not encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
var requestLogger retryablehttp.RequestLogHook = func(l retryablehttp.Logger, r *http.Request, i int) {
|
||||
file := openLogFile()
|
||||
defer file.Close()
|
||||
|
||||
token := r.Header.Get("Private-Token")
|
||||
r.Header.Set("Private-Token", "REDACTED")
|
||||
res, err := httputil.DumpRequest(r, true)
|
||||
if err != nil {
|
||||
log.Fatalf("Error dumping request: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
r.Header.Set("Private-Token", token)
|
||||
|
||||
_, err = file.Write([]byte("\n-- REQUEST --\n")) //nolint:all
|
||||
_, err = file.Write(res) //nolint:all
|
||||
_, err = file.Write([]byte("\n")) //nolint:all
|
||||
}
|
||||
|
||||
var responseLogger retryablehttp.ResponseLogHook = func(l retryablehttp.Logger, response *http.Response) {
|
||||
file := openLogFile()
|
||||
defer file.Close()
|
||||
|
||||
res, err := httputil.DumpResponse(response, true)
|
||||
if err != nil {
|
||||
log.Fatalf("Error dumping response: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = file.Write([]byte("\n-- RESPONSE --\n")) //nolint:all
|
||||
_, err = file.Write(res) //nolint:all
|
||||
_, err = file.Write([]byte("\n")) //nolint:all
|
||||
}
|
||||
|
||||
func openLogFile() *os.File {
|
||||
logFile := os.Args[len(os.Args)-1]
|
||||
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Log file %s does not exist", logFile)
|
||||
} else if os.IsPermission(err) {
|
||||
log.Printf("Permission denied for log file %s", logFile)
|
||||
} else {
|
||||
log.Printf("Error opening log file %s: %v", logFile, err)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
67
cmd/git.go
Normal file
67
cmd/git.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Extracts information about the current repository and returns
|
||||
it to the client for initialization. The current directory must be a valid
|
||||
Gitlab project and the branch must be a feature branch
|
||||
*/
|
||||
func ExtractGitInfo() (string, string, string, string, error) {
|
||||
|
||||
url, err := getProjectUrl()
|
||||
if err != nil {
|
||||
return "", "", "", "", fmt.Errorf("Could not get project Url: %v", err)
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`(?:[:\/])([^\/]+)\/([^\/]+)\.git$`)
|
||||
matches := re.FindStringSubmatch(url)
|
||||
if len(matches) != 3 {
|
||||
return "", "", "", "", fmt.Errorf("Invalid Git URL format: %s", url)
|
||||
}
|
||||
|
||||
namespace := matches[1]
|
||||
projectName := matches[2]
|
||||
|
||||
branch, err := getCurrentBranch()
|
||||
if err != nil {
|
||||
return "", "", "", "", fmt.Errorf("Failed to get current branch: %v", err)
|
||||
}
|
||||
|
||||
return url, namespace, projectName, branch, nil
|
||||
}
|
||||
|
||||
/* Gets the current branch */
|
||||
func getCurrentBranch() (res string, e error) {
|
||||
gitCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
|
||||
output, err := gitCmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error running git rev-parse: %w", err)
|
||||
}
|
||||
|
||||
branchName := strings.TrimSpace(string(output))
|
||||
|
||||
if branchName == "main" || branchName == "master" {
|
||||
return "", fmt.Errorf("Cannot run on %s branch", branchName)
|
||||
}
|
||||
|
||||
return branchName, nil
|
||||
}
|
||||
|
||||
/* Gets the project SSH or HTTPS url */
|
||||
func getProjectUrl() (res string, e error) {
|
||||
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||
url, err := cmd.Output()
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not get origin remote")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(url)), nil
|
||||
}
|
||||
47
cmd/main.go
47
cmd/main.go
@@ -7,29 +7,24 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
branchName, err := getCurrentBranch()
|
||||
|
||||
url, namespace, projectName, branchName, err := ExtractGitInfo()
|
||||
if err != nil {
|
||||
log.Fatalf("Failure: Failed to get current branch: %v", err)
|
||||
log.Fatalf("Failed to get git namespace, project, branch, or url: %v", err)
|
||||
}
|
||||
|
||||
if branchName == "main" || branchName == "master" {
|
||||
log.Fatalf("Cannot run on %s branch", branchName)
|
||||
}
|
||||
|
||||
/* Initialize Gitlab client */
|
||||
var c Client
|
||||
|
||||
if err := c.init(branchName); err != nil {
|
||||
if err := c.initGitlabClient(); err != nil {
|
||||
log.Fatalf("Failed to initialize Gitlab client: %v", err)
|
||||
}
|
||||
|
||||
if err := c.initProjectSettings(url, namespace, projectName, branchName); err != nil {
|
||||
log.Fatalf("Failed to initialize project settings: %v", err)
|
||||
}
|
||||
|
||||
m := http.NewServeMux()
|
||||
m.Handle("/ping", http.HandlerFunc(PingHandler))
|
||||
m.Handle("/mr/summary", withGitlabContext(http.HandlerFunc(SummaryHandler), c))
|
||||
@@ -47,7 +42,7 @@ func main() {
|
||||
m.Handle("/pipeline", withGitlabContext(http.HandlerFunc(PipelineHandler), c))
|
||||
m.Handle("/job", withGitlabContext(http.HandlerFunc(JobHandler), c))
|
||||
|
||||
port := os.Args[3]
|
||||
port := os.Args[2]
|
||||
if port == "" {
|
||||
// port was not specified
|
||||
port = "0"
|
||||
@@ -59,7 +54,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
listner_port := listener.Addr().(*net.TCPAddr).Port
|
||||
listenerPort := listener.Addr().(*net.TCPAddr).Port
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
@@ -69,10 +64,10 @@ func main() {
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", listner_port) + "/ping")
|
||||
resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", listenerPort) + "/ping")
|
||||
if resp.StatusCode == 200 && err == nil {
|
||||
/* This print is detected by the Lua code and used to fetch project information */
|
||||
fmt.Println("Server started on port: ", listner_port)
|
||||
fmt.Println("Server started on port: ", listenerPort)
|
||||
return
|
||||
}
|
||||
// Wait for healthcheck to pass - at most 1 sec.
|
||||
@@ -85,7 +80,11 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func PingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, "pong")
|
||||
}
|
||||
|
||||
func withGitlabContext(next http.HandlerFunc, c Client) http.Handler {
|
||||
@@ -94,19 +93,3 @@ func withGitlabContext(next http.HandlerFunc, c Client) http.Handler {
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
/* Gets the current branch */
|
||||
func getCurrentBranch() (res string, e error) {
|
||||
gitCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
|
||||
output, err := gitCmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error running git rev-parse: %w", err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
func PingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, "pong")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user