diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc02ba8a..8eb139b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,11 @@ jobs: - name: Go Init uses: ./.github/actions/go_init + - name: find deadcode + run: | + go install golang.org/x/tools/cmd/deadcode@latest + deadcode . | tee out && [ ! -s out ] + # get .golangci.yml from github.com/overmindtech/golangci-lint_config - name: Get .golangci.yml from github.com/overmindtech/golangci-lint_configs run: | diff --git a/.gitignore b/.gitignore index 1c21a073..bb704fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ terraform.tfstate terraform.tfstate.backup /tmp/ /node_modules +__debug_bin* diff --git a/cmd/auth_client.go b/cmd/auth_client.go index 501669e3..75eee193 100644 --- a/cmd/auth_client.go +++ b/cmd/auth_client.go @@ -11,14 +11,6 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -// AuthenticatedApiKeyClient Returns an apikey client that uses the auth -// embedded in the context and otel instrumentation -func AuthenticatedApiKeyClient(ctx context.Context, oi sdp.OvermindInstance) sdpconnect.ApiKeyServiceClient { - httpClient := NewAuthenticatedClient(ctx, otelhttp.DefaultClient) - log.WithContext(ctx).WithField("apiUrl", oi.ApiUrl).Debug("Connecting to overmind apikeys API (pre-authenticated)") - return sdpconnect.NewApiKeyServiceClient(httpClient, oi.ApiUrl.String()) -} - // UnauthenticatedApiKeyClient Returns an apikey client with otel instrumentation // but no authentication. Can only be used for ExchangeKeyForToken func UnauthenticatedApiKeyClient(ctx context.Context, oi sdp.OvermindInstance) sdpconnect.ApiKeyServiceClient { diff --git a/cmd/changes_submit_plan.go b/cmd/changes_submit_plan.go index df08c19f..48d46fc1 100644 --- a/cmd/changes_submit_plan.go +++ b/cmd/changes_submit_plan.go @@ -49,53 +49,6 @@ type plannedChangeGroups struct { unsupported map[string][]*sdp.MappedItemDiff } -func (g *plannedChangeGroups) NumUnsupportedChanges() int { - num := 0 - - for _, v := range g.unsupported { - num += len(v) - } - - return num -} - -func (g *plannedChangeGroups) NumSupportedChanges() int { - num := 0 - - for _, v := range g.supported { - num += len(v) - } - - return num -} - -func (g *plannedChangeGroups) MappedItemDiffs() []*sdp.MappedItemDiff { - mappedItemDiffs := make([]*sdp.MappedItemDiff, 0) - - for _, v := range g.supported { - mappedItemDiffs = append(mappedItemDiffs, v...) - } - - for _, v := range g.unsupported { - mappedItemDiffs = append(mappedItemDiffs, v...) - } - - return mappedItemDiffs -} - -// Add the specified item to the appropriate type group in the supported or unsupported section, based of whether it has a mapping query -func (g *plannedChangeGroups) Add(typ string, item *sdp.MappedItemDiff) { - groups := g.supported - if item.GetMappingQuery() == nil { - groups = g.unsupported - } - list, ok := groups[typ] - if !ok { - list = make([]*sdp.MappedItemDiff, 0) - } - groups[typ] = append(list, item) -} - func changeTitle(arg string) string { if arg != "" { // easy, return the user's choice diff --git a/cmd/explore.go b/cmd/explore.go index 14cf3258..1255aab7 100644 --- a/cmd/explore.go +++ b/cmd/explore.go @@ -61,13 +61,19 @@ func StartLocalSources(ctx context.Context, oi sdp.OvermindInstance, token *oaut p := pool.NewWithResults[*discovery.Engine]().WithErrors() p.Go(func() (*discovery.Engine, error) { + ec := discovery.EngineConfig{ + Version: fmt.Sprintf("cli-%v", cliVersion), + EngineType: "cli-stdlib", + SourceName: fmt.Sprintf("stdlib-source-%v", hostname), + SourceUUID: uuid.New(), + App: oi.ApiUrl.Host, + ApiKey: token.AccessToken, + MaxParallelExecutions: 2_000, + } stdlibEngine, err := stdlibSource.InitializeEngine( + &ec, natsOptions, - fmt.Sprintf("stdlib-source-%v", hostname), - fmt.Sprintf("cli-%v", cliVersion), - uuid.New(), heartbeatOptions, - 2_000, true, ) if err != nil { @@ -122,14 +128,20 @@ func StartLocalSources(ctx context.Context, oi sdp.OvermindInstance, token *oaut statusArea.Println("Using default AWS CLI config. No AWS terraform providers found.") configs = append(configs, userConfig) } + ec := discovery.EngineConfig{ + EngineType: "cli-aws", + Version: fmt.Sprintf("cli-%v", cliVersion), + SourceName: fmt.Sprintf("aws-source-%v", hostname), + SourceUUID: uuid.New(), + App: oi.ApiUrl.Host, + ApiKey: token.AccessToken, + MaxParallelExecutions: 2_000, + } awsEngine, err := proc.InitializeAwsSourceEngine( ctx, - fmt.Sprintf("aws-source-%v", hostname), - fmt.Sprintf("cli-%v", cliVersion), - uuid.New(), + &ec, natsOptions, heartbeatOptions, - 2_000, 1, // Don't retry as we want the user to get notified immediately configs..., ) diff --git a/cmd/logging.go b/cmd/logging.go index 5d3a5727..df40d96b 100644 --- a/cmd/logging.go +++ b/cmd/logging.go @@ -35,25 +35,7 @@ type TextStyle struct { underlying chalk.TextStyle } -func (t TextStyle) TextStyle(val string) string { - if !tty { - // Don't style if we're not in a TTY - return val - } - - return t.underlying.TextStyle(val) -} - // A type that wraps chalk.Color but adds detections for if we're in a TTY type Color struct { underlying chalk.Color } - -func (c Color) Color(val string) string { - if !tty { - // Don't style if we're not in a TTY - return val - } - - return c.underlying.Color(val) -} diff --git a/cmd/theme.go b/cmd/theme.go index 7d7dfd4e..dec84b4a 100644 --- a/cmd/theme.go +++ b/cmd/theme.go @@ -3,12 +3,10 @@ package cmd import ( _ "embed" "fmt" - "strings" "github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour/ansi" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/wordwrap" ) // constrain the maximum terminal width to avoid readability issues with too @@ -320,14 +318,6 @@ func styleH1() lipgloss.Style { PaddingRight(2) } -func styleH2() lipgloss.Style { - return lipgloss.NewStyle(). - Foreground(ColorPalette.BgMain). - Bold(true). - PaddingLeft(2). - PaddingRight(2) -} - // markdownToString converts the markdown string to a string containing ANSI // formatting sequences with at most maxWidth visible characters per line. Set // maxWidth to zero to use the underlying library's default. @@ -353,22 +343,6 @@ func markdownToString(maxWidth int, markdown string) string { return out } -// wrap ensures that the text is wrapped to the given width and everything but -// the first line is indented by the requested amount. Consider that the current -// implementation is very naive and for large indent values, the first line -// might not be wrapped too early. -// -// Indent is ignored when the requested indent is larger than the current width. -// This is expected to only occur in edge cases, e.g. when the terminal is -// resiyed to very narrow. -func wrap(s string, width, indent int) string { - if indent > width { - indent = 0 - } - - return strings.ReplaceAll(wordwrap.String(s, width-indent), "\n", "\n"+strings.Repeat(" ", indent)) -} - func OkSymbol() string { if IsConhost() { return "OK" diff --git a/go.mod b/go.mod index 118153de..aefd1934 100644 --- a/go.mod +++ b/go.mod @@ -22,12 +22,12 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.3-0.20240912151726-82936c5ea257 - github.com/overmindtech/aws-source v0.0.0-20241025011151-44d13fa1e1e0 - github.com/overmindtech/discovery v0.29.2 + github.com/overmindtech/aws-source v0.0.0-20241030131805-de96f8d7d8fb + github.com/overmindtech/discovery v0.30.0 github.com/overmindtech/k8s-source v0.8.1-0.20241025005632-15223645e039 github.com/overmindtech/pterm v0.0.0-20240919144758-04d94ccb2297 github.com/overmindtech/sdp-go v0.96.0 - github.com/overmindtech/stdlib-source v0.0.0-20241018075207-50872fb39aad + github.com/overmindtech/stdlib-source v0.0.0-20241030163048-1250dcbf584a github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/sirupsen/logrus v1.9.3 github.com/sourcegraph/conc v0.3.0 @@ -128,7 +128,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -175,7 +175,7 @@ require ( golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.6.0 // indirect + golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect diff --git a/go.sum b/go.sum index 4b5c76b3..827fc169 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -300,8 +300,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt/v2 v2.7.2 h1:SCRjfDLJ2q8naXp8YlGJJS5/yj3wGSODFYVi4nnwVMw= github.com/nats-io/jwt/v2 v2.7.2/go.mod h1:kB6QUmqHG6Wdrzj0KP2L+OX4xiTPBeV+NHVstFaATXU= -github.com/nats-io/nats-server/v2 v2.10.21 h1:gfG6T06wBdI25XyY2IsauarOc2srWoFxxfsOKjrzoRA= -github.com/nats-io/nats-server/v2 v2.10.21/go.mod h1:I1YxSAEWbXCfy0bthwvNb5X43WwIWMz7gx5ZVPDr5Rc= +github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= +github.com/nats-io/nats-server/v2 v2.10.22/go.mod h1:X/m1ye9NYansUXYFrbcDwUi/blHkrgHh2rgCJaakonk= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -314,10 +314,10 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/openrdap/rdap v0.9.2-0.20240517203139-eb57b3a8dedd h1:UuQycBx6K0lB0/IfHePshOYjlrptkF4FoApFP2Y4s3k= github.com/openrdap/rdap v0.9.2-0.20240517203139-eb57b3a8dedd/go.mod h1:391Ww1JbjG4FHOlvQqCd6n25CCCPE64JzC5cCYPxhyM= -github.com/overmindtech/aws-source v0.0.0-20241025011151-44d13fa1e1e0 h1:dE7+ToLG6BGrdcMP38dFumlPX1HSL4NApnX4c2Vub20= -github.com/overmindtech/aws-source v0.0.0-20241025011151-44d13fa1e1e0/go.mod h1:E0VRwYZjelHjMy2tGE5JGlT4BRMiOQC/njoUVk2dTPI= -github.com/overmindtech/discovery v0.29.2 h1:bRCaWG8T9RfgVURwvrP86XMTjXhP5RXXvMKx9FTLYEA= -github.com/overmindtech/discovery v0.29.2/go.mod h1:MUDUqB0vM7xJ9wQdpyNWFPmqtXNjh6QfYhQ4g7QIRmU= +github.com/overmindtech/aws-source v0.0.0-20241030131805-de96f8d7d8fb h1:6t0M63RsvsAHyYrl8Pnu2EL34PQ01v/aqzRN+b4Rhjo= +github.com/overmindtech/aws-source v0.0.0-20241030131805-de96f8d7d8fb/go.mod h1:nznIVTQxMuveB/2i33aEytOUxvjcybBKjyg6XQHoPqY= +github.com/overmindtech/discovery v0.30.0 h1:cZoYfHtLIzYTAtJO0r8+SAmtFdY+RaozD9hhOJ+6gro= +github.com/overmindtech/discovery v0.30.0/go.mod h1:rYAyBHfdvK57fUusO4+Qh/YkKMBvbxv6HYWNo23Dhe8= github.com/overmindtech/k8s-source v0.8.1-0.20241025005632-15223645e039 h1:0tyTikhvj0WRpcJ1j0XQwnURG13ZowRpRKZ1rD5NRr4= github.com/overmindtech/k8s-source v0.8.1-0.20241025005632-15223645e039/go.mod h1:8Ptd2eEIgLvOBGbnq0m3C7EB615gTjedk7Idv9x35ws= github.com/overmindtech/pterm v0.0.0-20240919144758-04d94ccb2297 h1:ih4bqBMHTCtg3lMwJszNkMGO9n7Uoe0WX5be1/x+s+g= @@ -326,8 +326,8 @@ github.com/overmindtech/sdp-go v0.96.0 h1:4rgO8VSkS4Kh/Yv8IluFrXD5c7rU2rgACO/xLh github.com/overmindtech/sdp-go v0.96.0/go.mod h1:6PPU8IBPWeMVXe1UW/LHb/hbwZPR0ndfjHRTbNQJy2w= github.com/overmindtech/sdpcache v1.6.4 h1:MJoYBDqDE3s8FrRzZ0RPgFiH39HWI/Mv2ImH1NdLT8k= github.com/overmindtech/sdpcache v1.6.4/go.mod h1:/F9XStVdntRJEQjlZ86BPuB1Y7VPo1PFcsCNiU1IoGE= -github.com/overmindtech/stdlib-source v0.0.0-20241018075207-50872fb39aad h1:t+u+OYAN15hME9skD9MwChPuOM3YyHdmM+ZIbYC8jqg= -github.com/overmindtech/stdlib-source v0.0.0-20241018075207-50872fb39aad/go.mod h1:K/rztWTrn3PEcwnStnnu15neyYKVVj1tpEnB28C8f5o= +github.com/overmindtech/stdlib-source v0.0.0-20241030163048-1250dcbf584a h1:sp/0m6VsA9do/OORiHS8x/arnlNHYWZoXVZbgom36D0= +github.com/overmindtech/stdlib-source v0.0.0-20241030163048-1250dcbf584a/go.mod h1:gEt8xo9RavtqpsVZxmnFMzX6rhNo/V9dvNgvAIIbLwg= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -514,8 +514,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/tfutils/plan.go b/tfutils/plan.go index ddfc6271..19624902 100644 --- a/tfutils/plan.go +++ b/tfutils/plan.go @@ -5,8 +5,6 @@ import ( "regexp" "strconv" "strings" - - "github.com/xiam/dig" ) // NOTE: These definitions are copied from the @@ -62,71 +60,6 @@ type ConfigModule struct { Variables variables `json:"variables,omitempty"` } -// Digs through a map using the same logic that terraform does i.e. foo.bar[0] -func TerraformDig(srcMapPtr interface{}, path string) interface{} { - // Split the path on each period - parts := strings.Split(path, ".") - - if len(parts) == 0 { - return nil - } - - // Check for an index in this section - indexMatches := indexBrackets.FindStringSubmatch(parts[0]) - - var value interface{} - - if len(indexMatches) == 0 { - // No index, just get the value - value = dig.Interface(srcMapPtr, parts[0]) - } else { - // strip the brackets - keyName := indexBrackets.ReplaceAllString(parts[0], "") - - // Get the index - index, err := strconv.Atoi(indexMatches[1]) - - if err != nil { - return nil - } - - // Get the value - arr, ok := dig.Interface(srcMapPtr, keyName).([]interface{}) - - if !ok { - return nil - } - - // Check if the index is in range - if index < 0 || index >= len(arr) { - return nil - } - - value = arr[index] - } - - if len(parts) == 1 { - return value - } else { - // Force it to another map[string]interface{} - valueMap := make(map[string]interface{}) - - if mapString, ok := value.(map[string]string); ok { - for k, v := range mapString { - valueMap[k] = v - } - } else if mapInterface, ok := value.(map[string]interface{}); ok { - valueMap = mapInterface - } else if mapAttributeValues, ok := value.(AttributeValues); ok { - valueMap = mapAttributeValues - } else { - return nil - } - - return TerraformDig(&valueMap, strings.Join(parts[1:], ".")) - } -} - var escapeRegex = regexp.MustCompile(`\${([\w\.\[\]]*)}`) // Digs for a config resource in this module or its children diff --git a/tfutils/plan_mapper.go b/tfutils/plan_mapper.go index 929bdaf0..e12b65c8 100644 --- a/tfutils/plan_mapper.go +++ b/tfutils/plan_mapper.go @@ -345,54 +345,6 @@ func isStateFile(bytes []byte) bool { return false } -// Returns the name of the provider from the config key. If the resource isn't -// in a module, the ProviderConfigKey will be something like "kubernetes", -// however if it's in a module it's be something like -// "module.something:kubernetes". In both scenarios we want to return -// "kubernetes" -func extractProviderNameFromConfigKey(providerConfigKey string) string { - sections := strings.Split(providerConfigKey, ":") - return sections[len(sections)-1] -} - -// InterpolateScope Will interpolate variables in the scope string. These -// variables can come from the following places: -// -// * `outputs` - These are the outputs from the plan -// * `values` - These are the values from the resource in question -// -// Interpolation is done using the Terraform interpolation syntax: -// https://www.terraform.io/docs/configuration/interpolation.html -func InterpolateScope(scope string, data map[string]any) (string, error) { - // Find all instances of ${} in the Scope - matches := escapeRegex.FindAllStringSubmatch(scope, -1) - - interpolated := scope - - for _, match := range matches { - // The first match is the entire string, the second match is the - // variable name - variableName := match[1] - - value := TerraformDig(&data, variableName) - - if value == nil { - return "", fmt.Errorf("variable '%v' not found", variableName) - } - - // Convert the value to a string - valueString, ok := value.(string) - - if !ok { - return "", fmt.Errorf("variable '%v' is not a string", variableName) - } - - interpolated = strings.Replace(interpolated, match[0], valueString, 1) - } - - return interpolated, nil -} - func countSensitiveValuesInConfig(m ConfigModule) int { removedSecrets := 0 for _, v := range m.Variables { diff --git a/tfutils/plan_mapper_test.go b/tfutils/plan_mapper_test.go index 2277218d..6f7666dd 100644 --- a/tfutils/plan_mapper_test.go +++ b/tfutils/plan_mapper_test.go @@ -4,11 +4,14 @@ import ( "context" "encoding/json" "fmt" + "strconv" + "strings" "testing" "github.com/overmindtech/sdp-go" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "github.com/xiam/dig" ) func TestWithStateFile(t *testing.T) { @@ -333,7 +336,7 @@ func TestInterpolateScope(t *testing.T) { t.Run("with no interpolation", func(t *testing.T) { t.Parallel() - result, err := InterpolateScope("foo", map[string]any{}) + result, err := interpolateScope("foo", map[string]any{}) if err != nil { t.Error(err) @@ -347,7 +350,7 @@ func TestInterpolateScope(t *testing.T) { t.Run("with a single variable", func(t *testing.T) { t.Parallel() - result, err := InterpolateScope("${outputs.overmind_kubernetes_cluster_name}", map[string]any{ + result, err := interpolateScope("${outputs.overmind_kubernetes_cluster_name}", map[string]any{ "outputs": map[string]any{ "overmind_kubernetes_cluster_name": "foo", }, @@ -365,7 +368,7 @@ func TestInterpolateScope(t *testing.T) { t.Run("with multiple variables", func(t *testing.T) { t.Parallel() - result, err := InterpolateScope("${outputs.overmind_kubernetes_cluster_name}.${values.metadata.namespace}", map[string]any{ + result, err := interpolateScope("${outputs.overmind_kubernetes_cluster_name}.${values.metadata.namespace}", map[string]any{ "outputs": map[string]any{ "overmind_kubernetes_cluster_name": "foo", }, @@ -388,7 +391,7 @@ func TestInterpolateScope(t *testing.T) { t.Run("with a variable that doesn't exist", func(t *testing.T) { t.Parallel() - _, err := InterpolateScope("${outputs.overmind_kubernetes_cluster_name}", map[string]any{}) + _, err := interpolateScope("${outputs.overmind_kubernetes_cluster_name}", map[string]any{}) if err == nil { t.Error("Expected error, got nil") @@ -632,3 +635,116 @@ func TestHandleKnownAfterApply(t *testing.T) { t.Errorf("expected data to be %v, got %v", KnownAfterApply, val) } } + +// Returns the name of the provider from the config key. If the resource isn't +// in a module, the ProviderConfigKey will be something like "kubernetes", +// however if it's in a module it's be something like +// "module.something:kubernetes". In both scenarios we want to return +// "kubernetes" +func extractProviderNameFromConfigKey(providerConfigKey string) string { + sections := strings.Split(providerConfigKey, ":") + return sections[len(sections)-1] +} + +// interpolateScope Will interpolate variables in the scope string. These +// variables can come from the following places: +// +// * `outputs` - These are the outputs from the plan +// * `values` - These are the values from the resource in question +// +// Interpolation is done using the Terraform interpolation syntax: +// https://www.terraform.io/docs/configuration/interpolation.html +func interpolateScope(scope string, data map[string]any) (string, error) { + // Find all instances of ${} in the Scope + matches := escapeRegex.FindAllStringSubmatch(scope, -1) + + interpolated := scope + + for _, match := range matches { + // The first match is the entire string, the second match is the + // variable name + variableName := match[1] + + value := terraformDig(&data, variableName) + + if value == nil { + return "", fmt.Errorf("variable '%v' not found", variableName) + } + + // Convert the value to a string + valueString, ok := value.(string) + + if !ok { + return "", fmt.Errorf("variable '%v' is not a string", variableName) + } + + interpolated = strings.Replace(interpolated, match[0], valueString, 1) + } + + return interpolated, nil +} + +// Digs through a map using the same logic that terraform does i.e. foo.bar[0] +func terraformDig(srcMapPtr interface{}, path string) interface{} { + // Split the path on each period + parts := strings.Split(path, ".") + + if len(parts) == 0 { + return nil + } + + // Check for an index in this section + indexMatches := indexBrackets.FindStringSubmatch(parts[0]) + + var value interface{} + + if len(indexMatches) == 0 { + // No index, just get the value + value = dig.Interface(srcMapPtr, parts[0]) + } else { + // strip the brackets + keyName := indexBrackets.ReplaceAllString(parts[0], "") + + // Get the index + index, err := strconv.Atoi(indexMatches[1]) + + if err != nil { + return nil + } + + // Get the value + arr, ok := dig.Interface(srcMapPtr, keyName).([]interface{}) + + if !ok { + return nil + } + + // Check if the index is in range + if index < 0 || index >= len(arr) { + return nil + } + + value = arr[index] + } + + if len(parts) == 1 { + return value + } else { + // Force it to another map[string]interface{} + valueMap := make(map[string]interface{}) + + if mapString, ok := value.(map[string]string); ok { + for k, v := range mapString { + valueMap[k] = v + } + } else if mapInterface, ok := value.(map[string]interface{}); ok { + valueMap = mapInterface + } else if mapAttributeValues, ok := value.(AttributeValues); ok { + valueMap = mapAttributeValues + } else { + return nil + } + + return terraformDig(&valueMap, strings.Join(parts[1:], ".")) + } +} diff --git a/tracing/main.go b/tracing/main.go index 4740db4a..85ff91f3 100644 --- a/tracing/main.go +++ b/tracing/main.go @@ -4,8 +4,6 @@ import ( "context" _ "embed" "fmt" - "os" - "runtime/debug" "time" "github.com/getsentry/sentry-go" @@ -191,50 +189,3 @@ func (h *UserAgentSampler) ShouldSample(parameters sdktrace.SamplingParameters) func (h *UserAgentSampler) Description() string { return "Simple Sampler based on the UserAgent of the request" } - -// LogRecoverToReturn Recovers from a panic, logs and forwards it sentry and otel, then returns -// Does nothing when there is no panic. -func LogRecoverToReturn(ctx context.Context, loc string) { - err := recover() - if err == nil { - return - } - - stack := string(debug.Stack()) - handleError(ctx, loc, err, stack) -} - -// LogRecoverToExit Recovers from a panic, logs and forwards it sentry and otel, then exits -// Does nothing when there is no panic. -func LogRecoverToExit(ctx context.Context, loc string) { - err := recover() - if err == nil { - return - } - - stack := string(debug.Stack()) - handleError(ctx, loc, err, stack) - - // ensure that errors still get sent out - ShutdownTracer() - - os.Exit(1) -} - -func handleError(ctx context.Context, loc string, err interface{}, stack string) { - msg := fmt.Sprintf("unhandled panic in %v, exiting: %v", loc, err) - - hub := sentry.CurrentHub() - if hub != nil { - hub.Recover(err) - } - - if ctx != nil { - log.WithContext(ctx).WithFields(log.Fields{"loc": loc, "stack": stack}).Error(msg) - span := trace.SpanFromContext(ctx) - span.SetAttributes(attribute.String("ovm.panic.loc", loc)) - span.SetAttributes(attribute.String("ovm.panic.stack", stack)) - } else { - log.WithFields(log.Fields{"loc": loc, "stack": stack}).Error(msg) - } -}