Repository: open-telemetry/opamp-go
Pull request #402 (2025-06-25 02:36:46)

Update community member listings
See https://github.com/open-telemetry/community/issues/2830 for details


Patch:
diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml
index 01dd01c..7439ae6 100644
--- a/.github/workflows/ossf-scorecard.yml
+++ b/.github/workflows/ossf-scorecard.yml
@@ -42,6 +42,6 @@ jobs:
       # Upload the results to GitHub's code scanning dashboard (optional).
       # Commenting out will disable upload of results to your repo's Code Scanning dashboard
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
+        uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1
         with:
           sarif_file: results.sarif
diff --git a/README.md b/README.md
index 5b5b3fb..25c0f8b 100644
--- a/README.md
+++ b/README.md
@@ -38,26 +38,30 @@ This repository is work-in-progress of an OpAMP implementation in Go.
 
 See [CONTRIBUTING.md](CONTRIBUTING.md).
 
-Approvers ([@open-telemetry/opamp-go-approvers](https://github.com/orgs/open-telemetry/teams/opamp-go-approvers)):
+### Maintainers
+
+- [Andy Keller](https://github.com/andykellr), Bindplane
+- [Tigran Najaryan](https://github.com/tigrannajaryan), Splunk
+
+For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer).
+
+### Approvers
 
 - [Anthony Mirabella](https://github.com/Aneurysm9), AWS
 - [Evan Bradley](https://github.com/evan-bradley), Dynatrace
 
-Emeritus Approvers
+For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver).
+
+### Emeritus Approvers
 
 - [Alex Boten](https://github.com/codeboten), Lightstep
 - [Przemek Maciolek](https://github.com/pmm-sumo), Sumo Logic
 - [Srikanth Chekuri](https://github.com/srikanthccv), signoz.io
 
-Maintainers ([@open-telemetry/opamp-go-maintainers](https://github.com/orgs/open-telemetry/teams/opamp-go-maintainers)):
-
-- [Andy Keller](https://github.com/andykellr), Bindplane
-- [Tigran Najaryan](https://github.com/tigrannajaryan), Splunk
-
-Learn more about roles in the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md).
+For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
 
-Thanks to all the people who already contributed!
+### Thanks to all of our contributors!
 
 <a href="https://github.com/open-telemetry/opamp-go/graphs/contributors">
-  <img src="https://contributors-img.web.app/image?repo=open-telemetry/opamp-go" />
+  <img alt="Repo contributors" src="https://contrib.rocks/image?repo=open-telemetry/opamp-go" />
 </a>
diff --git a/client/internal/packagessyncer.go b/client/internal/packagessyncer.go
index 2531961..158a186 100644
--- a/client/internal/packagessyncer.go
+++ b/client/internal/packagessyncer.go
@@ -24,9 +24,10 @@ type packagesSyncer struct {
 	sender            Sender
 	reporterInterval  time.Duration
 
-	statuses *protobufs.PackageStatuses
-	mux      *sync.Mutex
-	doneCh   chan struct{}
+	httpClientFactory func(context.Context, *protobufs.DownloadableFile) (*http.Client, error)
+	statuses          *protobufs.PackageStatuses
+	mux               *sync.Mutex
+	doneCh            chan struct{}
 }
 
 // NewPackagesSyncer creates a new packages syncer.
@@ -38,7 +39,11 @@ func NewPackagesSyncer(
 	packagesStateProvider types.PackagesStateProvider,
 	mux *sync.Mutex,
 	reporterInterval time.Duration,
-) *packagesSyncer {
+	httpClientFactory func(context.Context, *protobufs.DownloadableFile) (*http.Client, error),
+) (*packagesSyncer, error) {
+	if httpClientFactory == nil {
+		return nil, fmt.Errorf("httpClientFactory must not be nil")
+	}
 	return &packagesSyncer{
 		logger:            logger,
 		available:         available,
@@ -48,7 +53,8 @@ func NewPackagesSyncer(
 		doneCh:            make(chan struct{}),
 		mux:               mux,
 		reporterInterval:  reporterInterval,
-	}
+		httpClientFactory: httpClientFactory,
+	}, nil
 }
 
 // Sync performs the package syncing process.
@@ -295,7 +301,12 @@ func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file
 		}
 	}
 
-	resp, err := http.DefaultClient.Do(req)
+	client, err := s.httpClientFactory(ctx, file)
+	if err != nil {
+		return fmt.Errorf("failed to create an HTTP Client to download file from %s: %v", file.DownloadUrl, err)
+	}
+
+	resp, err := client.Do(req)
 	if err != nil {
 		return fmt.Errorf("cannot download file from %s: %v", file.DownloadUrl, err)
 	}
diff --git a/client/internal/receivedprocessor.go b/client/internal/receivedprocessor.go
index cbc23c2..5e53b71 100644
--- a/client/internal/receivedprocessor.go
+++ b/client/internal/receivedprocessor.go
@@ -128,7 +128,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 	if msg.PackagesAvailable != nil {
 		if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_AcceptsPackages) {
 			msgData.PackagesAvailable = msg.PackagesAvailable
-			msgData.PackageSyncer = NewPackagesSyncer(
+			pkgSyncer, err := NewPackagesSyncer(
 				r.logger,
 				msgData.PackagesAvailable,
 				r.sender,
@@ -136,7 +136,13 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				r.packagesStateProvider,
 				r.packageSyncMutex,
 				r.downloadReporterInt,
+				r.callbacks.DownloadHTTPClient,
 			)
+			if err != nil {
+				r.logger.Errorf(ctx, "failed to create package syncer: %v", err)
+			} else {
+				msgData.PackageSyncer = pkgSyncer
+			}
 		} else {
 			r.logger.Debugf(ctx, "Ignoring PackagesAvailable, agent does not have AcceptsPackages capability")
 		}
diff --git a/client/types/callbacks.go b/client/types/callbacks.go
index 48d5f83..25cbe6e 100644
--- a/client/types/callbacks.go
+++ b/client/types/callbacks.go
@@ -130,6 +130,11 @@ type Callbacks struct {
 	//
 	// The responses in the via parameter are passed with their bodies closed.
 	CheckRedirect func(req *http.Request, viaReq []*http.Request, via []*http.Response) error
+
+	// DownloadHTTPClient is called to create an HTTP client that is used to download files by the package syncer.
+	// If the callback is not set, a default HTTP client will be created with the default transport settings.
+	// The callback must return a non-nil HTTP client or an error.
+	DownloadHTTPClient func(ctx context.Context, file *protobufs.DownloadableFile) (*http.Client, error)
 }
 
 func (c *Callbacks) SetDefaults() {
@@ -157,4 +162,20 @@ func (c *Callbacks) SetDefaults() {
 	if c.SaveRemoteConfigStatus == nil {
 		c.SaveRemoteConfigStatus = func(ctx context.Context, status *protobufs.RemoteConfigStatus) {}
 	}
+	if c.DownloadHTTPClient == nil {
+		defaultHttpClient := newHttpClient()
+		c.DownloadHTTPClient = func(ctx context.Context, file *protobufs.DownloadableFile) (*http.Client, error) {
+			return defaultHttpClient, nil
+		}
+	}
+}
+
+func newHttpClient() *http.Client {
+	client := &http.Client{}
+	// Clone the default transport to avoid being affected by global state changes.
+	if tr, ok := http.DefaultTransport.(*http.Transport); ok {
+		tr = tr.Clone()
+		client.Transport = tr
+	}
+	return client
 }
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 6de0797..164d50b 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -6,7 +6,7 @@ toolchain go1.24.1
 
 require (
 	github.com/ory/go-acc v0.2.8
-	golang.org/x/tools v0.33.0
+	golang.org/x/tools v0.34.0
 	mvdan.cc/gofumpt v0.8.0
 )
 
@@ -29,8 +29,8 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.2.0 // indirect
-	golang.org/x/mod v0.24.0 // indirect
-	golang.org/x/sync v0.14.0 // indirect
+	golang.org/x/mod v0.25.0 // indirect
+	golang.org/x/sync v0.15.0 // indirect
 	golang.org/x/sys v0.33.0 // indirect
 	golang.org/x/text v0.3.2 // indirect
 	gopkg.in/ini.v1 v1.57.0 // indirect
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index d83ff2b..f968fa1 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -157,8 +157,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
-golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -169,8 +169,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
-golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -191,8 +191,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
-golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
+golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
+golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=


Test patch:
diff --git a/client/internal/httpsender_test.go b/client/internal/httpsender_test.go
index a6c9869..c6a3f86 100644
--- a/client/internal/httpsender_test.go
+++ b/client/internal/httpsender_test.go
@@ -12,12 +12,11 @@ import (
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/assert"
-
 	"github.com/open-telemetry/opamp-go/client/types"
 	sharedinternal "github.com/open-telemetry/opamp-go/internal"
 	"github.com/open-telemetry/opamp-go/internal/testhelpers"
 	"github.com/open-telemetry/opamp-go/protobufs"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestHTTPSenderRetryForStatusTooManyRequests(t *testing.T) {
@@ -247,14 +246,15 @@ func TestPackageUpdatesInParallel(t *testing.T) {
 
 	var messages atomic.Int32
 	var mux sync.Mutex
-	sender.callbacks = types.Callbacks{
-		OnMessage: func(ctx context.Context, msg *types.MessageData) {
-			err := msg.PackageSyncer.Sync(ctx)
-			assert.NoError(t, err)
-			messages.Add(1)
-			doneCh = append(doneCh, msg.PackageSyncer.Done())
-		},
+	callbacks := types.Callbacks{}
+	callbacks.SetDefaults()
+	callbacks.OnMessage = func(ctx context.Context, msg *types.MessageData) {
+		err := msg.PackageSyncer.Sync(ctx)
+		assert.NoError(t, err)
+		messages.Add(1)
+		doneCh = append(doneCh, msg.PackageSyncer.Done())
 	}
+	sender.callbacks = callbacks
 
 	clientSyncedState := &ClientSyncedState{}
 	capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsPackages
@@ -319,14 +319,16 @@ func TestPackageUpdatesWithError(t *testing.T) {
 	localPackageState := types.PackagesStateProvider(nil)
 	var messages atomic.Int32
 	var mux sync.Mutex
-	sender.callbacks = types.Callbacks{
-		OnMessage: func(ctx context.Context, msg *types.MessageData) {
-			// Make sure the call to Sync will return an error due to a nil PackageStateProvider
-			err := msg.PackageSyncer.Sync(ctx)
-			assert.Error(t, err)
-			messages.Add(1)
-		},
+
+	callbacks := types.Callbacks{}
+	callbacks.SetDefaults()
+	callbacks.OnMessage = func(ctx context.Context, msg *types.MessageData) {
+		// Make sure the call to Sync will return an error due to a nil PackageStateProvider
+		err := msg.PackageSyncer.Sync(ctx)
+		assert.Error(t, err)
+		messages.Add(1)
 	}
+	sender.callbacks = callbacks
 
 	clientSyncedState := &ClientSyncedState{}
 
diff --git a/client/internal/packagessyncer_test.go b/client/internal/packagessyncer_test.go
new file mode 100644
index 0000000..7a06b38
--- /dev/null
+++ b/client/internal/packagessyncer_test.go
@@ -0,0 +1,53 @@
+package internal
+
+import (
+	"context"
+	"net/http"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/open-telemetry/opamp-go/protobufs"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewPackagesSyncer(t *testing.T) {
+	tests := []struct {
+		name          string
+		clientFactory func(context.Context, *protobufs.DownloadableFile) (*http.Client, error)
+		err           string
+	}{
+		{
+			name:          "nil client factory",
+			clientFactory: nil,
+			err:           "httpClientFactory must not be nil",
+		},
+		{
+			name: "non-nil client factory",
+			clientFactory: func(context.Context, *protobufs.DownloadableFile) (*http.Client, error) {
+				return &http.Client{}, nil
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s, err := NewPackagesSyncer(
+				nil,
+				&protobufs.PackagesAvailable{},
+				nil,
+				&ClientSyncedState{},
+				&InMemPackagesStore{},
+				&sync.Mutex{},
+				time.Second,
+				tt.clientFactory,
+			)
+			if tt.err != "" {
+				assert.EqualError(t, err, tt.err)
+				assert.Nil(t, s)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, s)
+			}
+		})
+	}
+}
diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go
index 17c1677..0f0fef7 100644
--- a/client/internal/wsreceiver_test.go
+++ b/client/internal/wsreceiver_test.go
@@ -235,13 +235,13 @@ func TestWSPackageUpdatesInParallel(t *testing.T) {
 			<-blockSyncCh
 		}
 	}
-	callbacks := types.Callbacks{
-		OnMessage: func(ctx context.Context, msg *types.MessageData) {
-			err := msg.PackageSyncer.Sync(ctx)
-			assert.NoError(t, err)
-			messages.Add(1)
-			doneCh = append(doneCh, msg.PackageSyncer.Done())
-		},
+	callbacks := types.Callbacks{}
+	callbacks.SetDefaults()
+	callbacks.OnMessage = func(ctx context.Context, msg *types.MessageData) {
+		err := msg.PackageSyncer.Sync(ctx)
+		assert.NoError(t, err)
+		messages.Add(1)
+		doneCh = append(doneCh, msg.PackageSyncer.Done())
 	}
 	clientSyncedState := &ClientSyncedState{}
 	capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsPackages
diff --git a/client/types/callbacks_test.go b/client/types/callbacks_test.go
new file mode 100644
index 0000000..b03b7e8
--- /dev/null
+++ b/client/types/callbacks_test.go
@@ -0,0 +1,42 @@
+package types
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/open-telemetry/opamp-go/protobufs"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCallbacksDefaults(t *testing.T) {
+	c := Callbacks{}
+
+	c.SetDefaults()
+
+	assert.NotNil(t, c.OnConnect)
+	assert.NotNil(t, c.OnConnectFailed)
+	assert.NotNil(t, c.OnError)
+	assert.NotNil(t, c.OnMessage)
+	assert.NotNil(t, c.OnOpampConnectionSettings)
+	assert.NotNil(t, c.OnCommand)
+	assert.NotNil(t, c.GetEffectiveConfig)
+	assert.NotNil(t, c.SaveRemoteConfigStatus)
+
+	// Test default DownloadHTTPClient
+	require.NotNil(t, c.DownloadHTTPClient)
+	client, err := c.DownloadHTTPClient(context.Background(), &protobufs.DownloadableFile{})
+	require.NoError(t, err)
+	require.NotNil(t, client)
+
+	// ensure transport was set to *http.Transport
+	_, ok := client.Transport.(*http.Transport)
+	require.True(t, ok, "Expected the transport to be of type *http.Transport")
+
+	// ensure it returns the same client on subsequent calls
+	client2, err := c.DownloadHTTPClient(context.Background(), &protobufs.DownloadableFile{})
+	require.NoError(t, err)
+	require.NotNil(t, client)
+	assert.Equal(t, client, client2)
+}