Skip to content

Commit b831a45

Browse files
Implement stack install apis in-console (#170)
1 parent b7a71dc commit b831a45

File tree

13 files changed

+326
-12
lines changed

13 files changed

+326
-12
lines changed

lib/console/commands/plural.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defmodule Console.Commands.Plural do
1313

1414
def deploy(_repo \\ :ignore), do: plural("deploy", ["--silence"])
1515

16+
def install([_ | _] = repos), do: plural("deploy", ["--silence", "--ignore-console" | Enum.flat_map(repos, & ["--from", &1])])
1617
def install(repo), do: plural("deploy", ["--silence", "--ignore-console", "--from", repo])
1718

1819
def diff(_repo \\ :ignore), do: plural("diff", [])

lib/console/deployer.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,17 @@ defmodule Console.Deployer do
175175
], storage)
176176
end
177177

178+
defp perform(storage, %Build{type: :install, context: %{"configuration" => conf, "bundles" => bs}, message: message} = build) do
179+
with_build(build, [
180+
{storage, :init, []},
181+
{Context, :merge, [conf, Enum.map(bs, fn b -> %Context.Bundle{repository: b["repository"], name: b["name"]} end)]},
182+
{Plural, :build, []},
183+
{Plural, :install, [Enum.map(bs, & &1["repository"])]},
184+
{storage, :revise, [message]},
185+
{storage, :push, []}
186+
], storage)
187+
end
188+
178189
defp perform(storage, %Build{type: :approval, repository: repo, message: message} = build) do
179190
with_build(build, [
180191
{storage, :init, []},

lib/console/graphql/plural.ex

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ defmodule Console.GraphQl.Plural do
8484
field :operation, :string
8585
end
8686

87+
object :stack do
88+
field :id, non_null(:id)
89+
field :name, non_null(:string)
90+
field :bundles, list_of(:recipe)
91+
field :sections, list_of(:recipe_section)
92+
93+
timestamps()
94+
end
95+
8796
object :repository_context do
8897
field :repository, non_null(:string)
8998
field :context, :map
@@ -140,6 +149,13 @@ defmodule Console.GraphQl.Plural do
140149
resolve &Plural.get_recipe/2
141150
end
142151

152+
field :stack, :stack do
153+
middleware Authenticated
154+
arg :name, non_null(:string)
155+
156+
resolve &Plural.get_stack/2
157+
end
158+
143159
field :smtp, :smtp do
144160
middleware Authenticated
145161
middleware AdminRequired
@@ -155,13 +171,27 @@ defmodule Console.GraphQl.Plural do
155171
field :install_recipe, :build do
156172
middleware Authenticated
157173
middleware RequiresGit
174+
middleware Rbac, perm: :deploy, arg: :id
175+
158176
arg :id, non_null(:id)
159177
arg :context, non_null(:map)
160178
arg :oidc, :boolean
161179

162180
safe_resolve &Plural.install_recipe/2
163181
end
164182

183+
field :install_stack, :build do
184+
middleware Authenticated
185+
middleware RequiresGit
186+
middleware Rbac, perm: :deploy, arg: :id
187+
188+
arg :name, non_null(:string)
189+
arg :context, non_null(:map)
190+
arg :oidc, :boolean
191+
192+
safe_resolve &Plural.install_stack/2
193+
end
194+
165195
field :update_smtp, :smtp do
166196
middleware Authenticated
167197
middleware AdminRequired

lib/console/graphql/resolvers/plural.ex

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Console.GraphQl.Resolvers.Plural do
22
use Nebulex.Caching
33
alias Console.Plural.{Repositories, ExternalToken}
4-
alias Console.Plural.{Connection, PageInfo}
4+
alias Console.Plural.{Connection, PageInfo, Stack}
55
alias Console.Services.Plural
66
alias Kube.Client
77

@@ -28,13 +28,22 @@ defmodule Console.GraphQl.Resolvers.Plural do
2828

2929
def get_recipe(%{id: id}, _) do
3030
with {:ok, recipe} <- Repositories.get_recipe(id) do
31-
sections = Enum.map(recipe.recipeSections, fn section ->
32-
Map.put(section, :recipe_items, section.recipeItems)
33-
end)
34-
{:ok, Map.put(recipe, :recipe_sections, sections)}
31+
{:ok, format_recipe(recipe)}
3532
end
3633
end
3734

35+
defp format_section(section), do: Map.put(section, :recipe_items, section.recipeItems)
36+
37+
defp format_recipe(recipe) do
38+
sections = Enum.map(recipe.recipeSections, &format_section/1)
39+
Map.put(recipe, :recipe_sections, sections)
40+
end
41+
42+
def get_stack(%{name: name}, _) do
43+
with {:ok, %Stack{bundles: recipes, sections: sections} = stack} <- Repositories.get_stack(name),
44+
do: {:ok, %{stack | bundles: Enum.map(recipes, &format_recipe/1), sections: Enum.map(sections, &format_section/1)}}
45+
end
46+
3847
def list_applications(_, _) do
3948
with {:ok, %{items: items}} <- Client.list_applications(),
4049
do: {:ok, items}
@@ -53,6 +62,10 @@ defmodule Console.GraphQl.Resolvers.Plural do
5362
Plural.install_recipe(id, context, !!args[:oidc], user)
5463
end
5564

65+
def install_stack(%{name: name, context: context} = args, %{context: %{current_user: user}}) do
66+
Plural.install_stack(name, context, !!args[:oidc], user)
67+
end
68+
5669
def update_smtp(%{smtp: smtp}, _), do: Plural.update_smtp(smtp)
5770

5871
def resolve_configuration(%{metadata: %{name: name}}, first, second),

lib/console/plural/context.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ defmodule Console.Plural.Context do
4343
end
4444
end
4545

46-
def merge(ctx, bundle) do
46+
def merge(ctx, new_bundles) do
4747
with {:ok, %{configuration: config, bundles: bundles} = context} <- get() do
4848
updated = DeepMerge.deep_merge(config, ctx)
49-
write(%{context | configuration: updated, bundles: Enum.uniq([bundle | bundles])})
49+
write(%{context | configuration: updated, bundles: merge_bundles(new_bundles, bundles)})
5050
end
5151
end
5252

53+
defp merge_bundles([_ | _] = new, bundles), do: Enum.uniq(new ++ bundles)
54+
defp merge_bundles(b, bundles), do: merge_bundles([b], bundles)
55+
5356
def write(%__MODULE__{bundles: bundles, configuration: conf, smtp: smtp} = context) do
5457
sanitized = %{
5558
apiVersion: "plural.sh/v1alpha1",

lib/console/plural/models.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,19 @@ defmodule Console.Plural.Recipe do
125125
end
126126
end
127127

128+
defmodule Console.Plural.Stack do
129+
alias Console.Plural.{Recipe, RecipeSection}
130+
131+
defstruct [:id, :name, :bundles, :sections]
132+
133+
def spec() do
134+
%__MODULE__{
135+
bundles: [Recipe.spec()],
136+
sections: [RecipeSection.spec()]
137+
}
138+
end
139+
end
140+
128141
defmodule Console.Plural.Edge do
129142
defstruct [:node]
130143
end

lib/console/plural/queries.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ defmodule Console.Plural.Queries do
9393
#{@oidc_provider_fragment}
9494
"""
9595

96+
@stack_fragment """
97+
fragment StackFragment on Stack {
98+
id
99+
name
100+
bundles { ...RecipeFragment }
101+
sections { ...RecipeSectionFragment }
102+
}
103+
#{@recipe_fragment}
104+
#{@recipe_section_fragment}
105+
"""
106+
96107
@installation_query """
97108
query Installations($first: Int!, $cursor: String) {
98109
installations(first: $first, after: $cursor) {
@@ -185,6 +196,24 @@ defmodule Console.Plural.Queries do
185196
}
186197
"""
187198

199+
@install_stack """
200+
mutation Install($name: String!, $provider: Provider!) {
201+
installStack(name: $name, provider: $provider) {
202+
...RecipeFragment
203+
}
204+
}
205+
#{@recipe_fragment}
206+
"""
207+
208+
@get_stack """
209+
query Stack($name: String!, $provider: Provider!) {
210+
stack(name: $name, provider: $provider) {
211+
...StackFragment
212+
}
213+
}
214+
#{@stack_fragment}
215+
"""
216+
188217
@oidc_upsert """
189218
mutation OIDC($id: ID!, $attributes: OidcAttributes!) {
190219
upsertOidcProvider(installationId: $id, attributes: $attributes) {
@@ -247,4 +276,8 @@ defmodule Console.Plural.Queries do
247276
def update_incident_mutation(), do: @update_incident
248277

249278
def create_incident_mutation(), do: @create_incident
279+
280+
def get_stack_query(), do: @get_stack
281+
282+
def install_stack_mutation(), do: @install_stack
250283
end

lib/console/plural/repositories.ex

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ defmodule Console.Plural.Repositories do
99
Dashboard,
1010
Recipe,
1111
Workspace,
12-
OIDCProvider
12+
OIDCProvider,
13+
Stack
1314
}
1415

1516
defmodule Query do
16-
defstruct [:installations, :searchRepositories, :recipes, :recipe, :installation]
17+
defstruct [:installations, :searchRepositories, :recipes, :recipe, :installation, :stack]
1718
end
1819

1920
defmodule Mutation do
20-
defstruct [:installRecipe, :upsertOidcProvider]
21+
defstruct [:installRecipe, :upsertOidcProvider, :installStack]
2122
end
2223

2324
def search_repositories(query, first) do
@@ -59,6 +60,20 @@ defmodule Console.Plural.Repositories do
5960
|> when_ok(fn %{recipe: result} -> result end)
6061
end
6162

63+
def get_stack(name) do
64+
prov = Workspace.provider()
65+
get_stack_query()
66+
|> Client.run(%{name: name, provider: provider(prov)}, %Query{stack: Stack.spec()})
67+
|> when_ok(fn %{stack: s} -> s end)
68+
end
69+
70+
def install_stack(name) do
71+
prov = Workspace.provider()
72+
install_stack_mutation()
73+
|> Client.run(%{name: name, provider: provider(prov)}, %Mutation{installStack: [Recipe.spec()]})
74+
|> when_ok(fn %{installStack: recipes} -> recipes end)
75+
end
76+
6277
def install_recipe(id) do
6378
install_recipe_mutation()
6479
|> Client.run(

lib/console/services/base.ex

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ defmodule Console.Services.Base do
1616

1717
def start_transaction(), do: Ecto.Multi.new()
1818

19+
def short_circuit(), do: []
20+
21+
def short(circuit, name, fun) when is_list(circuit) and is_function(fun),
22+
do: [{name, fun} | circuit]
23+
1924
def add_operation(multi, name, fun) when is_function(fun) do
2025
Ecto.Multi.run(multi, name, fn _, params ->
2126
fun.(params)
2227
end)
2328
end
2429

25-
def execute(%Ecto.Multi{} = multi, opts \\ []) do
30+
def execute(operation, opts \\ [])
31+
def execute(%Ecto.Multi{} = multi, opts) do
2632
with {:ok, result} <- Console.Repo.transaction(multi) do
2733
case Map.new(opts) do
2834
%{extract: operation} -> {:ok, result[operation]}
@@ -33,6 +39,19 @@ defmodule Console.Services.Base do
3339
{:error, reason} -> {:error, reason}
3440
end
3541
end
42+
def execute(circuit, _) when is_list(circuit) do
43+
Enum.reverse(circuit)
44+
|> execute_short_circuit(%{})
45+
end
46+
47+
defp execute_short_circuit([], result), do: {:ok, result}
48+
defp execute_short_circuit([{name, fun} | rest], result) do
49+
case fun.() do
50+
{:ok, res} -> execute_short_circuit(rest, Map.put(result, name, res))
51+
:ok -> execute_short_circuit(rest, result)
52+
{:error, _} = error -> error
53+
end
54+
end
3655

3756
def broadcast(resource, delta) do
3857
topic = Topic.infer(resource, delta)

lib/console/services/plural.ex

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Console.Services.Plural do
2+
use Console.Services.Base
23
alias Console.Deployer
34
alias Console.Schema.{User, Manifest}
45
alias Console.Services.{Builds}
@@ -64,6 +65,32 @@ defmodule Console.Services.Plural do
6465
end
6566
end
6667

68+
def install_stack(name, context, oidc, %User{} = user) do
69+
with {:ok, [recipe | _] = recipes} <- Repositories.install_stack(name),
70+
{:ok, _} <- oidc_for_stack(recipes, context, oidc) do
71+
repos = Enum.map(recipes, & &1.repository.name)
72+
Builds.create(%{
73+
type: :install,
74+
repository: recipe.repository.name,
75+
message: "Installed stack #{name} with repositories #{Enum.join(repos, ", ")}",
76+
context: %{
77+
configuration: context,
78+
bundles: Enum.map(recipes, & %{name: &1.name, repository: &1.repository.name}),
79+
},
80+
}, user)
81+
end
82+
end
83+
84+
def oidc_for_stack([_ | _] = recipes, context, oidc) do
85+
Enum.reduce(recipes, short_circuit(), fn recipe, circuit ->
86+
short(circuit, recipe.id, fn ->
87+
with :ok <- configure_oidc(recipe, context, oidc),
88+
do: oidc_dependencies(recipe.recipeDependencies, context, oidc)
89+
end)
90+
end)
91+
|> execute()
92+
end
93+
6794
def configure_oidc(
6895
%Recipe{
6996
repository: %{name: name},

0 commit comments

Comments
 (0)