Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.42.5
github.com/aws/aws-sdk-go-v2/service/sns v1.41.0
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27
github.com/aws/aws-sdk-go-v2/service/ssm v1.71.0
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10
github.com/aws/smithy-go v1.27.3
github.com/databricks/databricks-sdk-go v0.144.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ github.com/aws/aws-sdk-go-v2/service/sns v1.41.0 h1:GT6QdvVfByxl1/AJQe7PNbLtQDj0
github.com/aws/aws-sdk-go-v2/service/sns v1.41.0/go.mod h1:5EnTxMpMVeiY0vcjjN/a958FFaHrS6XfXcyRBzDKDCE=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27 h1:QgaWXVmNDxv/U/3UIHfGb7ohvtFgerf/bYcYylj4i8E=
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27/go.mod h1:8S6ExnLprS0oIeA8ZlHkJUJ0BMpKqnRPws/S0jegTqQ=
github.com/aws/aws-sdk-go-v2/service/ssm v1.71.0 h1:Z5oWeBPXlKfYMctLgbnD0bXbhuqpxsV/KnZpsLLEMLQ=
github.com/aws/aws-sdk-go-v2/service/ssm v1.71.0/go.mod h1:xabzRvdbMs3FG9kU5M6RUOuCW6wXDkpdIqoXXNzA1nQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys=
Expand Down
77 changes: 77 additions & 0 deletions parameterstore/driver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Package driver defines the interface for SSM Parameter Store service implementations.
package driver

import "context"

// Parameter types, matching AWS SSM Parameter Store.
const (
// TypeString is a plain single-value string parameter.
TypeString = "String"
// TypeStringList is a comma-separated list parameter.
TypeStringList = "StringList"
// TypeSecureString is an (nominally) encrypted parameter. cloudemu stores
// the value as-is: there is no real KMS integration.
TypeSecureString = "SecureString"
)

// PutConfig describes a PutParameter request.
type PutConfig struct {
Name string
Value string
Type string
Description string
Overwrite bool
Tier string
DataType string
}

// Parameter is a single version of a stored parameter.
type Parameter struct {
Name string
Type string
Value string
Version int64
ARN string
DataType string
LastModified string
// Selector records how this value was addressed (e.g. ":3" for a version
// or ":prod" for a label), for the SDK's Selector response field.
Selector string
}

// ParameterMetadata describes a parameter without its value.
type ParameterMetadata struct {
Name string
Type string
Description string
Version int64
ARN string
Tier string
DataType string
LastModified string
LastModifiedUser string
}

// GetByPathInput describes a GetParametersByPath request.
type GetByPathInput struct {
Path string
Recursive bool
WithDecryption bool
}

// ParameterStore is the interface SSM Parameter Store provider implementations
// must satisfy.
type ParameterStore interface {
PutParameter(ctx context.Context, cfg PutConfig) (version int64, tier string, err error)
GetParameter(ctx context.Context, name string, withDecryption bool) (*Parameter, error)
GetParameters(ctx context.Context, names []string, withDecryption bool) (found []Parameter, invalid []string, err error)
GetParametersByPath(ctx context.Context, in GetByPathInput) ([]Parameter, error)
DeleteParameter(ctx context.Context, name string) error
DeleteParameters(ctx context.Context, names []string) (deleted, invalid []string, err error)
DescribeParameters(ctx context.Context) ([]ParameterMetadata, error)

// GetParameterHistory returns every version of a parameter, oldest first.
GetParameterHistory(ctx context.Context, name string) ([]Parameter, error)
// LabelParameterVersion attaches labels to a specific version (0 = latest).
LabelParameterVersion(ctx context.Context, name string, version int64, labels []string) (appliedVersion int64, invalid []string, err error)
}
236 changes: 236 additions & 0 deletions parameterstore/parameterstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Package parameterstore provides a portable SSM Parameter Store API with
// cross-cutting concerns (recorder, metrics, rate limiting, error injection,
// simulated latency) wrapping a driver.
package parameterstore

