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 supported versions for v2 #3070

Open
wants to merge 15 commits into
base: v2-dev
Choose a base branch
from
295 changes: 295 additions & 0 deletions .github/workflows/apps/gen_supported_versions_doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// 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 (
"context"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"time"

"github.com/DataDog/dd-trace-go/v2/instrumentation"
"golang.org/x/mod/modfile"
)

const outputPath = "./contrib/supported_versions.md"

// TODO: currently this is taken from the https://github.com/DataDog/orchestrion README, it will be fetched dynamically
// when Orchestrion aspects are moved to dd-trace-go.
var autoInstrumentedLibs = map[string]struct{}{
"database/sql": {},
"github.com/gin-gonic/gin": {},
"github.com/go-chi/chi/v5": {},
"github.com/go-chi/chi": {},
"github.com/go-redis/redis/v7": {},
"github.com/go-redis/redis/v8": {},
"github.com/gofiber/fiber/v2": {},
"github.com/gomodule/redigo/redis": {},
"github.com/gorilla/mux": {},
"github.com/jinzhu/gorm": {},
"github.com/labstack/echo/v4": {},
"google.golang.org/grpc": {},
"gorm.io/gorm": {},
"net/http": {},
"go.mongodb.org/mongo-driver/mongo": {},
"github.com/aws/aws-sdk-go": {},
"github.com/hashicorp/vault": {},
"github.com/IBM/sarama": {},
"github.com/Shopify/sarama": {},
"k8s.io/client-go": {},
"log/slog": {},
"os": {},
"github.com/aws/aws-sdk-go-v2": {},
"github.com/redis/go-redis/v9": {},
"github.com/gocql/gocql": {},
"cloud.google.com/go/pubsub": {},
"github.com/99designs/gqlgen": {},
"github.com/redis/go-redis": {},
"github.com/graph-gophers/graphql-go": {},
"github.com/graphql-go/graphql": {},
"github.com/jackc/pgx": {},
"github.com/elastic/go-elasticsearch": {},
"github.com/twitchtv/twirp": {},
"github.com/segmentio/kafka-go": {},
"github.com/confluentinc/confluent-kafka-go/kafka": {},
"github.com/confluentinc/confluent-kafka-go/kafka/v2": {},
"github.com/julienschmidt/httprouter": {},
"github.com/sirupsen/logrus": {},
}

// stdlibPackages are used to skip in version checking.
var stdlibPackages = map[string]struct{}{
"log/slog": {},
"os": {},
"net/http": {},
"database/sql": {},
}

type ModuleVersion struct {
Name string
MinVersion string
MaxVersion string
Repository string
isInstrumented bool
}

// modUpdate is the type returned by 'go list -m -u -json <module>'
type modUpdate struct {
Path string
Version string
Update struct {
Path string
Version string
}
}

func main() {
modules, err := processPackages()
if err != nil {
log.Fatalf("Error processing packages: %v\n", err)
}

// update with instrumented status
for i := range modules {
modules[i].isInstrumented = isModuleAutoInstrumented(modules[i].Repository)
}

modulesWithLatest := fetchAllLatestVersions(modules)

if err := writeMarkdownFile(modulesWithLatest, outputPath); err != nil {
fmt.Println(err)
}

fmt.Println("Version information written to", outputPath)
}

func fetchLatestVersion(module string) (string, error) {
if _, ok := stdlibPackages[module]; ok {
return "-", nil
}

ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()

// Run `go list -m -u -json <module>` to retrieve latest available upgrade
out, err := runCommand(ctx, "", "go", "list", "-m", "-u", "-json", module)
if err != nil {
return "", err
}

var m modUpdate
if err := json.Unmarshal(out, &m); err != nil {
return "", fmt.Errorf("unexpected 'go list -m -u -json' output: %v", err)
}

latest := m.Version
if m.Update.Version != "" {
latest = m.Update.Version
}
return latest, nil
}

// isModuleAutoInstrumented returns whether the module has automatic tracing supported (by Orchestrion)
func isModuleAutoInstrumented(moduleName string) bool {
for key := range autoInstrumentedLibs {
if isSubdirectory(moduleName, key) {
return true
}
}
return false
}

func isSubdirectory(url, pattern string) bool {
if strings.HasPrefix(url, pattern) {
// match is either exact or followed by a "/"
return len(url) == len(pattern) || url[len(pattern)] == '/'
}
return false
}

