Skip to content

Commit

Permalink
Project deletion api (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Jul 7, 2024
1 parent 1637994 commit 9fc475a
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 4 deletions.
16 changes: 16 additions & 0 deletions assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,8 @@ export type InfrastructureStack = {
/** A type for the stack, specifies the tool to use to apply it */
type: StackType;
updatedAt?: Maybe<Scalars['DateTime']['output']>;
/** Arbitrary variables to add to a stack run */
variables?: Maybe<Scalars['Json']['output']>;
/** the subdirectory you want to run the stack's commands w/in */
workdir?: Maybe<Scalars['String']['output']>;
writeBindings?: Maybe<Array<Maybe<PolicyBinding>>>;
Expand Down Expand Up @@ -4251,6 +4253,7 @@ export type RootMutationType = {
deletePipeline?: Maybe<Pipeline>;
deletePod?: Maybe<Pod>;
deletePrAutomation?: Maybe<PrAutomation>;
deleteProject?: Maybe<Project>;
deleteProviderCredential?: Maybe<ProviderCredential>;
deletePullRequest?: Maybe<PullRequest>;
deleteRole?: Maybe<Role>;
Expand Down Expand Up @@ -4703,6 +4706,11 @@ export type RootMutationTypeDeletePrAutomationArgs = {
};


export type RootMutationTypeDeleteProjectArgs = {
id: Scalars['ID']['input'];
};


export type RootMutationTypeDeleteProviderCredentialArgs = {
id: Scalars['ID']['input'];
};
Expand Down Expand Up @@ -6714,6 +6722,8 @@ export type ServiceDeployment = {
namespace: Scalars['String']['output'];
/** whether this service is controlled by a global service */
owner?: Maybe<GlobalService>;
/** the service that owns this service in a service-of-services setup */
parent?: Maybe<ServiceDeployment>;
/** how you'd like to perform a canary promotion */
promotion?: Maybe<ServicePromotion>;
/** if true, deletion of this service is not allowed */
Expand Down Expand Up @@ -6774,6 +6784,7 @@ export type ServiceDeploymentAttributes = {
kustomize?: InputMaybe<KustomizeAttributes>;
name: Scalars['String']['input'];
namespace: Scalars['String']['input'];
parentId?: InputMaybe<Scalars['ID']['input']>;
protect?: InputMaybe<Scalars['Boolean']['input']>;
readBindings?: InputMaybe<Array<InputMaybe<PolicyBindingAttributes>>>;
repositoryId?: InputMaybe<Scalars['ID']['input']>;
Expand Down Expand Up @@ -6926,6 +6937,7 @@ export type ServiceUpdateAttributes = {
helm?: InputMaybe<HelmConfigAttributes>;
interval?: InputMaybe<Scalars['String']['input']>;
kustomize?: InputMaybe<KustomizeAttributes>;
parentId?: InputMaybe<Scalars['ID']['input']>;
protect?: InputMaybe<Scalars['Boolean']['input']>;
readBindings?: InputMaybe<Array<InputMaybe<PolicyBindingAttributes>>>;
syncConfig?: InputMaybe<SyncConfigAttributes>;
Expand Down Expand Up @@ -7021,6 +7033,8 @@ export type StackAttributes = {
tags?: InputMaybe<Array<InputMaybe<TagAttributes>>>;
/** A type for the stack, specifies the tool to use to apply it */
type: StackType;
/** arbitrary variables to pass into the stack */
variables?: InputMaybe<Scalars['Json']['input']>;
/** the subdirectory you want to run the stack's commands w/in */
workdir?: InputMaybe<Scalars['String']['input']>;
writeBindings?: InputMaybe<Array<InputMaybe<PolicyBindingAttributes>>>;
Expand Down Expand Up @@ -7202,6 +7216,8 @@ export type StackRun = {
/** A type for the stack, specifies the tool to use to apply it */
type: StackType;
updatedAt?: Maybe<Scalars['DateTime']['output']>;
/** Arbitrary variables to add to a stack run */
variables?: Maybe<Scalars['Json']['output']>;
/** the subdirectory you want to run the stack's commands w/in */
workdir?: Maybe<Scalars['String']['output']>;
};
Expand Down
18 changes: 18 additions & 0 deletions lib/console/deployments/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@ defmodule Console.Deployments.Settings do
|> when_ok(:update)
end

@doc """
It will delete a repository if it's not currently in use
"""
@spec delete_project(binary, User.t) :: project_resp
def delete_project(id, %User{} = user) do
try do
get_project!(id)
|> Repo.preload([:read_bindings, :write_bindings])
|> Project.changeset()
|> allow(user, :write)
|> when_ok(:delete)
|> notify(:delete, user)
rescue
# foreign key constraint violated
_ -> {:error, "could not delete project"}
end
end

@doc """
creates an instance of the deployment settings object, only used in init
"""
Expand Down
4 changes: 3 additions & 1 deletion lib/console/deployments/stacks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -565,9 +565,11 @@ defmodule Console.Deployments.Stacks do
|> notify(:create)
end

@run_attrs ~w(approval variables actor_id workdir manage_state dry_run configuration type environment files job_spec repository_id cluster_id)a

defp stack_attrs(%Stack{} = stack, sha) do
Repo.preload(stack, [:environment, :files])
|> Map.take(~w(approval actor_id workdir manage_state dry_run configuration type environment files job_spec repository_id cluster_id)a)
|> Map.take(@run_attrs)
|> Map.put(:configuration, ensure_configuration(stack))
|> Console.clean()
|> Map.update(:environment, [], fn env -> Enum.map(env, &Map.delete(&1, :stack_id)) end)
Expand Down
3 changes: 3 additions & 0 deletions lib/console/graphql/deployments/service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule Console.GraphQl.Deployments.Service do
field :git, :git_ref_attributes
field :helm, :helm_config_attributes
field :kustomize, :kustomize_attributes
field :parent_id, :id
field :configuration, list_of(:config_attributes)
field :dependencies, list_of(:service_dependency_attributes)
field :read_bindings, list_of(:policy_binding_attributes)
Expand Down Expand Up @@ -73,6 +74,7 @@ defmodule Console.GraphQl.Deployments.Service do
field :helm, :helm_config_attributes
field :configuration, list_of(:config_attributes)
field :kustomize, :kustomize_attributes
field :parent_id, :id
field :dependencies, list_of(:service_dependency_attributes)
field :read_bindings, list_of(:policy_binding_attributes)
field :write_bindings, list_of(:policy_binding_attributes)
Expand Down Expand Up @@ -187,6 +189,7 @@ defmodule Console.GraphQl.Deployments.Service do
field :read_bindings, list_of(:policy_binding), resolve: dataloader(Deployments), description: "read policy for this service"
field :write_bindings, list_of(:policy_binding), resolve: dataloader(Deployments), description: "write policy of this service"

field :parent, :service_deployment, resolve: dataloader(Deployments), description: "the service that owns this service in a service-of-services setup"
field :errors, list_of(:service_error), resolve: dataloader(Deployments), description: "a list of errors generated by the deployment operator"
field :cluster, :cluster, resolve: dataloader(Deployments), description: "the cluster this service is deployed into"
field :revision, :revision, resolve: dataloader(Deployments), description: "the current revision of this service"
Expand Down
7 changes: 7 additions & 0 deletions lib/console/graphql/deployments/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,12 @@ defmodule Console.GraphQl.Deployments.Settings do

resolve &Deployments.update_project/2
end

field :delete_project, :project do
middleware Authenticated
arg :id, non_null(:id)

resolve &Deployments.delete_project/2
end
end
end
3 changes: 3 additions & 0 deletions lib/console/graphql/deployments/stack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defmodule Console.GraphQl.Deployments.Stack do
field :connection_id, :id, description: "id of an scm connection to use for pr callbacks"
field :definition_id, :id, description: "the id of a stack definition to use"
field :cron, :stack_cron_attributes, description: "a cron to spawn runs for this stack"
field :variables, :json, description: "arbitrary variables to pass into the stack"

field :read_bindings, list_of(:policy_binding_attributes)
field :write_bindings, list_of(:policy_binding_attributes)
Expand Down Expand Up @@ -142,6 +143,7 @@ defmodule Console.GraphQl.Deployments.Stack do
field :cancellation_reason, :string, description: "why this run was cancelled"
field :workdir, :string, description: "the subdirectory you want to run the stack's commands w/in"
field :manage_state, :boolean, description: "whether you want Plural to manage the state of this stack"
field :variables, :json, description: "Arbitrary variables to add to a stack run"

connection field :runs, node_type: :stack_run do
arg :pull_request_id, :id
Expand Down Expand Up @@ -231,6 +233,7 @@ defmodule Console.GraphQl.Deployments.Stack do
field :approved_at, :datetime, description: "when this run was approved"
field :workdir, :string, description: "the subdirectory you want to run the stack's commands w/in"
field :manage_state, :boolean, description: "whether you want Plural to manage the state of this stack"
field :variables, :json, description: "Arbitrary variables to add to a stack run"

field :state_urls, :state_urls, resolve: fn
run, _, _ -> {:ok, Stacks.state_urls(run)}
Expand Down
3 changes: 3 additions & 0 deletions lib/console/graphql/resolvers/deployments/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ defmodule Console.GraphQl.Resolvers.Deployments.Settings do
def update_project(%{attributes: attrs, id: id}, %{context: %{current_user: user}}),
do: Settings.update_project(attrs, id, user)

def delete_project(%{id: id}, %{context: %{current_user: user}}),
do: Settings.delete_project(id, user)

def settings(_, _), do: {:ok, Settings.fetch_consistent()}

def enable(_, %{context: %{current_user: user}}), do: Settings.enable(user)
Expand Down
5 changes: 5 additions & 0 deletions lib/console/schema/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ defmodule Console.Schema.Project do
|> cast(attrs, ~w(name description default)a)
|> cast_assoc(:read_bindings)
|> cast_assoc(:write_bindings)
|> foreign_key_constraint(:id, name: :stacks, match: :prefix, message: "there is an active stack in this project")
|> foreign_key_constraint(:id, name: :clusters, match: :prefix, message: "there is an active cluster in this project")
|> foreign_key_constraint(:id, name: :pipelines, match: :prefix, message: "there is an active pipeline in this project")
|> foreign_key_constraint(:id, name: :global_services, match: :prefix, message: "there is an active global_service in this project")
|> foreign_key_constraint(:id, name: :managed_namespaces, match: :prefix, message: "there is an active managed_namespace in this project")
|> put_new_change(:write_policy_id, &Ecto.UUID.generate/0)
|> put_new_change(:read_policy_id, &Ecto.UUID.generate/0)
|> validate_required(~w(name)a)
Expand Down
3 changes: 2 additions & 1 deletion lib/console/schema/service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ defmodule Console.Schema.Service do
belongs_to :cluster, Cluster
belongs_to :repository, GitRepository
belongs_to :owner, GlobalService
belongs_to :parent, __MODULE__

has_one :reference_cluster, Cluster
has_one :provider, ClusterProvider
Expand Down Expand Up @@ -268,7 +269,7 @@ defmodule Console.Schema.Service do
def docs_path(%__MODULE__{docs_path: p}) when is_binary(p), do: p
def docs_path(%__MODULE__{git: %{folder: p}}), do: Path.join(p, "docs")

@valid ~w(name protect interval docs_path component_status templated dry_run interval status version sha cluster_id repository_id namespace owner_id message)a
@valid ~w(name protect interval parent_id docs_path component_status templated dry_run interval status version sha cluster_id repository_id namespace owner_id message)a
@immutable ~w(cluster_id)a

def changeset(model, attrs \\ %{}) do
Expand Down
3 changes: 2 additions & 1 deletion lib/console/schema/stack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ defmodule Console.Schema.Stack do
field :workdir, :string
field :locked_at, :utc_datetime_usec
field :polled_sha, :string
field :variables, :map

field :actor_changed, :boolean, virtual: true

Expand Down Expand Up @@ -151,7 +152,7 @@ defmodule Console.Schema.Stack do

def stream(query \\ __MODULE__), do: ordered(query, asc: :id)

@valid ~w(name type paused actor_id definition_id workdir manage_state status approval project_id connection_id repository_id cluster_id)a
@valid ~w(name type paused actor_id variables definition_id workdir manage_state status approval project_id connection_id repository_id cluster_id)a
@immutable ~w(project_id)a


Expand Down
3 changes: 2 additions & 1 deletion lib/console/schema/stack_run.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defmodule Console.Schema.StackRun do
field :message, :binary
field :workdir, :string
field :manage_state, :boolean, default: false
field :variables, :map

field :cancellation_reason, :string

Expand Down Expand Up @@ -113,7 +114,7 @@ defmodule Console.Schema.StackRun do
from(r in query, order_by: ^order)
end

@valid ~w(type status workdir actor_id manage_state message approval dry_run repository_id pull_request_id cluster_id stack_id)a
@valid ~w(type status workdir actor_id variables manage_state message approval dry_run repository_id pull_request_id cluster_id stack_id)a

def changeset(model, attrs \\ %{}) do
model
Expand Down
15 changes: 15 additions & 0 deletions priv/repo/migrations/20240706030421_fix_project_fks.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Console.Repo.Migrations.FixProjectFks do
use Ecto.Migration

def change do
alter table(:global_services) do
modify :project_id, references(:projects, type: :uuid),
from: references(:projects, type: :uuid, on_delete: :nilify_all)
end

alter table(:managed_namespaces) do
modify :project_id, references(:projects, type: :uuid),
from: references(:projects, type: :uuid, on_delete: :nilify_all)
end
end
end
17 changes: 17 additions & 0 deletions priv/repo/migrations/20240706133612_add_stack_variables.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Console.Repo.Migrations.AddStackVariables do
use Ecto.Migration

def change do
alter table(:stacks) do
add :variables, :map
end

alter table(:stack_runs) do
add :variables, :map
end

alter table(:services) do
add :parent_id, references(:services, type: :uuid, on_delete: :nilify_all)
end
end
end
18 changes: 18 additions & 0 deletions schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,8 @@ type RootMutationType {

updateProject(id: ID!, attributes: ProjectAttributes!): Project

deleteProject(id: ID!): Project

"a reusable mutation for updating rbac settings on core services"
updateRbac(rbac: RbacAttributes!, serviceId: ID, clusterId: ID, providerId: ID, pipelineId: ID, stackId: ID): Boolean
}
Expand Down Expand Up @@ -1095,6 +1097,9 @@ input StackAttributes {
"a cron to spawn runs for this stack"
cron: StackCronAttributes

"arbitrary variables to pass into the stack"
variables: Json

readBindings: [PolicyBindingAttributes]

writeBindings: [PolicyBindingAttributes]
Expand Down Expand Up @@ -1284,6 +1289,9 @@ type InfrastructureStack {
"whether you want Plural to manage the state of this stack"
manageState: Boolean

"Arbitrary variables to add to a stack run"
variables: Json

runs(after: String, first: Int, before: String, last: Int, pullRequestId: ID): StackRunConnection

pullRequests(after: String, first: Int, before: String, last: Int): PullRequestConnection
Expand Down Expand Up @@ -1430,6 +1438,9 @@ type StackRun {
"whether you want Plural to manage the state of this stack"
manageState: Boolean

"Arbitrary variables to add to a stack run"
variables: Json

stateUrls: StateUrls

"the kubernetes job for this run (useful for debugging if issues arise)"
Expand Down Expand Up @@ -2725,6 +2736,8 @@ input ServiceDeploymentAttributes {

kustomize: KustomizeAttributes

parentId: ID

configuration: [ConfigAttributes]

dependencies: [ServiceDependencyAttributes]
Expand Down Expand Up @@ -2806,6 +2819,8 @@ input ServiceUpdateAttributes {

kustomize: KustomizeAttributes

parentId: ID

dependencies: [ServiceDependencyAttributes]

readBindings: [PolicyBindingAttributes]
Expand Down Expand Up @@ -2953,6 +2968,9 @@ type ServiceDeployment {
"write policy of this service"
writeBindings: [PolicyBinding]

"the service that owns this service in a service-of-services setup"
parent: ServiceDeployment

"a list of errors generated by the deployment operator"
errors: [ServiceError]

Expand Down
21 changes: 21 additions & 0 deletions test/console/deployments/settings_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,25 @@ defmodule Console.Deployments.SettingsTest do
{:error, _} = Settings.update_project(%{name: "test"}, proj.id, insert(:user))
end
end

describe "#delete_project/2" do
test "admins can delete a new project" do
proj = insert(:project)
{:ok, deleted} = Settings.delete_project(proj.id, admin_user())

assert deleted.id == proj.id
assert deleted.name == proj.name
end

test "you cannot delete a nonempty project" do
proj = insert(:project)
insert(:cluster, project: proj)
{:error, _} = Settings.delete_project(proj.id, admin_user())
end

test "nonadmins cannot delete projects" do
proj = insert(:project)
{:error, _} = Settings.delete_project(proj.id, insert(:user))
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,21 @@ defmodule Console.GraphQl.Deployments.SettingsMutationsTest do
assert update["name"] == "test"
end
end

describe "deleteProject" do
test "admins can delete a project" do
proj = insert(:project)
{:ok, %{data: %{"deleteProject" => deleted}}} = run_query("""
mutation delete($id: ID!) {
deleteProject(id: $id) {
id
name
}
}
""", %{"id" => proj.id}, %{current_user: admin_user()})

assert deleted["id"] == proj.id
assert deleted["name"] == proj.name
end
end
end

0 comments on commit 9fc475a

Please sign in to comment.