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

internal: add workflow to check for outdated integrations #3008

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions .github/workflows/apps/latest_major_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024 Datadog, Inc.

package main

import (
"encoding/json"
"fmt"
"sort"
"github.com/Masterminds/semver/v3"
"golang.org/x/mod/modfile"
"net/http"
"os"
"regexp"
"strings"
)

type Tag struct {
Name string
}

func getLatestMajorVersion(repo string) (string, error) {
// Get latest major version available for repo from github.
const apiURL = "https://api.github.com/repos/%s/tags"
url := fmt.Sprintf(apiURL, repo)

resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to fetch tags: %s", resp.Status)
}

var tags []struct {
Name string `json:"name"`
}

if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
return "", err
}
latestByMajor := make(map[int]*semver.Version)

for _, tag := range tags {
v, err := semver.NewVersion(tag.Name)
if err != nil {
continue // Skip invalid versions
}

if v.Prerelease() != "" {
continue // Ignore pre-release versions
}

major := int(v.Major())
if current, exists := latestByMajor[major]; !exists || v.GreaterThan(current) {
latestByMajor[major] = v
}
}

var latestMajor *semver.Version
for _, v := range latestByMajor {
if latestMajor == nil || v.Major() > latestMajor.Major() {
latestMajor = v
}
}

if latestMajor != nil {
return fmt.Sprintf("v%d", latestMajor.Major()), nil
}

return "", fmt.Errorf("no valid versions found")

}

func main() {

data, err := os.ReadFile("integration_go.mod")
if err != nil {
fmt.Println("Error reading integration_go.mod:", err)
return
}

modFile, err := modfile.Parse("integration_go.mod", data, nil)
if err != nil {
fmt.Println("Error parsing integration_go.mod:", err)
return
}

latestMajor := make(map[string]*semver.Version)

// Match on versions with /v{major}
versionRegex := regexp.MustCompile(`^(?P<module>.+?)/v(\d+)$`)

// Iterate over the required modules and update latest major version if necessary
for _, req := range modFile.Require {
module := req.Mod.Path

if match := versionRegex.FindStringSubmatch(module); match != nil {
url := match[1] // base module URL (e.g., github.com/foo)
majorVersionStr := "v" + match[2] // Create semantic version string (e.g., "v2")

moduleName := strings.TrimPrefix(strings.TrimSpace(url), "github.com/")

// Parse the semantic version
majorVersion, err := semver.NewVersion(majorVersionStr)
if err != nil {
fmt.Printf("Skip invalid version for module %s: %v\n", module, err)
continue
}

if existing, ok := latestMajor[moduleName]; !ok || majorVersion.GreaterThan(existing) {
latestMajor[moduleName] = majorVersion
}
}
}

// Output latest major version that we support.
// Check if a new major version in Github is available that we don't support.
// If so, output that a new latest is available.

// Sort the output
modules := make([]string, 0, len(latestMajor))
for module := range latestMajor {
modules = append(modules, module)
}
sort.Strings(modules)

for _, module := range modules {
major := latestMajor[module]

latestVersion, err := getLatestMajorVersion(module) // latest version available
if err != nil {
fmt.Printf("Error fetching latest version for module '%s': %v\n", module, err)
continue
}

latestVersionParsed, err := semver.NewVersion(latestVersion)
if err != nil {
fmt.Printf("Error parsing latest version '%s' for module '%s': %v\n", latestVersion, module, err)
continue
}

fmt.Printf("Latest DD major version of %s: %d\n", module, major.Major())
if major.LessThan(latestVersionParsed) {
fmt.Printf("New latest major version of %s available: %d\n", module, latestVersionParsed.Major())
}

}
}
41 changes: 41 additions & 0 deletions .github/workflows/outdated-integrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Outdated Integrations
on:
schedule:
- cron: "0 0 * * 0" # Runs every Sunday at midnight UTC
workflow_dispatch:

concurrency:
# Automatically cancel previous runs if a new one is triggered to conserve resources.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Find new major versions for the contrib package dependencies
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
pull-requests: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4

Choose a reason for hiding this comment

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

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

View in Datadog  Leave us feedback  Documentation


- run: go get github.com/Masterminds/semver/v3

- run: go run .github/workflows/apps/latest_major_version.go > latests.txt

- run: git diff

- name: Create Pull Request
id: pr
uses: peter-evans/create-pull-request@v6

