Pārlūkot izejas kodu

feat(tracing): add project hash attr

Hash the project config and add it as an attribute. This can be
used to group multiple spans.

Signed-off-by: Milas Bowman <[email protected]>
Milas Bowman 1 gadu atpakaļ
vecāks
revīzija
acf2ffb0c7
2 mainītis faili ar 93 papildinājumiem un 0 dzēšanām
  1. 26 0
      internal/tracing/attributes.go
  2. 67 0
      internal/tracing/attributes_test.go

+ 26 - 0
internal/tracing/attributes.go

@@ -17,6 +17,9 @@
 package tracing
 
 import (
+	"crypto/sha256"
+	"encoding/json"
+	"fmt"
 	"strings"
 	"time"
 
@@ -72,6 +75,9 @@ func ProjectOptions(proj *types.Project) SpanOptions {
 		attribute.StringSlice("project.extensions", keys(proj.Extensions)),
 		attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)),
 	}
+	if projHash, ok := projectHash(proj); ok {
+		attrs = append(attrs, attribute.String("project.hash", projHash))
+	}
 	return []trace.SpanStartEventOption{
 		trace.WithAttributes(attrs...),
 	}
@@ -158,3 +164,23 @@ func flattenIncludeReferences(includeRefs map[string][]types.IncludeConfig) []st
 	}
 	return ret.Elements()
 }
+
+// projectHash returns a checksum from the JSON encoding of the project.
+func projectHash(p *types.Project) (string, bool) {
+	if p == nil {
+		return "", false
+	}
+	// disabled services aren't included in the output, so make a copy with
+	// all the services active for hashing
+	var err error
+	p, err = p.WithServicesEnabled(append(p.ServiceNames(), p.DisabledServiceNames()...)...)
+	if err != nil {
+		return "", false
+	}
+	projData, err := json.Marshal(p)
+	if err != nil {
+		return "", false
+	}
+	d := sha256.Sum256(projData)
+	return fmt.Sprintf("%x", d), true
+}

+ 67 - 0
internal/tracing/attributes_test.go

@@ -0,0 +1,67 @@
+/*
+   Copyright 2024 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package tracing
+
+import (
+	"testing"
+
+	"github.com/compose-spec/compose-go/v2/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestProjectHash(t *testing.T) {
+	projA := &types.Project{
+		Name:       "fake-proj",
+		WorkingDir: "/tmp",
+		Services: map[string]types.ServiceConfig{
+			"foo": {Image: "fake-image"},
+		},
+		DisabledServices: map[string]types.ServiceConfig{
+			"bar": {Image: "diff-image"},
+		},
+	}
+	projB := &types.Project{
+		Name:       "fake-proj",
+		WorkingDir: "/tmp",
+		Services: map[string]types.ServiceConfig{
+			"foo": {Image: "fake-image"},
+			"bar": {Image: "diff-image"},
+		},
+	}
+	projC := &types.Project{
+		Name:       "fake-proj",
+		WorkingDir: "/tmp",
+		Services: map[string]types.ServiceConfig{
+			"foo": {Image: "fake-image"},
+			"bar": {Image: "diff-image"},
+			"baz": {Image: "yet-another-image"},
+		},
+	}
+
+	hashA, ok := projectHash(projA)
+	require.True(t, ok)
+	require.NotEmpty(t, hashA)
+	hashB, ok := projectHash(projB)
+	require.True(t, ok)
+	require.NotEmpty(t, hashB)
+	require.Equal(t, hashA, hashB)
+
+	hashC, ok := projectHash(projC)
+	require.True(t, ok)
+	require.NotEmpty(t, hashC)
+	require.NotEqual(t, hashC, hashA)
+}