-
Notifications
You must be signed in to change notification settings - Fork 487
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
Attach Agent ID to remote-write requests #5999
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9f4d6bc
pull out agentseed package for reading and storing uuid. Also put it …
captncraig 21c1c83
add uid header to prometheus.remote_write and loki.write
captncraig 7a57138
init func
captncraig c994c9a
cleaner api with fewer edge cases
captncraig d77d1c7
add to pyroscope
captncraig fa57893
compile
captncraig beb872c
add to static remote write
captncraig 1b25aea
add to static mode loki write
captncraig 086482e
remove return from write. we never need it.
captncraig 723ad5c
move loki write out of convert function
captncraig 643c8fc
move out of prometheus convert function
captncraig be737c0
static prom, get out of defaults function
captncraig f611604
static logs: take out of defaults function
captncraig 14796c8
constant for header. Work done in init with sync.once. Hardening
captncraig 97426ee
added some tests
captncraig ed4c24c
maybe fix tests
captncraig 42fe213
testmain?
captncraig 8dc7783
changelog
captncraig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package agentseed | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"sync" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/google/uuid" | ||
"github.com/grafana/agent/pkg/flow/logging/level" | ||
"github.com/prometheus/common/version" | ||
) | ||
|
||
// AgentSeed identifies a unique agent | ||
type AgentSeed struct { | ||
UID string `json:"UID"` | ||
CreatedAt time.Time `json:"created_at"` | ||
Version string `json:"version"` | ||
} | ||
|
||
const HeaderName = "X-Agent-Id" | ||
const filename = "agent_seed.json" | ||
|
||
var savedSeed *AgentSeed | ||
var once sync.Once | ||
|
||
// Init should be called by an app entrypoint as soon as it can to configure where the unique seed will be stored. | ||
// dir is the directory where we will read and store agent_seed.json | ||
// If left empty it will default to $APPDATA or /tmp | ||
// A unique agent seed will be generated when this method is first called, and reused for the lifetime of this agent. | ||
func Init(dir string, l log.Logger) { | ||
if l == nil { | ||
l = log.NewNopLogger() | ||
} | ||
once.Do(func() { | ||
loadOrGenerate(dir, l) | ||
}) | ||
} | ||
|
||
func loadOrGenerate(dir string, l log.Logger) { | ||
var err error | ||
var seed *AgentSeed | ||
// list of paths in preference order. | ||
// we will always write to the first path | ||
paths := []string{} | ||
if dir != "" { | ||
paths = append(paths, filepath.Join(dir, filename)) | ||
} | ||
paths = append(paths, legacyPath()) | ||
defer func() { | ||
// as a fallback, gen and save a new uid | ||
if seed == nil || seed.UID == "" { | ||
seed = generateNew() | ||
writeSeedFile(seed, paths[0], l) | ||
} | ||
// Finally save seed | ||
savedSeed = seed | ||
}() | ||
for i, p := range paths { | ||
if fileExists(p) { | ||
if seed, err = readSeedFile(p, l); err == nil { | ||
if i == 0 { | ||
// we found it at the preferred path. Just return it | ||
return | ||
} else { | ||
// it was at a backup path. write it to the preferred path. | ||
writeSeedFile(seed, paths[0], l) | ||
return | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
func generateNew() *AgentSeed { | ||
return &AgentSeed{ | ||
UID: uuid.NewString(), | ||
Version: version.Version, | ||
CreatedAt: time.Now(), | ||
} | ||
} | ||
|
||
// Get will return a unique agent seed for this agent. | ||
// It will always return a valid seed, even if previous attempts to | ||
// load or save the seed file have failed | ||
func Get() *AgentSeed { | ||
// Init should have been called before this. If not, call it now with defaults. | ||
once.Do(func() { | ||
loadOrGenerate("", log.NewNopLogger()) | ||
}) | ||
if savedSeed != nil { | ||
return savedSeed | ||
} | ||
// we should never get here. But if somehow we do, | ||
// still return a valid seed for this request only | ||
return generateNew() | ||
} | ||
|
||
// readSeedFile reads the agent seed file | ||
func readSeedFile(path string, logger log.Logger) (*AgentSeed, error) { | ||
data, err := os.ReadFile(path) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Reading seed file", "err", err) | ||
return nil, err | ||
} | ||
seed := &AgentSeed{} | ||
err = json.Unmarshal(data, seed) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Decoding seed file", "err", err) | ||
return nil, err | ||
} | ||
|
||
if seed.UID == "" { | ||
level.Error(logger).Log("msg", "Seed file has empty uid") | ||
} | ||
return seed, nil | ||
} | ||
|
||
func legacyPath() string { | ||
// windows | ||
if runtime.GOOS == "windows" { | ||
return filepath.Join(os.Getenv("APPDATA"), filename) | ||
} | ||
// linux/mac | ||
return filepath.Join("/tmp", filename) | ||
} | ||
|
||
func fileExists(path string) bool { | ||
_, err := os.Stat(path) | ||
return !errors.Is(err, os.ErrNotExist) | ||
} | ||
|
||
// writeSeedFile writes the agent seed file | ||
func writeSeedFile(seed *AgentSeed, path string, logger log.Logger) { | ||
data, err := json.Marshal(*seed) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Encoding seed file", "err", err) | ||
return | ||
} | ||
err = os.WriteFile(path, data, 0644) | ||
if err != nil { | ||
level.Error(logger).Log("msg", "Writing seed file", "err", err) | ||
return | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package agentseed | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
os.Remove(legacyPath()) | ||
exitVal := m.Run() | ||
os.Exit(exitVal) | ||
} | ||
|
||
func reset() { | ||
os.Remove(legacyPath()) | ||
savedSeed = nil | ||
once = sync.Once{} | ||
} | ||
|
||
func TestNoExistingFile(t *testing.T) { | ||
t.Cleanup(reset) | ||
dir := t.TempDir() | ||
l := log.NewNopLogger() | ||
f := filepath.Join(dir, filename) | ||
require.NoFileExists(t, f) | ||
Init(dir, l) | ||
require.FileExists(t, f) | ||
loaded, err := readSeedFile(f, l) | ||
require.NoError(t, err) | ||
seed := Get() | ||
require.Equal(t, seed.UID, loaded.UID) | ||
} | ||
|
||
func TestExistingFile(t *testing.T) { | ||
t.Cleanup(reset) | ||
dir := t.TempDir() | ||
l := log.NewNopLogger() | ||
f := filepath.Join(dir, filename) | ||
seed := generateNew() | ||
writeSeedFile(seed, f, l) | ||
Init(dir, l) | ||
require.NotNil(t, savedSeed) | ||
require.Equal(t, seed.UID, savedSeed.UID) | ||
require.Equal(t, seed.UID, Get().UID) | ||
} | ||
|
||
func TestNoInitCalled(t *testing.T) { | ||
t.Cleanup(reset) | ||
l := log.NewNopLogger() | ||
seed := Get() | ||
require.NotNil(t, seed) | ||
f := legacyPath() | ||
require.FileExists(t, f) | ||
loaded, err := readSeedFile(f, l) | ||
require.NoError(t, err) | ||
require.Equal(t, seed.UID, loaded.UID) | ||
} | ||
|
||
func TestLegacyExists(t *testing.T) { | ||
t.Cleanup(reset) | ||
dir := t.TempDir() | ||
l := log.NewNopLogger() | ||
f := filepath.Join(dir, filename) | ||
seed := generateNew() | ||
writeSeedFile(seed, legacyPath(), l) | ||
Init(dir, l) | ||
require.FileExists(t, f) | ||
require.NotNil(t, savedSeed) | ||
require.Equal(t, seed.UID, savedSeed.UID) | ||
require.Equal(t, seed.UID, Get().UID) | ||
loaded, err := readSeedFile(f, l) | ||
require.NoError(t, err) | ||
require.Equal(t, seed.UID, loaded.UID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to make this work for otelcol.exporter.* components?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unclear at present. I'd assume there's some mechanism for including metadata, even though it may not be over http. Needs more research.