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

depsync: add xk6-depsync command #72

Closed
wants to merge 11 commits into from
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ WORKDIR /build
COPY . .

ARG GOFLAGS="-ldflags=-w -ldflags=-s"
RUN CGO_ENABLED=0 go build -o xk6 -trimpath ./cmd/xk6/main.go
RUN CGO_ENABLED=0 go build -o xk6 -trimpath ./cmd/xk6/main.go && \
CGO_ENABLED=0 go build -o xk6-depsync -trimpath ./cmd/xk6-depsync/main.go
nadiamoe marked this conversation as resolved.
Show resolved Hide resolved


FROM golang:${GO_VERSION}-${VARIANT}
Expand All @@ -24,7 +25,7 @@ RUN USER=xk6 && \
mkdir -p /etc/fixuid && \
printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml

COPY --from=builder /build/xk6 /usr/local/bin/
COPY --from=builder /build/xk6 /build/xk6-depsync /usr/local/bin/

COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh

Expand Down
112 changes: 112 additions & 0 deletions cmd/xk6-depsync/main.go
nadiamoe marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// main is the main package for xk6-depsync, a script that checks and provides commands to synchronize
// common dependencies with k6 core.
package main

import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"sort"
"strings"
)

const (
k6Core = "go.k6.io/k6"
k6GoModURL = "https://proxy.golang.org/" + k6Core + "/@v/%s.mod"
)

func main() {
gomod := "go.mod"
if len(os.Args) >= 2 {
gomod = os.Args[1]
}

file, err := os.Open(gomod)
if err != nil {
log.Fatalf("opening local go.mod: %v", err)
}

ownDeps, err := dependencies(file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using https://pkg.go.dev/golang.org/x/[email protected]/modfile#Parse instead of custom parsing logic, it would be a more robust solution....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've given this a swing in the latest two commits. I wouldn't say I'm against it, but I'm not very convinced either. I don't see the code significantly less complex or significantly shorter either.

LMK what you think about this. If you prefer it this way, I'm happy letting it stay, otherwise I can drop the commits and get back to the original implementation. Your call :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't recommend it because of the complexity or length of the code. If there is a well-maintained, high-quality library for reading a file format, it is advisable to use it. This way you can avoid that your own parser does not exactly follow the file format specification.

BTW this is just a suggestion, it's up to the code maintainer to decide :)
( @olegbespalov ? )

if err != nil {
log.Fatalf("reading dependencies: %v", err)
}

k6Version := ownDeps[k6Core]
if k6Version == "" {
log.Fatalf("K6 core %q not found in %q", k6Core, gomod)
nadiamoe marked this conversation as resolved.
Show resolved Hide resolved
}

log.Printf("detected k6 core version %s", k6Version)

k6CoreVersionedURL := fmt.Sprintf(k6GoModURL, k6Version)
//nolint:bodyclose // Single-run script.
response, err := http.Get(k6CoreVersionedURL)
if err != nil {
log.Fatalf("error fetching k6 go.mod: %v", err)
}

nadiamoe marked this conversation as resolved.
Show resolved Hide resolved
if response.StatusCode != http.StatusOK {
log.Fatalf("got HTTP status %d for %s", response.StatusCode, k6CoreVersionedURL)
}

coreDeps, err := dependencies(response.Body)
if err != nil {
log.Fatalf("reading k6 core dependencies: %v", err)
}

//nolint:prealloc // Number of mismatched deps cannot be accurately predicted.
var mismatched []string
for dep, version := range ownDeps {
coreVersion, inCore := coreDeps[dep]
if !inCore {
continue
}

if version == coreVersion {
continue
}

log.Printf("Mismatched versions for %s: %s (this package) -> %s (core)", dep, version, coreVersion)
mismatched = append(mismatched, fmt.Sprintf("%s@%s", dep, coreVersion))
}

if len(mismatched) == 0 {
log.Println("All deps are in sync, nothing to do.")
return
}

// TODO: Use slices.Sort when we move to a go version that has it on the stdlib.
sort.Strings(mismatched)

//nolint:forbidigo // We are willingly writing to stdout here.
fmt.Printf("go get %s\n", strings.Join(mismatched, " "))
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
}

// dependencies reads a go.mod file from an io.Reader and returns a map of dependency name to their specified versions.
func dependencies(reader io.Reader) (map[string]string, error) {
buf := bufio.NewReader(reader)

deps := make(map[string]string)
for {
line, err := buf.ReadString('\n')
if errors.Is(err, io.EOF) {
return deps, nil
}

if err != nil {
return nil, fmt.Errorf("reading go.mod: %w", err)
}

if !strings.HasPrefix(line, "\t") {
continue
}

line = strings.Trim(line, "\t\n")
depVer := strings.Split(line, " ")
deps[depVer[0]] = depVer[1]
}
}