import (
"context"
"time"

"github.com/stackshy/cloudemu/inject"
"github.com/stackshy/cloudemu/metrics"
"github.com/stackshy/cloudemu/parameterstore/driver"
"github.com/stackshy/cloudemu/ratelimit"
"github.com/stackshy/cloudemu/recorder"
)

// ParameterStore is the portable Parameter Store type wrapping a driver with
// cross-cutting concerns.
type ParameterStore struct {
driver driver.ParameterStore
recorder *recorder.Recorder
metrics *metrics.Collector
limiter *ratelimit.Limiter
injector *inject.Injector
latency time.Duration
}

// New creates a new portable ParameterStore wrapping the given driver.
func New(d driver.ParameterStore, opts ...Option) *ParameterStore {
p := &ParameterStore{driver: d}
for _, opt := range opts {
opt(p)
}

return p
}

// Option configures a portable ParameterStore.
type Option func(*ParameterStore)

// WithRecorder sets the recorder.
func WithRecorder(r *recorder.Recorder) Option { return func(p *ParameterStore) { p.recorder = r } }

// WithMetrics sets the metrics collector.
func WithMetrics(m *metrics.Collector) Option { return func(p *ParameterStore) { p.metrics = m } }

// WithRateLimiter sets the rate limiter.
func WithRateLimiter(l *ratelimit.Limiter) Option { return func(p *ParameterStore) { p.limiter = l } }

// WithErrorInjection sets the error injector.
func WithErrorInjection(i *inject.Injector) Option { return func(p *ParameterStore) { p.injector = i } }

// WithLatency sets simulated latency.
func WithLatency(d time.Duration) Option { return func(p *ParameterStore) { p.latency = d } }

func (p *ParameterStore) do(_ context.Context, op string, input any, fn func() (any, error)) (any, error) {
start := time.Now()

if p.injector != nil {
if err := p.injector.Check("ssm", op); err != nil {
p.rec(op, input, nil, err, time.Since(start))
return nil, err
}
}

if p.limiter != nil {
if err := p.limiter.Allow(); err != nil {
p.rec(op, input, nil, err, time.Since(start))
return nil, err
}
}

if p.latency > 0 {
time.Sleep(p.latency)
}

out, err := fn()
dur := time.Since(start)

if p.metrics != nil {
labels := map[string]string{"service": "ssm", "operation": op}
p.metrics.Counter("calls_total", 1, labels)
p.metrics.Histogram("call_duration", dur, labels)

if err != nil {
p.metrics.Counter("errors_total", 1, labels)
}
}

p.rec(op, input, out, err, dur)

return out, err
}

func (p *ParameterStore) rec(op string, input, output any, err error, dur time.Duration) {
if p.recorder != nil {
p.recorder.Record("ssm", op, input, output, err, dur)
}
}

// PutParameter creates or updates a parameter, returning the new version and tier.
func (p *ParameterStore) PutParameter(ctx context.Context, cfg driver.PutConfig) (int64, string, error) {
type result struct {
version int64
tier string
}

out, err := p.do(ctx, "PutParameter", cfg, func() (any, error) {
v, tier, err := p.driver.PutParameter(ctx, cfg)
return result{v, tier}, err
})
if err != nil {
return 0, "", err
}

r := out.(result)

return r.version, r.tier, nil
}

// GetParameter retrieves a parameter by name (optionally with a version or label selector).
func (p *ParameterStore) GetParameter(ctx context.Context, name string, withDecryption bool) (*driver.Parameter, error) {
out, err := p.do(ctx, "GetParameter", name, func() (any, error) {
return p.driver.GetParameter(ctx, name, withDecryption)
})
if err != nil {
return nil, err
}

return out.(*driver.Parameter), nil
}

// GetParameters retrieves multiple parameters by name.
func (p *ParameterStore) GetParameters(ctx context.Context, names []string, withDecryption bool) ([]driver.Parameter, []string, error) {
type result struct {
found []driver.Parameter
invalid []string
}

out, err := p.do(ctx, "GetParameters", names, func() (any, error) {
found, invalid, err := p.driver.GetParameters(ctx, names, withDecryption)
return result{found, invalid}, err
})
if err != nil {
return nil, nil, err
}

r := out.(result)

return r.found, r.invalid, nil
}

