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

feat: support linux musl #454

Merged
merged 13 commits into from
Jan 23, 2025
41 changes: 38 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ on:
pull_request:
branches:
- master
workflow_dispatch:
inputs:
SKIP_ALPINE_PROVIDER_TESTS:
description: 'Skip alpine provider tests'
required: false
default: 'false'

env:
PACT_BROKER_BASE_URL: https://testdemo.pactflow.io
Expand Down Expand Up @@ -75,19 +81,48 @@ jobs:
if: ${{ always() }}

test-containers:
runs-on: ubuntu-latest
name: ${{ matrix.go-version }}-test-container
continue-on-error: ${{ matrix.experimental }}
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }}-${{ matrix.variant }}-${{ matrix.go-version }}-test-container
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
go-version: ["1.22", "1.23"]
variant: [debian]
experimental: [false]
include:
- variant: alpine
go-version: 1.22
experimental: true
os: ubuntu-latest
- variant: alpine
go-version: 1.23
experimental: true
os: ubuntu-latest
- variant: alpine
go-version: 1.22
experimental: true
os: ubuntu-24.04-arm
- variant: alpine
go-version: 1.23
experimental: true
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4

- name: Test dockerfile
run: make docker_test_all
run: |
if [[ "${{ matrix.variant }}" == "alpine" ]]; then
export SKIP_PROVIDER_TESTS=true
if [[ "${{ github.event.inputs.SKIP_ALPINE_PROVIDER_TESTS }}" == "false" ]]; then
export SKIP_PROVIDER_TESTS=false
fi
fi
make docker_test_all
env:
GO_VERSION: ${{ matrix.go-version }}
IMAGE_VARIANT: ${{ matrix.variant }}

