Skip to content

Commit f14ffd8

Browse files
committed
chore: add the core code with correct module
1 parent 891043f commit f14ffd8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+7431
-3
lines changed

go.mod

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,67 @@
11
module github.com/infracost/cli
22

3-
go 1.25.7
3+
go 1.25.5
4+
5+
require (
6+
github.com/Masterminds/semver/v3 v3.4.0
7+
github.com/MicahParks/keyfunc/v3 v3.7.0
8+
github.com/golang-jwt/jwt/v5 v5.3.0
9+
github.com/google/go-cmp v0.7.0
10+
github.com/google/uuid v1.6.0
11+
github.com/hashicorp/go-hclog v1.6.3
12+
github.com/hashicorp/go-plugin v1.7.0
13+
github.com/infracost/config v0.3.8
14+
github.com/infracost/go-proto v0.2.1
15+
github.com/infracost/proto v1.21.0
16+
github.com/liamg/tml v0.7.1
17+
github.com/rs/zerolog v1.34.0
18+
github.com/shirou/gopsutil v3.21.11+incompatible
19+
github.com/spf13/cobra v1.10.2
20+
github.com/spf13/pflag v1.0.9
21+
github.com/stretchr/testify v1.11.1
22+
golang.org/x/mod v0.32.0
23+
golang.org/x/oauth2 v0.34.0
24+
golang.org/x/text v0.33.0
25+
google.golang.org/grpc v1.78.0
26+
google.golang.org/protobuf v1.36.11
27+
)
28+
29+
require (
30+
github.com/MicahParks/jwkset v0.11.0 // indirect
31+
github.com/agext/levenshtein v1.2.3 // indirect
32+
github.com/agnivade/levenshtein v1.2.1 // indirect
33+
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
34+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
35+
github.com/dlclark/regexp2 v1.2.0 // indirect
36+
github.com/fatih/color v1.18.0 // indirect
37+
github.com/go-ole/go-ole v1.2.6 // indirect
38+
github.com/gobwas/glob v0.2.3 // indirect
39+
github.com/golang/protobuf v1.5.4 // indirect
40+
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
41+
github.com/hashicorp/yamux v0.1.2 // indirect
42+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
43+
github.com/json-iterator/go v1.1.12 // indirect
44+
github.com/kr/pretty v0.3.1 // indirect
45+
github.com/mattn/go-colorable v0.1.14 // indirect
46+
github.com/mattn/go-isatty v0.0.20 // indirect
47+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
48+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
49+
github.com/modern-go/reflect2 v1.0.2 // indirect
50+
github.com/oklog/run v1.1.0 // indirect
51+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
52+
github.com/soongo/path-to-regexp v1.6.4 // indirect
53+
github.com/tklauser/go-sysconf v0.3.16 // indirect
54+
github.com/tklauser/numcpus v0.11.0 // indirect
55+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
56+
github.com/zclconf/go-cty v1.17.0 // indirect
57+
go.opentelemetry.io/otel v1.39.0 // indirect
58+
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
59+
golang.org/x/net v0.49.0 // indirect
60+
golang.org/x/sync v0.19.0 // indirect
61+
golang.org/x/sys v0.41.0 // indirect
62+
golang.org/x/time v0.14.0 // indirect
63+
golang.org/x/tools v0.41.0 // indirect
64+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
65+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
66+
gopkg.in/yaml.v3 v3.0.1 // indirect
67+
)

go.sum

Lines changed: 185 additions & 0 deletions
Large diffs are not rendered by default.

internal/api/client.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/infracost/cli/internal/trace"
8+
"golang.org/x/oauth2"
9+
)
10+
11+
func Client(ctx context.Context, source oauth2.TokenSource, orgID string) *http.Client {
12+
base, transport := baseClient(ctx, source)
13+
return &http.Client{
14+
Transport: &CustomHeaders{
15+
Base: transport,
16+
Headers: map[string]string{
17+
"X-Infracost-Trace-ID": trace.ID,
18+
"User-Agent": trace.UserAgent,
19+
"x-infracost-org-id": orgID,
20+
},
21+
},
22+
CheckRedirect: base.CheckRedirect,
23+
Jar: base.Jar,
24+
Timeout: base.Timeout,
25+
}
26+
}
27+
28+
func baseClient(ctx context.Context, source oauth2.TokenSource) (*http.Client, http.RoundTripper) {
29+
if source == nil {
30+
return http.DefaultClient, http.DefaultTransport
31+
}
32+
client := oauth2.NewClient(ctx, source)
33+
return client, client.Transport
34+
}

internal/api/dashboard/client.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package dashboard
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
"strings"
10+
11+
"github.com/infracost/cli/internal/api/dashboard/graphql"
12+
)
13+
14+
type Client struct {
15+
client *http.Client
16+
config *Config
17+
}
18+
19+
type RunParameters struct {
20+
OrganizationID string `json:"organizationId"`
21+
RepositoryName string `json:"repositoryName"`
22+
23+
UsageDefaults json.RawMessage `json:"usageDefaults"`
24+
ProductionFilters []json.RawMessage `json:"productionFilters"`
25+
TagPolicies []json.RawMessage `json:"tagPolicies"`
26+
FinopsPolicies []json.RawMessage `json:"finopsPolicies"`
27+
}
28+
29+
func (c *Client) RunParameters(ctx context.Context, repoURL, branchName string) (RunParameters, error) {
30+
const query = `query RunParameters($repoUrl: String, $branchName: String) {
31+
runParameters(repoUrl: $repoUrl, branchName: $branchName) {
32+
organizationId
33+
repositoryName
34+
usageDefaults
35+
productionFilters
36+
tagPolicies
37+
finopsPolicies
38+
}
39+
}`
40+
41+
type response struct {
42+
RunParameters RunParameters `json:"runParameters"`
43+
}
44+
45+
variables := map[string]interface{}{}
46+
if repoURL != "" {
47+
variables["repoUrl"] = repoURL
48+
}
49+
if branchName != "" {
50+
variables["branchName"] = branchName
51+
}
52+
53+
r, err := graphql.Query[response](ctx, c.client, fmt.Sprintf("%s/graphql", c.config.Endpoint), query, variables)
54+
if err != nil {
55+
return RunParameters{}, err
56+
}
57+
58+
if len(r.Errors) > 0 {
59+
var errs []string
60+
for _, e := range r.Errors {
61+
errs = append(errs, e.Message)
62+
}
63+
return r.Data.RunParameters, errors.New(strings.Join(errs, ";"))
64+
}
65+
return r.Data.RunParameters, nil
66+
}

internal/api/dashboard/config.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dashboard
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/infracost/cli/pkg/environment"
7+
)
8+
9+
var (
10+
defaultValues = map[environment.Environment]map[string]string{
11+
environment.Production: {
12+
"endpoint": "https://dashboard.api.infracost.io",
13+
},
14+
environment.Development: {
15+
"endpoint": "https://dashboard.api.dev.infracost.io",
16+
},
17+
environment.Local: {
18+
"endpoint": "http://localhost:5000",
19+
},
20+
}
21+
)
22+
23+
type Config struct {
24+
Endpoint string `env:"INFRACOST_CLI_DASHBOARD_ENDPOINT" flag:"dashboard-endpoint;hidden" usage:"The endpoint for the Infracost dashboard"`
25+
}
26+
27+
func (c *Config) Client(client *http.Client) *Client {
28+
return &Client{
29+
client: client,
30+
config: c,
31+
}
32+
}
33+
34+
func (c *Config) ApplyDefaults(env environment.Environment) {
35+
if c.Endpoint == "" {
36+
c.Endpoint = defaultValues[env]["endpoint"]
37+
}
38+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package graphql
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
)
9+
10+
type Response[T any] struct {
11+
Data T `json:"data"`
12+
Errors []Error `json:"errors,omitempty"`
13+
}
14+
15+
type Error struct {
16+
Message string `json:"message"`
17+
}
18+
19+
type Request struct {
20+
Query string `json:"query"`
21+
Variables map[string]interface{} `json:"variables,omitempty"`
22+
}
23+
24+
func Query[T any](ctx context.Context, client *http.Client, endpoint string, query string, variables map[string]interface{}) (Response[T], error) {
25+
request := Request{
26+
Query: query,
27+
Variables: variables,
28+
}
29+
30+
bytes := new(bytes.Buffer)
31+
if err := json.NewEncoder(bytes).Encode(request); err != nil {
32+
return Response[T]{}, err
33+
}
34+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes)
35+
if err != nil {
36+
return Response[T]{}, err
37+
}
38+
39+
req.Header.Set("Content-Type", "application/json")
40+
r, err := client.Do(req) // #nosec G704 -- request target originates from config file
41+
if err != nil {
42+
return Response[T]{}, err
43+
}
44+
defer func() {
45+
_ = r.Body.Close()
46+
}()
47+
48+
var response Response[T]
49+
if err := json.NewDecoder(r.Body).Decode(&response); err != nil {
50+
return Response[T]{}, err
51+
}
52+
53+
return response, nil
54+
}

internal/api/events/client.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package events
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
10+
"github.com/infracost/cli/internal/logging"
11+
)
12+
13+
type Client struct {
14+
client *http.Client
15+
config *Config
16+
}
17+
18+
func (c *Client) Push(ctx context.Context, event string, extra ...interface{}) {
19+
if isTest, ok := metadata["isTest"].(bool); ok && isTest {
20+
return
21+
}
22+
23+
if len(extra)%2 != 0 {
24+
panic("events.Push: extra args must be key-value pairs")
25+
}
26+
27+
env := make(map[string]interface{}, len(metadata)+len(extra)/2)
28+
for k, v := range metadata {
29+
env[k] = v
30+
}
31+
for i := 0; i < len(extra); i += 2 {
32+
key, ok := extra[i].(string)
33+
if !ok {
34+
panic(fmt.Sprintf("events.Push: extra arg %d must be a string key", i))
35+
}
36+
env[key] = extra[i+1]
37+
}
38+
39+
body := struct {
40+
Event string `json:"event"`
41+
Env map[string]interface{} `json:"env"`
42+
}{
43+
Event: event,
44+
Env: env,
45+
}
46+
47+
buf, err := json.Marshal(body)
48+
if err != nil {
49+
logging.WithError(err).Msg("events: failed to marshal event")
50+
return
51+
}
52+
53+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/event", c.config.Endpoint), bytes.NewReader(buf))
54+
if err != nil {
55+
logging.WithError(err).Msg("events: failed to create request")
56+
return
57+
}
58+
req.Header.Set("Content-Type", "application/json")
59+
60+
resp, err := c.client.Do(req) //nolint:gosec // endpoint is from CLI config, not user input
61+
if err != nil {
62+
logging.WithError(err).Msg("events: failed to send event")
63+
return
64+
}
65+
if err := resp.Body.Close(); err != nil {
66+
logging.WithError(err).Msg("events: failed to close response body")
67+
return
68+
}
69+
}

internal/api/events/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package events
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
type Config struct {
8+
Endpoint string `env:"INFRACOST_CLI_EVENTS_ENDPOINT" flag:"events-endpoint;hidden" usage:"The endpoint for the Infracost events service" default:"https://pricing.api.infracost.io"`
9+
}
10+
11+
func (c *Config) Client(client *http.Client) *Client {
12+
// The events client may be used before config defaults are applied (e.g.
13+
// to report early errors), so ensure the endpoint is always set.
14+
if c.Endpoint == "" {
15+
c.Endpoint = "https://pricing.api.infracost.io"
16+
}
17+
return &Client{
18+
client: client,
19+
config: c,
20+
}
21+
}

0 commit comments

Comments
 (0)