mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-09-20 04:54:46 -04:00
terraform: remove cloud loggers (#2892)
* terraform: remove cloud logging apps * internal/cloud: remove loggers * bootstrapper: remove logging * qemu-metadata-api: remove logging endpoint * docs: add instructions on how to get boot logs * bazel: tidy * docs: fix typo * cloud: remove unused types * Update go.mod Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * bazel: tidy * Update docs/docs/workflows/troubleshooting.md Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * Update docs/docs/workflows/troubleshooting.md Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * Update docs/docs/workflows/troubleshooting.md Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com> * docs: elaborate on how to get boot logs * bazel: tidy --------- Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
This commit is contained in:
parent
dde3430da8
commit
901edd420b
25 changed files with 12 additions and 1456 deletions
|
@ -3,10 +3,7 @@ load("//bazel/go:go_test.bzl", "go_test")
|
|||
|
||||
go_library(
|
||||
name = "aws",
|
||||
srcs = [
|
||||
"aws.go",
|
||||
"logger.go",
|
||||
],
|
||||
srcs = ["aws.go"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/cloud/aws",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
|
@ -17,24 +14,18 @@ go_library(
|
|||
"@com_github_aws_aws_sdk_go_v2//aws",
|
||||
"@com_github_aws_aws_sdk_go_v2_config//:config",
|
||||
"@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_cloudwatchlogs//:cloudwatchlogs",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_cloudwatchlogs//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_elasticloadbalancingv2//:elasticloadbalancingv2",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_elasticloadbalancingv2//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_resourcegroupstaggingapi//:resourcegroupstaggingapi",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_resourcegroupstaggingapi//types",
|
||||
"@io_k8s_utils//clock",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "aws_test",
|
||||
srcs = [
|
||||
"aws_test.go",
|
||||
"logger_test.go",
|
||||
],
|
||||
srcs = ["aws_test.go"],
|
||||
embed = [":aws"],
|
||||
deps = [
|
||||
"//internal/cloud",
|
||||
|
@ -42,8 +33,6 @@ go_test(
|
|||
"//internal/role",
|
||||
"@com_github_aws_aws_sdk_go_v2//aws",
|
||||
"@com_github_aws_aws_sdk_go_v2_feature_ec2_imds//:imds",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_cloudwatchlogs//:cloudwatchlogs",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_cloudwatchlogs//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_ec2//types",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_elasticloadbalancingv2//:elasticloadbalancingv2",
|
||||
|
@ -51,8 +40,5 @@ go_test(
|
|||
"@com_github_aws_aws_sdk_go_v2_service_resourcegroupstaggingapi//:resourcegroupstaggingapi",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_resourcegroupstaggingapi//types",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@io_k8s_utils//clock/testing",
|
||||
"@org_uber_go_goleak//:goleak",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -36,10 +36,6 @@ import (
|
|||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
)
|
||||
|
||||
const (
|
||||
tagName = "Name"
|
||||
)
|
||||
|
||||
type resourceAPI interface {
|
||||
GetResources(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error)
|
||||
}
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
|
||||
logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// Logger is a Cloud Logger for AWS.
|
||||
// Log messages are collected and periodically flushed to AWS Cloudwatch Logs.
|
||||
type Logger struct {
|
||||
api logAPI
|
||||
|
||||
ec2API ec2API
|
||||
imdsAPI imdsAPI
|
||||
|
||||
groupName string
|
||||
streamName string
|
||||
|
||||
logs []types.InputLogEvent
|
||||
sequenceToken *string
|
||||
|
||||
mux sync.Mutex
|
||||
interval time.Duration
|
||||
clock clock.WithTicker
|
||||
wg sync.WaitGroup
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewLogger creates a new Cloud Logger for AWS.
|
||||
func NewLogger(ctx context.Context) (*Logger, error) {
|
||||
cfg, err := config.LoadDefaultConfig(ctx, config.WithEC2IMDSRegion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := logs.NewFromConfig(cfg)
|
||||
|
||||
l := &Logger{
|
||||
api: client,
|
||||
ec2API: ec2.NewFromConfig(cfg),
|
||||
imdsAPI: imds.NewFromConfig(cfg),
|
||||
interval: time.Second,
|
||||
clock: clock.RealClock{},
|
||||
wg: sync.WaitGroup{},
|
||||
stopCh: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
if err := l.createStream(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.flushLoop()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Disclose adds a message to the log queue.
|
||||
// The messages are flushed periodically to AWS Cloudwatch Logs.
|
||||
func (l *Logger) Disclose(msg string) {
|
||||
l.mux.Lock()
|
||||
defer l.mux.Unlock()
|
||||
l.logs = append(l.logs, types.InputLogEvent{
|
||||
Message: aws.String(msg),
|
||||
Timestamp: aws.Int64(l.clock.Now().UnixMilli()),
|
||||
})
|
||||
}
|
||||
|
||||
// Close flushes the logs a final time and stops the flush loop.
|
||||
func (l *Logger) Close() error {
|
||||
l.stopCh <- struct{}{}
|
||||
l.wg.Wait()
|
||||
return l.flushLogs()
|
||||
}
|
||||
|
||||
// flushLogs flushes the aggregated log messages to AWS Cloudwatch Logs.
|
||||
func (l *Logger) flushLogs() error {
|
||||
// make sure only one flush operation is running at a time
|
||||
l.mux.Lock()
|
||||
defer l.mux.Unlock()
|
||||
|
||||
if len(l.logs) == 0 {
|
||||
return nil // no logs to flush
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
logRequest := &logs.PutLogEventsInput{
|
||||
LogEvents: l.logs,
|
||||
LogGroupName: &l.groupName,
|
||||
LogStreamName: &l.streamName,
|
||||
SequenceToken: l.sequenceToken,
|
||||
}
|
||||
|
||||
for res, err := l.api.PutLogEvents(ctx, logRequest); ; res, err = l.api.PutLogEvents(ctx, logRequest) {
|
||||
if err == nil {
|
||||
l.sequenceToken = res.NextSequenceToken
|
||||
l.logs = nil
|
||||
return nil
|
||||
}
|
||||
// If the flush operation was called on a pre-existing stream,
|
||||
// or another operation sent logs to the same stream,
|
||||
// the sequence token may not be set correctly.
|
||||
// We can retrieve the correct sequence token from the error message.
|
||||
var sequenceErr *types.InvalidSequenceTokenException
|
||||
if !errors.As(err, &sequenceErr) {
|
||||
return err
|
||||
}
|
||||
logRequest.SequenceToken = sequenceErr.ExpectedSequenceToken
|
||||
}
|
||||
}
|
||||
|
||||
// flushLoop periodically flushes the logs to AWS Cloudwatch Logs.
|
||||
func (l *Logger) flushLoop() {
|
||||
l.wg.Add(1)
|
||||
ticker := l.clock.NewTicker(l.interval)
|
||||
|
||||
go func() {
|
||||
defer l.wg.Done()
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
_ = l.flushLogs()
|
||||
select {
|
||||
case <-ticker.C():
|
||||
case <-l.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// createStream creates a new log stream in AWS Cloudwatch Logs.
|
||||
func (l *Logger) createStream(ctx context.Context) error {
|
||||
name, uid, err := l.getNameAndUID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.streamName = name
|
||||
|
||||
// find log group with matching Constellation UID
|
||||
describeInput := &logs.DescribeLogGroupsInput{}
|
||||
for res, err := l.api.DescribeLogGroups(ctx, describeInput); ; res, err = l.api.DescribeLogGroups(ctx, describeInput) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, group := range res.LogGroups {
|
||||
tags, err := l.api.ListTagsLogGroup(ctx, &logs.ListTagsLogGroupInput{LogGroupName: group.LogGroupName})
|
||||
if err != nil {
|
||||
continue // we may not have permission to read the tags of a log group outside the Constellation scope
|
||||
}
|
||||
if tags.Tags[cloud.TagUID] == uid {
|
||||
l.groupName = *group.LogGroupName
|
||||
res.NextToken = nil // stop pagination
|
||||
break
|
||||
}
|
||||
}
|
||||
if res.NextToken == nil {
|
||||
break
|
||||
}
|
||||
describeInput.NextToken = res.NextToken
|
||||
}
|
||||
if l.groupName == "" {
|
||||
return fmt.Errorf("failed to find log group for UID %s", uid)
|
||||
}
|
||||
|
||||
// create or use existing log stream
|
||||
if _, err := l.api.CreateLogStream(ctx, &logs.CreateLogStreamInput{
|
||||
LogGroupName: &l.groupName,
|
||||
LogStreamName: &l.streamName,
|
||||
}); err != nil {
|
||||
// Ignore error if the stream already exists
|
||||
var createErr *types.ResourceAlreadyExistsException
|
||||
if !errors.As(err, &createErr) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Logger) getNameAndUID(ctx context.Context) (string, string, error) {
|
||||
identity, err := l.imdsAPI.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("retrieving instance identity: %w", err)
|
||||
}
|
||||
|
||||
out, err := l.ec2API.DescribeInstances(ctx, &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []string{identity.InstanceID},
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("descibing instances: %w", err)
|
||||
}
|
||||
|
||||
if len(out.Reservations) != 1 || len(out.Reservations[0].Instances) != 1 {
|
||||
return "", "", fmt.Errorf("expected 1 instance, got %d", len(out.Reservations[0].Instances))
|
||||
}
|
||||
|
||||
uid, err := findTag(out.Reservations[0].Instances[0].Tags, cloud.TagUID)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("finding tag %s: %w", cloud.TagUID, err)
|
||||
}
|
||||
|
||||
return identity.InstanceID, uid, err
|
||||
}
|
||||
|
||||
type logAPI interface {
|
||||
CreateLogStream(context.Context, *logs.CreateLogStreamInput, ...func(*logs.Options)) (*logs.CreateLogStreamOutput, error)
|
||||
DescribeLogGroups(context.Context, *logs.DescribeLogGroupsInput, ...func(*logs.Options)) (*logs.DescribeLogGroupsOutput, error)
|
||||
ListTagsLogGroup(context.Context, *logs.ListTagsLogGroupInput, ...func(*logs.Options)) (*logs.ListTagsLogGroupOutput, error)
|
||||
PutLogEvents(context.Context, *logs.PutLogEventsInput, ...func(*logs.Options)) (*logs.PutLogEventsOutput, error)
|
||||
}
|
|
@ -1,554 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
|
||||
logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
|
||||
cloudwatchtypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
testclock "k8s.io/utils/clock/testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"))
|
||||
}
|
||||
|
||||
func TestCreateStream(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
imdsAPI *stubIMDS
|
||||
ec2API *stubEC2
|
||||
logs *stubLogs
|
||||
wantGroup string
|
||||
wantStream string
|
||||
wantErr bool
|
||||
}{
|
||||
"success new stream minimal": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{LogGroupName: aws.String("test-group")},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
},
|
||||
wantStream: "test-instance",
|
||||
wantGroup: "test-group",
|
||||
},
|
||||
"success one group of many": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{
|
||||
LogGroupName: aws.String("random-group"),
|
||||
},
|
||||
{
|
||||
LogGroupName: aws.String("other-group"),
|
||||
},
|
||||
},
|
||||
NextToken: aws.String("next"),
|
||||
},
|
||||
describeRes2: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{
|
||||
LogGroupName: aws.String("another-group"),
|
||||
},
|
||||
{
|
||||
LogGroupName: aws.String("test-group"),
|
||||
},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{
|
||||
"random-group": {
|
||||
"some-tag": "random-tag",
|
||||
},
|
||||
"other-group": {
|
||||
cloud.TagUID: "other-uid",
|
||||
},
|
||||
"another-group": {
|
||||
"some-tag": "uid",
|
||||
},
|
||||
"test-group": {
|
||||
cloud.TagUID: "uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantStream: "test-instance",
|
||||
wantGroup: "test-group",
|
||||
},
|
||||
"success stream exists": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{LogGroupName: aws.String("test-group")},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
createErr: &cloudwatchtypes.ResourceAlreadyExistsException{},
|
||||
},
|
||||
wantStream: "test-instance",
|
||||
wantGroup: "test-group",
|
||||
},
|
||||
"create stream error": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{LogGroupName: aws.String("test-group")},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
createErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"missing uid tag": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{LogGroupName: aws.String("test-group")},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"missing identity document": {
|
||||
imdsAPI: &stubIMDS{
|
||||
getInstanceIdentityDocumentErr: assert.AnError,
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{
|
||||
LogGroups: []cloudwatchtypes.LogGroup{
|
||||
{LogGroupName: aws.String("test-group")},
|
||||
},
|
||||
},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"describe groups error": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeErr: someErr,
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no matching groups": {
|
||||
imdsAPI: &stubIMDS{
|
||||
instanceDocumentResp: &imds.GetInstanceIdentityDocumentOutput{
|
||||
InstanceIdentityDocument: imds.InstanceIdentityDocument{
|
||||
InstanceID: "test-instance",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2API: &stubEC2{
|
||||
selfInstance: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []ec2types.Reservation{
|
||||
{
|
||||
Instances: []ec2types.Instance{
|
||||
{
|
||||
InstanceId: aws.String("test-instance"),
|
||||
Tags: []ec2types.Tag{
|
||||
{
|
||||
Key: aws.String(tagName),
|
||||
Value: aws.String("test-instance"),
|
||||
},
|
||||
{
|
||||
Key: aws.String(cloud.TagUID),
|
||||
Value: aws.String("uid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
logs: &stubLogs{
|
||||
describeRes1: &logs.DescribeLogGroupsOutput{},
|
||||
listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
l := &Logger{
|
||||
api: tc.logs,
|
||||
imdsAPI: tc.imdsAPI,
|
||||
ec2API: tc.ec2API,
|
||||
}
|
||||
|
||||
err := l.createStream(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantGroup, l.groupName)
|
||||
assert.Equal(tc.wantStream, l.streamName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogging(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
logAPI := &stubLogs{}
|
||||
|
||||
l := &Logger{
|
||||
api: logAPI,
|
||||
interval: 1 * time.Millisecond,
|
||||
clock: testclock.NewFakeClock(time.Time{}),
|
||||
}
|
||||
|
||||
l.Disclose("msg")
|
||||
l.Disclose("msg")
|
||||
// no logs until we flush to the API
|
||||
assert.Len(logAPI.logs, 0)
|
||||
|
||||
// flush
|
||||
require.NoError(l.flushLogs())
|
||||
assert.Len(logAPI.logs, 2)
|
||||
|
||||
// flushing doesn't do anything if there are no logs
|
||||
require.NoError(l.flushLogs())
|
||||
assert.Len(logAPI.logs, 2)
|
||||
|
||||
// if we flush with an incorrect sequence token,
|
||||
// we should get a new sequence token and retry
|
||||
logAPI.logSequenceToken = 15
|
||||
l.Disclose("msg")
|
||||
require.NoError(l.flushLogs())
|
||||
assert.Len(logAPI.logs, 3)
|
||||
|
||||
logAPI.putErr = errors.New("failed")
|
||||
l.Disclose("msg")
|
||||
assert.Error(l.flushLogs())
|
||||
}
|
||||
|
||||
func TestFlushLoop(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
logAPI := &stubLogs{}
|
||||
clock := testclock.NewFakeClock(time.Time{})
|
||||
|
||||
l := &Logger{
|
||||
api: logAPI,
|
||||
interval: 1 * time.Second,
|
||||
clock: clock,
|
||||
stopCh: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
l.Disclose("msg")
|
||||
l.Disclose("msg")
|
||||
|
||||
l.flushLoop()
|
||||
clock.Step(1 * time.Second)
|
||||
require.NoError(l.Close())
|
||||
assert.Len(logAPI.logs, 2)
|
||||
}
|
||||
|
||||
func TestConcurrency(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
l := &Logger{
|
||||
api: &stubLogs{},
|
||||
interval: 1 * time.Second,
|
||||
clock: testclock.NewFakeClock(time.Time{}),
|
||||
stopCh: make(chan struct{}, 1),
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(100)
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
l.Disclose("msg")
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
assert.Len(l.logs, 100)
|
||||
require.NoError(l.flushLogs())
|
||||
assert.Len(l.logs, 0)
|
||||
|
||||
wg.Add(100)
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
l.Disclose("msg")
|
||||
require.NoError(l.flushLogs())
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
assert.Len(l.logs, 0)
|
||||
}
|
||||
|
||||
type stubLogs struct {
|
||||
createErr error
|
||||
describeErr error
|
||||
describeRes1 *logs.DescribeLogGroupsOutput
|
||||
describeRes2 *logs.DescribeLogGroupsOutput
|
||||
listTagsErr error
|
||||
listTags map[string]map[string]string
|
||||
putErr error
|
||||
logSequenceToken int
|
||||
logs []cloudwatchtypes.InputLogEvent
|
||||
}
|
||||
|
||||
func (s *stubLogs) CreateLogStream(context.Context, *logs.CreateLogStreamInput, ...func(*logs.Options)) (*logs.CreateLogStreamOutput, error) {
|
||||
return nil, s.createErr
|
||||
}
|
||||
|
||||
func (s *stubLogs) DescribeLogGroups(_ context.Context, in *logs.DescribeLogGroupsInput, _ ...func(*logs.Options)) (*logs.DescribeLogGroupsOutput, error) {
|
||||
if in.NextToken == nil {
|
||||
return s.describeRes1, s.describeErr
|
||||
}
|
||||
return s.describeRes2, s.describeErr
|
||||
}
|
||||
|
||||
func (s *stubLogs) ListTagsLogGroup(_ context.Context, in *logs.ListTagsLogGroupInput, _ ...func(*logs.Options)) (*logs.ListTagsLogGroupOutput, error) {
|
||||
return &logs.ListTagsLogGroupOutput{Tags: s.listTags[*in.LogGroupName]}, s.listTagsErr
|
||||
}
|
||||
|
||||
func (s *stubLogs) PutLogEvents(_ context.Context, in *logs.PutLogEventsInput, _ ...func(*logs.Options)) (*logs.PutLogEventsOutput, error) {
|
||||
if s.putErr != nil {
|
||||
return nil, s.putErr
|
||||
}
|
||||
if in.SequenceToken == nil || *in.SequenceToken == "" {
|
||||
in.SequenceToken = aws.String("0")
|
||||
}
|
||||
gotSeq, err := strconv.Atoi(*in.SequenceToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if gotSeq != s.logSequenceToken {
|
||||
return nil, &cloudwatchtypes.InvalidSequenceTokenException{ExpectedSequenceToken: aws.String(strconv.Itoa(s.logSequenceToken))}
|
||||
}
|
||||
|
||||
s.logs = append(s.logs, in.LogEvents...)
|
||||
s.logSequenceToken++
|
||||
|
||||
return &logs.PutLogEventsOutput{NextSequenceToken: aws.String(strconv.Itoa(s.logSequenceToken))}, nil
|
||||
}
|
|
@ -7,7 +7,6 @@ go_library(
|
|||
"azure.go",
|
||||
"imds.go",
|
||||
"interface.go",
|
||||
"logger.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/cloud/azure",
|
||||
visibility = ["//:__subpackages__"],
|
||||
|
@ -20,10 +19,8 @@ go_library(
|
|||
"//internal/role",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_azcore//runtime",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_azidentity//:azidentity",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_applicationinsights_armapplicationinsights//:armapplicationinsights",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_compute_armcompute_v5//:armcompute",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_network_armnetwork_v5//:armnetwork",
|
||||
"@com_github_microsoft_applicationinsights_go//appinsights",
|
||||
"@io_k8s_kubernetes//pkg/util/iptables",
|
||||
"@io_k8s_utils//exec",
|
||||
"@org_uber_go_zap//:zap",
|
||||
|
@ -35,7 +32,6 @@ go_test(
|
|||
srcs = [
|
||||
"azure_test.go",
|
||||
"imds_test.go",
|
||||
"logger_test.go",
|
||||
],
|
||||
embed = [":azure"],
|
||||
deps = [
|
||||
|
@ -44,10 +40,8 @@ go_test(
|
|||
"//internal/role",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_azcore//runtime",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_azcore//to",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_applicationinsights_armapplicationinsights//:armapplicationinsights",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_compute_armcompute_v5//:armcompute",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_network_armnetwork_v5//:armnetwork",
|
||||
"@com_github_azure_go_autorest_autorest_to//:to",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@org_golang_google_grpc//test/bufconn",
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v5"
|
||||
)
|
||||
|
@ -74,9 +73,3 @@ type loadBalancerAPI interface {
|
|||
NewListPager(resourceGroupName string, options *armnetwork.LoadBalancersClientListOptions,
|
||||
) *runtime.Pager[armnetwork.LoadBalancersClientListResponse]
|
||||
}
|
||||
|
||||
type applicationInsightsAPI interface {
|
||||
NewListByResourceGroupPager(resourceGroupName string,
|
||||
options *armapplicationinsights.ComponentsClientListByResourceGroupOptions,
|
||||
) *runtime.Pager[armapplicationinsights.ComponentsClientListByResourceGroupResponse]
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/microsoft/ApplicationInsights-Go/appinsights"
|
||||
)
|
||||
|
||||
// Logger implements CloudLogger interface for Azure to Disclose early boot
|
||||
// logs into Azure's App Insights service.
|
||||
type Logger struct {
|
||||
client appinsights.TelemetryClient
|
||||
}
|
||||
|
||||
// NewLogger creates a new client to store information in Azure Application Insights
|
||||
// https://github.com/Microsoft/ApplicationInsights-go
|
||||
func NewLogger(ctx context.Context) (*Logger, error) {
|
||||
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading credentials: %w", err)
|
||||
}
|
||||
imdsAPI := &IMDSClient{
|
||||
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
|
||||
}
|
||||
subscriptionID, err := imdsAPI.subscriptionID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving subscription ID: %w", err)
|
||||
}
|
||||
appInsightAPI, err := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setting up insights API client. %w", err)
|
||||
}
|
||||
|
||||
instrumentationKey, err := getAppInsightsKey(ctx, imdsAPI, appInsightAPI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting app insights instrumentation key: %w", err)
|
||||
}
|
||||
|
||||
client := appinsights.NewTelemetryClient(instrumentationKey)
|
||||
|
||||
name, err := imdsAPI.name(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving instance name: %w", err)
|
||||
}
|
||||
client.Context().CommonProperties["instance-name"] = name
|
||||
|
||||
return &Logger{client: client}, nil
|
||||
}
|
||||
|
||||
// Disclose stores log information in Azure Application Insights!
|
||||
// Do **NOT** log sensitive information!
|
||||
func (l *Logger) Disclose(msg string) {
|
||||
l.client.Track(appinsights.NewTraceTelemetry(msg, appinsights.Information))
|
||||
}
|
||||
|
||||
// Close blocks until all information are written to cloud API.
|
||||
func (l *Logger) Close() error {
|
||||
<-l.client.Channel().Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAppInsightsKey returns a instrumentation key needed to set up cloud logging on Azure.
|
||||
// The key is retrieved from the resource group of the instance the function is called from.
|
||||
func getAppInsightsKey(ctx context.Context, imdsAPI imdsAPI, appInsightAPI applicationInsightsAPI) (string, error) {
|
||||
resourceGroup, err := imdsAPI.resourceGroup(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uid, err := imdsAPI.uid(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pager := appInsightAPI.NewListByResourceGroupPager(resourceGroup, nil)
|
||||
for pager.More() {
|
||||
page, err := pager.NextPage(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving application insights: %w", err)
|
||||
}
|
||||
|
||||
for _, component := range page.Value {
|
||||
if component == nil || component.Tags == nil ||
|
||||
component.Tags[cloud.TagUID] == nil || *component.Tags[cloud.TagUID] != uid {
|
||||
continue
|
||||
}
|
||||
|
||||
if component.Properties == nil || component.Properties.InstrumentationKey == nil {
|
||||
return "", errors.New("unable to get instrumentation key")
|
||||
}
|
||||
return *component.Properties.InstrumentationKey, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("could not find correctly tagged application insights")
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetAppInsightsKey(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
goodAppInsights := armapplicationinsights.Component{
|
||||
Tags: map[string]*string{
|
||||
cloud.TagUID: to.StringPtr("uid"),
|
||||
},
|
||||
Properties: &armapplicationinsights.ComponentProperties{
|
||||
InstrumentationKey: to.StringPtr("key"),
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds *stubIMDSAPI
|
||||
appInsights *stubApplicationsInsightsAPI
|
||||
wantKey string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{goodAppInsights}},
|
||||
},
|
||||
wantKey: "key",
|
||||
},
|
||||
"multiple apps": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{
|
||||
{
|
||||
Tags: map[string]*string{
|
||||
cloud.TagUID: to.StringPtr("different-uid"),
|
||||
},
|
||||
Properties: &armapplicationinsights.ComponentProperties{
|
||||
InstrumentationKey: to.StringPtr("different-key"),
|
||||
},
|
||||
},
|
||||
goodAppInsights,
|
||||
}},
|
||||
},
|
||||
wantKey: "key",
|
||||
},
|
||||
"missing properties": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{
|
||||
{
|
||||
Tags: map[string]*string{
|
||||
cloud.TagUID: to.StringPtr("uid"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no app with matching uid": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{
|
||||
{
|
||||
Tags: map[string]*string{
|
||||
cloud.TagUID: to.StringPtr("different-uid"),
|
||||
},
|
||||
Properties: &armapplicationinsights.ComponentProperties{
|
||||
InstrumentationKey: to.StringPtr("different-key"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"imds resource group error": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupErr: someErr,
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{goodAppInsights}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"imds uid error": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidErr: someErr,
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{list: []armapplicationinsights.Component{goodAppInsights}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"app insights list error": {
|
||||
imds: &stubIMDSAPI{
|
||||
resourceGroupVal: "resource-group",
|
||||
uidVal: "uid",
|
||||
},
|
||||
appInsights: &stubApplicationsInsightsAPI{
|
||||
pager: &stubApplicationKeyPager{fetchErr: someErr},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
key, err := getAppInsightsKey(context.Background(), tc.imds, tc.appInsights)
|
||||
if tc.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantKey, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubApplicationKeyPager struct {
|
||||
list []armapplicationinsights.Component
|
||||
fetchErr error
|
||||
more bool
|
||||
}
|
||||
|
||||
func (p *stubApplicationKeyPager) moreFunc() func(armapplicationinsights.ComponentsClientListByResourceGroupResponse) bool {
|
||||
return func(armapplicationinsights.ComponentsClientListByResourceGroupResponse) bool {
|
||||
return p.more
|
||||
}
|
||||
}
|
||||
|
||||
func (p *stubApplicationKeyPager) fetcherFunc() func(context.Context, *armapplicationinsights.ComponentsClientListByResourceGroupResponse,
|
||||
) (armapplicationinsights.ComponentsClientListByResourceGroupResponse, error) {
|
||||
return func(context.Context, *armapplicationinsights.ComponentsClientListByResourceGroupResponse) (armapplicationinsights.ComponentsClientListByResourceGroupResponse, error) {
|
||||
page := make([]*armapplicationinsights.Component, len(p.list))
|
||||
for i := range p.list {
|
||||
page[i] = &p.list[i]
|
||||
}
|
||||
return armapplicationinsights.ComponentsClientListByResourceGroupResponse{
|
||||
ComponentListResult: armapplicationinsights.ComponentListResult{
|
||||
Value: page,
|
||||
},
|
||||
}, p.fetchErr
|
||||
}
|
||||
}
|
||||
|
||||
type stubApplicationsInsightsAPI struct {
|
||||
pager *stubApplicationKeyPager
|
||||
}
|
||||
|
||||
func (a *stubApplicationsInsightsAPI) NewListByResourceGroupPager(_ string, _ *armapplicationinsights.ComponentsClientListByResourceGroupOptions,
|
||||
) *runtime.Pager[armapplicationinsights.ComponentsClientListByResourceGroupResponse] {
|
||||
return runtime.NewPager(runtime.PagingHandler[armapplicationinsights.ComponentsClientListByResourceGroupResponse]{
|
||||
More: a.pager.moreFunc(),
|
||||
Fetcher: a.pager.fetcherFunc(),
|
||||
})
|
||||
}
|
|
@ -6,7 +6,6 @@ go_library(
|
|||
srcs = [
|
||||
"gcp.go",
|
||||
"interface.go",
|
||||
"logger.go",
|
||||
"wrappers.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/cloud/gcp",
|
||||
|
@ -20,7 +19,6 @@ go_library(
|
|||
"@com_google_cloud_go_compute//apiv1",
|
||||
"@com_google_cloud_go_compute//apiv1/computepb",
|
||||
"@com_google_cloud_go_compute_metadata//:metadata",
|
||||
"@com_google_cloud_go_logging//:logging",
|
||||
"@org_golang_google_api//iterator",
|
||||
"@org_golang_google_protobuf//proto",
|
||||
],
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"cloud.google.com/go/logging"
|
||||
)
|
||||
|
||||
// Logger logs to GCP cloud logging. Do not use to log sensitive information.
|
||||
type Logger struct {
|
||||
client *logging.Client
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewLogger creates a new Cloud Logger for GCP.
|
||||
// https://cloud.google.com/logging/docs/setup/go
|
||||
func NewLogger(ctx context.Context, logName string) (*Logger, error) {
|
||||
projectID, err := metadata.NewClient(nil).ProjectID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving project ID from imds: %w", err)
|
||||
}
|
||||
|
||||
client, err := logging.NewClient(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger := client.Logger(logName).StandardLogger(logging.Info)
|
||||
|
||||
return &Logger{
|
||||
client: client,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Disclose stores log information in GCP Cloud Logging! Do **NOT** log sensitive
|
||||
// information!
|
||||
func (l *Logger) Disclose(msg string) {
|
||||
l.logger.Println(msg)
|
||||
}
|
||||
|
||||
// Close waits for all buffer to be written.
|
||||
func (l *Logger) Close() error {
|
||||
return l.client.Close()
|
||||
}
|
|
@ -2,10 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|||
|
||||
go_library(
|
||||
name = "qemu",
|
||||
srcs = [
|
||||
"logger.go",
|
||||
"qemu.go",
|
||||
],
|
||||
srcs = ["qemu.go"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/cloud/qemu",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Logger is a Cloud Logger for QEMU.
|
||||
type Logger struct{}
|
||||
|
||||
// NewLogger creates a new Cloud Logger for QEMU.
|
||||
func NewLogger() *Logger {
|
||||
return &Logger{}
|
||||
}
|
||||
|
||||
// Disclose writes log information to QEMU's cloud log.
|
||||
// This is done by sending a POST request to the QEMU's metadata endpoint.
|
||||
func (l *Logger) Disclose(msg string) {
|
||||
url := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: qemuMetadataEndpoint,
|
||||
Path: "/log",
|
||||
}
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, url.String(), strings.NewReader(msg))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Close is a no-op.
|
||||
func (l *Logger) Close() error {
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue