How no-mistakes Integrates with GitLab Using the glab v1.5x API
no-mistakes delegates all GitLab backend operations to the glab CLI rather than using raw HTTP clients, implementing a Host struct in internal/scm/gitlab that executes version-aware glab commands for authentication, merge requests, and CI pipeline monitoring.
The kunchenguid/no-mistakes repository abstracts GitLab interactions through a clean interface that leverages glab v1.5x capabilities for robust GitOps workflows. This integration strategy eliminates manual token management while supporting advanced features like host-scoped authentication, merge request state tracking, and comprehensive CI check monitoring through structured command-line invocations.
Host Architecture and Construction
The GitLab integration centers on the Host type defined in internal/scm/gitlab/gitlab.go, which implements the generic scm.Host interface used throughout the codebase.
The constructor accepts a command factory and runtime configuration:
// internal/scm/gitlab/gitlab.go:30-45
func New(cmd CmdFactory, cliAvailable func() bool, host, projectPath string) *Host {
return &Host{
cmd: cmd,
cliAvailable: cliAvailable,
host: strings.TrimSpace(host),
projectPath: strings.TrimSpace(projectPath),
}
}
Key dependencies injected:
cmd: ACmdFactorythat createsexec.Cmdinstances to runglabin the caller's working directorycliAvailable: A runtime predicate that verifies theglabbinary exists onPATHhost: The GitLab instance hostname for authentication scoping (e.g.,gitlab.example.com)projectPath: The repository path (e.g.,group/project) enabling REST-based job queries independent of the current branch
Authentication Scoping and Availability
The Available method ensures the CLI is installed and authenticated before executing operations. It supports both scoped and unscoped authentication checks:
// internal/scm/gitlab/gitlab.go:21-34
func (h *Host) Available(ctx context.Context) error {
if h.cliAvailable != nil && !h.cliAvailable() {
return errors.New("glab CLI is not installed")
}
authArgs := []string{"auth", "status"}
if h.host != "" {
authArgs = append(authArgs, "--hostname", h.host)
}
if err := h.cmd(ctx, "glab", authArgs...).Run(); err != nil {
return errors.New("glab CLI is not authenticated")
}
return nil
}
When a host is specified, the code executes glab auth status --hostname <host> to prevent stale credentials for unrelated GitLab instances from causing false negatives. If the host is empty, the check falls back to the default unscoped authentication status for backward compatibility.
Merge Request Operations
The Host type implements the complete merge request lifecycle through glab's mr subcommand family. All operations return standardized scm.PR structs that decouple the GitLab implementation from the rest of the application.
Finding Existing Merge Requests
The FindPR method queries for merge requests by source branch:
// Implementation around lines 63-73
args := []string{"mr", "list", "--source-branch", branch, "--output", "json"}
if base != "" {
args = append(args, "--target-branch", base)
}
// Execute and unmarshal first MR payload into *scm.PR
Creating and Updating MRs
CreatePR constructs a glab mr create command with mandatory flags:
glab mr create --source-branch <branch> --target-branch <base> --title <title> --description <body> --yes
The implementation captures stdout and extracts the merge request URL using extractMRURL, optionally parsing the MR number from the response.
UpdatePR resolves the MR identifier and executes:
glab mr update <id> --title <title> --description <body> --yes
Mergeability and State Detection
The GetPRState method calls glab mr view <id> --output json through the internal viewMR helper, then normalizes GitLab's status strings using normalizePRState.
For mergeability checks, GetMergeableState inspects the has_conflicts and detailed_merge_status fields from the mr view JSON output, returning scm.MergeableOK, scm.MergeableConflict, or scm.MergeablePending accordingly.
CI Pipeline Integration
The GetChecks method monitors pipeline status through a dual-strategy approach that maintains compatibility across glab versions.
Primary Strategy: Native CI Status
For glab v1.5x and later, the method uses the dedicated CI status command:
// internal/scm/gitlab/gitlab.go:80-92
cmd := h.cmd(ctx, "glab", "ci", "status", "--mr", pr.Number, "--output", "json")
This command returns a JSON array of job objects that the code decodes and converts to scm.Check structs via jobsToChecks.
Fallback Strategy: REST API
When the installed glab version lacks the --mr flag, the code executes getChecksFallback (lines 118-140):
- Retrieve the pipeline ID via
glab mr view <id> --output json - Query jobs using paginated API calls:
glab api projects/<group%2Fproject>/pipelines/<pipelineID>/jobs
The decodeGitlabJobs helper handles multiple JSON document formats including single arrays, pipeline objects with embedded jobs fields, and concatenated paginated responses.
Retrieving Failed Job Logs
When CI checks fail, FetchFailedCheckLogs provides granular log access without requiring additional authentication:
// Implementation starts at line 48-77
func (h *Host) FetchFailedCheckLogs(ctx context.Context, pr *scm.PR, _, _ string, failedJobNames []string) (string, error) {
// 1. Retrieve pipeline ID from mr view
// 2. List jobs via glab api
// 3. Find matching job ID via findFailedJobID
// 4. Execute: glab ci trace <jobID>
}
This approach ensures that debug information is available even when the primary CI status command fails or returns incomplete data.
Utility Helpers and Data Normalization
The integration includes several utility functions in internal/scm/gitlab/gitlab.go for robust JSON handling:
bytesTrimToJSON: Discards banner lines that glab might emit before JSON payloadsnormalizePRState: Maps GitLab status values to genericscmenumsgitlabStatusBucket: Categorizes job statuses (success, failed, pending) intoscm.CheckBuckettypesProjectPath: Parses HTTPS, SSH, and scp-style remote URLs intogroup/projectformat for API calls
Complete Integration Example
The following example demonstrates the full lifecycle of the no-mistakes GitLab integration:
package main
import (
"context"
"fmt"
"os/exec"
"github.com/kunchenguid/no-mistakes/internal/scm"
"github.com/kunchenguid/no-mistakes/internal/scm/gitlab"
)
func main() {
// 1️⃣ Build a CmdFactory that runs commands in the current directory.
cmdFactory := func(ctx context.Context, name string, args ...string) *exec.Cmd {
return exec.CommandContext(ctx, name, args...)
}
// 2️⃣ Create a GitLab host for https://gitlab.example.com/group/project
host := gitlab.New(
cmdFactory,
func() bool { return true }, // assume glab is on PATH
"gitlab.example.com", // hostname for auth scoping
"group/project", // projectPath enables API job queries
)
// 3️⃣ Verify the CLI is present and authenticated.
if err := host.Available(context.Background()); err != nil {
panic(err)
}
// 4️⃣ Find an existing merge request for the current branch.
mr, err := host.FindPR(context.Background(), "feature-branch", "main")
if err != nil {
panic(err)
}
fmt.Printf("Found MR: %s (number %s)\n", mr.URL, mr.Number)
// 5️⃣ Retrieve CI checks for that MR.
checks, err := host.GetChecks(context.Background(), mr)
if err != nil {
panic(err)
}
for _, c := range checks {
fmt.Printf("Job %s: %s (completed %v)\n", c.Name, c.Bucket, c.CompletedAt)
}
// 6️⃣ If any job failed, fetch its log.
failingNames := []string{}
for _, c := range checks {
if c.Bucket == scm.CheckBucketFail {
failingNames = append(failingNames, c.Name)
}
}
if len(failingNames) > 0 {
log, _ := host.FetchFailedCheckLogs(context.Background(), mr, "", "", failingNames)
fmt.Println("Failed job log:\n", log)
}
}
This pattern instantiates the Host with dependency injection, verifies availability, locates merge requests, monitors CI status, and retrieves failure logs—all mediated through the glab CLI.
Summary
- no-mistakes implements GitLab integration through the
internal/scm/gitlabpackage, avoiding direct HTTP API management - The
Hoststruct encapsulates glab v1.5x command execution with support for host-scoped authentication viaglab auth status --hostname - Merge request operations use
glab mr list,create,update, andviewcommands with JSON output parsing - CI monitoring prefers
glab ci status --mrbut falls back toglab apicalls for older glab versions - Failed job logs are retrieved through
glab ci traceafter resolving job IDs via the pipeline API - All interactions are abstracted through the
scm.Hostinterface, enabling testable, version-resilient GitOps workflows
Frequently Asked Questions
How does no-mistakes handle authentication for multiple GitLab instances?
The Host struct accepts an optional host parameter that scopes authentication checks to specific GitLab instances. When provided, the Available method executes glab auth status --hostname <host> to verify credentials for that specific instance only, preventing conflicts with cached tokens for other GitLab URLs.
What happens if the glab CLI version doesn't support the --mr flag for CI checks?
The GetChecks method implements automatic fallback logic in getChecksFallback. It first retrieves the pipeline ID using glab mr view, then queries jobs directly via glab api projects/<path>/pipelines/<id>/jobs. This ensures compatibility with glab versions predating the ci status --mr feature.
Why does no-mistakes use the glab CLI instead of the GitLab REST API directly?
Using glab abstracts authentication management, pagination handling, and JSON formatting while eliminating the need to store and refresh personal access tokens within the application. This approach leverages the user's existing glab configuration and credential helpers, reducing security surface area and maintenance overhead.
How are GitLab job statuses mapped to the generic scm.Check format?
The integration uses gitlabStatusBucket and normalizePRState helper functions to translate GitLab-specific status strings (like detailed_merge_status and job states) into standardized scm.CheckBucket enums (Success, Fail, Pending) and mergeability constants that the rest of the no-mistakes pipeline can process agnostically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →