fix(deps): update github.com/prometheus/prometheus (main) (#15950)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paul Rogers <paul.rogers@grafana.com>
pull/16402/head
renovate[bot] 1 year ago committed by GitHub
parent 5b16c0be7d
commit b37eefe24f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      clients/cmd/fluent-bit/loki.go
  2. 4
      clients/pkg/logentry/stages/eventlogmessage.go
  3. 2
      clients/pkg/logentry/stages/labels.go
  4. 34
      clients/pkg/promtail/discovery/consulagent/consul.go
  5. 5
      clients/pkg/promtail/discovery/consulagent/consul_test.go
  6. 2
      clients/pkg/promtail/promtail_test.go
  7. 3
      clients/pkg/promtail/targets/docker/targetmanager.go
  8. 3
      clients/pkg/promtail/targets/file/filetargetmanager.go
  9. 2
      clients/pkg/promtail/targets/heroku/target.go
  10. 3
      clients/pkg/promtail/wal/wal.go
  11. 3
      clients/pkg/promtail/wal/watcher.go
  12. 41
      go.mod
  13. 153
      go.sum
  14. 2
      pkg/ingester/checkpoint.go
  15. 2
      pkg/ingester/wal.go
  16. 9
      pkg/querier/queryrange/queryrangebase/promql_test.go
  17. 8
      pkg/ruler/base/compat.go
  18. 5
      pkg/ruler/base/notifier.go
  19. 63
      pkg/ruler/base/ruler_test.go
  20. 3
      pkg/ruler/compat.go
  21. 6
      pkg/ruler/grouploader.go
  22. 19
      pkg/ruler/grouploader_test.go
  23. 24
      pkg/ruler/registry.go
  24. 9
      pkg/ruler/registry_test.go
  25. 2
      pkg/ruler/rulestore/local/local.go
  26. 5
      pkg/ruler/storage/instance/instance.go
  27. 6
      pkg/ruler/storage/instance/instance_test.go
  28. 13
      pkg/ruler/storage/wal/wal.go
  29. 3
      pkg/storage/stores/shipper/indexshipper/tsdb/head_wal.go
  30. 7
      pkg/util/log/log.go
  31. 29
      pkg/util/log/slogadapter.go
  32. 96
      pkg/util/log/slogadapter_test.go
  33. 14
      tools/lambda-promtail/go.mod
  34. 61
      tools/lambda-promtail/go.sum
  35. 16
      tools/lambda-promtail/lambda-promtail/main.go
  36. 7
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md
  37. 15
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go
  38. 12
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/op/op.go
  39. 2
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go
  40. 1
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pager.go
  41. 11
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/poller.go
  42. 20
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md
  43. 61
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md
  44. 29
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md
  45. 58
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TOKEN_CACHING.MD
  46. 26
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md
  47. 2
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/assets.json
  48. 18
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/authentication_record.go
  49. 34
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azidentity.go
  50. 6
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_cli_credential.go
  51. 6
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_developer_cli_credential.go
  52. 31
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_pipelines_credential.go
  53. 24
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/chained_token_credential.go
  54. 6
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml
  55. 16
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_assertion_credential.go
  56. 18
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_certificate_credential.go
  57. 14
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_secret_credential.go
  58. 18
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/confidential_client.go
  59. 23
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/default_azure_credential.go
  60. 43
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/device_code_credential.go
  61. 38
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/errors.go
  62. 43
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/interactive_browser_credential.go
  63. 86
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/cache.go
  64. 18
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/exported.go
  65. 31
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/internal.go
  66. 163
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go
  67. 52
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go
  68. 30
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/public_client.go
  69. 40
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-post.ps1
  70. 9
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources.bicep
  71. 28
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/username_password_credential.go
  72. 2
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go
  73. 10
      vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go
  74. 9
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go
  75. 30
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go
  76. 54
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go
  77. 3
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go
  78. 5
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go
  79. 109
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go
  80. 11
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go
  81. 17
      vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go
  82. 3092
      vendor/github.com/aws/aws-sdk-go/service/ec2/ec2iface/interface.go
  83. 60
      vendor/github.com/digitalocean/godo/CHANGELOG.md
  84. 110
      vendor/github.com/digitalocean/godo/apps.gen.go
  85. 70
      vendor/github.com/digitalocean/godo/apps.go
  86. 120
      vendor/github.com/digitalocean/godo/apps_accessors.go
  87. 243
      vendor/github.com/digitalocean/godo/databases.go
  88. 38
      vendor/github.com/digitalocean/godo/droplet_actions.go
  89. 258
      vendor/github.com/digitalocean/godo/droplet_autoscale.go
  90. 184
      vendor/github.com/digitalocean/godo/droplets.go
  91. 80
      vendor/github.com/digitalocean/godo/godo.go
  92. 12
      vendor/github.com/digitalocean/godo/kubernetes.go
  93. 1
      vendor/github.com/digitalocean/godo/load_balancers.go
  94. 196
      vendor/github.com/digitalocean/godo/monitoring.go
  95. 120
      vendor/github.com/digitalocean/godo/registry.go
  96. 135
      vendor/github.com/digitalocean/godo/reserved_ipv6.go
  97. 57
      vendor/github.com/digitalocean/godo/reserved_ipv6_actions.go
  98. 48
      vendor/github.com/digitalocean/godo/sizes.go
  99. 24
      vendor/github.com/digitalocean/godo/strings.go
  100. 4
      vendor/github.com/edsrzf/mmap-go/README.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -164,7 +164,7 @@ func extractLabels(records map[string]interface{}, keys []string) model.LabelSet
}
ln := model.LabelName(k)
// skips invalid name and values
if !ln.IsValid() {
if !ln.IsValidLegacy() {
continue
}
lv := model.LabelValue(fmt.Sprintf("%v", v))

@ -60,7 +60,7 @@ func validateEventLogMessageConfig(c *EventLogMessageConfig) error {
if c == nil {
return errors.New(ErrEmptyEvtLogMsgStageConfig)
}
if c.Source != nil && !model.LabelName(*c.Source).IsValid() {
if c.Source != nil && !model.LabelName(*c.Source).IsValidLegacy() {
return fmt.Errorf(ErrInvalidLabelName, *c.Source)
}
return nil
@ -108,7 +108,7 @@ func (m *eventLogMessageStage) processEntry(extracted map[string]interface{}, ke
continue
}
mkey := parts[0]
if !model.LabelName(mkey).IsValid() {
if !model.LabelName(mkey).IsValidLegacy() {
if m.cfg.DropInvalidLabels {
if Debug {
level.Debug(m.logger).Log("msg", "invalid label parsed from message", "key", mkey)

@ -26,7 +26,7 @@ func validateLabelsConfig(c LabelsConfig) error {
return errors.New(ErrEmptyLabelStageConfig)
}
for labelName, labelSrc := range c {
if !model.LabelName(labelName).IsValid() {
if !model.LabelName(labelName).IsValidLegacy() {
return fmt.Errorf(ErrInvalidLabelName, labelName)
}
// If no label source was specified, use the key name

@ -9,14 +9,14 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
consul "github.com/hashicorp/consul/api"
conntrack "github.com/mwitkow/go-conntrack"
"github.com/pkg/errors"
@ -152,19 +152,19 @@ type Discovery struct {
allowStale bool
refreshInterval time.Duration
finalizer func()
logger log.Logger
logger *slog.Logger
metrics *consulMetrics
}
// NewDiscovery returns a new Discovery for the given config.
func NewDiscovery(conf *SDConfig, logger log.Logger, metrics discovery.DiscovererMetrics) (*Discovery, error) {
func NewDiscovery(conf *SDConfig, logger *slog.Logger, metrics discovery.DiscovererMetrics) (*Discovery, error) {
m, ok := metrics.(*consulMetrics)
if !ok {
return nil, fmt.Errorf("invalid discovery metrics type")
}
if logger == nil {
logger = log.NewNopLogger()
logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
}
tls, err := config.NewTLSConfig(&conf.TLSConfig)
@ -265,7 +265,7 @@ func (d *Discovery) getDatacenter() error {
}
info, err := d.client.Agent().Self()
if err != nil {
level.Error(d.logger).Log("msg", "Error retrieving datacenter name", "err", err)
d.logger.Error("Error retrieving datacenter name", "err", err)
d.metrics.rpcFailuresCount.Inc()
return err
}
@ -273,7 +273,7 @@ func (d *Discovery) getDatacenter() error {
dc, ok := info["Config"]["Datacenter"].(string)
if !ok {
err := errors.Errorf("invalid value '%v' for Config.Datacenter", info["Config"]["Datacenter"])
level.Error(d.logger).Log("msg", "Error retrieving datacenter name", "err", err)
d.logger.Error("Error retrieving datacenter name", "err", err)
return err
}
@ -342,7 +342,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
// entire list of services.
func (d *Discovery) watchServices(ctx context.Context, ch chan<- []*targetgroup.Group, services map[string]func(error)) {
agent := d.client.Agent()
level.Debug(d.logger).Log("msg", "Watching services", "tags", strings.Join(d.watchedTags, ","))
d.logger.Debug("Watching services", "tags", strings.Join(d.watchedTags, ","))
t0 := time.Now()
srvs, err := agent.Services()
@ -357,7 +357,7 @@ func (d *Discovery) watchServices(ctx context.Context, ch chan<- []*targetgroup.
}
if err != nil {
level.Error(d.logger).Log("msg", "Error refreshing service list", "err", err)
d.logger.Error("Error refreshing service list", "err", err)
d.metrics.rpcFailuresCount.Inc()
time.Sleep(retryInterval)
return
@ -386,9 +386,7 @@ func (d *Discovery) watchServices(ctx context.Context, ch chan<- []*targetgroup.
// Check for removed services.
for name, cancel := range services {
if _, ok := discoveredServices[name]; !ok {
level.Debug(d.logger).Log(
"msg", "removing service since consul no longer has a record of it",
"name", name)
d.logger.Debug("removing service since consul no longer has a record of it", "name", name)
// Call the watch cancellation function.
cancel(errors.New("canceling service since consul no longer has a record of it"))
delete(services, name)
@ -420,7 +418,7 @@ type consulService struct {
discovery *Discovery
client *consul.Client
tagSeparator string
logger log.Logger
logger *slog.Logger
rpcFailuresCount prometheus.Counter
serviceRPCDuration prometheus.Observer
}
@ -464,7 +462,7 @@ func (d *Discovery) watchService(ctx context.Context, ch chan<- []*targetgroup.G
// Get updates for a service.
func (srv *consulService) watch(ctx context.Context, ch chan<- []*targetgroup.Group, agent *consul.Agent) {
level.Debug(srv.logger).Log("msg", "Watching service", "service", srv.name, "tags", strings.Join(srv.tags, ","))
srv.logger.Debug("Watching service", "service", srv.name, "tags", strings.Join(srv.tags, ","))
t0 := time.Now()
aggregatedStatus, serviceChecks, err := agent.AgentHealthServiceByName(srv.name)
@ -480,7 +478,7 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*targetgroup.Gr
}
if err != nil {
level.Error(srv.logger).Log("msg", "Error refreshing service", "service", srv.name, "tags", strings.Join(srv.tags, ","), "err", err)
srv.logger.Error("Error refreshing service", "service", srv.name, "tags", strings.Join(srv.tags, ","), "err", err)
srv.rpcFailuresCount.Inc()
time.Sleep(retryInterval)
return
@ -488,18 +486,18 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*targetgroup.Gr
self, err := agent.Self()
if err != nil {
level.Error(srv.logger).Log("msg", "failed to get agent info from agent api", "err", err)
srv.logger.Error("failed to get agent info from agent api", "err", err)
return
}
var member = consul.AgentMember{}
memberBytes, err := json.Marshal(self["Member"])
if err != nil {
level.Error(srv.logger).Log("msg", "failed to get member information from agent", "err", err)
srv.logger.Error("failed to get member information from agent", "err", err)
return
}
err = json.Unmarshal(memberBytes, &member)
if err != nil {
level.Error(srv.logger).Log("msg", "failed to unmarshal member information from agent", "err", err)
srv.logger.Error("failed to unmarshal member information from agent", "err", err)
return
}

@ -15,13 +15,14 @@ package consulagent
import (
"context"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
@ -364,7 +365,7 @@ func newServer(t *testing.T) (*httptest.Server, *SDConfig) {
}
func newDiscovery(t *testing.T, config *SDConfig) *Discovery {
logger := log.NewNopLogger()
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
metrics := NewTestMetrics(t, config, prometheus.NewRegistry())
d, err := NewDiscovery(config, logger, metrics)
require.NoError(t, err)

@ -522,7 +522,7 @@ func getPromMetrics(t *testing.T, httpListenAddr net.Addr) ([]byte, string) {
func parsePromMetrics(t *testing.T, bytes []byte, contentType string, metricName string, label string) map[string]float64 {
rb := map[string]float64{}
pr, err := textparse.New(bytes, contentType, false, nil)
pr, err := textparse.New(bytes, contentType, "", false, false, nil)
require.NoError(t, err)
for {
et, err := pr.Next()

@ -16,6 +16,7 @@ import (
"github.com/grafana/loki/v3/clients/pkg/promtail/targets/target"
"github.com/grafana/loki/v3/pkg/util"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
const (
@ -60,7 +61,7 @@ func NewTargetManager(
positions: positions,
manager: discovery.NewManager(
ctx,
log.With(logger, "component", "docker_discovery"),
util_log.SlogFromGoKit(log.With(logger, "component", "docker_discovery")),
noopRegistry,
noopSdMetrics,
),

@ -27,6 +27,7 @@ import (
"github.com/grafana/loki/v3/clients/pkg/promtail/targets/target"
"github.com/grafana/loki/v3/pkg/util"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
const (
@ -84,7 +85,7 @@ func NewFileTargetManager(
syncers: map[string]*targetSyncer{},
manager: discovery.NewManager(
ctx,
log.With(logger, "component", "discovery"),
util_log.SlogFromGoKit(log.With(logger, "component", "discovery")),
noopRegistry,
noopSdMetrics,
),

@ -68,7 +68,7 @@ func (h *Target) run() error {
// To prevent metric collisions because all metrics are going to be registered in the global Prometheus registry.
tentativeServerMetricNamespace := "promtail_heroku_drain_target_" + h.jobName
if !model.IsValidMetricName(model.LabelValue(tentativeServerMetricNamespace)) {
if !model.LabelName(tentativeServerMetricNamespace).IsValidLegacy() {
return fmt.Errorf("invalid prometheus-compatible job name: %s", h.jobName)
}
h.config.Server.MetricsNamespace = tentativeServerMetricNamespace

@ -10,6 +10,7 @@ import (
"github.com/prometheus/prometheus/tsdb/wlog"
"github.com/grafana/loki/v3/pkg/ingester/wal"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
var (
@ -37,7 +38,7 @@ type wrapper struct {
func New(cfg Config, log log.Logger, registerer prometheus.Registerer) (WAL, error) {
// TODO: We should fine-tune the WAL instantiated here to allow some buffering of written entries, but not written to disk
// yet. This will attest for the lack of buffering in the channel Writer exposes.
tsdbWAL, err := wlog.NewSize(log, registerer, cfg.Dir, wlog.DefaultSegmentSize, wlog.CompressionNone)
tsdbWAL, err := wlog.NewSize(util_log.SlogFromGoKit(log), registerer, cfg.Dir, wlog.DefaultSegmentSize, wlog.CompressionNone)
if err != nil {
return nil, fmt.Errorf("failde to create tsdb WAL: %w", err)
}

@ -15,6 +15,7 @@ import (
"github.com/prometheus/prometheus/tsdb/wlog"
"github.com/grafana/loki/v3/pkg/ingester/wal"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
const (
@ -160,7 +161,7 @@ func (w *Watcher) watch(segmentNum int) error {
}
defer segment.Close()
reader := wlog.NewLiveReader(w.logger, nil, segment)
reader := wlog.NewLiveReader(util_log.SlogFromGoKit(w.logger), nil, segment)
readTimer := newBackoffTimer(w.minReadFreq, w.maxReadFreq)

@ -83,10 +83,10 @@ require (
// github.com/pierrec/lz4 v2.0.5+incompatible
github.com/pierrec/lz4/v4 v4.1.22
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.5
github.com/prometheus/client_golang v1.21.0-rc.0
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.61.0
github.com/prometheus/prometheus v0.55.0
github.com/prometheus/common v0.62.0
github.com/prometheus/prometheus v0.302.0
github.com/redis/go-redis/v9 v9.7.0
github.com/segmentio/fasthash v1.0.3
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c
@ -133,10 +133,12 @@ require (
github.com/parquet-go/parquet-go v0.24.0
github.com/prometheus/alertmanager v0.28.0
github.com/prometheus/common/sigv4 v0.1.0
github.com/prometheus/sigv4 v0.1.1
github.com/richardartoul/molecule v1.0.0
github.com/schollz/progressbar/v3 v3.18.0
github.com/shirou/gopsutil/v4 v4.25.1
github.com/thanos-io/objstore v0.0.0-20250115091151-a54d0f04b42a
github.com/tjhop/slog-gokit v0.1.3
github.com/twmb/franz-go v1.18.1
github.com/twmb/franz-go/pkg/kadm v1.15.0
github.com/twmb/franz-go/pkg/kfake v0.0.0-20241015013301-cea7aa5d8037
@ -174,6 +176,7 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-redsync/redsync/v4 v4.13.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gophercloud/gophercloud/v2 v2.4.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/imdario/mergo v0.3.16 // indirect
@ -187,6 +190,9 @@ require (
github.com/moby/sys/userns v0.1.0 // indirect
github.com/ncw/swift v1.0.53 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.116.0 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.116.0 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.116.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/xattr v0.4.10 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@ -198,7 +204,13 @@ require (
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/component v0.118.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.118.0 // indirect
go.opentelemetry.io/collector/consumer v1.24.0 // indirect
go.opentelemetry.io/collector/pipeline v0.118.0 // indirect
go.opentelemetry.io/collector/processor v0.118.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.59.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
@ -209,8 +221,8 @@ require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.3.1 // indirect
cloud.google.com/go/longrunning v0.6.4 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 // indirect
@ -221,7 +233,7 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
github.com/Code-Hex/go-generics-cache v1.5.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
@ -252,7 +264,7 @@ require (
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/digitalocean/godo v1.122.0 // indirect
github.com/digitalocean/godo v1.132.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
@ -262,10 +274,10 @@ require (
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
@ -282,7 +294,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-zookeeper/zk v1.0.3 // indirect
github.com/go-zookeeper/zk v1.0.4 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@ -290,11 +302,10 @@ require (
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gophercloud/gophercloud v1.14.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -325,7 +336,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@ -362,9 +373,9 @@ require (
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
go.mongodb.org/mongo-driver v1.17.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/collector/semconv v0.108.1 // indirect
go.opentelemetry.io/collector/semconv v0.118.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0

153
go.sum

@ -66,10 +66,12 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4=
@ -109,8 +111,10 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=
@ -320,8 +324,8 @@ github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsY
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/digitalocean/godo v1.122.0 h1:ziytLQi8QKtDp2K1A+YrYl2dWLHLh2uaMzWvcz9HkKg=
github.com/digitalocean/godo v1.122.0/go.mod h1:WQVH83OHUy6gC4gXpEVQKtxTd4L5oCp+5OialidkPLY=
github.com/digitalocean/godo v1.132.0 h1:n0x6+ZkwbyQBtIU1wwBhv26EINqHg0wWQiBXlwYg/HQ=
github.com/digitalocean/godo v1.132.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
@ -359,8 +363,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/efficientgo/core v1.0.0-rc.3 h1:X6CdgycYWDcbYiJr1H1+lQGzx13o7bq3EUkbB9DsSPc=
github.com/efficientgo/core v1.0.0-rc.3/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps=
github.com/efficientgo/e2e v0.13.1-0.20220922081603-45de9fc588a8 h1:UFLc39BcUXahSNCLUrKjNGZABMUZaS4M74EZvTRnq3k=
@ -375,8 +379,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -470,14 +474,16 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=
github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
@ -581,8 +587,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
@ -599,8 +605,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gophercloud/gophercloud v1.14.0 h1:Bt9zQDhPrbd4qX7EILGmy+i7GP35cc+AAL2+wIJpUE8=
github.com/gophercloud/gophercloud v1.14.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gophercloud/gophercloud/v2 v2.4.0 h1:XhP5tVEH3ni66NSNK1+0iSO6kaGPH/6srtx6Cr+8eCg=
github.com/gophercloud/gophercloud/v2 v2.4.0/go.mod h1:uJWNpTgJPSl2gyzJqcU/pIAhFUWvIkp8eE8M15n9rs4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
@ -645,8 +651,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
@ -706,15 +712,15 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3 h1:fgVfQ4AC1avVOnu2cfms8VAiD8lUq3vWI8mTocOXN/w=
github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE=
github.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec h1:+YBzb977VrmffaCX/OBm17dEVJUcWn5dW+eqs3aIJ/A=
github.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/heroku/x v0.4.3 h1:HF1P4Mu79BKDVk4pt+oRDpcOSTRTpHq28RYAOkuJmds=
github.com/heroku/x v0.4.3/go.mod h1:htQnSDQPP7rNbrOQ8rczL7tbdNtQHXCPoSxYomu+eI8=
github.com/hetznercloud/hcloud-go/v2 v2.13.1 h1:jq0GP4QaYE5d8xR/Zw17s9qoaESRJMXfGmtD1a/qckQ=
github.com/hetznercloud/hcloud-go/v2 v2.13.1/go.mod h1:dhix40Br3fDiBhwaSG/zgaYOFFddpfBm/6R1Zz0IiF0=
github.com/hetznercloud/hcloud-go/v2 v2.18.0 h1:BemrVGeWI8Kn/pvaC1jBsHZxQMnRqOydS7Ju4BERB4Q=
github.com/hetznercloud/hcloud-go/v2 v2.18.0/go.mod h1:r5RTzv+qi8IbLcDIskTzxkFIji7Ovc8yNgepQR9M+UA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@ -733,8 +739,8 @@ github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b h1:i44CesU68Z
github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y=
github.com/influxdata/telegraf v1.33.1 h1:lOLJajvPeINZGm/sOdYTAH7D5O+XVggDhZUxO7VwH18=
github.com/influxdata/telegraf v1.33.1/go.mod h1:tVlgADshEXDBPYeNvH2zaX6NmnIiMycDHd/vAYKpOSY=
github.com/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY=
github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
github.com/ionos-cloud/sdk-go/v6 v6.3.2 h1:2mUmrZZz6cPyT9IRX0T8fBLc/7XU/eTxP2Y5tS7/09k=
github.com/ionos-cloud/sdk-go/v6 v6.3.2/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@ -775,6 +781,8 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kamstrup/intmap v0.5.1 h1:ENGAowczZA+PJPYYlreoqJvWgQVtAmX1l899WfYFVK0=
github.com/kamstrup/intmap v0.5.1/go.mod h1:gWUVWHKzWj8xpJVFf5GC0O26bWmv3GqdnIX/LMT6Aq4=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -785,6 +793,12 @@ github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kK
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -810,8 +824,8 @@ github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b h1:11UHH39
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linode/linodego v1.40.0 h1:7ESY0PwK94hoggoCtIroT1Xk6b1flrFBNZ6KwqbTqlI=
github.com/linode/linodego v1.40.0/go.mod h1:NsUw4l8QrLdIofRg1NYFBbW5ZERnmbZykVBszPZLORM=
github.com/linode/linodego v1.46.0 h1:+uOG4SD2MIrhbrLrvOD5HrbdLN3D19Wgn3MgdUNQjeU=
github.com/linode/linodego v1.46.0/go.mod h1:vyklQRzZUWhFVBZdYx4dcYJU/gG9yKB9VUcUs6ub0Lk=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@ -848,8 +862,8 @@ github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnE
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0=
github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
@ -930,6 +944,14 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.116.0 h1:Kxk5Ral+Dc6VB9UmTketVjs+rbMZP8JxQ4SXDx4RivQ=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.116.0/go.mod h1:ctT6oQmGmWGGGgUIKyx2fDwqz77N9+04gqKkDyAzKCg=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.116.0 h1:RlEK9MbxWyBHbLel8EJ1L7DbYVLai9dZL6Ljl2cBgyA=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.116.0/go.mod h1:AVUEyIjPb+0ARr7mhIkZkdNg3fd0ZcRhzAi53oZhl1Q=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.116.0 h1:jwnZYRBuPJnsKXE5H6ZvTEm91bXW5VP8+tLewzl54eg=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.116.0/go.mod h1:NT3Ag+DdnIAZQfD7l7OHwlYqnaAJ19SoPZ0nhD9yx4s=
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.116.0 h1:ZBmLuipJv7BT9fho/2yAFsS8AtMsCOCe4ON8oqkX3n8=
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.116.0/go.mod h1:f0GdYWGxUunyRZ088gHnoX78pc/gZc3dQlRtidiGXzg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@ -1003,8 +1025,8 @@ github.com/prometheus/client_golang v1.6.1-0.20200604110148-03575cad4e55/go.mod
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.0-rc.0 h1:bR+RxBlwcr4q8hXkgSOA/J18j6n0/qH0Gb0DH+8c+RY=
github.com/prometheus/client_golang v1.21.0-rc.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -1021,8 +1043,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
github.com/prometheus/exporter-toolkit v0.13.2 h1:Z02fYtbqTMy2i/f+xZ+UK5jy/bl1Ex3ndzh06T/Q9DQ=
@ -1037,8 +1059,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.55.0 h1:ITinOi1zr3HemoVWHf679PfRRmpxZOcR4nEvsze6eB0=
github.com/prometheus/prometheus v0.55.0/go.mod h1:GGS7QlWKCqCbcEzWsVahYIfQwiGhcExkarHyLJTsv6I=
github.com/prometheus/prometheus v0.302.0 h1:47EsaoBRroS2ekSyMSOPIjXwYnY/mxoFk0xt2dkFvfI=
github.com/prometheus/prometheus v0.302.0/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg=
github.com/prometheus/sigv4 v0.1.1 h1:UJxjOqVcXctZlwDjpUpZ2OiMWJdFijgSofwLzO1Xk0Q=
github.com/prometheus/sigv4 v0.1.1/go.mod h1:RAmWVKqx0bwi0Qm4lrKMXFM0nhpesBcenfCtz9qRyH8=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@ -1131,6 +1155,8 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKN
github.com/tencentyun/cos-go-sdk-v5 v0.7.40 h1:W6vDGKCHe4wBACI1d2UgE6+50sJFhRWU4O8IB2ozzxM=
github.com/tencentyun/cos-go-sdk-v5 v0.7.40/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o=
github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
@ -1211,22 +1237,52 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/collector/component v0.118.0 h1:sSO/ObxJ+yH77Z4DmT1mlSuxhbgUmY1ztt7xCA1F/8w=
go.opentelemetry.io/collector/component v0.118.0/go.mod h1:LUJ3AL2b+tmFr3hZol3hzKzCMvNdqNq0M5CF3SWdv4M=
go.opentelemetry.io/collector/component/componentstatus v0.118.0 h1:1aCIdUjqz0noKNQr1v04P+lwF89Lkua5U7BhH9IAxkE=
go.opentelemetry.io/collector/component/componentstatus v0.118.0/go.mod h1:ynO1Nyj0t1h6x/djIMJy35bhnnWEc2mlQaFgDNUO504=
go.opentelemetry.io/collector/component/componenttest v0.118.0 h1:knEHckoiL2fEWSIc0iehg39zP4IXzi9sHa45O+oxKo8=
go.opentelemetry.io/collector/component/componenttest v0.118.0/go.mod h1:aHc7t7zVwCpbhrWIWY+GMuaMxMCUP8C8P7pJOt8r/vU=
go.opentelemetry.io/collector/config/configtelemetry v0.118.0 h1:UlN46EViG2X42odWtXgWaqY7Y01ZKpsnswSwXTWx5mM=
go.opentelemetry.io/collector/config/configtelemetry v0.118.0/go.mod h1:SlBEwQg0qly75rXZ6W1Ig8jN25KBVBkFIIAUI1GiAAE=
go.opentelemetry.io/collector/confmap v1.22.0 h1:ZKQzRuj5lKu+seKArAAZ1yPRroDPricaIVIREm/jr3w=
go.opentelemetry.io/collector/confmap v1.22.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec=
go.opentelemetry.io/collector/consumer v1.24.0 h1:7DeyBm9qdr1EPuCfPjWyChPK16DbVc0wZeSa9LZprFU=
go.opentelemetry.io/collector/consumer v1.24.0/go.mod h1:0G6jvZprIp4dpKMD1ZxCjriiP9GdFvFMObsQEtTk71s=
go.opentelemetry.io/collector/consumer/consumertest v0.118.0 h1:8AAS9ejQapP1zqt0+cI6u+AUBheT3X0171N9WtXWsVY=
go.opentelemetry.io/collector/consumer/consumertest v0.118.0/go.mod h1:spRM2wyGr4QZzqMHlLmZnqRCxqXN4Wd0piogC4Qb5PQ=
go.opentelemetry.io/collector/consumer/xconsumer v0.118.0 h1:guWnzzRqgCInjnYlOQ1BPrimppNGIVvnknAjlIbWXuY=
go.opentelemetry.io/collector/consumer/xconsumer v0.118.0/go.mod h1:C5V2d6Ys/Fi6k3tzjBmbdZ9v3J/rZSAMlhx4KVcMIIg=
go.opentelemetry.io/collector/pdata v1.26.0 h1:o7nP0RTQOG0LXk55ZZjLrxwjX8x3wHF7Z7xPeOaskEA=
go.opentelemetry.io/collector/pdata v1.26.0/go.mod h1:18e8/xDZsqyj00h/5HM5GLdJgBzzG9Ei8g9SpNoiMtI=
go.opentelemetry.io/collector/semconv v0.108.1 h1:Txk9tauUnamZaxS5vlf1O0uZ4VD6nioRBR0nX8L/fU4=
go.opentelemetry.io/collector/semconv v0.108.1/go.mod h1:zCJ5njhWpejR+A40kiEoeFm1xq1uzyZwMnRNX6/D82A=
go.opentelemetry.io/collector/pdata/pprofile v0.118.0 h1:VK/fr65VFOwEhsSGRPj5c3lCv0yIK1Kt0sZxv9WZBb8=
go.opentelemetry.io/collector/pdata/pprofile v0.118.0/go.mod h1:eJyP/vBm179EghV3dPSnamGAWQwLyd+4z/3yG54YFoQ=
go.opentelemetry.io/collector/pdata/testdata v0.118.0 h1:5N0w1SX9KIRkwvtkrpzQgXy9eGk3vfNG0ds6mhEPMIM=
go.opentelemetry.io/collector/pdata/testdata v0.118.0/go.mod h1:UY+GHV5bOC1BnFburOZ0wiHReJj1XbW12mi2Ogbc5Lw=
go.opentelemetry.io/collector/pipeline v0.118.0 h1:RI1DMe7L0+5hGkx0EDGxG00TaJoh96MEQppgOlGx1Oc=
go.opentelemetry.io/collector/pipeline v0.118.0/go.mod h1:qE3DmoB05AW0C3lmPvdxZqd/H4po84NPzd5MrqgtL74=
go.opentelemetry.io/collector/processor v0.118.0 h1:NlqWiTTpPP+EPbrqTcNP9nh/4O4/9U9RGWVB49xo4ws=
go.opentelemetry.io/collector/processor v0.118.0/go.mod h1:Y8OD7wk51oPuBqrbn1qXIK91AbprRHP76hlvEzC24U4=
go.opentelemetry.io/collector/processor/processortest v0.118.0 h1:VfTLHuIaJWGyUmrvAOvf63gPMf1vAW68/jtJClEsKtU=
go.opentelemetry.io/collector/processor/processortest v0.118.0/go.mod h1:ZFWxsSoafGNOEk83FtGz43M5ypUzAOvGnfT0aQTDHdU=
go.opentelemetry.io/collector/processor/xprocessor v0.118.0 h1:M/EMhPRbadHLpv7g99fBjfgyuYexBZmgQqb2vjTXjvM=
go.opentelemetry.io/collector/processor/xprocessor v0.118.0/go.mod h1:lkoQoCv2Cz+C0kf2VHgBUDYWDecZLLeaHEvHDXbBCXU=
go.opentelemetry.io/collector/semconv v0.118.0 h1:V4vlMIK7TIaemrrn2VawvQPwruIKpj7Xgw9P5+BL56w=
go.opentelemetry.io/collector/semconv v0.118.0/go.mod h1:N6XE8Q0JKgBN2fAhkUQtqK9LT7rEGR6+Wu/Rtbal1iI=
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4=
go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.59.0 h1:iQZYNQ7WwIcYXzOPR46FQv9O0dS1PW16RjvR0TjDOe8=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.59.0/go.mod h1:54CaSNqYEXvpzDh8KPjiMVoWm60t5R0dZRt0leEPgAs=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
@ -1237,8 +1293,8 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -1271,7 +1327,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=

@ -348,7 +348,7 @@ func (w *WALCheckpointWriter) Advance() (bool, error) {
return false, fmt.Errorf("create checkpoint dir: %w", err)
}
checkpoint, err := wlog.NewSize(log.With(util_log.Logger, "component", "checkpoint_wal"), nil, checkpointDirTemp, walSegmentSize, wlog.CompressionNone)
checkpoint, err := wlog.NewSize(util_log.SlogFromGoKit(log.With(util_log.Logger, "component", "checkpoint_wal")), nil, checkpointDirTemp, walSegmentSize, wlog.CompressionNone)
if err != nil {
return false, fmt.Errorf("open checkpoint: %w", err)
}

@ -82,7 +82,7 @@ func newWAL(cfg WALConfig, registerer prometheus.Registerer, metrics *ingesterMe
return noopWAL{}, nil
}
tsdbWAL, err := wlog.NewSize(util_log.Logger, registerer, cfg.Dir, walSegmentSize, wlog.CompressionNone)
tsdbWAL, err := wlog.NewSize(util_log.SlogFromGoKit(util_log.Logger), registerer, cfg.Dir, walSegmentSize, wlog.CompressionNone)
if err != nil {
return nil, err
}

@ -19,6 +19,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/loki/v3/pkg/querier/astmapper"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
var (
@ -28,7 +29,7 @@ var (
ctx = context.Background()
engine = promql.NewEngine(promql.EngineOpts{
Reg: prometheus.DefaultRegisterer,
Logger: log.NewNopLogger(),
Logger: util_log.SlogFromGoKit(log.NewNopLogger()),
Timeout: 1 * time.Hour,
MaxSamples: 10e6,
ActiveQueryTracker: nil,
@ -518,12 +519,6 @@ func Test_FunctionParallelism(t *testing.T) {
fn: "round",
fArgs: []string{"20"},
},
{
fn: "holt_winters",
isTestMatrix: true,
fArgs: []string{"0.5", "0.7"},
approximate: true,
},
} {
t.Run(tc.fn, func(t *testing.T) {

@ -65,10 +65,16 @@ func (a *PusherAppender) AppendHistogram(_ storage.SeriesRef, _ labels.Labels, _
return 0, errors.New("native histograms are unsupported")
}
func (a *PusherAppender) AppendHistogramCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64, _ *histogram.Histogram, _ *histogram.FloatHistogram) (storage.SeriesRef, error) {
return 0, errors.New("histogram created timestamps are unsupported")
}
func (a *PusherAppender) AppendCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64) (storage.SeriesRef, error) {
return 0, errors.New("created timestamps are unsupported")
}
func (a *PusherAppender) SetOptions(_ *storage.AppendOptions) {}
func (a *PusherAppender) Commit() error {
a.totalWrites.Inc()
@ -257,7 +263,7 @@ func DefaultTenantManagerFactory(cfg Config, p Pusher, q storage.Queryable, engi
Context: user.InjectOrgID(ctx, userID),
ExternalURL: cfg.ExternalURL.URL,
NotifyFunc: SendAlerts(notifier, cfg.ExternalURL.URL.String(), cfg.DatasourceUID),
Logger: log.With(logger, "user", userID),
Logger: util_log.SlogFromGoKit(log.With(logger, "user", userID)),
Registerer: reg,
OutageTolerance: cfg.OutageTolerance,
ForGracePeriod: cfg.ForGracePeriod,

@ -21,6 +21,7 @@ import (
ruler_config "github.com/grafana/loki/v3/pkg/ruler/config"
"github.com/grafana/loki/v3/pkg/util"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
// TODO: Instead of using the same metrics for all notifiers,
@ -53,9 +54,9 @@ type rulerNotifier struct {
func newRulerNotifier(o *notifier.Options, l gklog.Logger) *rulerNotifier {
sdCtx, sdCancel := context.WithCancel(context.Background())
return &rulerNotifier{
notifier: notifier.NewManager(o, l),
notifier: notifier.NewManager(o, util_log.SlogFromGoKit(l)),
sdCancel: sdCancel,
sdManager: discovery.NewManager(sdCtx, l, util.NoopRegistry{}, sdMetrics),
sdManager: discovery.NewManager(sdCtx, util_log.SlogFromGoKit(l), util.NoopRegistry{}, sdMetrics),
logger: l,
}
}

@ -56,6 +56,7 @@ import (
"github.com/grafana/loki/v3/pkg/storage/chunk/client/testutils"
"github.com/grafana/loki/v3/pkg/util"
"github.com/grafana/loki/v3/pkg/util/constants"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
func defaultRulerConfig(t testing.TB, store rulestore.RuleStore) Config {
@ -121,7 +122,7 @@ func testQueryableFunc(q storage.Querier) storage.QueryableFunc {
func testSetup(t *testing.T, q storage.Querier) (*promql.Engine, storage.QueryableFunc, Pusher, log.Logger, RulesLimits, prometheus.Registerer) {
dir := t.TempDir()
tracker := promql.NewActiveQueryTracker(dir, 20, log.NewNopLogger())
tracker := promql.NewActiveQueryTracker(dir, 20, util_log.SlogFromGoKit(log.NewNopLogger()))
engine := promql.NewEngine(promql.EngineOpts{
MaxSamples: 1e6,
@ -311,12 +312,14 @@ func TestMultiTenantsNotifierSendsUserIDHeader(t *testing.T) {
amCfg := map[string]*config.AlertManagerConfig{
tenant1: {
AlertmanagerURL: ts1.URL,
AlertmanagerDiscovery: false,
AlertmanagerURL: ts1.URL,
AlertmanagerDiscovery: false,
AlertmanangerEnableV2API: true,
},
tenant2: {
AlertmanagerURL: ts2.URL,
AlertmanagerDiscovery: false,
AlertmanagerURL: ts2.URL,
AlertmanagerDiscovery: false,
AlertmanangerEnableV2API: true,
},
}
@ -329,30 +332,50 @@ func TestMultiTenantsNotifierSendsUserIDHeader(t *testing.T) {
n2, err := manager.getOrCreateNotifier(tenant2)
require.NoError(t, err)
// Loop until notifier discovery syncs up
for len(n1.Alertmanagers()) == 0 {
time.Sleep(10 * time.Millisecond)
// Wait for both notifiers to be ready with a timeout
ready := make(chan struct{})
go func() {
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
ams1 := n1.Alertmanagers()
ams2 := n2.Alertmanagers()
if len(ams1) > 0 && len(ams2) > 0 {
close(ready)
return
}
time.Sleep(100 * time.Millisecond)
}
}()
select {
case <-ready:
// Both notifiers are ready
case <-time.After(10 * time.Second):
t.Fatalf("Timeout waiting for alertmanagers to be ready. Notifier 1 has %d alertmanagers, Notifier 2 has %d alertmanagers",
len(n1.Alertmanagers()), len(n2.Alertmanagers()))
}
n1.Send(&notifier.Alert{
Labels: labels.Labels{labels.Label{Name: "alertname1", Value: "testalert1"}},
})
for len(n2.Alertmanagers()) == 0 {
time.Sleep(10 * time.Millisecond)
}
n2.Send(&notifier.Alert{
Labels: labels.Labels{labels.Label{Name: "alertname2", Value: "testalert2"}},
})
wg.Wait()
// Ensure we have metrics in the notifier.
assert.NoError(t, prom_testutil.GatherAndCompare(manager.registry.(*prometheus.Registry), strings.NewReader(`
# HELP loki_prometheus_notifications_dropped_total Total number of alerts dropped due to errors when sending to Alertmanager.
# TYPE loki_prometheus_notifications_dropped_total counter
loki_prometheus_notifications_dropped_total{user="tenant1"} 0
loki_prometheus_notifications_dropped_total{user="tenant2"} 0
`), "loki_prometheus_notifications_dropped_total"))
// Wait for notifications with a timeout
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
// Notifications received
case <-time.After(10 * time.Second):
t.Fatal("Timeout waiting for notifications")
}
}
func TestRuler_Rules(t *testing.T) {

@ -29,6 +29,7 @@ import (
"github.com/grafana/loki/v3/pkg/ruler/rulespb"
rulerutil "github.com/grafana/loki/v3/pkg/ruler/util"
"github.com/grafana/loki/v3/pkg/util"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
// RulesLimits is the one function we need from limits.Overrides, and
@ -164,7 +165,7 @@ func MultiTenantRuleManager(cfg Config, evaluator Evaluator, overrides RulesLimi
Context: user.InjectOrgID(ctx, userID),
ExternalURL: cfg.ExternalURL.URL,
NotifyFunc: ruler.SendAlerts(notifier, cfg.ExternalURL.URL.String(), cfg.DatasourceUID),
Logger: logger,
Logger: util_log.SlogFromGoKit(logger),
Registerer: reg,
OutageTolerance: cfg.OutageTolerance,
ForGracePeriod: cfg.ForGracePeriod,

@ -25,7 +25,7 @@ func (GroupLoader) Parse(query string) (parser.Expr, error) {
return exprAdapter{expr}, nil
}
func (g GroupLoader) Load(identifier string) (*rulefmt.RuleGroups, []error) {
func (g GroupLoader) Load(identifier string, _ bool) (*rulefmt.RuleGroups, []error) {
b, err := os.ReadFile(identifier)
if err != nil {
return nil, []error{errors.Wrap(err, identifier)}
@ -70,8 +70,8 @@ func NewCachingGroupLoader(l rules.GroupLoader) *CachingGroupLoader {
}
}
func (l *CachingGroupLoader) Load(identifier string) (*rulefmt.RuleGroups, []error) {
groups, errs := l.loader.Load(identifier)
func (l *CachingGroupLoader) Load(identifier string, ignoreUnknownFields bool) (*rulefmt.RuleGroups, []error) {
groups, errs := l.loader.Load(identifier, ignoreUnknownFields)
if errs != nil {
return nil, errs
}

@ -7,12 +7,17 @@ import (
"testing"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/rulefmt"
"github.com/prometheus/prometheus/promql/parser"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func init() {
model.NameValidationScheme = model.LegacyValidation
}
func Test_GroupLoader(t *testing.T) {
for _, tc := range []struct {
desc string
@ -250,7 +255,7 @@ groups:
err = os.WriteFile(f.Name(), []byte(tc.data), 0777)
require.Nil(t, err)
_, errs := loader.Load(f.Name())
_, errs := loader.Load(f.Name(), false)
if tc.match != "" {
require.NotNil(t, errs)
var found bool
@ -278,14 +283,14 @@ func TestCachingGroupLoader(t *testing.T) {
cl := NewCachingGroupLoader(l)
groups, errs := cl.Load("filename1")
groups, errs := cl.Load("filename1", true)
require.Nil(t, errs)
require.Equal(t, groups, ruleGroup1)
rules := cl.AlertingRules()
require.Equal(t, rulefmt.Rule{Alert: "alert-1-name"}, rules[0])
groups, errs = cl.Load("filename2")
groups, errs = cl.Load("filename2", true)
require.Nil(t, errs)
require.Equal(t, groups, ruleGroup2)
@ -303,7 +308,7 @@ func TestCachingGroupLoader(t *testing.T) {
cl := NewCachingGroupLoader(l)
groups, errs := cl.Load("filename1")
groups, errs := cl.Load("filename1", true)
require.Equal(t, l.loadErrs, errs)
require.Nil(t, groups)
@ -320,10 +325,10 @@ func TestCachingGroupLoader(t *testing.T) {
cl := NewCachingGroupLoader(l)
_, errs := cl.Load("filename1")
_, errs := cl.Load("filename1", true)
require.Nil(t, errs)
_, errs = cl.Load("filename2")
_, errs = cl.Load("filename2", true)
require.Nil(t, errs)
cl.Prune([]string{"filename2"})
@ -347,7 +352,7 @@ type fakeGroupLoader struct {
parseErr error
}
func (gl *fakeGroupLoader) Load(identifier string) (*rulefmt.RuleGroups, []error) {
func (gl *fakeGroupLoader) Load(identifier string, _ bool) (*rulefmt.RuleGroups, []error) {
return gl.ruleGroups[identifier], gl.loadErrs
}

@ -23,6 +23,7 @@ import (
"github.com/prometheus/prometheus/model/metadata"
"github.com/prometheus/prometheus/model/relabel"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/sigv4"
"gopkg.in/yaml.v2"
"github.com/grafana/loki/v3/pkg/ruler/storage/cleaner"
@ -312,7 +313,12 @@ func (r *walRegistry) getTenantRemoteWriteConfig(tenant string, base RemoteWrite
}
if v := r.overrides.RulerRemoteWriteSigV4Config(tenant); v != nil {
clt.SigV4Config = v
clt.SigV4Config = &sigv4.SigV4Config{}
clt.SigV4Config.Region = v.Region
clt.SigV4Config.AccessKey = v.AccessKey
clt.SigV4Config.SecretKey = v.SecretKey
clt.SigV4Config.Profile = v.Profile
clt.SigV4Config.RoleARN = v.RoleARN
}
if v := r.overrides.RulerRemoteWriteConfig(tenant, id); v != nil {
@ -388,8 +394,12 @@ func (n notReadyAppender) AppendHistogram(_ storage.SeriesRef, _ labels.Labels,
func (n notReadyAppender) AppendCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64) (storage.SeriesRef, error) {
return 0, errNotReady
}
func (n notReadyAppender) Commit() error { return errNotReady }
func (n notReadyAppender) Rollback() error { return errNotReady }
func (n notReadyAppender) AppendHistogramCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64, _ *histogram.Histogram, _ *histogram.FloatHistogram) (storage.SeriesRef, error) {
return 0, errNotReady
}
func (n notReadyAppender) SetOptions(_ *storage.AppendOptions) {}
func (n notReadyAppender) Commit() error { return errNotReady }
func (n notReadyAppender) Rollback() error { return errNotReady }
type discardingAppender struct{}
@ -408,8 +418,12 @@ func (n discardingAppender) AppendHistogram(_ storage.SeriesRef, _ labels.Labels
func (n discardingAppender) AppendCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64) (storage.SeriesRef, error) {
return 0, nil
}
func (n discardingAppender) Commit() error { return nil }
func (n discardingAppender) Rollback() error { return nil }
func (n discardingAppender) AppendHistogramCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64, _ *histogram.Histogram, _ *histogram.FloatHistogram) (storage.SeriesRef, error) {
return 0, nil
}
func (n discardingAppender) SetOptions(_ *storage.AppendOptions) {}
func (n discardingAppender) Commit() error { return nil }
func (n discardingAppender) Rollback() error { return nil }
type readyChecker interface {
isReady(tenant string) bool

@ -13,9 +13,10 @@ import (
"github.com/grafana/dskit/user"
promConfig "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/sigv4"
common_sigv4 "github.com/prometheus/common/sigv4"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/relabel"
prom_sigv4 "github.com/prometheus/sigv4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -209,7 +210,7 @@ func newFakeLimitsBackwardCompat() fakeLimits {
},
},
sigV4ConfigTenant: {
RulerRemoteWriteSigV4Config: &sigv4.SigV4Config{
RulerRemoteWriteSigV4Config: &common_sigv4.SigV4Config{
Region: sigV4TenantRegion,
},
},
@ -282,7 +283,7 @@ func newFakeLimits() fakeLimits {
sigV4ConfigTenant: {
RulerRemoteWriteConfig: map[string]config.RemoteWriteConfig{
remote1: {
SigV4Config: &sigv4.SigV4Config{
SigV4Config: &prom_sigv4.SigV4Config{
Region: sigV4TenantRegion,
},
},
@ -338,7 +339,7 @@ func setupSigV4Registry(t *testing.T, cfg Config, limits fakeLimits) *walRegistr
// Remove the basic auth config and replace with sigv4
for id, clt := range reg.config.RemoteWrite.Clients {
clt.HTTPClientConfig.BasicAuth = nil
clt.SigV4Config = &sigv4.SigV4Config{
clt.SigV4Config = &prom_sigv4.SigV4Config{
Region: sigV4GlobalRegion,
}
reg.config.RemoteWrite.Clients[id] = clt

@ -177,7 +177,7 @@ func (l *Client) loadAllRulesGroupsForUser(ctx context.Context, userID string) (
func (l *Client) loadAllRulesGroupsForUserAndNamespace(_ context.Context, userID string, namespace string) (rulespb.RuleGroupList, error) {
filename := filepath.Join(l.cfg.Directory, userID, namespace)
rulegroups, allErrors := l.loader.Load(filename)
rulegroups, allErrors := l.loader.Load(filename, true)
if len(allErrors) > 0 {
return nil, errors.Wrapf(allErrors[0], "error parsing %s", filename)
}

@ -30,6 +30,7 @@ import (
"github.com/grafana/loki/v3/pkg/ruler/storage/util"
"github.com/grafana/loki/v3/pkg/ruler/storage/wal"
"github.com/grafana/loki/v3/pkg/util/build"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
func init() {
@ -303,7 +304,7 @@ func (i *Instance) initialize(_ context.Context, reg prometheus.Registerer, cfg
// Setup the remote storage
remoteLogger := log.With(i.logger, "component", "remote")
i.remoteStore = remote.NewStorage(remoteLogger, reg, i.wal.StartTime, i.wal.Directory(), cfg.RemoteFlushDeadline, noopScrapeManager{}, false)
i.remoteStore = remote.NewStorage(util_log.SlogFromGoKit(remoteLogger), reg, i.wal.StartTime, i.wal.Directory(), cfg.RemoteFlushDeadline, noopScrapeManager{}, false)
err = i.remoteStore.ApplyConfig(&config.Config{
RemoteWriteConfigs: cfg.RemoteWrite,
})
@ -311,7 +312,7 @@ func (i *Instance) initialize(_ context.Context, reg prometheus.Registerer, cfg
return fmt.Errorf("failed applying config to remote storage: %w", err)
}
i.storage = storage.NewFanout(i.logger, i.wal, i.remoteStore)
i.storage = storage.NewFanout(util_log.SlogFromGoKit(i.logger), i.wal, i.remoteStore)
i.wal.SetWriteNotified(i.remoteStore)
i.initialized = true

@ -301,10 +301,16 @@ func (a *mockAppender) AppendHistogram(_ storage.SeriesRef, _ labels.Labels, _ i
return 0, nil
}
func (a *mockAppender) AppendHistogramCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64, _ *histogram.Histogram, _ *histogram.FloatHistogram) (storage.SeriesRef, error) {
return 0, nil
}
func (a *mockAppender) AppendCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64) (storage.SeriesRef, error) {
return 0, nil
}
func (a *mockAppender) SetOptions(_ *storage.AppendOptions) {}
func (a *mockAppender) Commit() error {
return nil
}

@ -29,6 +29,8 @@ import (
"github.com/prometheus/prometheus/tsdb/record"
"github.com/prometheus/prometheus/tsdb/wlog"
"go.uber.org/atomic"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
// ErrWALClosed is an error returned when a WAL operation can't run because the
@ -68,7 +70,7 @@ type Storage struct {
// NewStorage makes a new Storage.
func NewStorage(logger log.Logger, metrics *Metrics, registerer prometheus.Registerer, path string) (*Storage, error) {
w, err := wlog.NewSize(logger, registerer, SubDirectory(path), wlog.DefaultSegmentSize, wlog.CompressionSnappy)
w, err := wlog.NewSize(util_log.SlogFromGoKit(logger), registerer, SubDirectory(path), wlog.DefaultSegmentSize, wlog.CompressionSnappy)
if err != nil {
return nil, err
}
@ -378,7 +380,7 @@ func (w *Storage) Truncate(mint int64) error {
w.deletedMtx.Unlock()
return ok
}
if _, err = wlog.Checkpoint(w.logger, w.wal, first, last, keep, mint); err != nil {
if _, err = wlog.Checkpoint(util_log.SlogFromGoKit(w.logger), w.wal, first, last, keep, mint); err != nil {
return errors.Wrap(err, "create checkpoint")
}
if err := w.wal.Truncate(last + 1); err != nil {
@ -655,11 +657,18 @@ func (a *appender) AppendHistogram(_ storage.SeriesRef, _ labels.Labels, _ int64
return 0, nil
}
func (a *appender) AppendHistogramCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64, _ *histogram.Histogram, _ *histogram.FloatHistogram) (storage.SeriesRef, error) {
// TODO: support histogram created timestamps
return 0, nil
}
func (a *appender) AppendCTZeroSample(_ storage.SeriesRef, _ labels.Labels, _ int64, _ int64) (storage.SeriesRef, error) {
// TODO: support created timestamp
return 0, nil
}
func (a *appender) SetOptions(_ *storage.AppendOptions) {}
// Commit submits the collected samples and purges the batch.
func (a *appender) Commit() error {
a.w.walMtx.RLock()

@ -10,6 +10,7 @@ import (
"github.com/grafana/loki/v3/pkg/storage/stores/shipper/indexshipper/tsdb/index"
"github.com/grafana/loki/v3/pkg/util/encoding"
util_log "github.com/grafana/loki/v3/pkg/util/log"
)
type WAL interface {
@ -204,7 +205,7 @@ func newHeadWAL(log log.Logger, dir string, t time.Time) (*headWAL, error) {
// NB: if we use a non-nil Prometheus Registerer, ensure
// that the underlying metrics won't conflict with existing WAL metrics in the ingester.
// Likely, this can be done by adding extra label(s)
wal, err := wlog.NewSize(log, nil, dir, walSegmentSize, wlog.CompressionNone)
wal, err := wlog.NewSize(util_log.SlogFromGoKit(log), nil, dir, walSegmentSize, wlog.CompressionNone)
if err != nil {
return nil, err
}

@ -28,10 +28,17 @@ var (
bufferedLogger *dslog.BufferedLogger
plogger *prometheusLogger
// initialize log level to info, but it is set in the InitLogger function
logLevel = func() dslog.Level {
var l dslog.Level
_ = l.Set("info")
return l
}()
)
// InitLogger initialises the global gokit logger (util_log.Logger) and returns that logger.
func InitLogger(cfg *server.Config, reg prometheus.Registerer, sync bool) log.Logger {
logLevel = cfg.LogLevel
logger := newPrometheusLogger(cfg.LogLevel, cfg.LogFormat, reg, sync)
// when using util_log.Logger, skip 3 stack frames.
Logger = log.With(logger, "caller", log.Caller(3))

@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-only
package log
import (
"log/slog"
"github.com/go-kit/log"
slgk "github.com/tjhop/slog-gokit"
)
// SlogFromGoKit returns slog adapter for logger.
func SlogFromGoKit(logger log.Logger) *slog.Logger {
var sl slog.Level
switch logLevel.String() {
case "info":
sl = slog.LevelInfo
case "warn":
sl = slog.LevelWarn
case "error":
sl = slog.LevelError
default:
sl = slog.LevelDebug
}
lvl := slog.LevelVar{}
lvl.Set(sl)
return slog.New(slgk.NewGoKitHandler(logger, &lvl))
}

@ -0,0 +1,96 @@
// SPDX-License-Identifier: AGPL-3.0-only
package log
import (
"context"
"fmt"
"log/slog"
"testing"
"github.com/go-kit/log/level"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type mockLogger struct {
mock.Mock
}
func (m *mockLogger) Log(keyvals ...interface{}) error {
args := m.Called(keyvals...)
return args.Error(0)
}
func TestSlogFromGoKit(t *testing.T) {
levels := []level.Value{
level.DebugValue(),
level.InfoValue(),
level.WarnValue(),
level.ErrorValue(),
}
slogLevels := []slog.Level{
slog.LevelDebug,
slog.LevelInfo,
slog.LevelWarn,
slog.LevelError,
}
// Store original log level to restore after tests
originalLevel := logLevel.String()
t.Cleanup(func() {
_ = logLevel.Set(originalLevel)
})
t.Run("enabled for the right slog levels when go-kit level configured", func(t *testing.T) {
for i, l := range levels {
switch i {
case 0:
require.NoError(t, logLevel.Set("debug"))
case 1:
require.NoError(t, logLevel.Set("info"))
case 2:
require.NoError(t, logLevel.Set("warn"))
case 3:
require.NoError(t, logLevel.Set("error"))
default:
panic(fmt.Errorf("unhandled level %d", i))
}
mLogger := &mockLogger{}
logger := level.NewFilter(mLogger, logLevel.Option)
slogger := SlogFromGoKit(logger)
for j, sl := range slogLevels {
if j >= i {
assert.Truef(t, slogger.Enabled(context.Background(), sl), "slog logger should be enabled for go-kit level %v / slog level %v", l, sl)
} else {
assert.Falsef(t, slogger.Enabled(context.Background(), sl), "slog logger should not be enabled for go-kit level %v / slog level %v", l, sl)
}
}
}
})
t.Run("wraps go-kit logger", func(_ *testing.T) {
mLogger := &mockLogger{}
slogger := SlogFromGoKit(mLogger)
for _, l := range levels {
mLogger.On("Log", level.Key(), l, "caller", mock.AnythingOfType("string"), "time", mock.AnythingOfType("time.Time"), "msg", "test", "attr", slog.StringValue("value")).Times(1).Return(nil)
attrs := []any{"attr", "value"}
switch l {
case level.DebugValue():
slogger.Debug("test", attrs...)
case level.InfoValue():
slogger.Info("test", attrs...)
case level.WarnValue():
slogger.Warn("test", attrs...)
case level.ErrorValue():
slogger.Error("test", attrs...)
default:
panic(fmt.Errorf("unrecognized level %v", l))
}
}
})
}

@ -15,8 +15,8 @@ require (
github.com/grafana/dskit v0.0.0-20241007172036-53283a0f6b41
github.com/grafana/loki/v3 v3.4.2
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc
github.com/prometheus/common v0.61.0
github.com/prometheus/prometheus v1.8.2-0.20200727090838-6f296594a852
github.com/prometheus/common v0.62.0
github.com/prometheus/prometheus v1.99.0
github.com/stretchr/testify v1.10.0
)
@ -52,7 +52,7 @@ require (
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@ -91,7 +91,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
@ -106,7 +106,7 @@ require (
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_golang v1.21.0-rc.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/exporter-toolkit v0.13.2 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
@ -140,7 +140,7 @@ require (
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.28.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/grpc v1.70.0 // indirect
@ -151,4 +151,4 @@ require (
//replace k8s.io/client-go => k8s.io/client-go v0.21.0
// Using a fork of Prometheus with Mimir-specific changes.
replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.53.2-0.20240726125539-d4f098ae80fb
replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.302.0

@ -1,16 +1,23 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ=
cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@ -124,8 +131,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -218,9 +225,15 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/dskit v0.0.0-20241007172036-53283a0f6b41 h1:a4O59OU3FJZ+EJUVnlvvNTvdAc4uRN1P6EaGwqL9CnA=
@ -339,8 +352,8 @@ github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -396,8 +409,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.0-rc.0 h1:bR+RxBlwcr4q8hXkgSOA/J18j6n0/qH0Gb0DH+8c+RY=
github.com/prometheus/client_golang v1.21.0-rc.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -408,10 +421,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/exporter-toolkit v0.13.2 h1:Z02fYtbqTMy2i/f+xZ+UK5jy/bl1Ex3ndzh06T/Q9DQ=
github.com/prometheus/exporter-toolkit v0.13.2/go.mod h1:tCqnfx21q6qN1KA4U3Bfb8uWzXfijIrJz3/kTIqMV7g=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@ -421,8 +432,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.53.2-0.20240726125539-d4f098ae80fb h1:5fIFCLngxdbuVflXqK9MwbXa89QHvlRJ7B2js9w9nbI=
github.com/prometheus/prometheus v0.53.2-0.20240726125539-d4f098ae80fb/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY=
github.com/prometheus/prometheus v0.302.0 h1:47EsaoBRroS2ekSyMSOPIjXwYnY/mxoFk0xt2dkFvfI=
github.com/prometheus/prometheus v0.302.0/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg=
github.com/prometheus/sigv4 v0.1.1 h1:UJxjOqVcXctZlwDjpUpZ2OiMWJdFijgSofwLzO1Xk0Q=
github.com/prometheus/sigv4 v0.1.1/go.mod h1:RAmWVKqx0bwi0Qm4lrKMXFM0nhpesBcenfCtz9qRyH8=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
@ -484,12 +497,14 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/collector/pdata v1.24.0 h1:D6j92eAzmAbQgivNBUnt8r9juOl8ugb+ihYynoFZIEg=
go.opentelemetry.io/collector/pdata v1.24.0/go.mod h1:cf3/W9E/uIvPS4MR26SnMFJhraUCattzzM6qusuONuc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
@ -631,8 +646,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -641,6 +656,8 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA=
google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

@ -132,11 +132,15 @@ func parseExtraLabels(extraLabelsRaw string, omitPrefix bool) (model.LabelSet, e
return nil, errors.New(invalidExtraLabelsError)
}
for i := 0; i < len(extraLabelsSplit); i += 2 {
extractedLabels[model.LabelName(prefix+extraLabelsSplit[i])] = model.LabelValue(extraLabelsSplit[i+1])
}
err := extractedLabels.Validate()
if err != nil {
return nil, err
labelName := model.LabelName(prefix + extraLabelsSplit[i])
if !labelName.IsValidLegacy() {
return nil, fmt.Errorf("invalid name %q", labelName)
}
labelValue := model.LabelValue(extraLabelsSplit[i+1])
if !labelValue.IsValid() {
return nil, fmt.Errorf("invalid value %q", labelValue)
}
extractedLabels[labelName] = labelValue
}
fmt.Println("extra labels:", extractedLabels)
return extractedLabels, nil
@ -149,7 +153,7 @@ func getDropLabels() ([]model.LabelName, error) {
dropLabelsRawSplit := strings.Split(dropLabelsRaw, ",")
for _, dropLabelRaw := range dropLabelsRawSplit {
dropLabel := model.LabelName(dropLabelRaw)
if !dropLabel.IsValid() {
if !dropLabel.IsValidLegacy() {
return []model.LabelName{}, fmt.Errorf("invalid label name %s", dropLabelRaw)
}
result = append(result, dropLabel)

@ -1,5 +1,12 @@
# Release History
## 1.17.0 (2025-01-07)
### Features Added
* Added field `OperationLocationResultPath` to `runtime.NewPollerOptions[T]` for LROs that use the `Operation-Location` pattern.
* Support `encoding.TextMarshaler` and `encoding.TextUnmarshaler` interfaces in `arm.ResourceID`.
## 1.16.0 (2024-10-17)
### Features Added

@ -110,6 +110,21 @@ func (id *ResourceID) String() string {
return id.stringValue
}
// MarshalText returns a textual representation of the ResourceID
func (id *ResourceID) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
// UnmarshalText decodes the textual representation of a ResourceID
func (id *ResourceID) UnmarshalText(text []byte) error {
newId, err := ParseResourceID(string(text))
if err != nil {
return err
}
*id = *newId
return nil
}
func newResourceID(parent *ResourceID, resourceTypeName string, resourceName string) *ResourceID {
id := &ResourceID{}
id.init(parent, chooseResourceType(resourceTypeName, parent), resourceName, true)

@ -40,12 +40,13 @@ type Poller[T any] struct {
OrigURL string `json:"origURL"`
Method string `json:"method"`
FinalState pollers.FinalStateVia `json:"finalState"`
ResultPath string `json:"resultPath"`
CurState string `json:"state"`
}
// New creates a new Poller from the provided initial response.
// Pass nil for response to create an empty Poller for rehydration.
func New[T any](pl exported.Pipeline, resp *http.Response, finalState pollers.FinalStateVia) (*Poller[T], error) {
func New[T any](pl exported.Pipeline, resp *http.Response, finalState pollers.FinalStateVia, resultPath string) (*Poller[T], error) {
if resp == nil {
log.Write(log.EventLRO, "Resuming Operation-Location poller.")
return &Poller[T]{pl: pl}, nil
@ -82,6 +83,7 @@ func New[T any](pl exported.Pipeline, resp *http.Response, finalState pollers.Fi
OrigURL: resp.Request.URL.String(),
Method: resp.Request.Method,
FinalState: finalState,
ResultPath: resultPath,
CurState: curState,
}, nil
}
@ -116,10 +118,6 @@ func (p *Poller[T]) Result(ctx context.Context, out *T) error {
var req *exported.Request
var err error
// when the payload is included with the status monitor on
// terminal success it's in the "result" JSON property
payloadPath := "result"
if p.FinalState == pollers.FinalStateViaLocation && p.LocURL != "" {
req, err = exported.NewRequest(ctx, http.MethodGet, p.LocURL)
} else if rl, rlErr := poller.GetResourceLocation(p.resp); rlErr != nil && !errors.Is(rlErr, poller.ErrNoBody) {
@ -138,7 +136,7 @@ func (p *Poller[T]) Result(ctx context.Context, out *T) error {
// if a final GET request has been created, execute it
if req != nil {
// no JSON path when making a final GET request
payloadPath = ""
p.ResultPath = ""
resp, err := p.pl.Do(req)
if err != nil {
return err
@ -146,5 +144,5 @@ func (p *Poller[T]) Result(ctx context.Context, out *T) error {
p.resp = resp
}
return pollers.ResultHelper(p.resp, poller.Failed(p.CurState), payloadPath, out)
return pollers.ResultHelper(p.resp, poller.Failed(p.CurState), p.ResultPath, out)
}

@ -40,5 +40,5 @@ const (
Module = "azcore"
// Version is the semantic version (see http://semver.org) of this module.
Version = "v1.16.0"
Version = "v1.17.0"
)

@ -32,6 +32,7 @@ type PagingHandler[T any] struct {
}
// Pager provides operations for iterating over paged responses.
// Methods on this type are not safe for concurrent use.
type Pager[T any] struct {
current *T
handler PagingHandler[T]

@ -50,8 +50,14 @@ const (
// NewPollerOptions contains the optional parameters for NewPoller.
type NewPollerOptions[T any] struct {
// FinalStateVia contains the final-state-via value for the LRO.
// NOTE: used only for Azure-AsyncOperation and Operation-Location LROs.
FinalStateVia FinalStateVia
// OperationLocationResultPath contains the JSON path to the result's
// payload when it's included with the terminal success response.
// NOTE: only used for Operation-Location LROs.
OperationLocationResultPath string
// Response contains a preconstructed response type.
// The final payload will be unmarshaled into it and returned.
Response *T
@ -98,7 +104,7 @@ func NewPoller[T any](resp *http.Response, pl exported.Pipeline, options *NewPol
opr, err = async.New[T](pl, resp, options.FinalStateVia)
} else if op.Applicable(resp) {
// op poller must be checked before loc as it can also have a location header
opr, err = op.New[T](pl, resp, options.FinalStateVia)
opr, err = op.New[T](pl, resp, options.FinalStateVia, options.OperationLocationResultPath)
} else if loc.Applicable(resp) {
opr, err = loc.New[T](pl, resp)
} else if body.Applicable(resp) {
@ -172,7 +178,7 @@ func NewPollerFromResumeToken[T any](token string, pl exported.Pipeline, options
} else if loc.CanResume(asJSON) {
opr, _ = loc.New[T](pl, nil)
} else if op.CanResume(asJSON) {
opr, _ = op.New[T](pl, nil, "")
opr, _ = op.New[T](pl, nil, "", "")
} else {
return nil, fmt.Errorf("unhandled poller token %s", string(raw))
}
@ -200,6 +206,7 @@ type PollingHandler[T any] interface {
}
// Poller encapsulates a long-running operation, providing polling facilities until the operation reaches a terminal state.
// Methods on this type are not safe for concurrent use.
type Poller[T any] struct {
op PollingHandler[T]
resp *http.Response

@ -0,0 +1,20 @@
# Breaking Changes
## v1.8.0
### New errors from `NewManagedIdentityCredential` in some environments
`NewManagedIdentityCredential` now returns an error when `ManagedIdentityCredentialOptions.ID` is set in a hosting environment whose managed identity API doesn't support user-assigned identities. `ManagedIdentityCredential.GetToken()` formerly logged a warning in these cases. Returning an error instead prevents the credential authenticating an unexpected identity. The affected hosting environments are:
* Azure Arc
* Azure ML (when a resource or object ID is specified; client IDs are supported)
* Cloud Shell
* Service Fabric
## v1.6.0
### Behavioral change to `DefaultAzureCredential` in IMDS managed identity scenarios
As of `azidentity` v1.6.0, `DefaultAzureCredential` makes a minor behavioral change when it uses IMDS managed
identity. It sends its first request to IMDS without the "Metadata" header, to expedite validating whether the endpoint
is available. This precedes the credential's first token request and is guaranteed to fail with a 400 error. This error
response can appear in logs but doesn't indicate authentication failed.

@ -1,5 +1,66 @@
# Release History
## 1.8.1 (2025-01-15)
### Bugs Fixed
* User credential types inconsistently log access token scopes
* `DefaultAzureCredential` skips managed identity in Azure Container Instances
* Credentials having optional tenant IDs such as `AzureCLICredential` and
`InteractiveBrowserCredential` require setting `AdditionallyAllowedTenants`
when used with some clients
### Other Changes
* `ChainedTokenCredential` and `DefaultAzureCredential` continue to their next
credential after `ManagedIdentityCredential` receives an unexpected response
from IMDS, indicating the response is from something else such as a proxy
## 1.8.0 (2024-10-08)
### Other Changes
* `AzurePipelinesCredential` sets an additional OIDC request header so that it
receives a 401 instead of a 302 after presenting an invalid system access token
* Allow logging of debugging headers for `AzurePipelinesCredential` and include
them in error messages
## 1.8.0-beta.3 (2024-09-17)
### Features Added
* Added `ObjectID` type for `ManagedIdentityCredentialOptions.ID`
### Other Changes
* Removed redundant content from error messages
## 1.8.0-beta.2 (2024-08-06)
### Breaking Changes
* `NewManagedIdentityCredential` now returns an error when a user-assigned identity
is specified on a platform whose managed identity API doesn't support that.
`ManagedIdentityCredential.GetToken()` formerly logged a warning in these cases.
Returning an error instead prevents the credential authenticating an unexpected
identity, causing a client to act with unexpected privileges. The affected
platforms are:
* Azure Arc
* Azure ML (when a resource ID is specified; client IDs are supported)
* Cloud Shell
* Service Fabric
### Other Changes
* If `DefaultAzureCredential` receives a non-JSON response when probing IMDS before
attempting to authenticate a managed identity, it continues to the next credential
in the chain instead of immediately returning an error.
## 1.8.0-beta.1 (2024-07-17)
### Features Added
* Restored persistent token caching feature
### Breaking Changes
> These changes affect only code written against a beta version such as v1.7.0-beta.1
* Redesigned the persistent caching API. Encryption is now required in all cases
and persistent cache construction is separate from credential construction.
The `PersistentUserAuthentication` example in the package docs has been updated
to demonstrate the new API.
## 1.7.0 (2024-06-20)
### Features Added

@ -54,17 +54,7 @@ The `azidentity` module focuses on OAuth authentication with Microsoft Entra ID.
### DefaultAzureCredential
`DefaultAzureCredential` is appropriate for most apps that will be deployed to Azure. It combines common production credentials with development credentials. It attempts to authenticate via the following mechanisms in this order, stopping when one succeeds:
![DefaultAzureCredential authentication flow](img/mermaidjs/DefaultAzureCredentialAuthFlow.svg)
1. **Environment** - `DefaultAzureCredential` will read account information specified via [environment variables](#environment-variables) and use it to authenticate.
1. **Workload Identity** - If the app is deployed on Kubernetes with environment variables set by the workload identity webhook, `DefaultAzureCredential` will authenticate the configured identity.
1. **Managed Identity** - If the app is deployed to an Azure host with managed identity enabled, `DefaultAzureCredential` will authenticate with it.
1. **Azure CLI** - If a user or service principal has authenticated via the Azure CLI `az login` command, `DefaultAzureCredential` will authenticate that identity.
1. **Azure Developer CLI** - If the developer has authenticated via the Azure Developer CLI `azd auth login` command, the `DefaultAzureCredential` will authenticate with that account.
> Note: `DefaultAzureCredential` is intended to simplify getting started with the SDK by handling common scenarios with reasonable default behaviors. Developers who want more control or whose scenario isn't served by the default settings should use other credential types.
`DefaultAzureCredential` simplifies authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [DefaultAzureCredential overview][dac_overview].
## Managed Identity
@ -126,12 +116,17 @@ client := armresources.NewResourceGroupsClient("subscription ID", chain, nil)
## Credential Types
### Authenticating Azure Hosted Applications
### Credential chains
|Credential|Usage|Reference
|-|-|-
|[DefaultAzureCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential)|Simplified authentication experience for getting started developing Azure apps|[DefaultAzureCredential overview][dac_overview]|
|[ChainedTokenCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#ChainedTokenCredential)|Define custom authentication flows, composing multiple credentials|[ChainedTokenCredential overview][ctc_overview]|
### Authenticating Azure-Hosted Applications
|Credential|Usage
|-|-
|[DefaultAzureCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential)|Simplified authentication experience for getting started developing Azure apps
|[ChainedTokenCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#ChainedTokenCredential)|Define custom authentication flows, composing multiple credentials
|[EnvironmentCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#EnvironmentCredential)|Authenticate a service principal or user configured by environment variables
|[ManagedIdentityCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#ManagedIdentityCredential)|Authenticate the managed identity of an Azure resource
|[WorkloadIdentityCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#WorkloadIdentityCredential)|Authenticate a workload identity on Kubernetes
@ -158,7 +153,7 @@ client := armresources.NewResourceGroupsClient("subscription ID", chain, nil)
|Credential|Usage
|-|-
|[AzureCLICredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#AzureCLICredential)|Authenticate as the user signed in to the Azure CLI
|[`AzureDeveloperCLICredential`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#AzureDeveloperCLICredential)|Authenticates as the user signed in to the Azure Developer CLI
|[AzureDeveloperCLICredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#AzureDeveloperCLICredential)|Authenticates as the user signed in to the Azure Developer CLI
## Environment Variables
@ -255,4 +250,8 @@ For more information, see the
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any
additional questions or comments.
<!-- LINKS -->
[ctc_overview]: https://aka.ms/azsdk/go/identity/credential-chains#chainedtokencredential-overview
[dac_overview]: https://aka.ms/azsdk/go/identity/credential-chains#defaultazurecredential-overview
![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-go%2Fsdk%2Fazidentity%2FREADME.png)

@ -1,60 +1,43 @@
## Token caching in the Azure Identity client module
*Token caching* is a feature provided by the Azure Identity library that allows apps to:
Token caching helps apps:
- Improve their resilience and performance.
- Reduce the number of requests made to Microsoft Entra ID to obtain access tokens.
- Reduce the number of times the user is prompted to authenticate.
- Reduce the number of requests sent to Microsoft Entra ID to obtain access tokens.
- Reduce the number of times users are prompted to authenticate.
When an app needs to access a protected Azure resource, it typically needs to obtain an access token from Entra ID. Obtaining that token involves sending a request to Entra ID and may also involve prompting the user. Entra ID then validates the credentials provided in the request and issues an access token.
When an app needs to access a protected Azure resource, it typically needs to obtain an access token from Entra ID by sending an HTTP request and sometimes prompting a user to authenticate interactively. Credentials with caches (see [the below table](#credentials-supporting-token-caching) for a list) store access tokens either [in memory](#in-memory-token-caching) or, optionally, [on disk](#persistent-token-caching). These credentials return cached tokens whenever possible, to avoid unnecessary token requests or user interaction. Both cache implementations are safe for concurrent use.
Token caching, via the Azure Identity library, allows the app to store this access token [in memory](#in-memory-token-caching), where it's accessible to the current process, or [on disk](#persistent-token-caching) where it can be accessed across application or process invocations. The token can then be retrieved quickly and easily the next time the app needs to access the same resource. The app can avoid making another request to Entra ID, which reduces network traffic and improves resilience. Additionally, in scenarios where the app is authenticating users, token caching also avoids prompting the user each time new tokens are requested.
#### Caching can't be disabled
### In-memory token caching
*In-memory token caching* is the default option provided by the Azure Identity library. This caching approach allows apps to store access tokens in memory. With in-memory token caching, the library first determines if a valid access token for the requested resource is already stored in memory. If a valid token is found, it's returned to the app without the need to make another request to Entra ID. If a valid token isn't found, the library will automatically acquire a token by sending a request to Entra ID. The in-memory token cache provided by the Azure Identity library is thread-safe.
**Note:** When Azure Identity library credentials are used with Azure service libraries (for example, Azure Blob Storage), the in-memory token caching is active in the `Pipeline` layer as well. All `TokenCredential` implementations are supported there, including custom implementations external to the Azure Identity library.
Whether a credential caches tokens isn't configurable. If a credential has a cache of either kind, it requests a new token only when it can't provide one from its cache. Azure SDK service clients have an additional, independent layer of in-memory token caching, to prevent redundant token requests. This cache works with any credential type, even a custom implementation defined outside the Azure SDK, and can't be disabled. Disabling token caching is therefore impossible when using Azure SDK clients or most `azidentity` credential types. However, in-memory caches can be cleared by constructing new credential and client instances.
#### Caching cannot be disabled
### In-memory token caching
As there are many levels of caching, it's not possible disable in-memory caching. However, the in-memory cache may be cleared by creating a new credential instance.
Credential types that support caching store tokens in memory by default and require no configuration to do so. Each instance of these types has its own cache, and two credential instances never share an in-memory cache.
### Persistent token caching
> Only azidentity v1.5.0-beta versions support persistent token caching
*Persistent disk token caching* is an opt-in feature in the Azure Identity library. The feature allows apps to cache access tokens in an encrypted, persistent storage mechanism. As indicated in the following table, the storage mechanism differs across operating systems.
Some credential types support opt-in persistent token caching (see [the below table](#credentials-supporting-token-caching) for a list). This feature enables credentials to store and retrieve tokens across process executions, so an application doesn't need to authenticate every time it runs.
| Operating system | Storage mechanism |
|------------------|---------------------------------------|
| Linux | kernel key retention service (keyctl) |
| macOS | Keychain |
| Windows | DPAPI |
Persistent caches are encrypted at rest using a mechanism that depends on the operating system:
By default the token cache will protect any data which is persisted using the user data protection APIs available on the current platform.
However, there are cases where no data protection is available, and applications may choose to allow storing the token cache in an unencrypted state by setting `TokenCachePersistenceOptions.AllowUnencryptedStorage` to `true`. This allows a credential to fall back to unencrypted storage if it can't encrypt the cache. However, we do not recommend using this storage method due to its significantly lower security measures. In addition, tokens are not encrypted solely to the current user, which could potentially allow unauthorized access to the cache by individuals with machine access.
| Operating system | Encryption facility |
| ---------------- | ---------------------------------------------- |
| Linux | kernel key retention service (keyctl) |
| macOS | Keychain (requires cgo and native build tools) |
| Windows | Data Protection API (DPAPI) |
With persistent disk token caching enabled, the library first determines if a valid access token for the requested resource is already stored in the persistent cache. If a valid token is found, it's returned to the app without the need to make another request to Entra ID. Additionally, the tokens are preserved across app runs, which:
- Makes the app more resilient to failures.
- Ensures the app can continue to function during an Entra ID outage or disruption.
- Avoids having to prompt users to authenticate each time the process is restarted.
>IMPORTANT! The token cache contains sensitive data and **MUST** be protected to prevent compromising accounts. All application decisions regarding the persistence of the token cache must consider that a breach of its content will fully compromise all the accounts it contains.
#### Example code
See the [package documentation](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.6.0-beta.2#pkg-overview) for example code demonstrating how to configure persistent caching and access cached data.
Persistent caching requires encryption. When the required encryption facility is unuseable, or the application is running on an unsupported OS, the persistent cache constructor returns an error. This doesn't mean that authentication is impossible, only that credentials can't persist authentication data and the application will need to reauthenticate the next time it runs. See the package documentation for examples showing how to configure persistent caching and access cached data for [users][user_example] and [service principals][sp_example].
### Credentials supporting token caching
The following table indicates the state of in-memory and persistent caching in each credential type.
**Note:** In-memory caching is activated by default. Persistent token caching needs to be enabled as shown in [this example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.5.0-beta.1#example-package-PersistentCache).
**Note:** in-memory caching is enabled by default for every type supporting it. Persistent token caching must be enabled explicitly. See the [package documentation][user_example] for an example showing how to do this for credential types authenticating users. For types that authenticate service principals, set the `Cache` field on the constructor's options as shown in [this example][sp_example].
| Credential | In-memory token caching | Persistent token caching |
|--------------------------------|---------------------------------------------------------------------|--------------------------|
| ------------------------------ | ------------------------------------------------------------------- | ------------------------ |
| `AzureCLICredential` | Not Supported | Not Supported |
| `AzureDeveloperCLICredential` | Not Supported | Not Supported |
| `AzurePipelinesCredential` | Supported | Supported |
@ -66,6 +49,9 @@ The following table indicates the state of in-memory and persistent caching in e
| `EnvironmentCredential` | Supported | Not Supported |
| `InteractiveBrowserCredential` | Supported | Supported |
| `ManagedIdentityCredential` | Supported | Not Supported |
| `OnBehalfOfCredential` | Supported | Supported |
| `OnBehalfOfCredential` | Supported | Not Supported |
| `UsernamePasswordCredential` | Supported | Supported |
| `WorkloadIdentityCredential` | Supported | Supported |
[sp_example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentServicePrincipalAuthentication
[user_example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentUserAuthentication

@ -8,6 +8,7 @@ This troubleshooting guide covers failure investigation techniques, common error
- [Permission issues](#permission-issues)
- [Find relevant information in errors](#find-relevant-information-in-errors)
- [Enable and configure logging](#enable-and-configure-logging)
- [Troubleshoot persistent token caching issues](#troubleshoot-persistent-token-caching-issues)
- [Troubleshoot AzureCLICredential authentication issues](#troubleshoot-azureclicredential-authentication-issues)
- [Troubleshoot AzureDeveloperCLICredential authentication issues](#troubleshoot-azuredeveloperclicredential-authentication-issues)
- [Troubleshoot AzurePipelinesCredential authentication issues](#troubleshoot-azurepipelinescredential-authentication-issues)
@ -234,7 +235,30 @@ azd auth token --output json --scope https://management.core.windows.net/.defaul
|---|---|---|
| AADSTS900023: Specified tenant identifier 'some tenant ID' is neither a valid DNS name, nor a valid external domain.|The `tenantID` argument to `NewAzurePipelinesCredential` is incorrect| Verify the tenant ID. It must identify the tenant of the user-assigned managed identity or service principal configured for the service connection.|
| No service connection found with identifier |The `serviceConnectionID` argument to `NewAzurePipelinesCredential` is incorrect| Verify the service connection ID. This parameter refers to the `resourceId` of the Azure Service Connection. It can also be found in the query string of the service connection's configuration in Azure DevOps. [Azure Pipelines documentation](https://learn.microsoft.com/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) has more information about service connections.|
|302 (Found) response from OIDC endpoint|The `systemAccessToken` argument to `NewAzurePipelinesCredential` is incorrect|Check pipeline configuration. This value comes from the predefined variable `System.AccessToken` [as described in Azure Pipelines documentation](https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken).|
|401 (Unauthorized) response from OIDC endpoint|The `systemAccessToken` argument to `NewAzurePipelinesCredential` is incorrect|Check pipeline configuration. This value comes from the predefined variable `System.AccessToken` [as described in Azure Pipelines documentation](https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken).|
## Troubleshoot persistent token caching issues
### macOS
[azidentity/cache](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache) encrypts persistent caches with the system Keychain on macOS. You may see build and runtime errors there because calling the Keychain API requires cgo and macOS prohibits Keychain access in some scenarios.
#### Build errors
Build errors about undefined `accessor` symbols indicate that cgo wasn't enabled. For example:
```
$ GOOS=darwin go build
# github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache
../../go/pkg/mod/github.com/!azure/azure-sdk-for-go/sdk/azidentity/cache@v0.3.0/darwin.go:18:19: undefined: accessor.New
../../go/pkg/mod/github.com/!azure/azure-sdk-for-go/sdk/azidentity/cache@v0.3.0/darwin.go:18:38: undefined: accessor.WithAccount
```
Try `go build` again with `CGO_ENABLED=1`. You may need to install native build tools.
#### Runtime errors
macOS prohibits Keychain access from environments without a GUI such as SSH sessions. If your application calls the persistent cache constructor ([cache.New](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache#New)) from an SSH session on a macOS host, you'll see an error like
`persistent storage isn't available due to error "User interaction is not allowed. (-25308)"`. This doesn't mean authentication is impossible, only that credentials can't persist data and the application must reauthenticate the next time it runs.
## Get additional help

@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go",
"TagPrefix": "go/azidentity",
"Tag": "go/azidentity_087379b475"
"Tag": "go/azidentity_c55452bbf6"
}

@ -18,10 +18,10 @@ import (
var supportedAuthRecordVersions = []string{"1.0"}
// authenticationRecord is non-secret account information about an authenticated user that user credentials such as
// AuthenticationRecord is non-secret account information about an authenticated user that user credentials such as
// [DeviceCodeCredential] and [InteractiveBrowserCredential] can use to access previously cached authentication
// data. Call these credentials' Authenticate method to get an authenticationRecord for a user.
type authenticationRecord struct {
// data. Call these credentials' Authenticate method to get an AuthenticationRecord for a user.
type AuthenticationRecord struct {
// Authority is the URL of the authority that issued the token.
Authority string `json:"authority"`
@ -42,11 +42,11 @@ type authenticationRecord struct {
}
// UnmarshalJSON implements json.Unmarshaler for AuthenticationRecord
func (a *authenticationRecord) UnmarshalJSON(b []byte) error {
func (a *AuthenticationRecord) UnmarshalJSON(b []byte) error {
// Default unmarshaling is fine but we want to return an error if the record's version isn't supported i.e., we
// want to inspect the unmarshalled values before deciding whether to return an error. Unmarshaling a formally
// different type enables this by assigning all the fields without recursing into this method.
type r authenticationRecord
type r AuthenticationRecord
err := json.Unmarshal(b, (*r)(a))
if err != nil {
return err
@ -63,7 +63,7 @@ func (a *authenticationRecord) UnmarshalJSON(b []byte) error {
}
// account returns the AuthenticationRecord as an MSAL Account. The account is zero-valued when the AuthenticationRecord is zero-valued.
func (a *authenticationRecord) account() public.Account {
func (a *AuthenticationRecord) account() public.Account {
return public.Account{
Environment: a.Authority,
HomeAccountID: a.HomeAccountID,
@ -71,10 +71,10 @@ func (a *authenticationRecord) account() public.Account {
}
}
func newAuthenticationRecord(ar public.AuthResult) (authenticationRecord, error) {
func newAuthenticationRecord(ar public.AuthResult) (AuthenticationRecord, error) {
u, err := url.Parse(ar.IDToken.Issuer)
if err != nil {
return authenticationRecord{}, fmt.Errorf("Authenticate expected a URL issuer but got %q", ar.IDToken.Issuer)
return AuthenticationRecord{}, fmt.Errorf("Authenticate expected a URL issuer but got %q", ar.IDToken.Issuer)
}
tenant := ar.IDToken.TenantID
if tenant == "" {
@ -84,7 +84,7 @@ func newAuthenticationRecord(ar public.AuthResult) (authenticationRecord, error)
if username == "" {
username = ar.IDToken.UPN
}
return authenticationRecord{
return AuthenticationRecord{
Authority: fmt.Sprintf("%s://%s", u.Scheme, u.Host),
ClientID: ar.IDToken.Audience,
HomeAccountID: ar.Account.HomeAccountID,

@ -42,6 +42,8 @@ const (
developerSignOnClientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
defaultSuffix = "/.default"
scopeLogFmt = "%s.GetToken() acquired a token for scope %q"
traceNamespace = "Microsoft.Entra"
traceOpGetToken = "GetToken"
traceOpAuthenticate = "Authenticate"
@ -53,8 +55,14 @@ var (
errInvalidTenantID = errors.New("invalid tenantID. You can locate your tenantID by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names")
)
// tokenCachePersistenceOptions contains options for persistent token caching
type tokenCachePersistenceOptions = internal.TokenCachePersistenceOptions
// Cache represents a persistent cache that makes authentication data available across processes.
// Construct one with [github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache.New]. This package's
// [persistent user authentication example] shows how to use a persistent cache to reuse user
// logins across application runs. For service principal credential types such as
// [ClientCertificateCredential], simply set the Cache field on the credential options.
//
// [persistent user authentication example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentUserAuthentication
type Cache = internal.Cache
// setAuthorityHost initializes the authority host for credentials. Precedence is:
// 1. cloud.Configuration.ActiveDirectoryAuthorityHost value set by user
@ -97,7 +105,16 @@ func resolveAdditionalTenants(tenants []string) []string {
return cp
}
// resolveTenant returns the correct tenant for a token request
// resolveTenant returns the correct tenant for a token request, or "" when the calling credential doesn't
// have an explicitly configured tenant and the caller didn't specify a tenant for the token request.
//
// - defaultTenant: tenant set when constructing the credential, if any. "" is valid for credentials
// having an optional or implicit tenant such as dev tool and interactive user credentials. Those
// default to the tool's configured tenant or the user's home tenant, respectively.
// - specified: tenant specified for this token request i.e., TokenRequestOptions.TenantID. May be "".
// - credName: name of the calling credential type; for error messages
// - additionalTenants: optional allow list of tenants the credential may acquire tokens from in
// addition to defaultTenant i.e., the credential's AdditionallyAllowedTenants option
func resolveTenant(defaultTenant, specified, credName string, additionalTenants []string) (string, error) {
if specified == "" || specified == defaultTenant {
return defaultTenant, nil
@ -113,6 +130,17 @@ func resolveTenant(defaultTenant, specified, credName string, additionalTenants
return specified, nil
}
}
if len(additionalTenants) == 0 {
switch defaultTenant {
case "", organizationsTenantID:
// The application didn't specify a tenant or allow list when constructing the credential. Allow the
// tenant specified for this token request because we have nothing to compare it to (i.e., it vacuously
// satisfies the credential's configuration); don't know whether the application is multitenant; and
// don't want to return an error in the common case that the specified tenant matches the credential's
// default tenant determined elsewhere e.g., in some dev tool's configuration.
return specified, nil
}
}
return "", fmt.Errorf(`%s isn't configured to acquire tokens for tenant %q. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add "*" to allow acquiring tokens for any tenant`, credName, specified)
}

@ -30,9 +30,9 @@ type azTokenProvider func(ctx context.Context, scopes []string, tenant, subscrip
// AzureCLICredentialOptions contains optional parameters for AzureCLICredential.
type AzureCLICredentialOptions struct {
// AdditionallyAllowedTenants specifies tenants for which the credential may acquire tokens, in addition
// to TenantID. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the
// logged in account can access.
// AdditionallyAllowedTenants specifies tenants to which the credential may authenticate, in addition to
// TenantID. When TenantID is empty, this option has no effect and the credential will authenticate to
// any requested tenant. Add the wildcard value "*" to allow the credential to authenticate to any tenant.
AdditionallyAllowedTenants []string
// Subscription is the name or ID of a subscription. Set this to acquire tokens for an account other

@ -30,9 +30,9 @@ type azdTokenProvider func(ctx context.Context, scopes []string, tenant string)
// AzureDeveloperCLICredentialOptions contains optional parameters for AzureDeveloperCLICredential.
type AzureDeveloperCLICredentialOptions struct {
// AdditionallyAllowedTenants specifies tenants for which the credential may acquire tokens, in addition
// to TenantID. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant the
// logged in account can access.
// AdditionallyAllowedTenants specifies tenants to which the credential may authenticate, in addition to
// TenantID. When TenantID is empty, this option has no effect and the credential will authenticate to
// any requested tenant. Add the wildcard value "*" to allow the credential to authenticate to any tenant.
AdditionallyAllowedTenants []string
// TenantID identifies the tenant the credential should authenticate in. Defaults to the azd environment,

@ -20,6 +20,8 @@ const (
credNameAzurePipelines = "AzurePipelinesCredential"
oidcAPIVersion = "7.1"
systemOIDCRequestURI = "SYSTEM_OIDCREQUESTURI"
xMsEdgeRef = "x-msedge-ref"
xVssE2eId = "x-vss-e2eid"
)
// AzurePipelinesCredential authenticates with workload identity federation in an Azure Pipeline. See
@ -40,6 +42,11 @@ type AzurePipelinesCredentialOptions struct {
// application is registered.
AdditionallyAllowedTenants []string
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
@ -81,8 +88,11 @@ func NewAzurePipelinesCredential(tenantID, clientID, serviceConnectionID, system
if options == nil {
options = &AzurePipelinesCredentialOptions{}
}
// these headers are useful to the DevOps team when debugging OIDC error responses
options.ClientOptions.Logging.AllowedHeaders = append(options.ClientOptions.Logging.AllowedHeaders, xMsEdgeRef, xVssE2eId)
caco := ClientAssertionCredentialOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}
@ -108,33 +118,40 @@ func (a *AzurePipelinesCredential) getAssertion(ctx context.Context) (string, er
url := a.oidcURI + "?api-version=" + oidcAPIVersion + "&serviceConnectionId=" + a.connectionID
url, err := runtime.EncodeQueryParams(url)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't encode OIDC URL: "+err.Error(), nil, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't encode OIDC URL: "+err.Error(), nil)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't create OIDC token request: "+err.Error(), nil, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't create OIDC token request: "+err.Error(), nil)
}
req.Header.Set("Authorization", "Bearer "+a.systemAccessToken)
// instruct endpoint to return 401 instead of 302, if the system access token is invalid
req.Header.Set("X-TFS-FedAuthRedirect", "Suppress")
res, err := doForClient(a.cred.client.azClient, req)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't send OIDC token request: "+err.Error(), nil, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't send OIDC token request: "+err.Error(), nil)
}
if res.StatusCode != http.StatusOK {
msg := res.Status + " response from the OIDC endpoint. Check service connection ID and Pipeline configuration"
msg := res.Status + " response from the OIDC endpoint. Check service connection ID and Pipeline configuration."
for _, h := range []string{xMsEdgeRef, xVssE2eId} {
if v := res.Header.Get(h); v != "" {
msg += fmt.Sprintf("\n%s: %s", h, v)
}
}
// include the response because its body, if any, probably contains an error message.
// OK responses aren't included with errors because they probably contain secrets
return "", newAuthenticationFailedError(credNameAzurePipelines, msg, res, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, msg, res)
}
b, err := runtime.Payload(res)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't read OIDC response content: "+err.Error(), nil, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't read OIDC response content: "+err.Error(), nil)
}
var r struct {
OIDCToken string `json:"oidcToken"`
}
err = json.Unmarshal(b, &r)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "unexpected response from OIDC endpoint", nil, nil)
return "", newAuthenticationFailedError(credNameAzurePipelines, "unexpected response from OIDC endpoint", nil)
}
return r.OIDCToken, nil
}

@ -27,7 +27,10 @@ type ChainedTokenCredentialOptions struct {
}
// ChainedTokenCredential links together multiple credentials and tries them sequentially when authenticating. By default,
// it tries all the credentials until one authenticates, after which it always uses that credential.
// it tries all the credentials until one authenticates, after which it always uses that credential. For more information,
// see [ChainedTokenCredential overview].
//
// [ChainedTokenCredential overview]: https://aka.ms/azsdk/go/identity/credential-chains#chainedtokencredential-overview
type ChainedTokenCredential struct {
cond *sync.Cond
iterating bool
@ -46,6 +49,9 @@ func NewChainedTokenCredential(sources []azcore.TokenCredential, options *Chaine
if source == nil { // cannot have a nil credential in the chain or else the application will panic when GetToken() is called on nil
return nil, errors.New("sources cannot contain nil")
}
if mc, ok := source.(*ManagedIdentityCredential); ok {
mc.mic.chained = true
}
}
cp := make([]azcore.TokenCredential, len(sources))
copy(cp, sources)
@ -113,11 +119,19 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token
if err != nil {
// return credentialUnavailableError iff all sources did so; return AuthenticationFailedError otherwise
msg := createChainedErrorMessage(errs)
if errors.As(err, &unavailableErr) {
var authFailedErr *AuthenticationFailedError
switch {
case errors.As(err, &authFailedErr):
err = newAuthenticationFailedError(c.name, msg, authFailedErr.RawResponse)
if af, ok := err.(*AuthenticationFailedError); ok {
// stop Error() printing the response again; it's already in msg
af.omitResponse = true
}
case errors.As(err, &unavailableErr):
err = newCredentialUnavailableError(c.name, msg)
} else {
default:
res := getResponseFromError(err)
err = newAuthenticationFailedError(c.name, msg, res, err)
err = newAuthenticationFailedError(c.name, msg, res)
}
}
return token, err
@ -126,7 +140,7 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token
func createChainedErrorMessage(errs []error) string {
msg := "failed to acquire a token.\nAttempted credentials:"
for _, err := range errs {
msg += fmt.Sprintf("\n\t%s", err.Error())
msg += fmt.Sprintf("\n\t%s", strings.ReplaceAll(err.Error(), "\n", "\n\t\t"))
}
return msg
}

@ -27,15 +27,15 @@ extends:
CloudConfig:
Public:
SubscriptionConfigurations:
- $(sub-config-azure-cloud-test-resources)
- $(sub-config-identity-test-resources)
EnvVars:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
EnableRaceDetector: true
Location: westus2
RunLiveTests: true
ServiceDirectory: azidentity
UsePipelineProxy: false
${{ if endsWith(variables['Build.DefinitionName'], 'weekly') }}:
PersistOidcToken: true
MatrixConfigs:
- Name: managed_identity_matrix
GenerateVMJobs: true

@ -37,14 +37,16 @@ type ClientAssertionCredentialOptions struct {
// application is registered.
AdditionallyAllowedTenants []string
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
}
// NewClientAssertionCredential constructs a ClientAssertionCredential. The getAssertion function must be thread safe. Pass nil for options to accept defaults.
@ -61,10 +63,10 @@ func NewClientAssertionCredential(tenantID, clientID string, getAssertion func(c
},
)
msalOpts := confidentialClientOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
tokenCachePersistenceOptions: options.tokenCachePersistenceOptions,
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}
c, err := newConfidentialClient(tenantID, clientID, credNameAssertion, cred, msalOpts)
if err != nil {

@ -31,6 +31,11 @@ type ClientCertificateCredentialOptions struct {
// application is registered.
AdditionallyAllowedTenants []string
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
@ -41,9 +46,6 @@ type ClientCertificateCredentialOptions struct {
// header of each token request's JWT. This is required for Subject Name/Issuer (SNI) authentication.
// Defaults to False.
SendCertificateChain bool
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
}
// ClientCertificateCredential authenticates a service principal with a certificate.
@ -65,11 +67,11 @@ func NewClientCertificateCredential(tenantID string, clientID string, certs []*x
return nil, err
}
msalOpts := confidentialClientOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
SendX5C: options.SendCertificateChain,
tokenCachePersistenceOptions: options.tokenCachePersistenceOptions,
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
SendX5C: options.SendCertificateChain,
}
c, err := newConfidentialClient(tenantID, clientID, credNameCert, cred, msalOpts)
if err != nil {

@ -32,8 +32,10 @@ type ClientSecretCredentialOptions struct {
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
}
// ClientSecretCredential authenticates an application with a client secret.
@ -51,10 +53,10 @@ func NewClientSecretCredential(tenantID string, clientID string, clientSecret st
return nil, err
}
msalOpts := confidentialClientOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
tokenCachePersistenceOptions: options.tokenCachePersistenceOptions,
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}
c, err := newConfidentialClient(tenantID, clientID, credNameSecret, cred, msalOpts)
if err != nil {

@ -29,8 +29,8 @@ type confidentialClientOptions struct {
AdditionallyAllowedTenants []string
// Assertion for on-behalf-of authentication
Assertion string
Cache Cache
DisableInstanceDiscovery, SendX5C bool
tokenCachePersistenceOptions *tokenCachePersistenceOptions
}
// confidentialClient wraps the MSAL confidential client
@ -107,15 +107,15 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
}
}
if err != nil {
// We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code.
// We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError.
var unavailableErr credentialUnavailable
if !errors.As(err, &unavailableErr) {
res := getResponseFromError(err)
err = newAuthenticationFailedError(c.name, err.Error(), res, err)
var (
authFailedErr *AuthenticationFailedError
unavailableErr credentialUnavailable
)
if !(errors.As(err, &unavailableErr) || errors.As(err, &authFailedErr)) {
err = newAuthenticationFailedErrorFromMSAL(c.name, err)
}
} else {
msg := fmt.Sprintf("%s.GetToken() acquired a token for scope %q", c.name, strings.Join(ar.GrantedScopes, ", "))
msg := fmt.Sprintf(scopeLogFmt, c.name, strings.Join(ar.GrantedScopes, ", "))
log.Write(EventAuthentication, msg)
}
return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err
@ -145,7 +145,7 @@ func (c *confidentialClient) client(tro policy.TokenRequestOptions) (msalConfide
}
func (c *confidentialClient) newMSALClient(enableCAE bool) (msalConfidentialClient, error) {
cache, err := internal.NewCache(c.opts.tokenCachePersistenceOptions, enableCAE)
cache, err := internal.ExportReplace(c.opts.Cache, enableCAE)
if err != nil {
return nil, err
}

@ -23,23 +23,30 @@ type DefaultAzureCredentialOptions struct {
// to credential types that authenticate via external tools such as the Azure CLI.
azcore.ClientOptions
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire tokens. Add
// the wildcard value "*" to allow the credential to acquire tokens for any tenant. This value can also be
// set as a semicolon delimited list of tenants in the environment variable AZURE_ADDITIONALLY_ALLOWED_TENANTS.
// AdditionallyAllowedTenants specifies tenants to which the credential may authenticate, in addition to
// TenantID. When TenantID is empty, this option has no effect and the credential will authenticate to
// any requested tenant. Add the wildcard value "*" to allow the credential to authenticate to any tenant.
// This value can also be set as a semicolon delimited list of tenants in the environment variable
// AZURE_ADDITIONALLY_ALLOWED_TENANTS.
AdditionallyAllowedTenants []string
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
// TenantID sets the default tenant for authentication via the Azure CLI and workload identity.
TenantID string
}
// DefaultAzureCredential is a default credential chain for applications that will deploy to Azure.
// It combines credentials suitable for deployment with credentials suitable for local development.
// It attempts to authenticate with each of these credential types, in the following order, stopping
// when one provides a token:
// DefaultAzureCredential simplifies authentication while developing applications that deploy to Azure by
// combining credentials used in Azure hosting environments and credentials used in local development. In
// production, it's better to use a specific credential type so authentication is more predictable and easier
// to debug. For more information, see [DefaultAzureCredential overview].
//
// DefaultAzureCredential attempts to authenticate with each of these credential types, in the following order,
// stopping when one provides a token:
//
// - [EnvironmentCredential]
// - [WorkloadIdentityCredential], if environment variable configuration is set by the Azure workload
@ -52,6 +59,8 @@ type DefaultAzureCredentialOptions struct {
// Consult the documentation for these credential types for more information on how they authenticate.
// Once a credential has successfully authenticated, DefaultAzureCredential will use that credential for
// every subsequent authentication.
//
// [DefaultAzureCredential overview]: https://aka.ms/azsdk/go/identity/credential-chains#defaultazurecredential-overview
type DefaultAzureCredential struct {
chain *ChainedTokenCredential
}

@ -21,22 +21,31 @@ const credNameDeviceCode = "DeviceCodeCredential"
type DeviceCodeCredentialOptions struct {
azcore.ClientOptions
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire
// tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant.
// AdditionallyAllowedTenants specifies tenants to which the credential may authenticate, in addition to
// TenantID. When TenantID is empty, this option has no effect and the credential will authenticate to
// any requested tenant. Add the wildcard value "*" to allow the credential to authenticate to any tenant.
AdditionallyAllowedTenants []string
// authenticationRecord returned by a call to a credential's Authenticate method. Set this option
// AuthenticationRecord returned by a call to a credential's Authenticate method. Set this option
// to enable the credential to use data from a previous authentication.
authenticationRecord authenticationRecord
// ClientID is the ID of the application users will authenticate to.
// Defaults to the ID of an Azure development application.
AuthenticationRecord AuthenticationRecord
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// ClientID is the ID of the application to which users will authenticate. When not set, users
// will authenticate to an Azure development application, which isn't recommended for production
// scenarios. In production, developers should instead register their applications and assign
// appropriate roles. See https://aka.ms/azsdk/identity/AppRegistrationAndRoleAssignment for more
// information.
ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// DisableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, GetToken will return AuthenticationRequiredError when user interaction is necessary
// to acquire a token.
disableAutomaticAuthentication bool
DisableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
@ -49,9 +58,6 @@ type DeviceCodeCredentialOptions struct {
// applications.
TenantID string
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
// UserPrompt controls how the credential presents authentication instructions. The credential calls
// this function with authentication details when it receives a device code. By default, the credential
// prints these details to stdout.
@ -101,12 +107,12 @@ func NewDeviceCodeCredential(options *DeviceCodeCredentialOptions) (*DeviceCodeC
cp.init()
msalOpts := publicClientOptions{
AdditionallyAllowedTenants: cp.AdditionallyAllowedTenants,
Cache: cp.Cache,
ClientOptions: cp.ClientOptions,
DeviceCodePrompt: cp.UserPrompt,
DisableAutomaticAuthentication: cp.disableAutomaticAuthentication,
DisableAutomaticAuthentication: cp.DisableAutomaticAuthentication,
DisableInstanceDiscovery: cp.DisableInstanceDiscovery,
Record: cp.authenticationRecord,
TokenCachePersistenceOptions: cp.tokenCachePersistenceOptions,
Record: cp.AuthenticationRecord,
}
c, err := newPublicClient(cp.TenantID, cp.ClientID, credNameDeviceCode, msalOpts)
if err != nil {
@ -116,8 +122,9 @@ func NewDeviceCodeCredential(options *DeviceCodeCredentialOptions) (*DeviceCodeC
return &DeviceCodeCredential{client: c}, nil
}
// Authenticate a user via the device code flow. Subsequent calls to GetToken will automatically use the returned AuthenticationRecord.
func (c *DeviceCodeCredential) authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (authenticationRecord, error) {
// Authenticate prompts a user to log in via the device code flow. Subsequent
// GetToken calls will automatically use the returned AuthenticationRecord.
func (c *DeviceCodeCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (AuthenticationRecord, error) {
var err error
ctx, endSpan := runtime.StartSpan(ctx, credNameDeviceCode+"."+traceOpAuthenticate, c.client.azClient.Tracer(), nil)
defer func() { endSpan(err) }()

@ -38,18 +38,30 @@ type AuthenticationFailedError struct {
// RawResponse is the HTTP response motivating the error, if available.
RawResponse *http.Response
credType string
message string
err error
credType, message string
omitResponse bool
}
func newAuthenticationFailedError(credType string, message string, resp *http.Response, err error) error {
return &AuthenticationFailedError{credType: credType, message: message, RawResponse: resp, err: err}
func newAuthenticationFailedError(credType, message string, resp *http.Response) error {
return &AuthenticationFailedError{credType: credType, message: message, RawResponse: resp}
}
// newAuthenticationFailedErrorFromMSAL creates an AuthenticationFailedError from an MSAL error.
// If the error is an MSAL CallErr, the new error includes an HTTP response and not the MSAL error
// message, because that message is redundant given the response. If the original error isn't a
// CallErr, the returned error incorporates its message.
func newAuthenticationFailedErrorFromMSAL(credType string, err error) error {
msg := ""
res := getResponseFromError(err)
if res == nil {
msg = err.Error()
}
return newAuthenticationFailedError(credType, msg, res)
}
// Error implements the error interface. Note that the message contents are not contractual and can change over time.
func (e *AuthenticationFailedError) Error() string {
if e.RawResponse == nil {
if e.RawResponse == nil || e.omitResponse {
return e.credType + ": " + e.message
}
msg := &bytes.Buffer{}
@ -62,7 +74,7 @@ func (e *AuthenticationFailedError) Error() string {
fmt.Fprintln(msg, "Request information not available")
}
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
fmt.Fprintf(msg, "RESPONSE %s\n", e.RawResponse.Status)
fmt.Fprintf(msg, "RESPONSE %d: %s\n", e.RawResponse.StatusCode, e.RawResponse.Status)
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
body, err := runtime.Payload(e.RawResponse)
switch {
@ -109,17 +121,17 @@ func (*AuthenticationFailedError) NonRetriable() {
var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)
// authenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
// AuthenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
// because the credential requires user interaction and is configured not to request it automatically.
type authenticationRequiredError struct {
type AuthenticationRequiredError struct {
credentialUnavailableError
// TokenRequestOptions for the required token. Pass this to the credential's Authenticate method.
TokenRequestOptions policy.TokenRequestOptions
}
func newauthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
return &authenticationRequiredError{
func newAuthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
return &AuthenticationRequiredError{
credentialUnavailableError: credentialUnavailableError{
credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively",
},
@ -128,8 +140,8 @@ func newauthenticationRequiredError(credType string, tro policy.TokenRequestOpti
}
var (
_ credentialUnavailable = (*authenticationRequiredError)(nil)
_ errorinfo.NonRetriable = (*authenticationRequiredError)(nil)
_ credentialUnavailable = (*AuthenticationRequiredError)(nil)
_ errorinfo.NonRetriable = (*AuthenticationRequiredError)(nil)
)
type credentialUnavailable interface {

@ -20,22 +20,31 @@ const credNameBrowser = "InteractiveBrowserCredential"
type InteractiveBrowserCredentialOptions struct {
azcore.ClientOptions
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire
// tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant.
// AdditionallyAllowedTenants specifies tenants to which the credential may authenticate, in addition to
// TenantID. When TenantID is empty, this option has no effect and the credential will authenticate to
// any requested tenant. Add the wildcard value "*" to allow the credential to authenticate to any tenant.
AdditionallyAllowedTenants []string
// authenticationRecord returned by a call to a credential's Authenticate method. Set this option
// AuthenticationRecord returned by a call to a credential's Authenticate method. Set this option
// to enable the credential to use data from a previous authentication.
authenticationRecord authenticationRecord
// ClientID is the ID of the application users will authenticate to.
// Defaults to the ID of an Azure development application.
AuthenticationRecord AuthenticationRecord
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// ClientID is the ID of the application to which users will authenticate. When not set, users
// will authenticate to an Azure development application, which isn't recommended for production
// scenarios. In production, developers should instead register their applications and assign
// appropriate roles. See https://aka.ms/azsdk/identity/AppRegistrationAndRoleAssignment for more
// information.
ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// DisableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, GetToken will return AuthenticationRequiredError when user interaction is necessary
// to acquire a token.
disableAutomaticAuthentication bool
DisableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
@ -54,9 +63,6 @@ type InteractiveBrowserCredentialOptions struct {
// TenantID is the Microsoft Entra tenant the credential authenticates in. Defaults to the
// "organizations" tenant, which can authenticate work and school accounts.
TenantID string
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
}
func (o *InteractiveBrowserCredentialOptions) init() {
@ -82,13 +88,13 @@ func NewInteractiveBrowserCredential(options *InteractiveBrowserCredentialOption
cp.init()
msalOpts := publicClientOptions{
AdditionallyAllowedTenants: cp.AdditionallyAllowedTenants,
Cache: cp.Cache,
ClientOptions: cp.ClientOptions,
DisableAutomaticAuthentication: cp.disableAutomaticAuthentication,
DisableAutomaticAuthentication: cp.DisableAutomaticAuthentication,
DisableInstanceDiscovery: cp.DisableInstanceDiscovery,
LoginHint: cp.LoginHint,
Record: cp.authenticationRecord,
Record: cp.AuthenticationRecord,
RedirectURL: cp.RedirectURL,
TokenCachePersistenceOptions: cp.tokenCachePersistenceOptions,
}
c, err := newPublicClient(cp.TenantID, cp.ClientID, credNameBrowser, msalOpts)
if err != nil {
@ -97,8 +103,9 @@ func NewInteractiveBrowserCredential(options *InteractiveBrowserCredentialOption
return &InteractiveBrowserCredential{client: c}, nil
}
// Authenticate a user via the default browser. Subsequent calls to GetToken will automatically use the returned AuthenticationRecord.
func (c *InteractiveBrowserCredential) authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (authenticationRecord, error) {
// Authenticate opens the default browser so a user can log in. Subsequent
// GetToken calls will automatically use the returned AuthenticationRecord.
func (c *InteractiveBrowserCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (AuthenticationRecord, error) {
var err error
ctx, endSpan := runtime.StartSpan(ctx, credNameBrowser+"."+traceOpAuthenticate, c.client.azClient.Tracer(), nil)
defer func() { endSpan(err) }()

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package internal
import (
"sync"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
)
// Cache represents a persistent cache that makes authentication data available across processes.
// Construct one with [github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache.New]. This package's
// [persistent user authentication example] shows how to use a persistent cache to reuse user
// logins across application runs. For service principal credential types such as
// [ClientCertificateCredential], simply set the Cache field on the credential options.
//
// [persistent user authentication example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentUserAuthentication
type Cache struct {
// impl is a pointer so a Cache can carry persistent state across copies
impl *impl
}
// impl is a Cache's private implementation
type impl struct {
// factory constructs storage implementations
factory func(bool) (cache.ExportReplace, error)
// cae and noCAE are previously constructed storage implementations. CAE
// and non-CAE tokens must be stored separately because MSAL's cache doesn't
// observe token claims. If a single storage implementation held both kinds
// of tokens, it could create a reauthentication or error loop by returning
// a non-CAE token lacking a required claim.
cae, noCAE cache.ExportReplace
// mu synchronizes around cae and noCAE
mu *sync.RWMutex
}
func (i *impl) exportReplace(cae bool) (cache.ExportReplace, error) {
if i == nil {
// zero-value Cache: return a nil ExportReplace and MSAL will cache in memory
return nil, nil
}
var (
err error
xr cache.ExportReplace
)
i.mu.RLock()
xr = i.cae
if !cae {
xr = i.noCAE
}
i.mu.RUnlock()
if xr != nil {
return xr, nil
}
i.mu.Lock()
defer i.mu.Unlock()
if cae {
if i.cae == nil {
if xr, err = i.factory(cae); err == nil {
i.cae = xr
}
}
return i.cae, err
}
if i.noCAE == nil {
if xr, err = i.factory(cae); err == nil {
i.noCAE = xr
}
}
return i.noCAE, err
}
// NewCache is the constructor for Cache. It takes a factory instead of an instance
// because it doesn't know whether the Cache will store both CAE and non-CAE tokens.
func NewCache(factory func(cae bool) (cache.ExportReplace, error)) Cache {
return Cache{&impl{factory: factory, mu: &sync.RWMutex{}}}
}
// ExportReplace returns an implementation satisfying MSAL's ExportReplace interface.
// It's a function instead of a method on Cache so packages in azidentity and
// azidentity/cache can call it while applications can't. "cae" declares whether the
// caller intends this implementation to store CAE tokens.
func ExportReplace(c Cache, cae bool) (cache.ExportReplace, error) {
return c.impl.exportReplace(cae)
}

@ -1,18 +0,0 @@
//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package internal
// TokenCachePersistenceOptions contains options for persistent token caching
type TokenCachePersistenceOptions struct {
// AllowUnencryptedStorage controls whether the cache should fall back to storing its data in plain text
// when encryption isn't possible. Setting this true doesn't disable encryption. The cache always attempts
// encryption before falling back to plaintext storage.
AllowUnencryptedStorage bool
// Name identifies the cache. Set this to isolate data from other applications.
Name string
}

@ -1,31 +0,0 @@
//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package internal
import (
"errors"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
)
var errMissingImport = errors.New("import github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache to enable persistent caching")
// NewCache constructs a persistent token cache when "o" isn't nil. Applications that intend to
// use a persistent cache must first import the cache module, which will replace this function
// with a platform-specific implementation.
var NewCache = func(o *TokenCachePersistenceOptions, enableCAE bool) (cache.ExportReplace, error) {
if o == nil {
return nil, nil
}
return nil, errMissingImport
}
// CacheFilePath returns the path to the cache file for the given name.
// Defining it in this package makes it available to azidentity tests.
var CacheFilePath = func(name string) (string, error) {
return "", errMissingImport
}

@ -65,6 +65,9 @@ type managedIdentityClient struct {
id ManagedIDKind
msiType msiType
probeIMDS bool
// chained indicates whether the client is part of a credential chain. If true, the client will return
// a credentialUnavailableError instead of an AuthenticationFailedError for an unexpected IMDS response.
chained bool
}
// arcKeyDirectory returns the directory expected to contain Azure Arc keys
@ -143,6 +146,9 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
if endpoint, ok := os.LookupEnv(identityEndpoint); ok {
if _, ok := os.LookupEnv(identityHeader); ok {
if _, ok := os.LookupEnv(identityServerThumbprint); ok {
if options.ID != nil {
return nil, errors.New("the Service Fabric API doesn't support specifying a user-assigned identity at runtime. The identity is determined by cluster resource configuration. See https://aka.ms/servicefabricmi")
}
env = "Service Fabric"
c.endpoint = endpoint
c.msiType = msiTypeServiceFabric
@ -152,6 +158,9 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
c.msiType = msiTypeAppService
}
} else if _, ok := os.LookupEnv(arcIMDSEndpoint); ok {
if options.ID != nil {
return nil, errors.New("the Azure Arc API doesn't support specifying a user-assigned managed identity at runtime")
}
env = "Azure Arc"
c.endpoint = endpoint
c.msiType = msiTypeAzureArc
@ -159,9 +168,15 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
} else if endpoint, ok := os.LookupEnv(msiEndpoint); ok {
c.endpoint = endpoint
if _, ok := os.LookupEnv(msiSecret); ok {
if options.ID != nil && options.ID.idKind() != miClientID {
return nil, errors.New("the Azure ML API supports specifying a user-assigned managed identity by client ID only")
}
env = "Azure ML"
c.msiType = msiTypeAzureML
} else {
if options.ID != nil {
return nil, errors.New("the Cloud Shell API doesn't support user-assigned managed identities")
}
env = "Cloud Shell"
c.msiType = msiTypeCloudShell
}
@ -203,14 +218,15 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
// no need to synchronize around this value because it's true only when DefaultAzureCredential constructed the client,
// and in that case ChainedTokenCredential.GetToken synchronizes goroutines that would execute this block
if c.probeIMDS {
// send a malformed request (no Metadata header) to IMDS to determine whether the endpoint is available
cx, cancel := context.WithTimeout(ctx, imdsProbeTimeout)
defer cancel()
cx = policy.WithRetryOptions(cx, policy.RetryOptions{MaxRetries: -1})
req, err := azruntime.NewRequest(cx, http.MethodGet, c.endpoint)
if err == nil {
_, err = c.azClient.Pipeline().Do(req)
}
if err != nil {
return azcore.AccessToken{}, fmt.Errorf("failed to create IMDS probe request: %s", err)
}
if _, err = c.azClient.Pipeline().Do(req); err != nil {
msg := err.Error()
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
msg = "managed identity timed out. See https://aka.ms/azsdk/go/identity/troubleshoot#dac for more information"
@ -228,18 +244,26 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
resp, err := c.azClient.Pipeline().Do(msg)
if err != nil {
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil, err)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil)
}
if azruntime.HasStatusCode(resp, http.StatusOK, http.StatusCreated) {
return c.createAccessToken(resp)
tk, err := c.createAccessToken(resp)
if err != nil && c.chained && c.msiType == msiTypeIMDS {
// failure to unmarshal a 2xx implies the response is from something other than IMDS such as a proxy listening at
// the same address. Return a credentialUnavailableError so credential chains continue to their next credential
err = newCredentialUnavailableError(credNameManagedIdentity, err.Error())
}
return tk, err
}
if c.msiType == msiTypeIMDS {
switch resp.StatusCode {
case http.StatusBadRequest:
if id != nil {
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp, nil)
// return authenticationFailedError, halting any encompassing credential chain,
// because the explicit user-assigned identity implies the developer expected this to work
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp)
}
msg := "failed to authenticate a system assigned identity"
if body, err := azruntime.Payload(resp); err == nil && len(body) > 0 {
@ -254,9 +278,16 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body)))
}
}
if c.chained {
// the response may be from something other than IMDS, for example a proxy returning
// 404. Return credentialUnavailableError so credential chains continue to their
// next credential, include the response in the error message to help debugging
err = newAuthenticationFailedError(credNameManagedIdentity, "", resp)
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, err.Error())
}
}
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "authentication failed", resp, nil)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "", resp)
}
func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.AccessToken, error) {
@ -268,7 +299,7 @@ func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.Ac
ExpiresOn interface{} `json:"expires_on,omitempty"` // the value returned in this field varies between a number and a date string
}{}
if err := azruntime.UnmarshalAsJSON(res, &value); err != nil {
return azcore.AccessToken{}, fmt.Errorf("internal AccessToken: %v", err)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "Unexpected response content", res)
}
if value.ExpiresIn != "" {
expiresIn, err := json.Number(value.ExpiresIn).Int64()
@ -284,10 +315,10 @@ func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.Ac
if expiresOn, err := strconv.Atoi(v); err == nil {
return azcore.AccessToken{Token: value.Token, ExpiresOn: time.Unix(int64(expiresOn), 0).UTC()}, nil
}
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "unexpected expires_on value: "+v, res, nil)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "unexpected expires_on value: "+v, res)
default:
msg := fmt.Sprintf("unsupported type received in expires_on: %T, %v", v, v)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, msg, res, nil)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, msg, res)
}
}
@ -302,15 +333,15 @@ func (c *managedIdentityClient) createAuthRequest(ctx context.Context, id Manage
key, err := c.getAzureArcSecretKey(ctx, scopes)
if err != nil {
msg := fmt.Sprintf("failed to retreive secret key from the identity endpoint: %v", err)
return nil, newAuthenticationFailedError(credNameManagedIdentity, msg, nil, err)
return nil, newAuthenticationFailedError(credNameManagedIdentity, msg, nil)
}
return c.createAzureArcAuthRequest(ctx, id, scopes, key)
return c.createAzureArcAuthRequest(ctx, scopes, key)
case msiTypeAzureML:
return c.createAzureMLAuthRequest(ctx, id, scopes)
case msiTypeServiceFabric:
return c.createServiceFabricAuthRequest(ctx, id, scopes)
return c.createServiceFabricAuthRequest(ctx, scopes)
case msiTypeCloudShell:
return c.createCloudShellAuthRequest(ctx, id, scopes)
return c.createCloudShellAuthRequest(ctx, scopes)
default:
return nil, newCredentialUnavailableError(credNameManagedIdentity, "managed identity isn't supported in this environment")
}
@ -323,13 +354,16 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id Ma
}
request.Raw().Header.Set(headerMetadata, "true")
q := request.Raw().URL.Query()
q.Add("api-version", imdsAPIVersion)
q.Add("resource", strings.Join(scopes, " "))
q.Set("api-version", imdsAPIVersion)
q.Set("resource", strings.Join(scopes, " "))
if id != nil {
if id.idKind() == miResourceID {
q.Add(msiResID, id.String())
} else {
q.Add(qpClientID, id.String())
switch id.idKind() {
case miClientID:
q.Set(qpClientID, id.String())
case miObjectID:
q.Set("object_id", id.String())
case miResourceID:
q.Set(msiResID, id.String())
}
}
request.Raw().URL.RawQuery = q.Encode()
@ -343,13 +377,16 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
}
request.Raw().Header.Set("X-IDENTITY-HEADER", os.Getenv(identityHeader))
q := request.Raw().URL.Query()
q.Add("api-version", "2019-08-01")
q.Add("resource", scopes[0])
q.Set("api-version", "2019-08-01")
q.Set("resource", scopes[0])
if id != nil {
if id.idKind() == miResourceID {
q.Add(miResID, id.String())
} else {
q.Add(qpClientID, id.String())
switch id.idKind() {
case miClientID:
q.Set(qpClientID, id.String())
case miObjectID:
q.Set("principal_id", id.String())
case miResourceID:
q.Set(miResID, id.String())
}
}
request.Raw().URL.RawQuery = q.Encode()
@ -363,23 +400,24 @@ func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id
}
request.Raw().Header.Set("secret", os.Getenv(msiSecret))
q := request.Raw().URL.Query()
q.Add("api-version", "2017-09-01")
q.Add("resource", strings.Join(scopes, " "))
q.Add("clientid", os.Getenv(defaultIdentityClientID))
q.Set("api-version", "2017-09-01")
q.Set("resource", strings.Join(scopes, " "))
q.Set("clientid", os.Getenv(defaultIdentityClientID))
if id != nil {
if id.idKind() == miResourceID {
log.Write(EventAuthentication, "WARNING: Azure ML doesn't support specifying a managed identity by resource ID")
q.Set("clientid", "")
q.Set(miResID, id.String())
} else {
switch id.idKind() {
case miClientID:
q.Set("clientid", id.String())
case miObjectID:
return nil, newAuthenticationFailedError(credNameManagedIdentity, "Azure ML doesn't support specifying a managed identity by object ID", nil)
case miResourceID:
return nil, newAuthenticationFailedError(credNameManagedIdentity, "Azure ML doesn't support specifying a managed identity by resource ID", nil)
}
}
request.Raw().URL.RawQuery = q.Encode()
return request, nil
}
func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, scopes []string) (*policy.Request, error) {
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
@ -387,16 +425,8 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
q := request.Raw().URL.Query()
request.Raw().Header.Set("Accept", "application/json")
request.Raw().Header.Set("Secret", os.Getenv(identityHeader))
q.Add("api-version", serviceFabricAPIVersion)
q.Add("resource", strings.Join(scopes, " "))
if id != nil {
log.Write(EventAuthentication, "WARNING: Service Fabric doesn't support selecting a user-assigned identity at runtime")
if id.idKind() == miResourceID {
q.Add(miResID, id.String())
} else {
q.Add(qpClientID, id.String())
}
}
q.Set("api-version", serviceFabricAPIVersion)
q.Set("resource", strings.Join(scopes, " "))
request.Raw().URL.RawQuery = q.Encode()
return request, nil
}
@ -409,8 +439,8 @@ func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resour
}
request.Raw().Header.Set(headerMetadata, "true")
q := request.Raw().URL.Query()
q.Add("api-version", azureArcAPIVersion)
q.Add("resource", strings.Join(resources, " "))
q.Set("api-version", azureArcAPIVersion)
q.Set("resource", strings.Join(resources, " "))
request.Raw().URL.RawQuery = q.Encode()
// send the initial request to get the short-lived secret key
response, err := c.azClient.Pipeline().Do(request)
@ -421,39 +451,39 @@ func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resour
// of the secret key file. Any other status code indicates an error in the request.
if response.StatusCode != 401 {
msg := fmt.Sprintf("expected a 401 response, received %d", response.StatusCode)
return "", newAuthenticationFailedError(credNameManagedIdentity, msg, response, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, msg, response)
}
header := response.Header.Get("WWW-Authenticate")
if len(header) == 0 {
return "", newAuthenticationFailedError(credNameManagedIdentity, "HIMDS response has no WWW-Authenticate header", nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, "HIMDS response has no WWW-Authenticate header", nil)
}
// the WWW-Authenticate header is expected in the following format: Basic realm=/some/file/path.key
_, p, found := strings.Cut(header, "=")
if !found {
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected WWW-Authenticate header from HIMDS: "+header, nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected WWW-Authenticate header from HIMDS: "+header, nil)
}
expected, err := arcKeyDirectory()
if err != nil {
return "", err
}
if filepath.Dir(p) != expected || !strings.HasSuffix(p, ".key") {
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected file path from HIMDS service: "+p, nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected file path from HIMDS service: "+p, nil)
}
f, err := os.Stat(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not stat %q: %v", p, err), nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not stat %q: %v", p, err), nil)
}
if s := f.Size(); s > 4096 {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("key is too large (%d bytes)", s), nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("key is too large (%d bytes)", s), nil)
}
key, err := os.ReadFile(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not read %q: %v", p, err), nil, nil)
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not read %q: %v", p, err), nil)
}
return string(key), nil
}
func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, id ManagedIDKind, resources []string, key string) (*policy.Request, error) {
func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, resources []string, key string) (*policy.Request, error) {
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
@ -461,21 +491,13 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, i
request.Raw().Header.Set(headerMetadata, "true")
request.Raw().Header.Set("Authorization", fmt.Sprintf("Basic %s", key))
q := request.Raw().URL.Query()
q.Add("api-version", azureArcAPIVersion)
q.Add("resource", strings.Join(resources, " "))
if id != nil {
log.Write(EventAuthentication, "WARNING: Azure Arc doesn't support user-assigned managed identities")
if id.idKind() == miResourceID {
q.Add(miResID, id.String())
} else {
q.Add(qpClientID, id.String())
}
}
q.Set("api-version", azureArcAPIVersion)
q.Set("resource", strings.Join(resources, " "))
request.Raw().URL.RawQuery = q.Encode()
return request, nil
}
func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, scopes []string) (*policy.Request, error) {
request, err := azruntime.NewRequest(ctx, http.MethodPost, c.endpoint)
if err != nil {
return nil, err
@ -488,14 +510,5 @@ func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context,
if err := request.SetBody(body, "application/x-www-form-urlencoded"); err != nil {
return nil, err
}
if id != nil {
log.Write(EventAuthentication, "WARNING: Cloud Shell doesn't support user-assigned managed identities")
q := request.Raw().URL.Query()
if id.idKind() == miResourceID {
q.Add(miResID, id.String())
} else {
q.Add(qpClientID, id.String())
}
}
return request, nil
}

@ -22,8 +22,9 @@ const credNameManagedIdentity = "ManagedIdentityCredential"
type managedIdentityIDKind int
const (
miClientID managedIdentityIDKind = 0
miResourceID managedIdentityIDKind = 1
miClientID managedIdentityIDKind = iota
miObjectID
miResourceID
)
// ManagedIDKind identifies the ID of a managed identity as either a client or resource ID
@ -32,7 +33,12 @@ type ManagedIDKind interface {
idKind() managedIdentityIDKind
}
// ClientID is the client ID of a user-assigned managed identity.
// ClientID is the client ID of a user-assigned managed identity. [NewManagedIdentityCredential]
// returns an error when a ClientID is specified on the following platforms:
//
// - Azure Arc
// - Cloud Shell
// - Service Fabric
type ClientID string
func (ClientID) idKind() managedIdentityIDKind {
@ -44,7 +50,31 @@ func (c ClientID) String() string {
return string(c)
}
// ResourceID is the resource ID of a user-assigned managed identity.
// ObjectID is the object ID of a user-assigned managed identity. [NewManagedIdentityCredential]
// returns an error when an ObjectID is specified on the following platforms:
//
// - Azure Arc
// - Azure ML
// - Cloud Shell
// - Service Fabric
type ObjectID string
func (ObjectID) idKind() managedIdentityIDKind {
return miObjectID
}
// String returns the string value of the ID.
func (o ObjectID) String() string {
return string(o)
}
// ResourceID is the resource ID of a user-assigned managed identity. [NewManagedIdentityCredential]
// returns an error when a ResourceID is specified on the following platforms:
//
// - Azure Arc
// - Azure ML
// - Cloud Shell
// - Service Fabric
type ResourceID string
func (ResourceID) idKind() managedIdentityIDKind {
@ -60,9 +90,10 @@ func (r ResourceID) String() string {
type ManagedIdentityCredentialOptions struct {
azcore.ClientOptions
// ID is the ID of a managed identity the credential should authenticate. Set this field to use a specific identity
// instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that
// some platforms don't accept resource IDs.
// ID of a managed identity the credential should authenticate. Set this field to use a specific identity instead of
// the hosting environment's default. The value may be the identity's client, object, or resource ID.
// NewManagedIdentityCredential returns an error when the hosting environment doesn't support user-assigned managed
// identities, or the specified kind of ID.
ID ManagedIDKind
// dac indicates whether the credential is part of DefaultAzureCredential. When true, and the environment doesn't have
@ -73,10 +104,11 @@ type ManagedIdentityCredentialOptions struct {
dac bool
}
// ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities.
// ManagedIdentityCredential authenticates an [Azure managed identity] in any hosting environment supporting managed identities.
// This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a
// user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities:
// https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview
// user-assigned identity.
//
// [Azure managed identity]: https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview
type ManagedIdentityCredential struct {
client *confidentialClient
mic *managedIdentityClient

@ -30,12 +30,12 @@ type publicClientOptions struct {
azcore.ClientOptions
AdditionallyAllowedTenants []string
Cache Cache
DeviceCodePrompt func(context.Context, DeviceCodeMessage) error
DisableAutomaticAuthentication bool
DisableInstanceDiscovery bool
LoginHint, RedirectURL string
Record authenticationRecord
TokenCachePersistenceOptions *tokenCachePersistenceOptions
Record AuthenticationRecord
Username, Password string
}
@ -48,7 +48,7 @@ type publicClient struct {
host string
name string
opts publicClientOptions
record authenticationRecord
record AuthenticationRecord
azClient *azcore.Client
}
@ -107,19 +107,19 @@ func newPublicClient(tenantID, clientID, name string, o publicClientOptions) (*p
}, nil
}
func (p *publicClient) Authenticate(ctx context.Context, tro *policy.TokenRequestOptions) (authenticationRecord, error) {
func (p *publicClient) Authenticate(ctx context.Context, tro *policy.TokenRequestOptions) (AuthenticationRecord, error) {
if tro == nil {
tro = &policy.TokenRequestOptions{}
}
if len(tro.Scopes) == 0 {
if p.defaultScope == nil {
return authenticationRecord{}, errScopeRequired
return AuthenticationRecord{}, errScopeRequired
}
tro.Scopes = p.defaultScope
}
client, mu, err := p.client(*tro)
if err != nil {
return authenticationRecord{}, err
return AuthenticationRecord{}, err
}
mu.Lock()
defer mu.Unlock()
@ -152,14 +152,9 @@ func (p *publicClient) GetToken(ctx context.Context, tro policy.TokenRequestOpti
return p.token(ar, err)
}
if p.opts.DisableAutomaticAuthentication {
return azcore.AccessToken{}, newauthenticationRequiredError(p.name, tro)
return azcore.AccessToken{}, newAuthenticationRequiredError(p.name, tro)
}
at, err := p.reqToken(ctx, client, tro)
if err == nil {
msg := fmt.Sprintf("%s.GetToken() acquired a token for scope %q", p.name, strings.Join(ar.GrantedScopes, ", "))
log.Write(EventAuthentication, msg)
}
return at, err
return p.reqToken(ctx, client, tro)
}
// reqToken requests a token from the MSAL public client. It's separate from GetToken() to enable Authenticate() to bypass the cache.
@ -222,13 +217,13 @@ func (p *publicClient) client(tro policy.TokenRequestOptions) (msalPublicClient,
}
func (p *publicClient) newMSALClient(enableCAE bool) (msalPublicClient, error) {
cache, err := internal.NewCache(p.opts.TokenCachePersistenceOptions, enableCAE)
c, err := internal.ExportReplace(p.opts.Cache, enableCAE)
if err != nil {
return nil, err
}
o := []public.Option{
public.WithAuthority(runtime.JoinPaths(p.host, p.tenantID)),
public.WithCache(cache),
public.WithCache(c),
public.WithHTTPClient(p),
}
if enableCAE {
@ -242,10 +237,11 @@ func (p *publicClient) newMSALClient(enableCAE bool) (msalPublicClient, error) {
func (p *publicClient) token(ar public.AuthResult, err error) (azcore.AccessToken, error) {
if err == nil {
msg := fmt.Sprintf(scopeLogFmt, p.name, strings.Join(ar.GrantedScopes, ", "))
log.Write(EventAuthentication, msg)
p.record, err = newAuthenticationRecord(ar)
} else {
res := getResponseFromError(err)
err = newAuthenticationFailedError(p.name, err.Error(), res, err)
err = newAuthenticationFailedErrorFromMSAL(p.name, err)
}
return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err
}

@ -5,7 +5,27 @@
param (
[hashtable] $AdditionalParameters = @{},
[hashtable] $DeploymentOutputs
[hashtable] $DeploymentOutputs,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $SubscriptionId,
[Parameter(ParameterSetName = 'Provisioner', Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $TenantId,
[Parameter()]
[ValidatePattern('^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$')]
[string] $TestApplicationId,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $Environment,
# Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors).
[Parameter(ValueFromRemainingArguments = $true)]
$RemainingArguments
)
$ErrorActionPreference = 'Stop'
@ -16,14 +36,15 @@ if ($CI) {
Write-Host "Skipping post-provisioning script because resources weren't deployed"
return
}
az login --service-principal -u $DeploymentOutputs['AZIDENTITY_CLIENT_ID'] -p $DeploymentOutputs['AZIDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['AZIDENTITY_TENANT_ID']
az account set --subscription $DeploymentOutputs['AZIDENTITY_SUBSCRIPTION_ID']
az cloud set -n $Environment
az login --federated-token $env:ARM_OIDC_TOKEN --service-principal -t $TenantId -u $TestApplicationId
az account set --subscription $SubscriptionId
}
Write-Host "Building container"
$image = "$($DeploymentOutputs['AZIDENTITY_ACR_LOGIN_SERVER'])/azidentity-managed-id-test"
Set-Content -Path "$PSScriptRoot/Dockerfile" -Value @"
FROM mcr.microsoft.com/oss/go/microsoft/golang:latest as builder
FROM mcr.microsoft.com/oss/go/microsoft/golang:latest AS builder
ENV GOARCH=amd64 GOWORK=off
COPY . /azidentity
WORKDIR /azidentity/testdata/managed-id-test
@ -50,12 +71,17 @@ $aciName = "azidentity-test"
az container create -g $rg -n $aciName --image $image `
--acr-identity $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--assign-identity [system] $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--cpu 1 `
--memory 1.0 `
--os-type Linux `
--role "Storage Blob Data Reader" `
--scope $($DeploymentOutputs['AZIDENTITY_STORAGE_ID']) `
-e AZIDENTITY_STORAGE_NAME=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME']) `
AZIDENTITY_STORAGE_NAME_USER_ASSIGNED=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
FUNCTIONS_CUSTOMHANDLER_PORT=80
AZIDENTITY_STORAGE_NAME_USER_ASSIGNED=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY_OBJECT_ID=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_OBJECT_ID']) `
FUNCTIONS_CUSTOMHANDLER_PORT=80
Write-Host "##vso[task.setvariable variable=AZIDENTITY_ACI_NAME;]$aciName"
# Azure Functions deployment: copy the Windows binary from the Docker image, deploy it in a zip

@ -135,6 +135,14 @@ resource azfunc 'Microsoft.Web/sites@2021-03-01' = if (deployResources) {
name: 'AZIDENTITY_USER_ASSIGNED_IDENTITY'
value: deployResources ? usermgdid.id : null
}
{
name: 'AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID'
value: deployResources ? usermgdid.properties.clientId : null
}
{
name: 'AZIDENTITY_USER_ASSIGNED_IDENTITY_OBJECT_ID'
value: deployResources ? usermgdid.properties.principalId : null
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${deployResources ? sa.name : ''};EndpointSuffix=${deployResources ? environment().suffixes.storage : ''};AccountKey=${deployResources ? sa.listKeys().keys[0].value : ''}'
@ -217,3 +225,4 @@ output AZIDENTITY_STORAGE_NAME_USER_ASSIGNED string = deployResources ? saUserAs
output AZIDENTITY_USER_ASSIGNED_IDENTITY string = deployResources ? usermgdid.id : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID string = deployResources ? usermgdid.properties.clientId : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_NAME string = deployResources ? usermgdid.name : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_OBJECT_ID string = deployResources ? usermgdid.properties.principalId : ''

@ -25,18 +25,20 @@ type UsernamePasswordCredentialOptions struct {
// application is registered.
AdditionallyAllowedTenants []string
// authenticationRecord returned by a call to a credential's Authenticate method. Set this option
// AuthenticationRecord returned by a call to a credential's Authenticate method. Set this option
// to enable the credential to use data from a previous authentication.
authenticationRecord authenticationRecord
AuthenticationRecord AuthenticationRecord
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
// tokenCachePersistenceOptions enables persistent token caching when not nil.
tokenCachePersistenceOptions *tokenCachePersistenceOptions
}
// UsernamePasswordCredential authenticates a user with a password. Microsoft doesn't recommend this kind of authentication,
@ -54,13 +56,13 @@ func NewUsernamePasswordCredential(tenantID string, clientID string, username st
options = &UsernamePasswordCredentialOptions{}
}
opts := publicClientOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
Password: password,
Record: options.authenticationRecord,
TokenCachePersistenceOptions: options.tokenCachePersistenceOptions,
Username: username,
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
Password: password,
Record: options.AuthenticationRecord,
Username: username,
}
c, err := newPublicClient(tenantID, clientID, credNameUserPassword, opts)
if err != nil {
@ -70,7 +72,7 @@ func NewUsernamePasswordCredential(tenantID string, clientID string, username st
}
// Authenticate the user. Subsequent calls to GetToken will automatically use the returned AuthenticationRecord.
func (c *UsernamePasswordCredential) authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (authenticationRecord, error) {
func (c *UsernamePasswordCredential) Authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (AuthenticationRecord, error) {
var err error
ctx, endSpan := runtime.StartSpan(ctx, credNameUserPassword+"."+traceOpAuthenticate, c.client.azClient.Tracer(), nil)
defer func() { endSpan(err) }()

@ -14,5 +14,5 @@ const (
module = "github.com/Azure/azure-sdk-for-go/sdk/" + component
// Version is the semantic version (see http://semver.org) of this module.
version = "v1.7.0"
version = "v1.8.1"
)

@ -39,15 +39,24 @@ type WorkloadIdentityCredentialOptions struct {
// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the
// application is registered.
AdditionallyAllowedTenants []string
// Cache is a persistent cache the credential will use to store the tokens it acquires, making
// them available to other processes and credential instances. The default, zero value means the
// credential will store tokens in memory and not share them with any other credential instance.
Cache Cache
// ClientID of the service principal. Defaults to the value of the environment variable AZURE_CLIENT_ID.
ClientID string
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
// TenantID of the service principal. Defaults to the value of the environment variable AZURE_TENANT_ID.
TenantID string
// TokenFilePath is the path of a file containing a Kubernetes service account token. Defaults to the value of the
// environment variable AZURE_FEDERATED_TOKEN_FILE.
TokenFilePath string
@ -81,6 +90,7 @@ func NewWorkloadIdentityCredential(options *WorkloadIdentityCredentialOptions) (
w := WorkloadIdentityCredential{file: file, mtx: &sync.RWMutex{}}
caco := ClientAssertionCredentialOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
Cache: options.Cache,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}

@ -18,6 +18,8 @@ import (
"encoding/pem"
"errors"
"fmt"
"os"
"strings"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base"
@ -315,16 +317,21 @@ func New(authority, clientID string, cred Credential, options ...Option) (Client
if err != nil {
return Client{}, err
}
autoEnabledRegion := os.Getenv("MSAL_FORCE_REGION")
opts := clientOptions{
authority: authority,
// if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache
disableInstanceDiscovery: cred.tokenProvider != nil,
httpClient: shared.DefaultClient,
azureRegion: autoEnabledRegion,
}
for _, o := range options {
o(&opts)
}
if strings.EqualFold(opts.azureRegion, "DisableMsalForceRegion") {
opts.azureRegion = ""
}
baseOpts := []base.Option{
base.WithCacheAccessor(opts.accessor),
base.WithClientCapabilities(opts.capabilities),

@ -89,8 +89,23 @@ type AuthResult struct {
ExpiresOn time.Time
GrantedScopes []string
DeclinedScopes []string
Metadata AuthResultMetadata
}
// AuthResultMetadata which contains meta data for the AuthResult
type AuthResultMetadata struct {
TokenSource TokenSource
}
type TokenSource int
// These are all the types of token flows.
const (
SourceUnknown TokenSource = 0
IdentityProvider TokenSource = 1
Cache TokenSource = 2
)
// AuthResultFromStorage creates an AuthResult from a storage token response (which is generated from the cache).
func AuthResultFromStorage(storageTokenResponse storage.TokenResponse) (AuthResult, error) {
if err := storageTokenResponse.AccessToken.Validate(); err != nil {
@ -109,7 +124,17 @@ func AuthResultFromStorage(storageTokenResponse storage.TokenResponse) (AuthResu
return AuthResult{}, fmt.Errorf("problem decoding JWT token: %w", err)
}
}
return AuthResult{account, idToken, accessToken, storageTokenResponse.AccessToken.ExpiresOn.T, grantedScopes, nil}, nil
return AuthResult{
Account: account,
IDToken: idToken,
AccessToken: accessToken,
ExpiresOn: storageTokenResponse.AccessToken.ExpiresOn.T,
GrantedScopes: grantedScopes,
DeclinedScopes: nil,
Metadata: AuthResultMetadata{
TokenSource: Cache,
},
}, nil
}
// NewAuthResult creates an AuthResult.
@ -123,6 +148,9 @@ func NewAuthResult(tokenResponse accesstokens.TokenResponse, account shared.Acco
AccessToken: tokenResponse.AccessToken,
ExpiresOn: tokenResponse.ExpiresOn.T,
GrantedScopes: tokenResponse.GrantedScopes.Slice,
Metadata: AuthResultMetadata{
TokenSource: IdentityProvider,
},
}, nil
}

@ -18,10 +18,6 @@ import (
)
const addField = "AdditionalFields"
const (
marshalJSON = "MarshalJSON"
unmarshalJSON = "UnmarshalJSON"
)
var (
leftBrace = []byte("{")[0]
@ -106,48 +102,38 @@ func delimIs(got json.Token, want rune) bool {
// hasMarshalJSON will determine if the value or a pointer to this value has
// the MarshalJSON method.
func hasMarshalJSON(v reflect.Value) bool {
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
_, ok := v.Interface().(json.Marshaler)
return ok
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
} else {
if !v.CanAddr() {
return false
ok := false
if _, ok = v.Interface().(json.Marshaler); !ok {
var i any
if v.Kind() == reflect.Ptr {
i = v.Elem().Interface()
} else if v.CanAddr() {
i = v.Addr().Interface()
}
v = v.Addr()
}
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
_, ok := v.Interface().(json.Marshaler)
return ok
_, ok = i.(json.Marshaler)
}
return false
return ok
}
// callMarshalJSON will call MarshalJSON() method on the value or a pointer to this value.
// This will panic if the method is not defined.
func callMarshalJSON(v reflect.Value) ([]byte, error) {
if method := v.MethodByName(marshalJSON); method.Kind() != reflect.Invalid {
marsh := v.Interface().(json.Marshaler)
if marsh, ok := v.Interface().(json.Marshaler); ok {
return marsh.MarshalJSON()
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if marsh, ok := v.Elem().Interface().(json.Marshaler); ok {
return marsh.MarshalJSON()
}
} else {
if v.CanAddr() {
v = v.Addr()
if marsh, ok := v.Addr().Interface().(json.Marshaler); ok {
return marsh.MarshalJSON()
}
}
}
if method := v.MethodByName(unmarshalJSON); method.Kind() != reflect.Invalid {
marsh := v.Interface().(json.Marshaler)
return marsh.MarshalJSON()
}
panic(fmt.Sprintf("callMarshalJSON called on type %T that does not have MarshalJSON defined", v.Interface()))
}
@ -162,12 +148,8 @@ func hasUnmarshalJSON(v reflect.Value) bool {
v = v.Addr()
}
if method := v.MethodByName(unmarshalJSON); method.Kind() != reflect.Invalid {
_, ok := v.Interface().(json.Unmarshaler)
return ok
}
return false
_, ok := v.Interface().(json.Unmarshaler)
return ok
}
// hasOmitEmpty indicates if the field has instructed us to not output

@ -7,6 +7,7 @@ package local
import (
"context"
"fmt"
"html"
"net"
"net/http"
"strconv"
@ -141,7 +142,7 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
headerErr := q.Get("error")
if headerErr != "" {
desc := q.Get("error_description")
desc := html.EscapeString(q.Get("error_description"))
// Note: It is a little weird we handle some errors by not going to the failPage. If they all should,
// change this to s.error() and make s.error() write the failPage instead of an error code.
_, _ = w.Write([]byte(fmt.Sprintf(failPage, headerErr, desc)))

@ -10,6 +10,8 @@ import (
"io"
"time"
"github.com/google/uuid"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
internalTime "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time"
@ -18,7 +20,6 @@ import (
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs"
"github.com/google/uuid"
)
// ResolveEndpointer contains the methods for resolving authority endpoints.
@ -331,7 +332,7 @@ func (t *Client) DeviceCode(ctx context.Context, authParams authority.AuthParams
func (t *Client) resolveEndpoint(ctx context.Context, authParams *authority.AuthParams, userPrincipalName string) error {
endpoints, err := t.Resolver.ResolveEndpoints(ctx, authParams.AuthorityInfo, userPrincipalName)
if err != nil {
return fmt.Errorf("unable to resolve an endpoint: %s", err)
return fmt.Errorf("unable to resolve an endpoint: %w", err)
}
authParams.Endpoints = endpoints
return nil

@ -23,7 +23,7 @@ import (
const (
authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize"
instanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
aadInstanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
tenantDiscoveryEndpointWithRegion = "https://%s.%s/%s/v2.0/.well-known/openid-configuration"
regionName = "REGION_NAME"
defaultAPIVersion = "2021-10-01"
@ -47,13 +47,12 @@ type jsonCaller interface {
}
var aadTrustedHostList = map[string]bool{
"login.windows.net": true, // Microsoft Azure Worldwide - Used in validation scenarios where host is not this list
"login.chinacloudapi.cn": true, // Microsoft Azure China
"login.microsoftonline.de": true, // Microsoft Azure Blackforest
"login-us.microsoftonline.com": true, // Microsoft Azure US Government - Legacy
"login.microsoftonline.us": true, // Microsoft Azure US Government
"login.microsoftonline.com": true, // Microsoft Azure Worldwide
"login.cloudgovapi.us": true, // Microsoft Azure US Government
"login.windows.net": true, // Microsoft Azure Worldwide - Used in validation scenarios where host is not this list
"login.partner.microsoftonline.cn": true, // Microsoft Azure China
"login.microsoftonline.de": true, // Microsoft Azure Blackforest
"login-us.microsoftonline.com": true, // Microsoft Azure US Government - Legacy
"login.microsoftonline.us": true, // Microsoft Azure US Government
"login.microsoftonline.com": true, // Microsoft Azure Worldwide
}
// TrustedHost checks if an AAD host is trusted/valid.
@ -137,8 +136,12 @@ const (
const (
AAD = "MSSTS"
ADFS = "ADFS"
DSTS = "DSTS"
)
// DSTSTenant is referenced throughout multiple files, let us use a const in case we ever need to change it.
const DSTSTenant = "7a433bfc-2514-4697-b467-e0933190487f"
// AuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
type AuthenticationScheme interface {
// Extra parameters that are added to the request to the /token endpoint.
@ -236,23 +239,26 @@ func NewAuthParams(clientID string, authorityInfo Info) AuthParams {
// - the client is configured to authenticate only Microsoft accounts via the "consumers" endpoint
// - the resulting authority URL is invalid
func (p AuthParams) WithTenant(ID string) (AuthParams, error) {
switch ID {
case "", p.AuthorityInfo.Tenant:
// keep the default tenant because the caller didn't override it
if ID == "" || ID == p.AuthorityInfo.Tenant {
return p, nil
case "common", "consumers", "organizations":
if p.AuthorityInfo.AuthorityType == AAD {
}
var authority string
switch p.AuthorityInfo.AuthorityType {
case AAD:
if ID == "common" || ID == "consumers" || ID == "organizations" {
return p, fmt.Errorf(`tenant ID must be a specific tenant, not "%s"`, ID)
}
// else we'll return a better error below
}
if p.AuthorityInfo.AuthorityType != AAD {
return p, errors.New("the authority doesn't support tenants")
}
if p.AuthorityInfo.Tenant == "consumers" {
return p, errors.New(`client is configured to authenticate only personal Microsoft accounts, via the "consumers" endpoint`)
if p.AuthorityInfo.Tenant == "consumers" {
return p, errors.New(`client is configured to authenticate only personal Microsoft accounts, via the "consumers" endpoint`)
}
authority = "https://" + path.Join(p.AuthorityInfo.Host, ID)
case ADFS:
return p, errors.New("ADFS authority doesn't support tenants")
case DSTS:
return p, errors.New("dSTS authority doesn't support tenants")
}
authority := "https://" + path.Join(p.AuthorityInfo.Host, ID)
info, err := NewInfoFromAuthorityURI(authority, p.AuthorityInfo.ValidateAuthority, p.AuthorityInfo.InstanceDiscoveryDisabled)
if err == nil {
info.Region = p.AuthorityInfo.Region
@ -344,44 +350,57 @@ type Info struct {
Host string
CanonicalAuthorityURI string
AuthorityType string
UserRealmURIPrefix string
ValidateAuthority bool
Tenant string
Region string
InstanceDiscoveryDisabled bool
}
func firstPathSegment(u *url.URL) (string, error) {
pathParts := strings.Split(u.EscapedPath(), "/")
if len(pathParts) >= 2 {
return pathParts[1], nil
}
return "", errors.New(`authority must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
}
// NewInfoFromAuthorityURI creates an AuthorityInfo instance from the authority URL provided.
func NewInfoFromAuthorityURI(authority string, validateAuthority bool, instanceDiscoveryDisabled bool) (Info, error) {
u, err := url.Parse(strings.ToLower(authority))
if err != nil || u.Scheme != "https" {
return Info{}, errors.New(`authority must be an https URL such as "https://login.microsoftonline.com/<your tenant>"`)
cannonicalAuthority := authority
// suffix authority with / if it doesn't have one
if !strings.HasSuffix(cannonicalAuthority, "/") {
cannonicalAuthority += "/"
}
tenant, err := firstPathSegment(u)
u, err := url.Parse(strings.ToLower(cannonicalAuthority))
if err != nil {
return Info{}, err
return Info{}, fmt.Errorf("couldn't parse authority url: %w", err)
}
if u.Scheme != "https" {
return Info{}, errors.New("authority url scheme must be https")
}
pathParts := strings.Split(u.EscapedPath(), "/")
if len(pathParts) < 3 {
return Info{}, errors.New(`authority must be an URL such as "https://login.microsoftonline.com/<your tenant>"`)
}
authorityType := AAD
if tenant == "adfs" {
tenant := pathParts[1]
switch tenant {
case "adfs":
authorityType = ADFS
case "dstsv2":
if len(pathParts) != 4 {
return Info{}, fmt.Errorf("dSTS authority must be an https URL such as https://<authority>/dstsv2/%s", DSTSTenant)
}
if pathParts[2] != DSTSTenant {
return Info{}, fmt.Errorf("dSTS authority only accepts a single tenant %q", DSTSTenant)
}
authorityType = DSTS
tenant = DSTSTenant
}
// u.Host includes the port, if any, which is required for private cloud deployments
return Info{
Host: u.Host,
CanonicalAuthorityURI: fmt.Sprintf("https://%v/%v/", u.Host, tenant),
CanonicalAuthorityURI: cannonicalAuthority,
AuthorityType: authorityType,
UserRealmURIPrefix: fmt.Sprintf("https://%v/common/userrealm/", u.Hostname()),
ValidateAuthority: validateAuthority,
Tenant: tenant,
InstanceDiscoveryDisabled: instanceDiscoveryDisabled,
@ -525,7 +544,7 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I
discoveryHost = authorityInfo.Host
}
endpoint := fmt.Sprintf(instanceDiscoveryEndpoint, discoveryHost)
endpoint := fmt.Sprintf(aadInstanceDiscoveryEndpoint, discoveryHost)
err = c.Comm.JSONCall(ctx, endpoint, http.Header{}, qv, nil, &resp)
}
return resp, err
@ -543,17 +562,19 @@ func detectRegion(ctx context.Context) string {
client := http.Client{
Timeout: time.Duration(2 * time.Second),
}
req, _ := http.NewRequest("GET", imdsEndpoint, nil)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imdsEndpoint, nil)
req.Header.Set("Metadata", "true")
resp, err := client.Do(req)
if err == nil {
defer resp.Body.Close()
}
// If the request times out or there is an error, it is retried once
if err != nil || resp.StatusCode != 200 {
if err != nil || resp.StatusCode != http.StatusOK {
resp, err = client.Do(req)
if err != nil || resp.StatusCode != 200 {
if err != nil || resp.StatusCode != http.StatusOK {
return ""
}
}
defer resp.Body.Close()
response, err := io.ReadAll(resp.Body)
if err != nil {
return ""

@ -18,10 +18,11 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
customJSON "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version"
"github.com/google/uuid"
)
// HTTPClient represents an HTTP client.
@ -70,15 +71,13 @@ func (c *Client) JSONCall(ctx context.Context, endpoint string, headers http.Hea
unmarshal = customJSON.Unmarshal
}
u, err := url.Parse(endpoint)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s?%s", endpoint, qv.Encode()), nil)
if err != nil {
return fmt.Errorf("could not parse path URL(%s): %w", endpoint, err)
return fmt.Errorf("could not create request: %w", err)
}
u.RawQuery = qv.Encode()
addStdHeaders(headers)
req := &http.Request{Method: http.MethodGet, URL: u, Header: headers}
req.Header = headers
if body != nil {
// Note: In case your wondering why we are not gzip encoding....

@ -18,9 +18,6 @@ import (
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
)
// ADFS is an active directory federation service authority type.
const ADFS = "ADFS"
type cacheEntry struct {
Endpoints authority.Endpoints
ValidForDomainsInList map[string]bool
@ -51,7 +48,7 @@ func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo
return endpoints, nil
}
endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo, userPrincipalName)
endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo)
if err != nil {
return authority.Endpoints{}, err
}
@ -83,7 +80,7 @@ func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPr
defer m.mu.Unlock()
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
if authorityInfo.AuthorityType == ADFS {
if authorityInfo.AuthorityType == authority.ADFS {
domain, err := adfsDomainFromUpn(userPrincipalName)
if err == nil {
if _, ok := cacheEntry.ValidForDomainsInList[domain]; ok {
@ -102,7 +99,7 @@ func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, use
updatedCacheEntry := createcacheEntry(endpoints)
if authorityInfo.AuthorityType == ADFS {
if authorityInfo.AuthorityType == authority.ADFS {
// Since we're here, we've made a call to the backend. We want to ensure we're caching
// the latest values from the server.
if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok {
@ -119,9 +116,12 @@ func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, use
m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry
}
func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (string, error) {
if authorityInfo.Tenant == "adfs" {
func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, error) {
if authorityInfo.AuthorityType == authority.ADFS {
return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), nil
} else if authorityInfo.AuthorityType == authority.DSTS {
return fmt.Sprintf("https://%s/dstsv2/%s/v2.0/.well-known/openid-configuration", authorityInfo.Host, authority.DSTSTenant), nil
} else if authorityInfo.ValidateAuthority && !authority.TrustedHost(authorityInfo.Host) {
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
if err != nil {
@ -134,7 +134,6 @@ func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, aut
return "", err
}
return resp.TenantDiscoveryEndpoint, nil
}
return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil

File diff suppressed because it is too large Load Diff

@ -1,5 +1,65 @@
# Change Log
## [v1.132.0] - 2024-12-17
- #764 - @greeshmapill - APPS-9365: Add bitbucket source to App Spec
## [v1.131.1] - 2024-12-10
- #762 - @imaskm - Updated list ipv6 response
## [v1.131.0] - 2024-11-25
- #760 - @jvasilevsky - LBAAS: add ipv6 field to loadbalancer model
- #759 - @imaskm - Add reserved ipv6 changes as Beta
- #758 - @dvigueras - Add Rules field to create Databases with Firewall Rules
- #751 - @blesswinsamuel - APPS-9766 Add method to restart apps
## [v1.130.0] - 2024-11-14
- #755 - @vsharma6855 - Add Missing Database Configs for Postgresql and MYSQL
- #754 - @blesswinsamuel - APPS-9858 Add method to obtain websocket URL to get console access into components
## [v1.129.0] - 2024-11-06
- #752 - @andrewsomething - Support maps in Stringify
- #749 - @loosla - [droplets]: add droplet backup policies
- #730 - @rak16 - DOCR-1201: Add new RegistriesService to support methods for multiple-registry open beta
- #748 - @andrewsomething - Support Droplet GPU information
## [v1.128.0] - 2024-10-24
- #746 - @blesswinsamuel - Add archive field to AppSpec to archive/restore apps
- #745 - @asaha2 - Add load balancer monitoring endpoints
- #744 - @asaha2 - Adjust delete dangerous
- #743 - @asaha2 - Introduce droplet autoscale godo methods
- #740 - @blesswinsamuel - Add maintenance field to AppSpec to enable/disable maintenance mode
- #739 - @markusthoemmes - Add protocol to AppSpec and pending to detect responses
## [v1.127.0] - 2024-10-18
- #737 - @loosla - [databases]: change Opensearch ism_history_max_docs type to int64 to …
- #735 - @loosla - [databases]: add a missing field to Opensearch advanced configuration
- #729 - @loosla - [databases]: add support for Opensearch advanced configuration
## [v1.126.0] - 2024-09-25
- #732 - @gottwald - DOKS: add custom CIDR fields
- #727 - @loosla - [databases]: add support for Kafka advanced configuration
## [v1.125.0] - 2024-09-17
- #726 - @loosla - [databases]: add support for MongoDB advanced configuration
- #724 - @andrewsomething - Bump go version to 1.22
- #723 - @jauderho - Update Go dependencies and remove replace statements
## [v1.124.0] - 2024-09-10
- #721 - @vsharma6855 - [DBAAS] | Add API endpoint for applying cluster patches
## [v1.123.0] - 2024-09-06
- #719 - @andrewsomething - apps: mark ListTiers and GetTier as deprecated
## [v1.122.0] - 2024-09-04
- #717 - @danaelhe - DB: Fix Logsink Attribute Types

@ -285,10 +285,11 @@ const (
// AppFunctionsSpec struct for AppFunctionsSpec
type AppFunctionsSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// An optional path to the working directory to use for the build. Must be relative to the root of the repo.
SourceDir string `json:"source_dir,omitempty"`
// A list of environment variables made available to the component.
@ -365,11 +366,12 @@ type AppIngressSpecRuleStringMatch struct {
// AppJobSpec struct for AppJobSpec
type AppJobSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
@ -462,6 +464,14 @@ type AppLogDestinationSpecPapertrail struct {
Endpoint string `json:"endpoint"`
}
// AppMaintenanceSpec struct for AppMaintenanceSpec
type AppMaintenanceSpec struct {
// Indicates whether maintenance mode should be enabled for the app.
Enabled bool `json:"enabled,omitempty"`
// Indicates whether the app should be archived. Setting this to true implies that enabled is set to true. Note that this feature is currently in closed beta.
Archive bool `json:"archive,omitempty"`
}
// AppRouteSpec struct for AppRouteSpec
type AppRouteSpec struct {
// (Deprecated) An HTTP path prefix. Paths must start with / and must be unique across all components within an app.
@ -473,11 +483,12 @@ type AppRouteSpec struct {
// AppServiceSpec struct for AppServiceSpec
type AppServiceSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
@ -495,7 +506,8 @@ type AppServiceSpec struct {
InstanceCount int64 `json:"instance_count,omitempty"`
Autoscaling *AppAutoscalingSpec `json:"autoscaling,omitempty"`
// The internal port on which this service's run command will listen. Default: 8080 If there is not an environment variable with the name `PORT`, one will be automatically added with its value set to the value of this field.
HTTPPort int64 `json:"http_port,omitempty"`
HTTPPort int64 `json:"http_port,omitempty"`
Protocol ServingProtocol `json:"protocol,omitempty"`
// (Deprecated) A list of HTTP routes that should be routed to this component.
Routes []*AppRouteSpec `json:"routes,omitempty"`
HealthCheck *AppServiceSpecHealthCheck `json:"health_check,omitempty"`
@ -559,19 +571,21 @@ type AppSpec struct {
// A list of environment variables made available to all components in the app.
Envs []*AppVariableDefinition `json:"envs,omitempty"`
// A list of alerts which apply to the app.
Alerts []*AppAlertSpec `json:"alerts,omitempty"`
Ingress *AppIngressSpec `json:"ingress,omitempty"`
Egress *AppEgressSpec `json:"egress,omitempty"`
Features []string `json:"features,omitempty"`
Alerts []*AppAlertSpec `json:"alerts,omitempty"`
Ingress *AppIngressSpec `json:"ingress,omitempty"`
Egress *AppEgressSpec `json:"egress,omitempty"`
Features []string `json:"features,omitempty"`
Maintenance *AppMaintenanceSpec `json:"maintenance,omitempty"`
}
// AppStaticSiteSpec struct for AppStaticSiteSpec
type AppStaticSiteSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
@ -607,11 +621,12 @@ type AppVariableDefinition struct {
// AppWorkerSpec struct for AppWorkerSpec
type AppWorkerSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
Image *ImageSourceSpec `json:"image,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
@ -641,6 +656,13 @@ type AppWorkerSpecTermination struct {
GracePeriodSeconds int32 `json:"grace_period_seconds,omitempty"`
}
// BitbucketSourceSpec struct for BitbucketSourceSpec
type BitbucketSourceSpec struct {
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
DeployOnPush bool `json:"deploy_on_push,omitempty"`
}
// Buildpack struct for Buildpack
type Buildpack struct {
// The ID of the buildpack.
@ -692,12 +714,13 @@ type DeploymentCauseDetailsDOCRPush struct {
// DeploymentCauseDetailsGitPush struct for DeploymentCauseDetailsGitPush
type DeploymentCauseDetailsGitPush struct {
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Username string `json:"username,omitempty"`
CommitAuthor string `json:"commit_author,omitempty"`
CommitSHA string `json:"commit_sha,omitempty"`
CommitMessage string `json:"commit_message,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
Username string `json:"username,omitempty"`
CommitAuthor string `json:"commit_author,omitempty"`
CommitSHA string `json:"commit_sha,omitempty"`
CommitMessage string `json:"commit_message,omitempty"`
}
// AppCORSPolicy struct for AppCORSPolicy
@ -901,9 +924,10 @@ type DeploymentWorker struct {
// DetectRequest struct for DetectRequest
type DetectRequest struct {
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
GitLab *GitLabSourceSpec `json:"gitlab,omitempty"`
Bitbucket *BitbucketSourceSpec `json:"bitbucket,omitempty"`
// An optional commit hash to use instead of the branch specified in the source spec.
CommitSHA string `json:"commit_sha,omitempty"`
// An optional path to the working directory for the detection process.
@ -917,6 +941,8 @@ type DetectResponse struct {
TemplateFound bool `json:"template_found,omitempty"`
TemplateValid bool `json:"template_valid,omitempty"`
TemplateError string `json:"template_error,omitempty"`
// Whether or not the underlying detection is still pending. If true, the request can be retried as-is until this field is false and the response contains the detection result.
Pending bool `json:"pending,omitempty"`
}
// DetectResponseComponent struct for DetectResponseComponent
@ -991,6 +1017,7 @@ const (
DeploymentCauseDetailsDigitalOceanUserActionName_RollbackApp DeploymentCauseDetailsDigitalOceanUserActionName = "ROLLBACK_APP"
DeploymentCauseDetailsDigitalOceanUserActionName_RevertAppRollback DeploymentCauseDetailsDigitalOceanUserActionName = "REVERT_APP_ROLLBACK"
DeploymentCauseDetailsDigitalOceanUserActionName_UpgradeBuildpack DeploymentCauseDetailsDigitalOceanUserActionName = "UPGRADE_BUILDPACK"
DeploymentCauseDetailsDigitalOceanUserActionName_Restart DeploymentCauseDetailsDigitalOceanUserActionName = "RESTART"
)
// AppDomain struct for AppDomain
@ -1252,6 +1279,15 @@ type ResetDatabasePasswordResponse struct {
Deployment *Deployment `json:"deployment,omitempty"`
}
// ServingProtocol - HTTP: The app is serving the HTTP protocol. Default. - HTTP2: The app is serving the HTTP/2 protocol. Currently, this needs to be implemented in the service by serving HTTP/2 with prior knowledge.
type ServingProtocol string
// List of ServingProtocol
const (
SERVINGPROTOCOL_HTTP ServingProtocol = "HTTP"
SERVINGPROTOCOL_HTTP2 ServingProtocol = "HTTP2"
)
// AppStringMatch struct for AppStringMatch
type AppStringMatch struct {
// Exact string match. Only 1 of `exact`, `prefix`, or `regex` must be set.

@ -35,11 +35,13 @@ type AppsService interface {
Delete(ctx context.Context, appID string) (*Response, error)
Propose(ctx context.Context, propose *AppProposeRequest) (*AppProposeResponse, *Response, error)
Restart(ctx context.Context, appID string, opts *AppRestartRequest) (*Deployment, *Response, error)
GetDeployment(ctx context.Context, appID, deploymentID string) (*Deployment, *Response, error)
ListDeployments(ctx context.Context, appID string, opts *ListOptions) ([]*Deployment, *Response, error)
CreateDeployment(ctx context.Context, appID string, create ...*DeploymentCreateRequest) (*Deployment, *Response, error)
GetLogs(ctx context.Context, appID, deploymentID, component string, logType AppLogType, follow bool, tailLines int) (*AppLogs, *Response, error)
GetExec(ctx context.Context, appID, deploymentID, component string) (*AppExec, *Response, error)
ListRegions(ctx context.Context) ([]*AppRegion, *Response, error)
@ -77,6 +79,11 @@ type AppLogs struct {
HistoricURLs []string `json:"historic_urls"`
}
// AppExec represents the websocket URL used for sending/receiving console input and output.
type AppExec struct {
URL string `json:"url"`
}
// AppUpdateRequest represents a request to update an app.
type AppUpdateRequest struct {
Spec *AppSpec `json:"spec"`
@ -89,6 +96,11 @@ type DeploymentCreateRequest struct {
ForceBuild bool `json:"force_build"`
}
// AppRestartRequest represents a request to restart an app.
type AppRestartRequest struct {
Components []string `json:"components"`
}
// AlertDestinationUpdateRequest represents a request to update alert destinations.
type AlertDestinationUpdateRequest struct {
Emails []string `json:"emails"`
@ -279,6 +291,22 @@ func (s *AppsServiceOp) Propose(ctx context.Context, propose *AppProposeRequest)
return res, resp, nil
}
// Restart restarts an app.
func (s *AppsServiceOp) Restart(ctx context.Context, appID string, opts *AppRestartRequest) (*Deployment, *Response, error) {
path := fmt.Sprintf("%s/%s/restart", appsBasePath, appID)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, opts)
if err != nil {
return nil, nil, err
}
root := new(deploymentRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Deployment, resp, nil
}
// GetDeployment gets an app deployment.
func (s *AppsServiceOp) GetDeployment(ctx context.Context, appID, deploymentID string) (*Deployment, *Response, error) {
path := fmt.Sprintf("%s/%s/deployments/%s", appsBasePath, appID, deploymentID)
@ -368,6 +396,27 @@ func (s *AppsServiceOp) GetLogs(ctx context.Context, appID, deploymentID, compon
return logs, resp, nil
}
// GetExec retrieves the websocket URL used for sending/receiving console input and output.
func (s *AppsServiceOp) GetExec(ctx context.Context, appID, deploymentID, component string) (*AppExec, *Response, error) {
var url string
if deploymentID == "" {
url = fmt.Sprintf("%s/%s/components/%s/exec", appsBasePath, appID, component)
} else {
url = fmt.Sprintf("%s/%s/deployments/%s/components/%s/exec", appsBasePath, appID, deploymentID, component)
}
req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, nil, err
}
logs := new(AppExec)
resp, err := s.client.Do(ctx, req, logs)
if err != nil {
return nil, resp, err
}
return logs, resp, nil
}
// ListRegions lists all regions supported by App Platform.
func (s *AppsServiceOp) ListRegions(ctx context.Context) ([]*AppRegion, *Response, error) {
path := fmt.Sprintf("%s/regions", appsBasePath)
@ -384,6 +433,9 @@ func (s *AppsServiceOp) ListRegions(ctx context.Context) ([]*AppRegion, *Respons
}
// ListTiers lists available app tiers.
//
// Deprecated: The '/v2/apps/tiers' endpoint has been deprecated as app tiers
// are no longer tied to instance sizes. The concept of tiers is being retired.
func (s *AppsServiceOp) ListTiers(ctx context.Context) ([]*AppTier, *Response, error) {
path := fmt.Sprintf("%s/tiers", appsBasePath)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
@ -399,6 +451,9 @@ func (s *AppsServiceOp) ListTiers(ctx context.Context) ([]*AppTier, *Response, e
}
// GetTier retrieves information about a specific app tier.
//
// Deprecated: The '/v2/apps/tiers/{slug}' endpoints have been deprecated as app
// tiers are no longer tied to instance sizes. The concept of tiers is being retired.
func (s *AppsServiceOp) GetTier(ctx context.Context, slug string) (*AppTier, *Response, error) {
path := fmt.Sprintf("%s/tiers/%s", appsBasePath, slug)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
@ -634,6 +689,7 @@ type AppBuildableComponentSpec interface {
GetGit() *GitSourceSpec
GetGitHub() *GitHubSourceSpec
GetGitLab() *GitLabSourceSpec
GetBitbucket() *BitbucketSourceSpec
GetSourceDir() string
@ -676,10 +732,11 @@ type AppRoutableComponentSpec interface {
type AppSourceType string
const (
AppSourceTypeGitHub AppSourceType = "github"
AppSourceTypeGitLab AppSourceType = "gitlab"
AppSourceTypeGit AppSourceType = "git"
AppSourceTypeImage AppSourceType = "image"
AppSourceTypeBitbucket AppSourceType = "bitbucket"
AppSourceTypeGitHub AppSourceType = "github"
AppSourceTypeGitLab AppSourceType = "gitlab"
AppSourceTypeGit AppSourceType = "git"
AppSourceTypeImage AppSourceType = "image"
)
// SourceSpec represents a source.
@ -687,6 +744,11 @@ type SourceSpec interface {
GetType() AppSourceType
}
// GetType returns the Bitbucket source type.
func (s *BitbucketSourceSpec) GetType() AppSourceType {
return AppSourceTypeBitbucket
}
// GetType returns the GitHub source type.
func (s *GitHubSourceSpec) GetType() AppSourceType {
return AppSourceTypeGitHub

@ -805,6 +805,14 @@ func (a *AppFunctionsSpec) GetAlerts() []*AppAlertSpec {
return a.Alerts
}
// GetBitbucket returns the Bitbucket field.
func (a *AppFunctionsSpec) GetBitbucket() *BitbucketSourceSpec {
if a == nil {
return nil
}
return a.Bitbucket
}
// GetCORS returns the CORS field.
func (a *AppFunctionsSpec) GetCORS() *AppCORSPolicy {
if a == nil {
@ -1141,6 +1149,14 @@ func (a *AppJobSpec) GetAlerts() []*AppAlertSpec {
return a.Alerts
}
// GetBitbucket returns the Bitbucket field.
func (a *AppJobSpec) GetBitbucket() *BitbucketSourceSpec {
if a == nil {
return nil
}
return a.Bitbucket
}
// GetBuildCommand returns the BuildCommand field.
func (a *AppJobSpec) GetBuildCommand() string {
if a == nil {
@ -1421,6 +1437,22 @@ func (a *AppLogDestinationSpecPapertrail) GetEndpoint() string {
return a.Endpoint
}
// GetArchive returns the Archive field.
func (a *AppMaintenanceSpec) GetArchive() bool {
if a == nil {
return false
}
return a.Archive
}
// GetEnabled returns the Enabled field.
func (a *AppMaintenanceSpec) GetEnabled() bool {
if a == nil {
return false
}
return a.Enabled
}
// GetAppID returns the AppID field.
func (a *AppProposeRequest) GetAppID() string {
if a == nil {
@ -1629,6 +1661,14 @@ func (a *AppServiceSpec) GetAutoscaling() *AppAutoscalingSpec {
return a.Autoscaling
}
// GetBitbucket returns the Bitbucket field.
func (a *AppServiceSpec) GetBitbucket() *BitbucketSourceSpec {
if a == nil {
return nil
}
return a.Bitbucket
}
// GetBuildCommand returns the BuildCommand field.
func (a *AppServiceSpec) GetBuildCommand() string {
if a == nil {
@ -1757,6 +1797,14 @@ func (a *AppServiceSpec) GetName() string {
return a.Name
}
// GetProtocol returns the Protocol field.
func (a *AppServiceSpec) GetProtocol() ServingProtocol {
if a == nil {
return ""
}
return a.Protocol
}
// GetRoutes returns the Routes field.
func (a *AppServiceSpec) GetRoutes() []*AppRouteSpec {
if a == nil {
@ -1941,6 +1989,14 @@ func (a *AppSpec) GetJobs() []*AppJobSpec {
return a.Jobs
}
// GetMaintenance returns the Maintenance field.
func (a *AppSpec) GetMaintenance() *AppMaintenanceSpec {
if a == nil {
return nil
}
return a.Maintenance
}
// GetName returns the Name field.
func (a *AppSpec) GetName() string {
if a == nil {
@ -1981,6 +2037,14 @@ func (a *AppSpec) GetWorkers() []*AppWorkerSpec {
return a.Workers
}
// GetBitbucket returns the Bitbucket field.
func (a *AppStaticSiteSpec) GetBitbucket() *BitbucketSourceSpec {
if a == nil {
return nil
}
return a.Bitbucket
}
// GetBuildCommand returns the BuildCommand field.
func (a *AppStaticSiteSpec) GetBuildCommand() string {
if a == nil {
@ -2205,6 +2269,14 @@ func (a *AppWorkerSpec) GetAutoscaling() *AppAutoscalingSpec {
return a.Autoscaling
}
// GetBitbucket returns the Bitbucket field.
func (a *AppWorkerSpec) GetBitbucket() *BitbucketSourceSpec {
if a == nil {
return nil
}
return a.Bitbucket
}
// GetBuildCommand returns the BuildCommand field.
func (a *AppWorkerSpec) GetBuildCommand() string {
if a == nil {
@ -2333,6 +2405,30 @@ func (a *AppWorkerSpecTermination) GetGracePeriodSeconds() int32 {
return a.GracePeriodSeconds
}
// GetBranch returns the Branch field.
func (b *BitbucketSourceSpec) GetBranch() string {
if b == nil {
return ""
}
return b.Branch
}
// GetDeployOnPush returns the DeployOnPush field.
func (b *BitbucketSourceSpec) GetDeployOnPush() bool {
if b == nil {
return false
}
return b.DeployOnPush
}
// GetRepo returns the Repo field.
func (b *BitbucketSourceSpec) GetRepo() string {
if b == nil {
return ""
}
return b.Repo
}
// GetDescription returns the Description field.
func (b *Buildpack) GetDescription() []string {
if b == nil {
@ -2669,6 +2765,14 @@ func (d *DeploymentCauseDetailsDOCRPush) GetTag() string {
return d.Tag
}
// GetBitbucket returns the Bitbucket field.
func (d *DeploymentCauseDetailsGitPush) GetBitbucket() *BitbucketSourceSpec {
if d == nil {
return nil
}
return d.Bitbucket
}
// GetCommitAuthor returns the CommitAuthor field.
func (d *DeploymentCauseDetailsGitPush) GetCommitAuthor() string {
if d == nil {
@ -3045,6 +3149,14 @@ func (d *DeployTemplate) GetSpec() *AppSpec {
return d.Spec
}
// GetBitbucket returns the Bitbucket field.
func (d *DetectRequest) GetBitbucket() *BitbucketSourceSpec {
if d == nil {
return nil
}
return d.Bitbucket
}
// GetCommitSHA returns the CommitSHA field.
func (d *DetectRequest) GetCommitSHA() string {
if d == nil {
@ -3093,6 +3205,14 @@ func (d *DetectResponse) GetComponents() []*DetectResponseComponent {
return d.Components
}
// GetPending returns the Pending field.
func (d *DetectResponse) GetPending() bool {
if d == nil {
return false
}
return d.Pending
}
// GetTemplate returns the Template field.
func (d *DetectResponse) GetTemplate() *DeployTemplate {
if d == nil {

@ -3,6 +3,7 @@ package godo
import (
"context"
"fmt"
"math/big"
"net/http"
"strings"
"time"
@ -16,6 +17,7 @@ const (
databaseResizePath = databaseBasePath + "/%s/resize"
databaseMigratePath = databaseBasePath + "/%s/migrate"
databaseMaintenancePath = databaseBasePath + "/%s/maintenance"
databaseUpdateInstallationPath = databaseBasePath + "/%s/install_update"
databaseBackupsPath = databaseBasePath + "/%s/backups"
databaseUsersPath = databaseBasePath + "/%s/users"
databaseUserPath = databaseBasePath + "/%s/users/%s"
@ -120,6 +122,7 @@ type DatabasesService interface {
Resize(context.Context, string, *DatabaseResizeRequest) (*Response, error)
Migrate(context.Context, string, *DatabaseMigrateRequest) (*Response, error)
UpdateMaintenance(context.Context, string, *DatabaseUpdateMaintenanceRequest) (*Response, error)
InstallUpdate(context.Context, string) (*Response, error)
ListBackups(context.Context, string, *ListOptions) ([]DatabaseBackup, *Response, error)
GetUser(context.Context, string, string) (*DatabaseUser, *Response, error)
ListUsers(context.Context, string, *ListOptions) ([]DatabaseUser, *Response, error)
@ -150,9 +153,15 @@ type DatabasesService interface {
GetPostgreSQLConfig(context.Context, string) (*PostgreSQLConfig, *Response, error)
GetRedisConfig(context.Context, string) (*RedisConfig, *Response, error)
GetMySQLConfig(context.Context, string) (*MySQLConfig, *Response, error)
GetMongoDBConfig(context.Context, string) (*MongoDBConfig, *Response, error)
GetOpensearchConfig(context.Context, string) (*OpensearchConfig, *Response, error)
GetKafkaConfig(context.Context, string) (*KafkaConfig, *Response, error)
UpdatePostgreSQLConfig(context.Context, string, *PostgreSQLConfig) (*Response, error)
UpdateRedisConfig(context.Context, string, *RedisConfig) (*Response, error)
UpdateMySQLConfig(context.Context, string, *MySQLConfig) (*Response, error)
UpdateMongoDBConfig(context.Context, string, *MongoDBConfig) (*Response, error)
UpdateOpensearchConfig(context.Context, string, *OpensearchConfig) (*Response, error)
UpdateKafkaConfig(context.Context, string, *KafkaConfig) (*Response, error)
ListOptions(todo context.Context) (*DatabaseOptions, *Response, error)
UpgradeMajorVersion(context.Context, string, *UpgradeVersionRequest) (*Response, error)
ListTopics(context.Context, string, *ListOptions) ([]DatabaseTopic, *Response, error)
@ -290,19 +299,27 @@ type DatabaseBackupRestore struct {
BackupCreatedAt string `json:"backup_created_at,omitempty"`
}
// DatabaseCreateFirewallRule is a rule describing an inbound source to a database
type DatabaseCreateFirewallRule struct {
UUID string `json:"uuid"`
Type string `json:"type"`
Value string `json:"value"`
}
// DatabaseCreateRequest represents a request to create a database cluster
type DatabaseCreateRequest struct {
Name string `json:"name,omitempty"`
EngineSlug string `json:"engine,omitempty"`
Version string `json:"version,omitempty"`
SizeSlug string `json:"size,omitempty"`
Region string `json:"region,omitempty"`
NumNodes int `json:"num_nodes,omitempty"`
PrivateNetworkUUID string `json:"private_network_uuid"`
Tags []string `json:"tags,omitempty"`
BackupRestore *DatabaseBackupRestore `json:"backup_restore,omitempty"`
ProjectID string `json:"project_id"`
StorageSizeMib uint64 `json:"storage_size_mib,omitempty"`
Name string `json:"name,omitempty"`
EngineSlug string `json:"engine,omitempty"`
Version string `json:"version,omitempty"`
SizeSlug string `json:"size,omitempty"`
Region string `json:"region,omitempty"`
NumNodes int `json:"num_nodes,omitempty"`
PrivateNetworkUUID string `json:"private_network_uuid"`
Tags []string `json:"tags,omitempty"`
BackupRestore *DatabaseBackupRestore `json:"backup_restore,omitempty"`
ProjectID string `json:"project_id"`
StorageSizeMib uint64 `json:"storage_size_mib,omitempty"`
Rules []*DatabaseCreateFirewallRule `json:"rules"`
}
// DatabaseResizeRequest can be used to initiate a database resize operation.
@ -580,6 +597,9 @@ type PostgreSQLConfig struct {
BackupMinute *int `json:"backup_minute,omitempty"`
WorkMem *int `json:"work_mem,omitempty"`
TimeScaleDB *PostgreSQLTimeScaleDBConfig `json:"timescaledb,omitempty"`
SynchronousReplication *string `json:"synchronous_replication,omitempty"`
StatMonitorEnable *bool `json:"stat_monitor_enable,omitempty"`
MaxFailoverReplicationTimeLag *int64 `json:"max_failover_replication_time_lag,omitempty"`
}
// PostgreSQLBouncerConfig configuration
@ -644,6 +664,85 @@ type MySQLConfig struct {
BackupHour *int `json:"backup_hour,omitempty"`
BackupMinute *int `json:"backup_minute,omitempty"`
BinlogRetentionPeriod *int `json:"binlog_retention_period,omitempty"`
InnodbChangeBufferMaxSize *int `json:"innodb_change_buffer_max_size,omitempty"`
InnodbFlushNeighbors *int `json:"innodb_flush_neighbors,omitempty"`
InnodbReadIoThreads *int `json:"innodb_read_io_threads,omitempty"`
InnodbThreadConcurrency *int `json:"innodb_thread_concurrency,omitempty"`
InnodbWriteIoThreads *int `json:"innodb_write_io_threads,omitempty"`
NetBufferLength *int `json:"net_buffer_length,omitempty"`
LogOutput *string `json:"log_output,omitempty"`
}
// MongoDBConfig holds advanced configurations for MongoDB database clusters.
type MongoDBConfig struct {
DefaultReadConcern *string `json:"default_read_concern,omitempty"`
DefaultWriteConcern *string `json:"default_write_concern,omitempty"`
TransactionLifetimeLimitSeconds *int `json:"transaction_lifetime_limit_seconds,omitempty"`
SlowOpThresholdMs *int `json:"slow_op_threshold_ms,omitempty"`
Verbosity *int `json:"verbosity,omitempty"`
}
// KafkaConfig holds advanced configurations for Kafka database clusters.
type KafkaConfig struct {
GroupInitialRebalanceDelayMs *int `json:"group_initial_rebalance_delay_ms,omitempty"`
GroupMinSessionTimeoutMs *int `json:"group_min_session_timeout_ms,omitempty"`
GroupMaxSessionTimeoutMs *int `json:"group_max_session_timeout_ms,omitempty"`
MessageMaxBytes *int `json:"message_max_bytes,omitempty"`
LogCleanerDeleteRetentionMs *int64 `json:"log_cleaner_delete_retention_ms,omitempty"`
LogCleanerMinCompactionLagMs *uint64 `json:"log_cleaner_min_compaction_lag_ms,omitempty"`
LogFlushIntervalMs *uint64 `json:"log_flush_interval_ms,omitempty"`
LogIndexIntervalBytes *int `json:"log_index_interval_bytes,omitempty"`
LogMessageDownconversionEnable *bool `json:"log_message_downconversion_enable,omitempty"`
LogMessageTimestampDifferenceMaxMs *uint64 `json:"log_message_timestamp_difference_max_ms,omitempty"`
LogPreallocate *bool `json:"log_preallocate,omitempty"`
LogRetentionBytes *big.Int `json:"log_retention_bytes,omitempty"`
LogRetentionHours *int `json:"log_retention_hours,omitempty"`
LogRetentionMs *big.Int `json:"log_retention_ms,omitempty"`
LogRollJitterMs *uint64 `json:"log_roll_jitter_ms,omitempty"`
LogSegmentDeleteDelayMs *int `json:"log_segment_delete_delay_ms,omitempty"`
AutoCreateTopicsEnable *bool `json:"auto_create_topics_enable,omitempty"`
}
// OpensearchConfig holds advanced configurations for Opensearch database clusters.
type OpensearchConfig struct {
HttpMaxContentLengthBytes *int `json:"http_max_content_length_bytes,omitempty"`
HttpMaxHeaderSizeBytes *int `json:"http_max_header_size_bytes,omitempty"`
HttpMaxInitialLineLengthBytes *int `json:"http_max_initial_line_length_bytes,omitempty"`
IndicesQueryBoolMaxClauseCount *int `json:"indices_query_bool_max_clause_count,omitempty"`
IndicesFielddataCacheSizePercentage *int `json:"indices_fielddata_cache_size_percentage,omitempty"`
IndicesMemoryIndexBufferSizePercentage *int `json:"indices_memory_index_buffer_size_percentage,omitempty"`
IndicesMemoryMinIndexBufferSizeMb *int `json:"indices_memory_min_index_buffer_size_mb,omitempty"`
IndicesMemoryMaxIndexBufferSizeMb *int `json:"indices_memory_max_index_buffer_size_mb,omitempty"`
IndicesQueriesCacheSizePercentage *int `json:"indices_queries_cache_size_percentage,omitempty"`
IndicesRecoveryMaxMbPerSec *int `json:"indices_recovery_max_mb_per_sec,omitempty"`
IndicesRecoveryMaxConcurrentFileChunks *int `json:"indices_recovery_max_concurrent_file_chunks,omitempty"`
ThreadPoolSearchSize *int `json:"thread_pool_search_size,omitempty"`
ThreadPoolSearchThrottledSize *int `json:"thread_pool_search_throttled_size,omitempty"`
ThreadPoolGetSize *int `json:"thread_pool_get_size,omitempty"`
ThreadPoolAnalyzeSize *int `json:"thread_pool_analyze_size,omitempty"`
ThreadPoolWriteSize *int `json:"thread_pool_write_size,omitempty"`
ThreadPoolForceMergeSize *int `json:"thread_pool_force_merge_size,omitempty"`
ThreadPoolSearchQueueSize *int `json:"thread_pool_search_queue_size,omitempty"`
ThreadPoolSearchThrottledQueueSize *int `json:"thread_pool_search_throttled_queue_size,omitempty"`
ThreadPoolGetQueueSize *int `json:"thread_pool_get_queue_size,omitempty"`
ThreadPoolAnalyzeQueueSize *int `json:"thread_pool_analyze_queue_size,omitempty"`
ThreadPoolWriteQueueSize *int `json:"thread_pool_write_queue_size,omitempty"`
IsmEnabled *bool `json:"ism_enabled,omitempty"`
IsmHistoryEnabled *bool `json:"ism_history_enabled,omitempty"`
IsmHistoryMaxAgeHours *int `json:"ism_history_max_age_hours,omitempty"`
IsmHistoryMaxDocs *int64 `json:"ism_history_max_docs,omitempty"`
IsmHistoryRolloverCheckPeriodHours *int `json:"ism_history_rollover_check_period_hours,omitempty"`
IsmHistoryRolloverRetentionPeriodDays *int `json:"ism_history_rollover_retention_period_days,omitempty"`
SearchMaxBuckets *int `json:"search_max_buckets,omitempty"`
ActionAutoCreateIndexEnabled *bool `json:"action_auto_create_index_enabled,omitempty"`
EnableSecurityAudit *bool `json:"enable_security_audit,omitempty"`
ActionDestructiveRequiresName *bool `json:"action_destructive_requires_name,omitempty"`
ClusterMaxShardsPerNode *int `json:"cluster_max_shards_per_node,omitempty"`
OverrideMainResponseVersion *bool `json:"override_main_response_version,omitempty"`
ScriptMaxCompilationsRate *string `json:"script_max_compilations_rate,omitempty"`
ClusterRoutingAllocationNodeConcurrentRecoveries *int `json:"cluster_routing_allocation_node_concurrent_recoveries,omitempty"`
ReindexRemoteWhitelist []string `json:"reindex_remote_whitelist,omitempty"`
PluginsAlertingFilterByBackendRolesEnabled *bool `json:"plugins_alerting_filter_by_backend_roles_enabled,omitempty"`
}
type databaseUserRoot struct {
@ -686,6 +785,18 @@ type databaseMySQLConfigRoot struct {
Config *MySQLConfig `json:"config"`
}
type databaseMongoDBConfigRoot struct {
Config *MongoDBConfig `json:"config"`
}
type databaseOpensearchConfigRoot struct {
Config *OpensearchConfig `json:"config"`
}
type databaseKafkaConfigRoot struct {
Config *KafkaConfig `json:"config"`
}
type databaseBackupsRoot struct {
Backups []DatabaseBackup `json:"backups"`
}
@ -940,6 +1051,20 @@ func (svc *DatabasesServiceOp) UpdateMaintenance(ctx context.Context, databaseID
return resp, nil
}
// InstallUpdate starts installation of updates
func (svc *DatabasesServiceOp) InstallUpdate(ctx context.Context, databaseID string) (*Response, error) {
path := fmt.Sprintf(databaseUpdateInstallationPath, databaseID)
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, nil)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// ListBackups returns a list of the current backups of a database
func (svc *DatabasesServiceOp) ListBackups(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseBackup, *Response, error) {
path := fmt.Sprintf(databaseBackupsPath, databaseID)
@ -1483,6 +1608,102 @@ func (svc *DatabasesServiceOp) UpdateMySQLConfig(ctx context.Context, databaseID
return resp, nil
}
// GetMongoDBConfig retrieves the config for a MongoDB database cluster.
func (svc *DatabasesServiceOp) GetMongoDBConfig(ctx context.Context, databaseID string) (*MongoDBConfig, *Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(databaseMongoDBConfigRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Config, resp, nil
}
// UpdateMongoDBConfig updates the config for a MongoDB database cluster.
func (svc *DatabasesServiceOp) UpdateMongoDBConfig(ctx context.Context, databaseID string, config *MongoDBConfig) (*Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
root := &databaseMongoDBConfigRoot{
Config: config,
}
req, err := svc.client.NewRequest(ctx, http.MethodPatch, path, root)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// GetKafkaConfig retrieves the config for a Kafka database cluster.
func (svc *DatabasesServiceOp) GetKafkaConfig(ctx context.Context, databaseID string) (*KafkaConfig, *Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(databaseKafkaConfigRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Config, resp, nil
}
// UpdateKafkaConfig updates the config for a Kafka database cluster.
func (svc *DatabasesServiceOp) UpdateKafkaConfig(ctx context.Context, databaseID string, config *KafkaConfig) (*Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
root := &databaseKafkaConfigRoot{
Config: config,
}
req, err := svc.client.NewRequest(ctx, http.MethodPatch, path, root)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// GetOpensearchConfig retrieves the config for a Opensearch database cluster.
func (svc *DatabasesServiceOp) GetOpensearchConfig(ctx context.Context, databaseID string) (*OpensearchConfig, *Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(databaseOpensearchConfigRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Config, resp, nil
}
// UpdateOpensearchConfig updates the config for a Opensearch database cluster.
func (svc *DatabasesServiceOp) UpdateOpensearchConfig(ctx context.Context, databaseID string, config *OpensearchConfig) (*Response, error) {
path := fmt.Sprintf(databaseConfigPath, databaseID)
root := &databaseOpensearchConfigRoot{
Config: config,
}
req, err := svc.client.NewRequest(ctx, http.MethodPatch, path, root)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// ListOptions gets the database options available.
func (svc *DatabasesServiceOp) ListOptions(ctx context.Context) (*DatabaseOptions, *Response, error) {
root := new(databaseOptionsRoot)

@ -30,6 +30,8 @@ type DropletActionsService interface {
SnapshotByTag(context.Context, string, string) ([]Action, *Response, error)
EnableBackups(context.Context, int) (*Action, *Response, error)
EnableBackupsByTag(context.Context, string) ([]Action, *Response, error)
EnableBackupsWithPolicy(context.Context, int, *DropletBackupPolicyRequest) (*Action, *Response, error)
ChangeBackupPolicy(context.Context, int, *DropletBackupPolicyRequest) (*Action, *Response, error)
DisableBackups(context.Context, int) (*Action, *Response, error)
DisableBackupsByTag(context.Context, string) ([]Action, *Response, error)
PasswordReset(context.Context, int) (*Action, *Response, error)
@ -169,6 +171,42 @@ func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag st
return s.doActionByTag(ctx, tag, request)
}
// EnableBackupsWithPolicy enables droplet's backup with a backup policy applied.
func (s *DropletActionsServiceOp) EnableBackupsWithPolicy(ctx context.Context, id int, policy *DropletBackupPolicyRequest) (*Action, *Response, error) {
if policy == nil {
return nil, nil, NewArgError("policy", "policy can't be nil")
}
policyMap := map[string]interface{}{
"plan": policy.Plan,
"weekday": policy.Weekday,
}
if policy.Hour != nil {
policyMap["hour"] = policy.Hour
}
request := &ActionRequest{"type": "enable_backups", "backup_policy": policyMap}
return s.doAction(ctx, id, request)
}
// ChangeBackupPolicy updates a backup policy when backups are enabled.
func (s *DropletActionsServiceOp) ChangeBackupPolicy(ctx context.Context, id int, policy *DropletBackupPolicyRequest) (*Action, *Response, error) {
if policy == nil {
return nil, nil, NewArgError("policy", "policy can't be nil")
}
policyMap := map[string]interface{}{
"plan": policy.Plan,
"weekday": policy.Weekday,
}
if policy.Hour != nil {
policyMap["hour"] = policy.Hour
}
request := &ActionRequest{"type": "change_backup_policy", "backup_policy": policyMap}
return s.doAction(ctx, id, request)
}
// DisableBackups disables backups for a Droplet.
func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "disable_backups"}

@ -0,0 +1,258 @@
package godo
import (
"context"
"fmt"
"net/http"
"time"
)
const (
dropletAutoscaleBasePath = "/v2/droplets/autoscale"
)
// DropletAutoscaleService defines an interface for managing droplet autoscale pools through DigitalOcean API
type DropletAutoscaleService interface {
Create(context.Context, *DropletAutoscalePoolRequest) (*DropletAutoscalePool, *Response, error)
Get(context.Context, string) (*DropletAutoscalePool, *Response, error)
List(context.Context, *ListOptions) ([]*DropletAutoscalePool, *Response, error)
ListMembers(context.Context, string, *ListOptions) ([]*DropletAutoscaleResource, *Response, error)
ListHistory(context.Context, string, *ListOptions) ([]*DropletAutoscaleHistoryEvent, *Response, error)
Update(context.Context, string, *DropletAutoscalePoolRequest) (*DropletAutoscalePool, *Response, error)
Delete(context.Context, string) (*Response, error)
DeleteDangerous(context.Context, string) (*Response, error)
}
// DropletAutoscalePool represents a DigitalOcean droplet autoscale pool
type DropletAutoscalePool struct {
ID string `json:"id"`
Name string `json:"name"`
Config *DropletAutoscaleConfiguration `json:"config"`
DropletTemplate *DropletAutoscaleResourceTemplate `json:"droplet_template"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CurrentUtilization *DropletAutoscaleResourceUtilization `json:"current_utilization,omitempty"`
Status string `json:"status"`
}
// DropletAutoscaleConfiguration represents a DigitalOcean droplet autoscale pool configuration
type DropletAutoscaleConfiguration struct {
MinInstances uint64 `json:"min_instances,omitempty"`
MaxInstances uint64 `json:"max_instances,omitempty"`
TargetCPUUtilization float64 `json:"target_cpu_utilization,omitempty"`
TargetMemoryUtilization float64 `json:"target_memory_utilization,omitempty"`
CooldownMinutes uint32 `json:"cooldown_minutes,omitempty"`
TargetNumberInstances uint64 `json:"target_number_instances,omitempty"`
}
// DropletAutoscaleResourceTemplate represents a DigitalOcean droplet autoscale pool resource template
type DropletAutoscaleResourceTemplate struct {
Size string `json:"size"`
Region string `json:"region"`
Image string `json:"image"`
Tags []string `json:"tags"`
SSHKeys []string `json:"ssh_keys"`
VpcUUID string `json:"vpc_uuid"`
WithDropletAgent bool `json:"with_droplet_agent"`
ProjectID string `json:"project_id"`
IPV6 bool `json:"ipv6"`
UserData string `json:"user_data"`
}
// DropletAutoscaleResourceUtilization represents a DigitalOcean droplet autoscale pool resource utilization
type DropletAutoscaleResourceUtilization struct {
Memory float64 `json:"memory,omitempty"`
CPU float64 `json:"cpu,omitempty"`
}
// DropletAutoscaleResource represents a DigitalOcean droplet autoscale pool resource
type DropletAutoscaleResource struct {
DropletID uint64 `json:"droplet_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
HealthStatus string `json:"health_status"`
UnhealthyReason string `json:"unhealthy_reason,omitempty"`
Status string `json:"status"`
CurrentUtilization *DropletAutoscaleResourceUtilization `json:"current_utilization,omitempty"`
}
// DropletAutoscaleHistoryEvent represents a DigitalOcean droplet autoscale pool history event
type DropletAutoscaleHistoryEvent struct {
HistoryEventID string `json:"history_event_id"`
CurrentInstanceCount uint64 `json:"current_instance_count"`
DesiredInstanceCount uint64 `json:"desired_instance_count"`
Reason string `json:"reason"`
Status string `json:"status"`
ErrorReason string `json:"error_reason,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// DropletAutoscalePoolRequest represents a DigitalOcean droplet autoscale pool create/update request
type DropletAutoscalePoolRequest struct {
Name string `json:"name"`
Config *DropletAutoscaleConfiguration `json:"config"`
DropletTemplate *DropletAutoscaleResourceTemplate `json:"droplet_template"`
}
type dropletAutoscalePoolRoot struct {
AutoscalePool *DropletAutoscalePool `json:"autoscale_pool"`
}
type dropletAutoscalePoolsRoot struct {
AutoscalePools []*DropletAutoscalePool `json:"autoscale_pools"`
Links *Links `json:"links"`
Meta *Meta `json:"meta"`
}
type dropletAutoscaleMembersRoot struct {
Droplets []*DropletAutoscaleResource `json:"droplets"`
Links *Links `json:"links"`
Meta *Meta `json:"meta"`
}
type dropletAutoscaleHistoryEventsRoot struct {
History []*DropletAutoscaleHistoryEvent `json:"history"`
Links *Links `json:"links"`
Meta *Meta `json:"meta"`
}
// DropletAutoscaleServiceOp handles communication with droplet autoscale-related methods of the DigitalOcean API
type DropletAutoscaleServiceOp struct {
client *Client
}
var _ DropletAutoscaleService = &DropletAutoscaleServiceOp{}
// Create a new droplet autoscale pool
func (d *DropletAutoscaleServiceOp) Create(ctx context.Context, createReq *DropletAutoscalePoolRequest) (*DropletAutoscalePool, *Response, error) {
req, err := d.client.NewRequest(ctx, http.MethodPost, dropletAutoscaleBasePath, createReq)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscalePoolRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
return root.AutoscalePool, resp, nil
}
// Get an existing droplet autoscale pool
func (d *DropletAutoscaleServiceOp) Get(ctx context.Context, id string) (*DropletAutoscalePool, *Response, error) {
req, err := d.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s", dropletAutoscaleBasePath, id), nil)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscalePoolRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
return root.AutoscalePool, resp, err
}
// List all existing droplet autoscale pools
func (d *DropletAutoscaleServiceOp) List(ctx context.Context, opts *ListOptions) ([]*DropletAutoscalePool, *Response, error) {
path, err := addOptions(dropletAutoscaleBasePath, opts)
if err != nil {
return nil, nil, err
}
req, err := d.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscalePoolsRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
if root.Links != nil {
resp.Links = root.Links
}
if root.Meta != nil {
resp.Meta = root.Meta
}
return root.AutoscalePools, resp, err
}
// ListMembers all members for an existing droplet autoscale pool
func (d *DropletAutoscaleServiceOp) ListMembers(ctx context.Context, id string, opts *ListOptions) ([]*DropletAutoscaleResource, *Response, error) {
path, err := addOptions(fmt.Sprintf("%s/%s/members", dropletAutoscaleBasePath, id), opts)
if err != nil {
return nil, nil, err
}
req, err := d.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscaleMembersRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
if root.Links != nil {
resp.Links = root.Links
}
if root.Meta != nil {
resp.Meta = root.Meta
}
return root.Droplets, resp, err
}
// ListHistory all history events for an existing droplet autoscale pool
func (d *DropletAutoscaleServiceOp) ListHistory(ctx context.Context, id string, opts *ListOptions) ([]*DropletAutoscaleHistoryEvent, *Response, error) {
path, err := addOptions(fmt.Sprintf("%s/%s/history", dropletAutoscaleBasePath, id), opts)
if err != nil {
return nil, nil, err
}
req, err := d.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscaleHistoryEventsRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
if root.Links != nil {
resp.Links = root.Links
}
if root.Meta != nil {
resp.Meta = root.Meta
}
return root.History, resp, err
}
// Update an existing autoscale pool
func (d *DropletAutoscaleServiceOp) Update(ctx context.Context, id string, updateReq *DropletAutoscalePoolRequest) (*DropletAutoscalePool, *Response, error) {
req, err := d.client.NewRequest(ctx, http.MethodPut, fmt.Sprintf("%s/%s", dropletAutoscaleBasePath, id), updateReq)
if err != nil {
return nil, nil, err
}
root := new(dropletAutoscalePoolRoot)
resp, err := d.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
return root.AutoscalePool, resp, nil
}
// Delete an existing autoscale pool
func (d *DropletAutoscaleServiceOp) Delete(ctx context.Context, id string) (*Response, error) {
req, err := d.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", dropletAutoscaleBasePath, id), nil)
if err != nil {
return nil, err
}
return d.client.Do(ctx, req, nil)
}
// DeleteDangerous deletes an existing autoscale pool with all underlying resources
func (d *DropletAutoscaleServiceOp) DeleteDangerous(ctx context.Context, id string) (*Response, error) {
req, err := d.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/dangerous", dropletAutoscaleBasePath, id), nil)
req.Header.Set("X-Dangerous", "true")
if err != nil {
return nil, err
}
return d.client.Do(ctx, req, nil)
}

@ -17,6 +17,7 @@ var errNoNetworks = errors.New("no networks have been defined")
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Droplets
type DropletsService interface {
List(context.Context, *ListOptions) ([]Droplet, *Response, error)
ListWithGPUs(context.Context, *ListOptions) ([]Droplet, *Response, error)
ListByName(context.Context, string, *ListOptions) ([]Droplet, *Response, error)
ListByTag(context.Context, string, *ListOptions) ([]Droplet, *Response, error)
Get(context.Context, int) (*Droplet, *Response, error)
@ -29,6 +30,9 @@ type DropletsService interface {
Backups(context.Context, int, *ListOptions) ([]Image, *Response, error)
Actions(context.Context, int, *ListOptions) ([]Action, *Response, error)
Neighbors(context.Context, int) ([]Droplet, *Response, error)
GetBackupPolicy(context.Context, int) (*DropletBackupPolicy, *Response, error)
ListBackupPolicies(context.Context, *ListOptions) (map[int]*DropletBackupPolicy, *Response, error)
ListSupportedBackupPolicies(context.Context) ([]*SupportedBackupPolicy, *Response, error)
}
// DropletsServiceOp handles communication with the Droplet related methods of the
@ -217,37 +221,46 @@ func (d DropletCreateSSHKey) MarshalJSON() ([]byte, error) {
// DropletCreateRequest represents a request to create a Droplet.
type DropletCreateRequest struct {
Name string `json:"name"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
Name string `json:"name"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
BackupPolicy *DropletBackupPolicyRequest `json:"backup_policy,omitempty"`
}
// DropletMultiCreateRequest is a request to create multiple Droplets.
type DropletMultiCreateRequest struct {
Names []string `json:"names"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
Names []string `json:"names"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
BackupPolicy *DropletBackupPolicyRequest `json:"backup_policy,omitempty"`
}
// DropletBackupPolicyRequest defines the backup policy when creating a Droplet.
type DropletBackupPolicyRequest struct {
Plan string `json:"plan,omitempty"`
Weekday string `json:"weekday,omitempty"`
Hour *int `json:"hour,omitempty"`
}
func (d DropletCreateRequest) String() string {
@ -321,6 +334,17 @@ func (s *DropletsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Dropl
return s.list(ctx, path)
}
// ListWithGPUs lists all Droplets with GPUs.
func (s *DropletsServiceOp) ListWithGPUs(ctx context.Context, opt *ListOptions) ([]Droplet, *Response, error) {
path := fmt.Sprintf("%s?type=gpus", dropletBasePath)
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
return s.list(ctx, path)
}
// ListByName lists all Droplets filtered by name returning only exact matches.
// It is case-insensitive
func (s *DropletsServiceOp) ListByName(ctx context.Context, name string, opt *ListOptions) ([]Droplet, *Response, error) {
@ -606,3 +630,109 @@ func (s *DropletsServiceOp) dropletActionStatus(ctx context.Context, uri string)
return action.Status, nil
}
// DropletBackupPolicy defines the information about a droplet's backup policy.
type DropletBackupPolicy struct {
DropletID int `json:"droplet_id,omitempty"`
BackupEnabled bool `json:"backup_enabled,omitempty"`
BackupPolicy *DropletBackupPolicyConfig `json:"backup_policy,omitempty"`
NextBackupWindow *BackupWindow `json:"next_backup_window,omitempty"`
}
// DropletBackupPolicyConfig defines the backup policy for a Droplet.
type DropletBackupPolicyConfig struct {
Plan string `json:"plan,omitempty"`
Weekday string `json:"weekday,omitempty"`
Hour int `json:"hour,omitempty"`
WindowLengthHours int `json:"window_length_hours,omitempty"`
RetentionPeriodDays int `json:"retention_period_days,omitempty"`
}
// dropletBackupPolicyRoot represents a DropletBackupPolicy root
type dropletBackupPolicyRoot struct {
DropletBackupPolicy *DropletBackupPolicy `json:"policy,omitempty"`
}
type dropletBackupPoliciesRoot struct {
DropletBackupPolicies map[int]*DropletBackupPolicy `json:"policies,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta"`
}
// Get individual droplet backup policy.
func (s *DropletsServiceOp) GetBackupPolicy(ctx context.Context, dropletID int) (*DropletBackupPolicy, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}
path := fmt.Sprintf("%s/%d/backups/policy", dropletBasePath, dropletID)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletBackupPolicyRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.DropletBackupPolicy, resp, err
}
// List all droplet backup policies.
func (s *DropletsServiceOp) ListBackupPolicies(ctx context.Context, opt *ListOptions) (map[int]*DropletBackupPolicy, *Response, error) {
path := fmt.Sprintf("%s/backups/policies", dropletBasePath)
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletBackupPoliciesRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
if m := root.Meta; m != nil {
resp.Meta = m
}
return root.DropletBackupPolicies, resp, nil
}
type SupportedBackupPolicy struct {
Name string `json:"name,omitempty"`
PossibleWindowStarts []int `json:"possible_window_starts,omitempty"`
WindowLengthHours int `json:"window_length_hours,omitempty"`
RetentionPeriodDays int `json:"retention_period_days,omitempty"`
PossibleDays []string `json:"possible_days,omitempty"`
}
type dropletSupportedBackupPoliciesRoot struct {
SupportedBackupPolicies []*SupportedBackupPolicy `json:"supported_policies,omitempty"`
}
// List supported droplet backup policies.
func (s *DropletsServiceOp) ListSupportedBackupPolicies(ctx context.Context) ([]*SupportedBackupPolicy, *Response, error) {
path := fmt.Sprintf("%s/backups/supported_policies", dropletBasePath)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(dropletSupportedBackupPoliciesRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.SupportedBackupPolicies, resp, nil
}

@ -21,7 +21,7 @@ import (
)
const (
libraryVersion = "1.122.0"
libraryVersion = "1.132.0"
defaultBaseURL = "https://api.digitalocean.com/"
userAgent = "godo/" + libraryVersion
mediaType = "application/json"
@ -54,41 +54,45 @@ type Client struct {
ratemtx sync.Mutex
// Services used for communicating with the API
Account AccountService
Actions ActionsService
Apps AppsService
Balance BalanceService
BillingHistory BillingHistoryService
CDNs CDNService
Certificates CertificatesService
Databases DatabasesService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
Firewalls FirewallsService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Functions FunctionsService
Images ImagesService
ImageActions ImageActionsService
Invoices InvoicesService
Keys KeysService
Kubernetes KubernetesService
LoadBalancers LoadBalancersService
Monitoring MonitoringService
OneClick OneClickService
Projects ProjectsService
Regions RegionsService
Registry RegistryService
ReservedIPs ReservedIPsService
ReservedIPActions ReservedIPActionsService
Sizes SizesService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
UptimeChecks UptimeChecksService
VPCs VPCsService
Account AccountService
Actions ActionsService
Apps AppsService
Balance BalanceService
BillingHistory BillingHistoryService
CDNs CDNService
Certificates CertificatesService
Databases DatabasesService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
DropletAutoscale DropletAutoscaleService
Firewalls FirewallsService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Functions FunctionsService
Images ImagesService
ImageActions ImageActionsService
Invoices InvoicesService
Keys KeysService
Kubernetes KubernetesService
LoadBalancers LoadBalancersService
Monitoring MonitoringService
OneClick OneClickService
Projects ProjectsService
Regions RegionsService
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPV6s ReservedIPV6sService
ReservedIPActions ReservedIPActionsService
ReservedIPV6Actions ReservedIPV6ActionsService
Sizes SizesService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
UptimeChecks UptimeChecksService
VPCs VPCsService
// Optional function called after every successful request made to the DO APIs
onRequestCompleted RequestCompletionCallback
@ -275,6 +279,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Domains = &DomainsServiceOp{client: c}
c.Droplets = &DropletsServiceOp{client: c}
c.DropletActions = &DropletActionsServiceOp{client: c}
c.DropletAutoscale = &DropletAutoscaleServiceOp{client: c}
c.Firewalls = &FirewallsServiceOp{client: c}
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
@ -290,8 +295,11 @@ func NewClient(httpClient *http.Client) *Client {
c.Projects = &ProjectsServiceOp{client: c}
c.Regions = &RegionsServiceOp{client: c}
c.Registry = &RegistryServiceOp{client: c}
c.Registries = &RegistriesServiceOp{client: c}
c.ReservedIPs = &ReservedIPsServiceOp{client: c}
c.ReservedIPV6s = &ReservedIPV6sServiceOp{client: c}
c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
c.ReservedIPV6Actions = &ReservedIPV6ActionsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Storage = &StorageServiceOp{client: c}

@ -65,11 +65,13 @@ type KubernetesServiceOp struct {
// KubernetesClusterCreateRequest represents a request to create a Kubernetes cluster.
type KubernetesClusterCreateRequest struct {
Name string `json:"name,omitempty"`
RegionSlug string `json:"region,omitempty"`
VersionSlug string `json:"version,omitempty"`
Tags []string `json:"tags,omitempty"`
VPCUUID string `json:"vpc_uuid,omitempty"`
Name string `json:"name,omitempty"`
RegionSlug string `json:"region,omitempty"`
VersionSlug string `json:"version,omitempty"`
Tags []string `json:"tags,omitempty"`
VPCUUID string `json:"vpc_uuid,omitempty"`
ClusterSubnet string `json:"cluster_subnet,omitempty"`
ServiceSubnet string `json:"service_subnet,omitempty"`
// Create cluster with highly available control plane
HA bool `json:"ha"`

@ -45,6 +45,7 @@ type LoadBalancer struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
IP string `json:"ip,omitempty"`
IPv6 string `json:"ipv6,omitempty"`
// SizeSlug is mutually exclusive with SizeUnit. Only one should be specified
SizeSlug string `json:"size,omitempty"`
// SizeUnit is mutually exclusive with SizeSlug. Only one should be specified

@ -10,9 +10,10 @@ import (
)
const (
monitoringBasePath = "v2/monitoring"
alertPolicyBasePath = monitoringBasePath + "/alerts"
dropletMetricsBasePath = monitoringBasePath + "/metrics/droplet"
monitoringBasePath = "v2/monitoring"
alertPolicyBasePath = monitoringBasePath + "/alerts"
dropletMetricsBasePath = monitoringBasePath + "/metrics/droplet"
loadBalancerMetricsBasePath = monitoringBasePath + "/metrics/load_balancer"
DropletCPUUtilizationPercent = "v1/insights/droplet/cpu"
DropletMemoryUtilizationPercent = "v1/insights/droplet/memory_utilization_percent"
@ -67,6 +68,34 @@ type MonitoringService interface {
GetDropletCachedMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
GetDropletFreeMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
GetDropletTotalMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendHttpRequestsPerSecond(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendConnectionsCurrent(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendConnectionsLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendCpuUtilization(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendNetworkThroughputHttp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendNetworkThroughputUdp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendNetworkThroughputTcp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendNlbTcpNetworkThroughput(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendNlbUdpNetworkThroughput(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendFirewallDroppedBytes(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendFirewallDroppedPackets(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendHttpResponses(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendTlsConnectionsCurrent(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendTlsConnectionsLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerFrontendTlsConnectionsExceedingRateLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpSessionDurationAvg(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpSessionDuration50P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpSessionDuration95P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpResponseTimeAvg(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpResponseTime50P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpResponseTime95P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpResponseTime99P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsQueueSize(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHttpResponses(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsConnections(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsHealthChecks(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
GetLoadBalancerDropletsDowntime(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error)
}
// MonitoringServiceOp handles communication with monitoring related methods of the
@ -163,6 +192,13 @@ type DropletBandwidthMetricsRequest struct {
Direction string
}
// LoadBalancerMetricsRequest holds the information needed to retrieve Load Balancer various metrics.
type LoadBalancerMetricsRequest struct {
LoadBalancerID string
Start time.Time
End time.Time
}
// MetricsResponse holds a Metrics query response.
type MetricsResponse struct {
Status string `json:"status"`
@ -372,3 +408,157 @@ func (s *MonitoringServiceOp) getDropletMetrics(ctx context.Context, path string
return root, resp, err
}
// GetLoadBalancerFrontendHttpRequestsPerSecond retrieves frontend HTTP requests per second for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendHttpRequestsPerSecond(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_http_requests_per_second", args)
}
// GetLoadBalancerFrontendConnectionsCurrent retrieves frontend total current active connections for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendConnectionsCurrent(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_connections_current", args)
}
// GetLoadBalancerFrontendConnectionsLimit retrieves frontend max connections limit for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendConnectionsLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_connections_limit", args)
}
// GetLoadBalancerFrontendCpuUtilization retrieves frontend average percentage cpu utilization for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendCpuUtilization(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_cpu_utilization", args)
}
// GetLoadBalancerFrontendNetworkThroughputHttp retrieves frontend HTTP throughput for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendNetworkThroughputHttp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_network_throughput_http", args)
}
// GetLoadBalancerFrontendNetworkThroughputUdp retrieves frontend UDP throughput for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendNetworkThroughputUdp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_network_throughput_udp", args)
}
// GetLoadBalancerFrontendNetworkThroughputTcp retrieves frontend TCP throughput for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendNetworkThroughputTcp(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_network_throughput_tcp", args)
}
// GetLoadBalancerFrontendNlbTcpNetworkThroughput retrieves frontend TCP throughput for a given network load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendNlbTcpNetworkThroughput(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_nlb_tcp_network_throughput", args)
}
// GetLoadBalancerFrontendNlbUdpNetworkThroughput retrieves frontend UDP throughput for a given network load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendNlbUdpNetworkThroughput(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_nlb_udp_network_throughput", args)
}
// GetLoadBalancerFrontendFirewallDroppedBytes retrieves firewall dropped bytes for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendFirewallDroppedBytes(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_firewall_dropped_bytes", args)
}
// GetLoadBalancerFrontendFirewallDroppedPackets retrieves firewall dropped packets for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendFirewallDroppedPackets(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_firewall_dropped_packets", args)
}
// GetLoadBalancerFrontendHttpResponses retrieves frontend HTTP rate of response code for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendHttpResponses(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_http_responses", args)
}
// GetLoadBalancerFrontendTlsConnectionsCurrent retrieves frontend current TLS connections rate for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendTlsConnectionsCurrent(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_tls_connections_current", args)
}
// GetLoadBalancerFrontendTlsConnectionsLimit retrieves frontend max TLS connections limit for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendTlsConnectionsLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_tls_connections_limit", args)
}
// GetLoadBalancerFrontendTlsConnectionsExceedingRateLimit retrieves frontend closed TLS connections for exceeded rate limit for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerFrontendTlsConnectionsExceedingRateLimit(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/frontend_tls_connections_exceeding_rate_limit", args)
}
// GetLoadBalancerDropletsHttpSessionDurationAvg retrieves droplet average HTTP session duration for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpSessionDurationAvg(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_session_duration_avg", args)
}
// GetLoadBalancerDropletsHttpSessionDuration50P retrieves droplet 50th percentile HTTP session duration for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpSessionDuration50P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_session_duration_50p", args)
}
// GetLoadBalancerDropletsHttpSessionDuration95P retrieves droplet 95th percentile HTTP session duration for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpSessionDuration95P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_session_duration_95p", args)
}
// GetLoadBalancerDropletsHttpResponseTimeAvg retrieves droplet average HTTP response time for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpResponseTimeAvg(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_response_time_avg", args)
}
// GetLoadBalancerDropletsHttpResponseTime50P retrieves droplet 50th percentile HTTP response time for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpResponseTime50P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_response_time_50p", args)
}
// GetLoadBalancerDropletsHttpResponseTime95P retrieves droplet 95th percentile HTTP response time for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpResponseTime95P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_response_time_95p", args)
}
// GetLoadBalancerDropletsHttpResponseTime99P retrieves droplet 99th percentile HTTP response time for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpResponseTime99P(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_response_time_99p", args)
}
// GetLoadBalancerDropletsQueueSize retrieves droplet queue size for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsQueueSize(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_queue_size", args)
}
// GetLoadBalancerDropletsHttpResponses retrieves droplet HTTP rate of response code for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHttpResponses(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_http_responses", args)
}
// GetLoadBalancerDropletsConnections retrieves droplet active connections for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsConnections(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_connections", args)
}
// GetLoadBalancerDropletsHealthChecks retrieves droplet health check status for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsHealthChecks(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_health_checks", args)
}
// GetLoadBalancerDropletsDowntime retrieves droplet downtime status for a given load balancer.
func (s *MonitoringServiceOp) GetLoadBalancerDropletsDowntime(ctx context.Context, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
return s.getLoadBalancerMetrics(ctx, "/droplets_downtime", args)
}
func (s *MonitoringServiceOp) getLoadBalancerMetrics(ctx context.Context, path string, args *LoadBalancerMetricsRequest) (*MetricsResponse, *Response, error) {
fullPath := loadBalancerMetricsBasePath + path
req, err := s.client.NewRequest(ctx, http.MethodGet, fullPath, nil)
if err != nil {
return nil, nil, err
}
q := req.URL.Query()
q.Add("lb_id", args.LoadBalancerID)
q.Add("start", fmt.Sprintf("%d", args.Start.Unix()))
q.Add("end", fmt.Sprintf("%d", args.End.Unix()))
req.URL.RawQuery = q.Encode()
root := new(MetricsResponse)
resp, err := s.client.Do(ctx, req, root)
return root, resp, err
}

@ -14,6 +14,9 @@ const (
registryPath = "/v2/registry"
// RegistryServer is the hostname of the DigitalOcean registry service
RegistryServer = "registry.digitalocean.com"
// Multi-registry Open Beta API constants
registriesPath = "/v2/registries"
)
// RegistryService is an interface for interfacing with the Registry endpoints
@ -240,6 +243,19 @@ type RegistryValidateNameRequest struct {
Name string `json:"name"`
}
// Multi-registry Open Beta API structs
type registriesRoot struct {
Registries []*Registry `json:"registries,omitempty"`
TotalStorageUsageBytes uint64 `json:"total_storage_usage_bytes,omitempty"`
}
// RegistriesCreateRequest represents a request to create a secondary registry.
type RegistriesCreateRequest struct {
Name string `json:"name,omitempty"`
Region string `json:"region,omitempty"`
}
// Get retrieves the details of a Registry.
func (svc *RegistryServiceOp) Get(ctx context.Context) (*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodGet, registryPath, nil)
@ -610,3 +626,107 @@ func (svc *RegistryServiceOp) ValidateName(ctx context.Context, request *Registr
}
return resp, nil
}
// RegistriesService is an interface for interfacing with the new multiple-registry beta endpoints
// of the DigitalOcean API.
//
// We are creating a separate Service in alignment with the new /v2/registries endpoints.
type RegistriesService interface {
Get(context.Context, string) (*Registry, *Response, error)
List(context.Context) ([]*Registry, *Response, error)
Create(context.Context, *RegistriesCreateRequest) (*Registry, *Response, error)
Delete(context.Context, string) (*Response, error)
DockerCredentials(context.Context, string, *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error)
}
var _ RegistriesService = &RegistriesServiceOp{}
// RegistriesServiceOp handles communication with the multiple-registry beta methods.
type RegistriesServiceOp struct {
client *Client
}
// Get returns the details of a named Registry.
func (svc *RegistriesServiceOp) Get(ctx context.Context, registry string) (*Registry, *Response, error) {
path := fmt.Sprintf("%s/%s", registriesPath, registry)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(registryRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registry, resp, nil
}
// List returns a list of the named Registries.
func (svc *RegistriesServiceOp) List(ctx context.Context) ([]*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodGet, registriesPath, nil)
if err != nil {
return nil, nil, err
}
root := new(registriesRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registries, resp, nil
}
// Create creates a named Registry.
func (svc *RegistriesServiceOp) Create(ctx context.Context, create *RegistriesCreateRequest) (*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodPost, registriesPath, create)
if err != nil {
return nil, nil, err
}
root := new(registryRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registry, resp, nil
}
// Delete deletes a named Registry. There is no way to recover a Registry once it has
// been destroyed.
func (svc *RegistriesServiceOp) Delete(ctx context.Context, registry string) (*Response, error) {
path := fmt.Sprintf("%s/%s", registriesPath, registry)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// DockerCredentials retrieves a Docker config file containing named Registry's credentials.
func (svc *RegistriesServiceOp) DockerCredentials(ctx context.Context, registry string, request *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error) {
path := fmt.Sprintf("%s/%s/%s", registriesPath, registry, "docker-credentials")
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
q := req.URL.Query()
q.Add("read_write", strconv.FormatBool(request.ReadWrite))
if request.ExpirySeconds != nil {
q.Add("expiry_seconds", strconv.Itoa(*request.ExpirySeconds))
}
req.URL.RawQuery = q.Encode()
var buf bytes.Buffer
resp, err := svc.client.Do(ctx, req, &buf)
if err != nil {
return nil, resp, err
}
dc := &DockerCredentials{
DockerConfigJSON: buf.Bytes(),
}
return dc, resp, nil
}

@ -0,0 +1,135 @@
package godo
import (
"context"
"fmt"
"net/http"
"time"
)
const resourceV6Type = "ReservedIPv6"
const reservedIPV6sBasePath = "v2/reserved_ipv6"
// ReservedIPV6sService is an interface for interfacing with the reserved IPV6s
// endpoints of the Digital Ocean API.
type ReservedIPV6sService interface {
List(context.Context, *ListOptions) ([]ReservedIPV6, *Response, error)
Get(context.Context, string) (*ReservedIPV6, *Response, error)
Create(context.Context, *ReservedIPV6CreateRequest) (*ReservedIPV6, *Response, error)
Delete(context.Context, string) (*Response, error)
}
// ReservedIPV6sServiceOp handles communication with the reserved IPs related methods of the
// DigitalOcean API.
type ReservedIPV6sServiceOp struct {
client *Client
}
var _ ReservedIPV6sService = (*ReservedIPV6sServiceOp)(nil)
// ReservedIPV6 represents a Digital Ocean reserved IP.
type ReservedIPV6 struct {
RegionSlug string `json:"region_slug"`
IP string `json:"ip"`
ReservedAt time.Time `json:"reserved_at"`
Droplet *Droplet `json:"droplet,omitempty"`
}
type reservedIPV6Root struct {
ReservedIPV6 *ReservedIPV6 `json:"reserved_ipv6"`
}
type reservedIPV6sRoot struct {
ReservedIPV6s []ReservedIPV6 `json:"reserved_ipv6s"`
Links *Links `json:"links"`
Meta *Meta `json:"meta"`
}
func (f ReservedIPV6) String() string {
return Stringify(f)
}
// URN returns the reserved IP in a valid DO API URN form.
func (f ReservedIPV6) URN() string {
return ToURN(resourceV6Type, f.IP)
}
// ReservedIPV6CreateRequest represents a request to reserve a reserved IP.
type ReservedIPV6CreateRequest struct {
Region string `json:"region_slug,omitempty"`
}
// List all reserved IPV6s.
func (r *ReservedIPV6sServiceOp) List(ctx context.Context, opt *ListOptions) ([]ReservedIPV6, *Response, error) {
path := reservedIPV6sBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(reservedIPV6sRoot)
resp, err := r.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
if root.Meta != nil {
resp.Meta = root.Meta
}
if root.Links != nil {
resp.Links = root.Links
}
return root.ReservedIPV6s, resp, err
}
// Get an individual reserved IPv6.
func (r *ReservedIPV6sServiceOp) Get(ctx context.Context, ip string) (*ReservedIPV6, *Response, error) {
path := fmt.Sprintf("%s/%s", reservedIPV6sBasePath, ip)
req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(reservedIPV6Root)
resp, err := r.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.ReservedIPV6, resp, err
}
// Create a new IPv6
func (r *ReservedIPV6sServiceOp) Create(ctx context.Context, reserveRequest *ReservedIPV6CreateRequest) (*ReservedIPV6, *Response, error) {
path := reservedIPV6sBasePath
req, err := r.client.NewRequest(ctx, http.MethodPost, path, reserveRequest)
if err != nil {
return nil, nil, err
}
root := new(reservedIPV6Root)
resp, err := r.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.ReservedIPV6, resp, err
}
// Delete a reserved IPv6.
func (r *ReservedIPV6sServiceOp) Delete(ctx context.Context, ip string) (*Response, error) {
path := fmt.Sprintf("%s/%s", reservedIPV6sBasePath, ip)
req, err := r.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return r.client.Do(ctx, req, nil)
}

@ -0,0 +1,57 @@
package godo
import (
"context"
"fmt"
"net/http"
)
// ReservedIPActionsService is an interface for interfacing with the
// reserved IPs actions endpoints of the Digital Ocean API.
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Reserved-IP-Actions
type ReservedIPV6ActionsService interface {
Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error)
Unassign(ctx context.Context, ip string) (*Action, *Response, error)
}
// ReservedIPActionsServiceOp handles communication with the reserved IPs
// action related methods of the DigitalOcean API.
type ReservedIPV6ActionsServiceOp struct {
client *Client
}
// Assign a reserved IP to a droplet.
func (s *ReservedIPV6ActionsServiceOp) Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error) {
request := &ActionRequest{
"type": "assign",
"droplet_id": dropletID,
}
return s.doV6Action(ctx, ip, request)
}
// Unassign a rerserved IP from the droplet it is currently assigned to.
func (s *ReservedIPV6ActionsServiceOp) Unassign(ctx context.Context, ip string) (*Action, *Response, error) {
request := &ActionRequest{"type": "unassign"}
return s.doV6Action(ctx, ip, request)
}
func (s *ReservedIPV6ActionsServiceOp) doV6Action(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
path := reservedIPV6ActionPath(ip)
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
if err != nil {
return nil, nil, err
}
root := new(actionRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Event, resp, err
}
func reservedIPV6ActionPath(ip string) string {
return fmt.Sprintf("%s/%s/actions", reservedIPV6sBasePath, ip)
}

@ -22,16 +22,44 @@ var _ SizesService = &SizesServiceOp{}
// Size represents a DigitalOcean Size
type Size struct {
Slug string `json:"slug,omitempty"`
Memory int `json:"memory,omitempty"`
Vcpus int `json:"vcpus,omitempty"`
Disk int `json:"disk,omitempty"`
PriceMonthly float64 `json:"price_monthly,omitempty"`
PriceHourly float64 `json:"price_hourly,omitempty"`
Regions []string `json:"regions,omitempty"`
Available bool `json:"available,omitempty"`
Transfer float64 `json:"transfer,omitempty"`
Description string `json:"description,omitempty"`
Slug string `json:"slug,omitempty"`
Memory int `json:"memory,omitempty"`
Vcpus int `json:"vcpus,omitempty"`
Disk int `json:"disk,omitempty"`
PriceMonthly float64 `json:"price_monthly,omitempty"`
PriceHourly float64 `json:"price_hourly,omitempty"`
Regions []string `json:"regions,omitempty"`
Available bool `json:"available,omitempty"`
Transfer float64 `json:"transfer,omitempty"`
Description string `json:"description,omitempty"`
GPUInfo *GPUInfo `json:"gpu_info,omitempty"`
DiskInfo []DiskInfo `json:"disk_info,omitempty"`
}
// DiskInfo containing information about the disks available to Droplets created
// with this size.
type DiskInfo struct {
Type string `json:"type,omitempty"`
Size *DiskSize `json:"size,omitempty"`
}
// DiskSize provides information about the size of a disk.
type DiskSize struct {
Amount int `json:"amount,omitempty"`
Unit string `json:"unit,omitempty"`
}
// GPUInfo provides information about the GPU available to Droplets created with this size.
type GPUInfo struct {
Count int `json:"count,omitempty"`
VRAM *VRAM `json:"vram,omitempty"`
Model string `json:"model,omitempty"`
}
// VRAM provides information about the amount of VRAM available to the GPU.
type VRAM struct {
Amount int `json:"amount,omitempty"`
Unit string `json:"unit,omitempty"`
}
func (s Size) String() string {

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"reflect"
"sort"
"strings"
)
@ -46,6 +47,8 @@ func stringifyValue(w io.Writer, val reflect.Value) {
return
case reflect.Struct:
stringifyStruct(w, v)
case reflect.Map:
stringifyMap(w, v)
default:
if v.CanInterface() {
fmt.Fprint(w, v.Interface())
@ -66,6 +69,27 @@ func stringifySlice(w io.Writer, v reflect.Value) {
_, _ = w.Write([]byte{']'})
}
func stringifyMap(w io.Writer, v reflect.Value) {
_, _ = w.Write([]byte("map["))
// Sort the keys so that the output is stable
keys := v.MapKeys()
sort.Slice(keys, func(i, j int) bool {
return fmt.Sprintf("%v", keys[i]) < fmt.Sprintf("%v", keys[j])
})
for i, key := range keys {
stringifyValue(w, key)
_, _ = w.Write([]byte{':'})
stringifyValue(w, v.MapIndex(key))
if i < len(keys)-1 {
_, _ = w.Write([]byte(", "))
}
}
_, _ = w.Write([]byte("]"))
}
func stringifyStruct(w io.Writer, v reflect.Value) {
if v.Type().Name() != "" {
_, _ = w.Write([]byte(v.Type().String()))

@ -9,6 +9,6 @@ Operating System Support
========================
This package is tested using GitHub Actions on Linux, macOS, and Windows. It should also work on other Unix-like platforms, but hasn't been tested with them. I'm interested to hear about the results.
I haven't been able to add more features without adding significant complexity, so mmap-go doesn't support `mprotect`, `mincore`, and maybe a few other things. If you're running on a Unix-like platform and need some of these features, I suggest Gustavo Niemeyer's [gommap](http://labix.org/gommap).
This package compiles for Plan 9 and WebAssembly, but its functions always return errors.
This package compiles on Plan 9, but its functions always return errors.
Related functions such as `mprotect` and `mincore` aren't included. I haven't found a way to implement them on Windows without introducing significant complexity. If you're running on a Unix-like platform and really need these features, it should still be possible to implement them on top of this package via `syscall`.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save