// GetParametersByPath retrieves parameters under a hierarchical path.
func (p *ParameterStore) GetParametersByPath(ctx context.Context, in driver.GetByPathInput) ([]driver.Parameter, error) {
out, err := p.do(ctx, "GetParametersByPath", in, func() (any, error) {
return p.driver.GetParametersByPath(ctx, in)
})
if err != nil {
return nil, err
}

return out.([]driver.Parameter), nil
}

// DeleteParameter deletes a parameter by name.
func (p *ParameterStore) DeleteParameter(ctx context.Context, name string) error {
_, err := p.do(ctx, "DeleteParameter", name, func() (any, error) {
return nil, p.driver.DeleteParameter(ctx, name)
})

return err
}

// DeleteParameters deletes multiple parameters, returning deleted and invalid names.
func (p *ParameterStore) DeleteParameters(ctx context.Context, names []string) ([]string, []string, error) {
type result struct {
deleted []string
invalid []string
}

out, err := p.do(ctx, "DeleteParameters", names, func() (any, error) {
deleted, invalid, err := p.driver.DeleteParameters(ctx, names)
return result{deleted, invalid}, err
})
if err != nil {
return nil, nil, err
}

r := out.(result)

return r.deleted, r.invalid, nil
}

// DescribeParameters lists parameter metadata.
func (p *ParameterStore) DescribeParameters(ctx context.Context) ([]driver.ParameterMetadata, error) {
out, err := p.do(ctx, "DescribeParameters", nil, func() (any, error) {
return p.driver.DescribeParameters(ctx)
})
if err != nil {
return nil, err
}

return out.([]driver.ParameterMetadata), nil
}

// GetParameterHistory returns every version of a parameter, oldest first.
func (p *ParameterStore) GetParameterHistory(ctx context.Context, name string) ([]driver.Parameter, error) {
out, err := p.do(ctx, "GetParameterHistory", name, func() (any, error) {
return p.driver.GetParameterHistory(ctx, name)
})
if err != nil {
return nil, err
}

return out.([]driver.Parameter), nil
}

// LabelParameterVersion attaches labels to a specific version (0 = latest).
func (p *ParameterStore) LabelParameterVersion(ctx context.Context, name string, version int64, labels []string) (int64, []string, error) {
type result struct {
applied int64
invalid []string
}

out, err := p.do(ctx, "LabelParameterVersion", map[string]any{"name": name, "version": version}, func() (any, error) {
applied, invalid, err := p.driver.LabelParameterVersion(ctx, name, version, labels)
return result{applied, invalid}, err
})
if err != nil {
return 0, nil, err
}

r := out.(result)

return r.applied, r.invalid, nil
}
3 changes: 3 additions & 0 deletions providers/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/stackshy/cloudemu/providers/aws/secretsmanager"
"github.com/stackshy/cloudemu/providers/aws/sns"
"github.com/stackshy/cloudemu/providers/aws/sqs"
"github.com/stackshy/cloudemu/providers/aws/ssm"
"github.com/stackshy/cloudemu/providers/aws/vpc"
"github.com/stackshy/cloudemu/resourcediscovery"
)
Expand Down Expand Up @@ -50,6 +51,7 @@ type Provider struct {
EKS *eks.Mock
Bedrock *bedrock.Mock
SageMaker *sagemaker.Mock
SSM *ssm.Mock
ResourceDiscovery *resourcediscovery.Engine
}

Expand Down Expand Up @@ -78,6 +80,7 @@ func New(opts ...config.Option) *Provider {
EKS: eks.New(o),
Bedrock: bedrock.New(o),
SageMaker: sagemaker.New(o),
SSM: ssm.New(o),
}
p.EC2.SetMonitoring(p.CloudWatch)
p.S3.SetMonitoring(p.CloudWatch)
Expand Down
Loading
Loading