Skip to content

Commit 7bd7437

Browse files
authored
infracost integration (backend) (#3460)
1 parent acf8b57 commit 7bd7437

15 files changed

Lines changed: 617 additions & 50 deletions

File tree

assets/src/generated/graphql.ts

Lines changed: 87 additions & 16 deletions
Large diffs are not rendered by default.

assets/src/generated/persisted-queries/client.json

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

assets/src/graph/stacks.graphql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ fragment StackRun on StackRun {
140140
insight {
141141
...AiInsight
142142
}
143+
infracostResources(limit: 5) {
144+
...StackInfracostResource
145+
}
143146
}
144147

145148
fragment ObservableMetric on ObservableMetric {
@@ -224,6 +227,21 @@ fragment StackRunDetails on StackRun {
224227
type
225228
maxSeverity
226229
}
230+
infracostResources(limit: 10) {
231+
...StackInfracostResource
232+
}
233+
}
234+
235+
fragment StackInfracostResource on StackInfracostResource {
236+
id
237+
resourceScope
238+
projectName
239+
name
240+
resourceType
241+
hourlyCost
242+
monthlyCost
243+
monthlyUsageCost
244+
rawResource
227245
}
228246

229247
fragment CustomStackRun on CustomStackRun {

go/client/models_gen.go

Lines changed: 43 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/console/deployments/stacks.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Console.Deployments.Stacks do
44
import Console.Deployments.Policies
55
import Console.Deployments.Stacks.Commands
66
import Console.AI.Fixer.Base, only: [blacklist: 1]
7+
require Logger
78
alias Console.PubSub
89
alias Console.Deployments.{Services, Clusters, Settings, Git, Stacks.Stability, Tar}
910
alias Console.Deployments.Git.Discovery
@@ -288,7 +289,7 @@ defmodule Console.Deployments.Stacks do
288289
start_transaction()
289290
|> add_operation(:run, fn _ ->
290291
get_run!(id)
291-
|> Repo.preload([:state, violations: :causes])
292+
|> Repo.preload([:state, :infracost_resources, violations: :causes])
292293
|> StackRun.update_changeset(attrs)
293294
|> allow(actor, :write)
294295
|> when_ok(:update)
@@ -429,7 +430,7 @@ defmodule Console.Deployments.Stacks do
429430
start_transaction()
430431
|> add_operation(:run, fn _ ->
431432
get_run!(id)
432-
|> Repo.preload([:state, :output, :errors, :stack, violations: :causes])
433+
|> Repo.preload([:state, :output, :errors, :stack, :infracost_resources, violations: :causes])
433434
|> StackRun.complete_changeset(attrs)
434435
|> allow(actor, :write)
435436
|> when_ok(:update)

lib/console/graphql/deployments/stack.ex

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ defmodule Console.GraphQl.Deployments.Stack do
110110
field :errors, list_of(:service_error_attributes), description: "Any errors detected when trying to run this stack"
111111
field :cancellation_reason, :string, description: "Why you decided to cancel this run"
112112
field :violations, list_of(:stack_policy_violation_attributes), description: "the violations detected by the policy engine"
113+
field :infracost_resources, list_of(:stack_infracost_resource_attributes), description: "Infracost resource rows to persist for this run"
114+
end
115+
116+
input_object :stack_infracost_resource_attributes do
117+
field :resource_scope, non_null(:string), description: "breakdown | past_breakdown | diff | free"
118+
field :project_name, :string, description: "Infracost project name this resource belongs to"
119+
field :name, non_null(:string)
120+
field :resource_type, :string
121+
field :hourly_cost, :float
122+
field :monthly_cost, :float
123+
field :monthly_usage_cost, :float
124+
field :raw_resource, :json
113125
end
114126

115127
input_object :run_step_attributes do
@@ -270,6 +282,12 @@ defmodule Console.GraphQl.Deployments.Stack do
270282

271283
field :tags, list_of(:tag), resolve: dataloader(Deployments), description: "key/value tags to filter stacks"
272284

285+
field :infracost_resources, list_of(:stack_infracost_resource),
286+
description: "Infracost resource rows attached to this stack (newest first)" do
287+
arg :limit, :integer
288+
resolve &Deployments.list_stack_infracost_resources/3
289+
end
290+
273291
field :read_bindings, list_of(:policy_binding), resolve: dataloader(Deployments)
274292
field :write_bindings, list_of(:policy_binding), resolve: dataloader(Deployments)
275293

@@ -429,9 +447,37 @@ defmodule Console.GraphQl.Deployments.Stack do
429447
field :repository, :git_repository, resolve: dataloader(Deployments), description: "the git repository you're sourcing IaC from"
430448
field :violations, list_of(:stack_policy_violation), resolve: dataloader(Deployments), description: "policy violations for this stack"
431449

450+
field :infracost_resources, list_of(:stack_infracost_resource),
451+
description: "Infracost resource rows attached to this run (newest first)" do
452+
arg :limit, :integer
453+
resolve &Deployments.list_stack_run_infracost_resources/3
454+
end
455+
456+
timestamps()
457+
end
458+
459+
object :stack_infracost_resource do
460+
field :id, non_null(:id)
461+
field :resource_scope, non_null(:string), description: "breakdown | past_breakdown | diff | free"
462+
field :project_name, :string, description: "Infracost project name this resource belongs to"
463+
field :name, non_null(:string)
464+
field :resource_type, :string
465+
466+
field :hourly_cost, :float, resolve: fn p, _, _ -> {:ok, decimal_float(p.hourly_cost)} end
467+
field :monthly_cost, :float, resolve: fn p, _, _ -> {:ok, decimal_float(p.monthly_cost)} end
468+
field :monthly_usage_cost, :float, resolve: fn p, _, _ -> {:ok, decimal_float(p.monthly_usage_cost)} end
469+
470+
field :raw_resource, :map
471+
472+
field :stack_run, :stack_run, resolve: dataloader(Deployments)
473+
432474
timestamps()
433475
end
434476

477+
defp decimal_float(nil), do: nil
478+
defp decimal_float(%Decimal{} = d), do: Decimal.to_float(d)
479+
defp decimal_float(_), do: nil
480+
435481
object :run_step do
436482
field :id, non_null(:id)
437483
field :status, non_null(:step_status)

lib/console/graphql/resolvers/deployments.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ defmodule Console.GraphQl.Resolvers.Deployments do
8383
ClusterScalingRecommendation,
8484
StackPolicyViolation,
8585
StackViolationCause,
86+
StackInfracostResource,
8687
Alert,
8788
AlertResolution,
8889
Flow,
@@ -194,6 +195,7 @@ defmodule Console.GraphQl.Resolvers.Deployments do
194195
def query(ClusterScalingRecommendation, _), do: ClusterScalingRecommendation
195196
def query(StackPolicyViolation, _), do: StackPolicyViolation
196197
def query(StackViolationCause, _), do: StackViolationCause
198+
def query(StackInfracostResource, _), do: StackInfracostResource
197199
def query(Alert, _), do: Alert
198200
def query(AlertResolution, _), do: AlertResolution
199201
def query(Flow, _), do: Flow

lib/console/graphql/resolvers/deployments/stack.ex

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule Console.GraphQl.Resolvers.Deployments.Stack do
22
use Console.GraphQl.Resolvers.Deployments.Base
3+
import Ecto.Query
34
alias Console.Deployments.{Stacks, Services, Settings}
45
alias Console.Schema.{
56
Stack,
@@ -8,6 +9,7 @@ defmodule Console.GraphQl.Resolvers.Deployments.Stack do
89
CustomStackRun,
910
DeploymentSettings,
1011
StackDefinition,
12+
StackInfracostResource,
1113
AgentSession
1214
}
1315

@@ -91,6 +93,36 @@ defmodule Console.GraphQl.Resolvers.Deployments.Stack do
9193
|> paginate(args)
9294
end
9395

96+
def list_stack_infracost_resources(%Stack{id: stack_id} = stack, args, ctx) do
97+
case safe_stack_field(stack, true, ctx) do
98+
{:error, _} = err ->
99+
err
100+
101+
_ ->
102+
StackInfracostResource
103+
|> where([r], r.stack_id == ^stack_id)
104+
|> order_by([r], desc: r.inserted_at)
105+
|> maybe_limit_infracost(args)
106+
|> Console.Repo.all()
107+
|> ok()
108+
end
109+
end
110+
111+
def list_stack_run_infracost_resources(%StackRun{id: run_id} = run, args, ctx) do
112+
case safe_stack_field(run, true, ctx) do
113+
{:error, _} = err ->
114+
err
115+
116+
_ ->
117+
StackInfracostResource
118+
|> where([r], r.stack_run_id == ^run_id)
119+
|> order_by([r], desc: r.inserted_at)
120+
|> maybe_limit_infracost(args)
121+
|> Console.Repo.all()
122+
|> ok()
123+
end
124+
end
125+
94126
def stack_runs_for_cluster(args, %{context: %{cluster: cluster}}) do
95127
StackRun.for_cluster(cluster.id)
96128
|> StackRun.pending()
@@ -213,4 +245,9 @@ defmodule Console.GraphQl.Resolvers.Deployments.Stack do
213245
do: Stacks.stack_files(id, user)
214246

215247
def plural_creds(run, _, ctx), do: Stacks.plural_creds(run, actor(ctx))
248+
249+
defp maybe_limit_infracost(query, %{limit: limit}) when is_integer(limit) and limit > 0,
250+
do: limit(query, ^limit)
251+
252+
defp maybe_limit_infracost(query, _), do: query
216253
end

lib/console/schema/stack.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ defmodule Console.Schema.Stack do
2020
Service,
2121
Project,
2222
StackDefinition,
23-
AiInsight
23+
AiInsight,
24+
StackInfracostResource
2425
}
2526

2627
defenum Type, terraform: 0, ansible: 1, custom: 2
@@ -186,6 +187,7 @@ defmodule Console.Schema.Stack do
186187
has_many :files, StackFile, on_replace: :delete
187188
has_many :output, StackOutput, on_replace: :delete
188189
has_many :runs, StackRun
190+
has_many :infracost_resources, StackInfracostResource, foreign_key: :stack_id
189191
has_many :tags, Tag, on_replace: :delete
190192

191193
has_many :observable_metrics, ObservableMetric, on_replace: :delete
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule Console.Schema.StackInfracostResource do
2+
use Piazza.Ecto.Schema
3+
alias Console.Schema.{Stack, StackRun}
4+
5+
@scopes ~w(breakdown past_breakdown diff free)
6+
7+
schema "stack_infracost_resources" do
8+
field :resource_scope, :string
9+
field :project_name, :string
10+
field :name, :string
11+
field :resource_type, :string
12+
13+
field :hourly_cost, :decimal
14+
field :monthly_cost, :decimal
15+
field :monthly_usage_cost, :decimal
16+
17+
field :raw_resource, :map
18+
19+
belongs_to :stack, Stack
20+
belongs_to :stack_run, StackRun
21+
22+
timestamps()
23+
end
24+
25+
@cast ~w(resource_scope project_name name resource_type
26+
hourly_cost monthly_cost monthly_usage_cost
27+
raw_resource stack_id stack_run_id)a
28+
29+
def changeset(model, attrs \\ %{}) do
30+
model
31+
|> cast(attrs, @cast)
32+
|> foreign_key_constraint(:stack_id)
33+
|> foreign_key_constraint(:stack_run_id)
34+
|> validate_required(~w(resource_scope name stack_id)a)
35+
|> validate_inclusion(:resource_scope, @scopes)
36+
|> put_new_change(:id, &Piazza.Ecto.UUID.generate_monotonic/0)
37+
end
38+
end

0 commit comments

Comments
 (0)