Choose a reason for hiding this comment

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

🟠 Code Vulnerability

Workflow depends on a GitHub actions pinned by tag (...read more)

View in Datadog  Leave us feedback  Documentation

with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: "upgrade-latest-major-version"
commit-message: "Update latests file"
base: main
title: "chore: update latest majors"
labels: changelog/no-changelog
body: "Auto-generated PR from Outdated Integrations workflow to update latests major versions"
4 changes: 2 additions & 2 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,15 +1069,15 @@ func WithRuntimeMetrics() StartOption {
}
}

// WithDogstatsdAddress specifies the address to connect to for sending metrics to the Datadog
// WithDogstatsdAddr specifies the address to connect to for sending metrics to the Datadog
// Agent. It should be a "host:port" string, or the path to a unix domain socket.If not set, it
// attempts to determine the address of the statsd service according to the following rules:
// 1. Look for /var/run/datadog/dsd.socket and use it if present. IF NOT, continue to #2.
// 2. The host is determined by DD_AGENT_HOST, and defaults to "localhost"
// 3. The port is retrieved from the agent. If not present, it is determined by DD_DOGSTATSD_PORT, and defaults to 8125
//
// This option is in effect when WithRuntimeMetrics is enabled.
func WithDogstatsdAddress(addr string) StartOption {
func WithDogstatsdAddr(addr string) StartOption {
return func(cfg *config) {
cfg.dogstatsdAddr = addr
globalconfig.SetDogstatsdAddr(addr)
Expand Down
6 changes: 3 additions & 3 deletions ddtrace/tracer/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ func TestTracerOptionsDefaults(t *testing.T) {
t.Run("option", func(t *testing.T) {
o := make([]StartOption, len(opts))
copy(o, opts)
o = append(o, WithDogstatsdAddress("10.1.0.12:4002"))
o = append(o, WithDogstatsdAddr("10.1.0.12:4002"))
tracer, err := newTracer(o...)
assert.NoError(t, err)
defer tracer.Stop()
Expand All @@ -642,7 +642,7 @@ func TestTracerOptionsDefaults(t *testing.T) {
o := make([]StartOption, len(opts))
copy(o, opts)
fail = true
o = append(o, WithDogstatsdAddress("10.1.0.12:4002"))
o = append(o, WithDogstatsdAddr("10.1.0.12:4002"))
tracer, err := newTracer(o...)
assert.NoError(t, err)
defer tracer.Stop()
Expand All @@ -663,7 +663,7 @@ func TestTracerOptionsDefaults(t *testing.T) {
}
addr := filepath.Join(dir, "dsd.socket")
defer os.RemoveAll(addr)
tracer, err := newTracer(WithDogstatsdAddress("unix://" + addr))
tracer, err := newTracer(WithDogstatsdAddr("unix://" + addr))
assert.NoError(err)
defer tracer.Stop()
c := tracer.config
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/tracer/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type tracerStatSpan struct {

// newConcentrator creates a new concentrator using the given tracer
// configuration c. It creates buckets of bucketSize nanoseconds duration.
func newConcentrator(c *config, bucketSize int64) *concentrator {
func newConcentrator(c *config, bucketSize int64, statsdClient internal.StatsdClient) *concentrator {
sCfg := &stats.SpanConcentratorConfig{
ComputeStatsBySpanKind: false,
BucketInterval: defaultStatsBucketSize,
Expand Down Expand Up @@ -86,6 +86,7 @@ func newConcentrator(c *config, bucketSize int64) *concentrator {
cfg: c,
aggregationKey: aggKey,
spanConcentrator: spanConcentrator,
statsdClient: statsdClient,
}
}

Expand Down
16 changes: 10 additions & 6 deletions ddtrace/tracer/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (

"github.com/stretchr/testify/assert"

"github.com/DataDog/datadog-go/v5/statsd"
"github.com/DataDog/dd-trace-go/v2/internal/civisibility/constants"
"github.com/DataDog/dd-trace-go/v2/internal/civisibility/utils"
"github.com/DataDog/dd-trace-go/v2/internal/statsdtest"
)

func TestAlignTs(t *testing.T) {
Expand All @@ -39,7 +41,7 @@ func TestConcentrator(t *testing.T) {
}
t.Run("start-stop", func(t *testing.T) {
assert := assert.New(t)
c := newConcentrator(&config{}, bucketSize)
c := newConcentrator(&config{}, bucketSize, &statsd.NoOpClient{})
assert.EqualValues(atomic.LoadUint32(&c.stopped), 1)
c.Start()
assert.EqualValues(atomic.LoadUint32(&c.stopped), 0)
Expand All @@ -58,7 +60,7 @@ func TestConcentrator(t *testing.T) {
t.Run("flusher", func(t *testing.T) {
t.Run("old", func(t *testing.T) {
transport := newDummyTransport()
c := newConcentrator(&config{transport: transport, env: "someEnv"}, 500_000)
c := newConcentrator(&config{transport: transport, env: "someEnv"}, 500_000, &statsd.NoOpClient{})
assert.Len(t, transport.Stats(), 0)
ss1, ok := c.newTracerStatSpan(&s1, nil)
assert.True(t, ok)
Expand All @@ -73,9 +75,10 @@ func TestConcentrator(t *testing.T) {
assert.Equal(t, "http.request", actualStats[0].Stats[0].Stats[0].Name)
})

t.Run("recent", func(t *testing.T) {
t.Run("recent+stats", func(t *testing.T) {
transport := newDummyTransport()
c := newConcentrator(&config{transport: transport, env: "someEnv"}, (10 * time.Second).Nanoseconds())
testStats := &statsdtest.TestStatsdClient{}
c := newConcentrator(&config{transport: transport, env: "someEnv"}, (10 * time.Second).Nanoseconds(), testStats)
assert.Len(t, transport.Stats(), 0)
ss1, ok := c.newTracerStatSpan(&s1, nil)
assert.True(t, ok)
Expand All @@ -96,12 +99,13 @@ func TestConcentrator(t *testing.T) {
assert.Len(t, names, 2)
assert.NotNil(t, names["http.request"])
assert.NotNil(t, names["potato"])
assert.Contains(t, testStats.CallNames(), "datadog.tracer.stats.spans_in")
})

t.Run("ciGitSha", func(t *testing.T) {
utils.AddCITags(constants.GitCommitSHA, "DEADBEEF")
transport := newDummyTransport()
c := newConcentrator(&config{transport: transport, env: "someEnv"}, (10 * time.Second).Nanoseconds())
c := newConcentrator(&config{transport: transport, env: "someEnv"}, (10 * time.Second).Nanoseconds(), &statsd.NoOpClient{})
assert.Len(t, transport.Stats(), 0)
ss1, ok := c.newTracerStatSpan(&s1, nil)
assert.True(t, ok)
Expand All @@ -115,7 +119,7 @@ func TestConcentrator(t *testing.T) {
// stats should be sent if the concentrator is stopped
t.Run("stop", func(t *testing.T) {
transport := newDummyTransport()
c := newConcentrator(&config{transport: transport}, 500000)
c := newConcentrator(&config{transport: transport}, 500000, &statsd.NoOpClient{})
assert.Len(t, transport.Stats(), 0)
ss1, ok := c.newTracerStatSpan(&s1, nil)
assert.True(t, ok)
Expand Down
2 changes: 1 addition & 1 deletion ddtrace/tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func newUnstartedTracer(opts ...StartOption) (*tracer, error) {
prioritySampling: sampler,
pid: os.Getpid(),
logDroppedTraces: time.NewTicker(1 * time.Second),
stats: newConcentrator(c, defaultStatsBucketSize),
stats: newConcentrator(c, defaultStatsBucketSize, statsd),
obfuscator: obfuscate.NewObfuscator(obfuscate.Config{
SQL: obfuscate.SQLConfig{
TableNames: c.agent.HasFlag("table_names"),
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/tracer/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"testing"
"time"

"github.com/DataDog/datadog-go/v5/statsd"
"github.com/DataDog/dd-trace-go/v2/ddtrace"
"github.com/DataDog/dd-trace-go/v2/ddtrace/ext"
"github.com/DataDog/dd-trace-go/v2/ddtrace/internal/tracerstats"
Expand Down Expand Up @@ -2347,7 +2348,7 @@ func TestFlush(t *testing.T) {
tr.statsd = ts

transport := newDummyTransport()
c := newConcentrator(&config{transport: transport, env: "someEnv"}, defaultStatsBucketSize)
c := newConcentrator(&config{transport: transport, env: "someEnv"}, defaultStatsBucketSize, &statsd.NoOpClient{})
tr.stats = c
c.Start()
defer c.Stop()
Expand Down
Loading
Loading