finish:
needs: [test,test-containers]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ _*
*.log
pact-go
pacts
examples/pacts/*.json
logs
tmp
coverage.txt
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ARG VERSION=1.22
FROM golang:${VERSION}-alpine

RUN apk add --no-cache curl gcc musl-dev gzip openjdk17-jre bash protoc protobuf-dev make file

COPY . /go/src/github.com/pact-foundation/pact-go

WORKDIR /go/src/github.com/pact-foundation/pact-go

CMD ["make", "test"]
File renamed without changes.
39 changes: 30 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ TEST?=./...
.DEFAULT_GOAL := ci
DOCKER_HOST_HTTP?="http://host.docker.internal"
PACT_CLI="docker run --rm -v ${PWD}:${PWD} -e PACT_BROKER_BASE_URL=$(DOCKER_HOST_HTTP) -e PACT_BROKER_USERNAME -e PACT_BROKER_PASSWORD pactfoundation/pact-cli"
PLUGIN_PACT_PROTOBUF_VERSION=0.3.15
PLUGIN_PACT_PROTOBUF_VERSION=0.5.4
PLUGIN_PACT_CSV_VERSION=0.0.6
PLUGIN_PACT_MATT_VERSION=0.1.1
PLUGIN_PACT_AVRO_VERSION=0.0.6

GO_VERSION?=1.22
IMAGE_VARIANT?=debian
ci:: docker deps clean bin test pact
PACT_DOWNLOAD_DIR=/tmp
ifeq ($(OS),Windows_NT)
PACT_DOWNLOAD_DIR=$$TMP
endif

SKIP_RACE?=false
RACE?=-race
ifeq ($(SKIP_RACE),true)
RACE=
endif
# Run the ci target from a developer machine with the environment variables
# set as if it was on Travis CI.
# Use this for quick feedback when playing around with your workflows.
Expand All @@ -37,24 +42,32 @@ docker:
docker compose up -d

docker_build:
docker build -f Dockerfile --build-arg GO_VERSION=${GO_VERSION} -t pactfoundation/pact-go-test .
docker build -f Dockerfile.$(IMAGE_VARIANT) --build-arg GO_VERSION=${GO_VERSION} -t pactfoundation/pact-go-test-$(IMAGE_VARIANT) .

docker_test: docker_build
docker run \
-e LOG_LEVEL=INFO \
-e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \
-e SKIP_RACE=$(SKIP_RACE) \
--rm \
pactfoundation/pact-go-test \
-it \
pactfoundation/pact-go-test-$(IMAGE_VARIANT) \
/bin/sh -c "make test"
docker_pact: docker_build
docker run \
-e LOG_LEVEL=INFO \
-e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \
-e SKIP_RACE=$(SKIP_RACE) \
--rm \
pactfoundation/pact-go-test \
pactfoundation/pact-go-test-$(IMAGE_VARIANT) \
/bin/sh -c "make pact_local"
docker_test_all: docker_build
docker run \
-e LOG_LEVEL=INFO \
-e SKIP_PROVIDER_TESTS=$(SKIP_PROVIDER_TESTS) \
-e SKIP_RACE=$(SKIP_RACE) \
--rm \
pactfoundation/pact-go-test \
pactfoundation/pact-go-test-$(IMAGE_VARIANT) \
/bin/sh -c "make test && make pact_local"

bin:
Expand Down Expand Up @@ -114,7 +127,9 @@ pact: clean install docker
pact_local: clean download_plugins install
@echo "--- 🔨 Running Pact examples"
go test -v -tags=consumer -count=1 github.com/pact-foundation/pact-go/v2/examples/...
SKIP_PUBLISH=true go test -v -timeout=30s -tags=provider -count=1 github.com/pact-foundation/pact-go/v2/examples/...
if [ "$(SKIP_PROVIDER_TESTS)" != "true" ]; then \
SKIP_PUBLISH=true go test -v -timeout=30s -tags=provider -count=1 github.com/pact-foundation/pact-go/v2/examples/...; \
fi

publish:
@echo "-- 📃 Publishing pacts"
Expand All @@ -124,13 +139,19 @@ release:
echo "--- 🚀 Releasing it"
"$(CURDIR)/scripts/release.sh"

ifeq ($(SKIP_PROVIDER_TESTS),true)
PROVIDER_TEST_TAGS=
else
PROVIDER_TEST_TAGS=-tags=provider
endif

test: deps install
@echo "--- ✅ Running tests"
@if [ -f coverage.txt ]; then rm coverage.txt; fi;
@echo "mode: count" > coverage.txt
@for d in $$(go list ./... | grep -v vendor | grep -v examples); \
do \
go test -v -race -coverprofile=profile.out -covermode=atomic $$d; \
go test -v $(RACE) -coverprofile=profile.out $(PROVIDER_TEST_TAGS) -covermode=atomic $$d; \
if [ $$? != 0 ]; then \
exit 1; \
fi; \
Expand All @@ -143,7 +164,7 @@ test: deps install


testrace:
go test -race $(TEST) $(TESTARGS)
go test $(RACE) $(TEST) $(TESTARGS)

updatedeps:
go get -d -v -p 2 ./...
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,17 @@ _\*_ v3 support is limited to the subset of functionality required to enable lan
| MacOS | arm64 | ✅ | All |
| Linux (libc) | x86_64 | ✅ | All |
| Linux (libc) | arm64 | ✅ | All |
| Linux (musl) | x86_64 | | - |
| Linux (musl) | arm64 | | - |
| Linux (musl) | x86_64 | | v2.2.0 |
| Linux (musl) | arm64 | | v2.2.0 |
| Windows | x86_64 | ✅ | All |
| Windows | x86 | ❌ | - |
| Windows | arm64 | ❌ | - |

⚠️ - Known issues

> - Linux (musl) provider verification is known to experience segmentation faults, reproducible in our CI system.
> - Assistance is greatly appreciated if musl support is important to you.

</details>

## Roadmap
Expand Down
2 changes: 1 addition & 1 deletion consumer/http_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestHttpV4TypeSystem(t *testing.T) {
UponReceiving("some scenario").
UsingPlugin(PluginConfig{
Plugin: "protobuf",
Version: "0.3.15",
Version: "0.5.4",
}).
WithRequest("GET", "/").
// WithRequest("GET", "/", func(b *V4InteractionWithPluginRequestBuilder) {
Expand Down
6 changes: 3 additions & 3 deletions examples/grpc/grpc_consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestGetFeatureSuccess(t *testing.T) {
Given("feature 'Big Tree' exists").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.15",
Version: "0.5.4",
}).
WithContents(grpcInteraction, "application/protobuf").
StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional
Expand Down Expand Up @@ -123,7 +123,7 @@ func TestGetFeatureError(t *testing.T) {
Given("feature does not exist at -1, -1").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.15",
Version: "0.5.4",
}).
WithContents(grpcInteraction, "application/protobuf").
StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional
Expand Down Expand Up @@ -194,7 +194,7 @@ func TestSaveFeature(t *testing.T) {
Given("feature does not exist at -1, -1").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.15",
Version: "0.5.4",
}).
WithContents(grpcInteraction, "application/protobuf").
StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional
Expand Down
2 changes: 1 addition & 1 deletion examples/protobuf-message/protobuf_consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestPluginMessageConsumer(t *testing.T) {
ExpectsToReceive("feature message").
UsingPlugin(message.PluginConfig{
Plugin: "protobuf",
Version: "0.3.15",
Version: "0.5.4",
}).
WithContents(protoMessage, "application/protobuf").
ExecuteTest(t, func(m message.AsynchronousMessage) error {
Expand Down
2 changes: 1 addition & 1 deletion examples/protobuf-message/protobuf_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestPluginMessageProvider(t *testing.T) {
},
})
return feature, message.Metadata{
"contentType": "application/protobuf;message=Feature", // <- This is required to ensure the correct type is matched
"contentType": "application/protobuf;message=.routeguide.Feature", // <- This is required to ensure the correct type is matched
}, nil
},
}
Expand Down
27 changes: 14 additions & 13 deletions installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,6 @@ func NewInstaller(opts ...installerConfig) (*Installer, error) {
log.Println("[WARN] amd64 architecture not detected, defaulting to x86_64. Behaviour may be undefined")
}

// Only perform a check if current OS is linux
if runtime.GOOS == "linux" {
err := i.checkMusl()
if err != nil {
log.Println("[DEBUG] unable to check for presence musl library due to error:", err)
}
}

return i, nil
}

Expand Down Expand Up @@ -260,7 +252,13 @@ func (i *Installer) getDownloadURLForPackage(pkg string) (string, error) {
return "", fmt.Errorf("unable to find package details for package: %s", pkg)
}

return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch, osToExtension[i.os]), nil
if checkMusl() && i.os == linux {
return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch+"-musl", osToExtension[i.os]), nil
} else {
return fmt.Sprintf(downloadTemplate, pkg, pkgInfo.version, osToLibName[i.os], i.os, i.arch, osToExtension[i.os]), nil

}

}

func (i *Installer) getLibDstForPackage(pkg string) (string, error) {
Expand Down Expand Up @@ -331,20 +329,23 @@ func checkVersion(lib, version, versionRange string) error {
}

// checkMusl checks if the OS uses musl library instead of glibc
func (i *Installer) checkMusl() error {
func checkMusl() bool {
lddPath, err := exec.LookPath("ldd")
if err != nil {
return fmt.Errorf("could not find ldd in environment path")
return false
}

cmd := exec.Command(lddPath, "/bin/echo")
out, err := cmd.CombinedOutput()

if err != nil {
return false
}
if strings.Contains(string(out), "musl") {
log.Println("[WARN] Usage of musl library is known to cause problems, prefer using glibc instead.")
return true
}

return err
return false
}

// download template structure: "https://github.com/pact-foundation/pact-reference/releases/download/PACKAGE-vVERSION/LIBNAME-OS-ARCH.EXTENSION.gz"
Expand Down
29 changes: 25 additions & 4 deletions installer/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,37 @@ func TestInstallerDownloader(t *testing.T) {
test Installer
}{
{
name: "ffi lib - linux x86",
name: "ffi lib - linux x86_64",
pkg: FFIPackage,
want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64.so.gz", packages[FFIPackage].version),
want: func() string {
if checkMusl() {
return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64-musl.so.gz", packages[FFIPackage].version)
} else {
return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-x86_64.so.gz", packages[FFIPackage].version)
}
}(),
test: Installer{
os: linux,
arch: x86_64,
},
},
{
name: "ffi lib - macos x86",
name: "ffi lib - linux aarch64",
pkg: FFIPackage,
want: func() string {
if checkMusl() {
return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-aarch64-musl.so.gz", packages[FFIPackage].version)
} else {
return fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-linux-aarch64.so.gz", packages[FFIPackage].version)
}
}(),
test: Installer{
os: linux,
arch: aarch64,
},
},
{
name: "ffi lib - macos x86_64",
pkg: FFIPackage,
want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/libpact_ffi-macos-x86_64.dylib.gz", packages[FFIPackage].version),
test: Installer{
Expand All @@ -62,7 +83,7 @@ func TestInstallerDownloader(t *testing.T) {
},
},
{
name: "ffi lib - windows x86",
name: "ffi lib - windows x86_64",
pkg: FFIPackage,
want: fmt.Sprintf("https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v%s/pact_ffi-windows-x86_64.dll.gz", packages[FFIPackage].version),
test: Installer{
Expand Down
Loading
Loading