// getCurrentVersion parses the go.mod file for a package and extracts the version of a given repository.
func getCurrentVersion(integrationName, modName string) (ModuleVersion, error) {
if _, ok := stdlibPackages[integrationName]; ok {
return ModuleVersion{
Name: modName,
MinVersion: "-",
MaxVersion: "-",
Repository: modName,
isInstrumented: false,
}, nil
}

// Path to contrib/{packageName}
contribPath := filepath.Join("contrib", integrationName)
goModPath := filepath.Join(contribPath, "go.mod")

// Check if go.mod exists in directory
if _, err := os.Stat(goModPath); os.IsNotExist(err) {
return ModuleVersion{}, fmt.Errorf("go.mod not found in %s", contribPath)
}

// Read the go.mod
data, err := os.ReadFile(goModPath)
if err != nil {
return ModuleVersion{}, fmt.Errorf("failed to read go.mod: %w", err)
}

// Parse the go.mod
f, err := modfile.Parse(goModPath, data, nil)
if err != nil {
return ModuleVersion{}, fmt.Errorf("failed to parse go.mod: %w", err)
}

// match the repository name
repoPattern := fmt.Sprintf(`\b%s\b`, strings.ReplaceAll(modName, "/", `/`))
repoRegex, err := regexp.Compile(repoPattern)
if err != nil {
return ModuleVersion{}, fmt.Errorf("invalid repository regex pattern: %w", err)
}

// Iterate through require dependencies
for _, req := range f.Require {
if repoRegex.MatchString(req.Mod.Path) {
return ModuleVersion{
Name: modName,
MinVersion: req.Mod.Version,
MaxVersion: "",
Repository: req.Mod.Path,
isInstrumented: false,
}, nil
}
}
return ModuleVersion{}, fmt.Errorf("repository %s not found in go.mod", modName)
}

// fetchAllLatestVersions concurrently fetches the latest version of each module.
func fetchAllLatestVersions(modules []ModuleVersion) []ModuleVersion {
var wg sync.WaitGroup

updatedModules := make([]ModuleVersion, len(modules))

wg.Add(len(modules))
for i, mod := range modules {
go func(i int, mod ModuleVersion) {
defer wg.Done()
latestVersion, err := fetchLatestVersion(mod.Repository)
if err != nil {
fmt.Printf("Error fetching latest version for %s: %v\n", mod.Repository, err)
updatedModules[i] = ModuleVersion{mod.Name, mod.MinVersion, "Error", mod.Repository, mod.isInstrumented}
return
}

updatedModules[i] = ModuleVersion{
Name: mod.Name,
MinVersion: mod.MinVersion,
MaxVersion: latestVersion,
Repository: mod.Repository,
isInstrumented: mod.isInstrumented,
}
}(i, mod)
}

wg.Wait()
return updatedModules
}

func writeMarkdownFile(modules []ModuleVersion, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("error creating file: %v", err)
}
defer file.Close()

fmt.Fprintln(file, "| Dependency | Repository | Minimum Version | Maximum Version | Auto-Instrumented |")
fmt.Fprintln(file, "|------------|-----------------|-----------------|-----------------|-----------------|")

// Sort modules by name
sort.Slice(modules, func(i, j int) bool {
return modules[i].Name < modules[j].Name
})

for _, mod := range modules {
if mod.Name != "" {
fmt.Fprintf(file, "| %s | %s | %s | %s | %t |\n", mod.Name, mod.Repository, mod.MinVersion, mod.MaxVersion, mod.isInstrumented)
}
}
return nil
}

func processPackages() ([]ModuleVersion, error) {
var modules []ModuleVersion
for integrationName, mod := range instrumentation.GetPackages() {
module, err := getCurrentVersion(string(integrationName), mod)
if err != nil {
return nil, err
}
modules = append(modules, module)
}
return modules, nil

}

func runCommand(ctx context.Context, dir string, commandAndArgs ...string) ([]byte, error) {
log.Printf("running command: %q\n", strings.Join(commandAndArgs, " "))

cmd := exec.CommandContext(ctx, commandAndArgs[0], commandAndArgs[1:]...)
cmd.Stderr = os.Stderr
cmd.Dir = dir

b, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to run command %q: %v", strings.Join(commandAndArgs, " "), err)
}
return b, nil
}
67 changes: 67 additions & 0 deletions .github/workflows/apps/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,74 @@ require (
)

require (
github.com/DataDog/appsec-internal-go v1.9.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.59.0 // indirect
github.com/DataDog/datadog-agent/pkg/proto v0.59.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.59.0 // indirect
github.com/DataDog/datadog-agent/pkg/trace v0.59.0 // indirect
github.com/DataDog/datadog-agent/pkg/util/log v0.59.0 // indirect
github.com/DataDog/datadog-agent/pkg/util/scrubber v0.59.0 // indirect
github.com/DataDog/datadog-go/v5 v5.5.0 // indirect
github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect
github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 // indirect
github.com/DataDog/go-sqllexer v0.0.16 // indirect
github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect
github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect
github.com/DataDog/sketches-go v1.4.6 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect
github.com/ebitengine/purego v0.8.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tinylib/msgp v1.2.2 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/collector/component v0.104.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.104.0 // indirect
go.opentelemetry.io/collector/pdata v1.11.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.104.0 // indirect
go.opentelemetry.io/collector/semconv v0.104.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/grpc v1.69.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/DataDog/dd-trace-go/v2 => ../../..
Loading
Loading