diff --git a/assets/src/generated/graphql.ts b/assets/src/generated/graphql.ts index dbecf981e..f208417c0 100644 --- a/assets/src/generated/graphql.ts +++ b/assets/src/generated/graphql.ts @@ -3595,6 +3595,17 @@ export type PullRequestEdge = { node?: Maybe; }; +/** attributes for a pull request pointer record */ +export type PullRequestUpdateAttributes = { + cluster?: InputMaybe; + clusterId?: InputMaybe; + labels?: InputMaybe>>; + service?: InputMaybe; + serviceId?: InputMaybe; + status: PrStatus; + title: Scalars['String']['input']; +}; + export type RbacAttributes = { readBindings?: InputMaybe>>; writeBindings?: InputMaybe>>; @@ -3931,6 +3942,7 @@ export type RootMutationType = { deletePod?: Maybe; deletePrAutomation?: Maybe; deleteProviderCredential?: Maybe; + deletePullRequest?: Maybe; deleteRole?: Maybe; deleteScmConnection?: Maybe; deleteServiceContext?: Maybe; @@ -3995,6 +4007,7 @@ export type RootMutationType = { updateObjectStore?: Maybe; updatePersona?: Maybe; updatePrAutomation?: Maybe; + updatePullRequest?: Maybe; /** a reusable mutation for updating rbac settings on core services */ updateRbac?: Maybe; updateRole?: Maybe; @@ -4352,6 +4365,11 @@ export type RootMutationTypeDeleteProviderCredentialArgs = { }; +export type RootMutationTypeDeletePullRequestArgs = { + id: Scalars['ID']['input']; +}; + + export type RootMutationTypeDeleteRoleArgs = { id: Scalars['ID']['input']; }; @@ -4650,6 +4668,12 @@ export type RootMutationTypeUpdatePrAutomationArgs = { }; +export type RootMutationTypeUpdatePullRequestArgs = { + attributes?: InputMaybe; + id: Scalars['ID']['input']; +}; + + export type RootMutationTypeUpdateRbacArgs = { clusterId?: InputMaybe; providerId?: InputMaybe; @@ -5753,6 +5777,7 @@ export type RootSubscriptionType = { commandDelta?: Maybe; notificationDelta?: Maybe; podDelta?: Maybe; + runLogsDelta?: Maybe; }; @@ -5765,6 +5790,11 @@ export type RootSubscriptionTypeCommandDeltaArgs = { buildId: Scalars['ID']['input']; }; + +export type RootSubscriptionTypeRunLogsDeltaArgs = { + stepId: Scalars['ID']['input']; +}; + export type RouterFilterAttributes = { /** whether to enable delivery for events associated with this cluster */ clusterId?: InputMaybe; @@ -5792,6 +5822,12 @@ export type RunLogs = { updatedAt?: Maybe; }; +export type RunLogsDelta = { + __typename?: 'RunLogsDelta'; + delta?: Maybe; + payload?: Maybe; +}; + export type RunStep = { __typename?: 'RunStep'; args?: Maybe>; diff --git a/charts/controller/crds/deployments.plural.sh_globalservices.yaml b/charts/controller/crds/deployments.plural.sh_globalservices.yaml index 116ed7394..34f214a92 100644 --- a/charts/controller/crds/deployments.plural.sh_globalservices.yaml +++ b/charts/controller/crds/deployments.plural.sh_globalservices.yaml @@ -278,6 +278,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml b/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml index dad3526db..8ada0bafa 100644 --- a/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml +++ b/charts/controller/crds/deployments.plural.sh_managednamespaces.yaml @@ -190,6 +190,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/charts/controller/crds/deployments.plural.sh_servicedeployments.yaml b/charts/controller/crds/deployments.plural.sh_servicedeployments.yaml index 8cee44d2a..3dd15d4a3 100644 --- a/charts/controller/crds/deployments.plural.sh_servicedeployments.yaml +++ b/charts/controller/crds/deployments.plural.sh_servicedeployments.yaml @@ -268,6 +268,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm chart + from (useful if you're using a multi-source configuration for + values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/controller/api/v1alpha1/servicedeployment_types.go b/controller/api/v1alpha1/servicedeployment_types.go index 812bffb8a..40d33fe89 100644 --- a/controller/api/v1alpha1/servicedeployment_types.go +++ b/controller/api/v1alpha1/servicedeployment_types.go @@ -32,6 +32,9 @@ type ServiceHelm struct { // name of the helm release to use when applying // +kubebuilder:validation:Optional Release *string `json:"release,omitempty"` + // reference to a GitRepository to source the helm chart from (useful if you're using a multi-source configuration for values files) + // +kubebuilder:validation:Optional + RepositoryRef *corev1.ObjectReference `json:"repositoryRef"` // arbitrary yaml values to overlay // +kubebuilder:validation:Optional Values *runtime.RawExtension `json:"values,omitempty"` diff --git a/controller/api/v1alpha1/zz_generated.deepcopy.go b/controller/api/v1alpha1/zz_generated.deepcopy.go index 36cc7cde9..6da3a5751 100644 --- a/controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/controller/api/v1alpha1/zz_generated.deepcopy.go @@ -2591,6 +2591,11 @@ func (in *ServiceHelm) DeepCopyInto(out *ServiceHelm) { *out = new(string) **out = **in } + if in.RepositoryRef != nil { + in, out := &in.RepositoryRef, &out.RepositoryRef + *out = new(v1.ObjectReference) + **out = **in + } if in.Values != nil { in, out := &in.Values, &out.Values *out = new(runtime.RawExtension) diff --git a/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml b/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml index 116ed7394..34f214a92 100644 --- a/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml +++ b/controller/config/crd/bases/deployments.plural.sh_globalservices.yaml @@ -278,6 +278,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml b/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml index dad3526db..8ada0bafa 100644 --- a/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml +++ b/controller/config/crd/bases/deployments.plural.sh_managednamespaces.yaml @@ -190,6 +190,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/controller/config/crd/bases/deployments.plural.sh_servicedeployments.yaml b/controller/config/crd/bases/deployments.plural.sh_servicedeployments.yaml index 8cee44d2a..3dd15d4a3 100644 --- a/controller/config/crd/bases/deployments.plural.sh_servicedeployments.yaml +++ b/controller/config/crd/bases/deployments.plural.sh_servicedeployments.yaml @@ -268,6 +268,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm chart + from (useful if you're using a multi-source configuration for + values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/controller/docs/api.md b/controller/docs/api.md index dca3e58db..3e1a0e41a 100644 --- a/controller/docs/api.md +++ b/controller/docs/api.md @@ -1316,6 +1316,7 @@ _Appears in:_ | `valuesFrom` _[SecretReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretreference-v1-core)_ | Fetches the helm values from a secret in this cluster, will consider any key with yaml data a values file and merge them iteratively | | Optional: {}
| | `valuesConfigMapRef` _[ConfigMapKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#configmapkeyselector-v1-core)_ | | | Optional: {}
| | `release` _string_ | name of the helm release to use when applying | | Optional: {}
| +| `repositoryRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | reference to a GitRepository to source the helm chart from (useful if you're using a multi-source configuration for values files) | | Optional: {}
| | `values` _[RawExtension](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime#RawExtension)_ | arbitrary yaml values to overlay | | Optional: {}
| | `valuesFiles` _string array_ | individual values files to overlay | | Optional: {}
| | `chart` _string_ | chart to use | | Optional: {}
| diff --git a/controller/internal/controller/servicedeployment_controller.go b/controller/internal/controller/servicedeployment_controller.go index c7e953eb1..7a932ed23 100644 --- a/controller/internal/controller/servicedeployment_controller.go +++ b/controller/internal/controller/servicedeployment_controller.go @@ -3,6 +3,7 @@ package controller import ( "context" "encoding/json" + "fmt" "sort" console "github.com/pluralsh/console-client-go" @@ -303,6 +304,21 @@ func (r *ServiceReconciler) genServiceAttributes(ctx context.Context, service *v Namespace: service.Spec.Helm.Repository.Namespace, } } + + if service.Spec.RepositoryRef != nil { + ref := service.Spec.RepositoryRef + var repo v1alpha1.GitRepository + if err := r.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, &repo); err != nil { + return nil, err + } + + if repo.Status.ID == nil { + return nil, fmt.Errorf("GitRepository %s/%s is not yet ready", ref.Namespace, ref.Name) + } + + attr.Helm.RepositoryID = repo.Status.ID + } + if service.Spec.Helm.ValuesConfigMapRef != nil { val, err := utils.GetConfigMapData(ctx, r.Client, service.GetNamespace(), service.Spec.Helm.ValuesConfigMapRef) if err != nil { diff --git a/lib/console.ex b/lib/console.ex index e1a4a06a8..f66886d94 100644 --- a/lib/console.ex +++ b/lib/console.ex @@ -218,5 +218,7 @@ defmodule Console do end end + def lines(str), do: String.split(str, ~r/\R/) + def storage, do: Console.Storage.Git end diff --git a/lib/console/deployments/events.ex b/lib/console/deployments/events.ex index f4a5eb04a..e24cf6cc0 100644 --- a/lib/console/deployments/events.ex +++ b/lib/console/deployments/events.ex @@ -45,7 +45,6 @@ defmodule Console.PubSub.ObjectStoreCreated, do: use Piazza.PubSub.Event defmodule Console.PubSub.ObjectStoreUpdated, do: use Piazza.PubSub.Event defmodule Console.PubSub.ObjectStoreDeleted, do: use Piazza.PubSub.Event - defmodule Console.PubSub.PullRequestCreated, do: use Piazza.PubSub.Event defmodule Console.PubSub.PullRequestUpdated, do: use Piazza.PubSub.Event @@ -62,3 +61,5 @@ defmodule Console.PubSub.StackRunCreated, do: use Piazza.PubSub.Event defmodule Console.PubSub.StackRunUpdated, do: use Piazza.PubSub.Event defmodule Console.PubSub.StackRunDeleted, do: use Piazza.PubSub.Event defmodule Console.PubSub.StackRunCompleted, do: use Piazza.PubSub.Event + +defmodule Console.PubSub.RunLogsCreated, do: use Piazza.PubSub.Event diff --git a/lib/console/deployments/git.ex b/lib/console/deployments/git.ex index 94e97ca3a..c13b38db7 100644 --- a/lib/console/deployments/git.ex +++ b/lib/console/deployments/git.ex @@ -321,6 +321,28 @@ defmodule Console.Deployments.Git do |> notify(:create) end + @doc """ + Deletes this reference to a pull request from the db + """ + @spec delete_pr(binary, User.t) :: pull_request_resp + def delete_pr(id, %User{} = user) do + Repo.get(PullRequest, id) + |> PullRequest.changeset() + |> allow(user, :write) + |> when_ok(:delete) + end + + @doc """ + Updates this pr reference in the db + """ + @spec update_pr(map, binary, User.t) :: pull_request_resp + def update_pr(attrs, id, %User{} = user) do + Repo.get(PullRequest, id) + |> PullRequest.changeset(attrs) + |> allow(user, :write) + |> when_ok(:update) + end + @doc """ Fetches all helm repos registered in this cluster so far """ diff --git a/lib/console/deployments/git/agent.ex b/lib/console/deployments/git/agent.ex index 8c1036ee0..250375208 100644 --- a/lib/console/deployments/git/agent.ex +++ b/lib/console/deployments/git/agent.ex @@ -32,6 +32,8 @@ defmodule Console.Deployments.Git.Agent do def sha(pid, ref), do: GenServer.call(pid, {:sha, ref}, 30_000) + def changes(pid, sha1, sha2, folder), do: GenServer.call(pid, {:changes, sha1, sha2, folder}, 30_000) + def kick(pid), do: send(pid, :pull) def start(%GitRepository{} = repo) do @@ -83,6 +85,10 @@ defmodule Console.Deployments.Git.Agent do {:reply, Cache.commit(cache, ref), state} end + def handle_call({:changes, sha1, sha2, folder}, _, %State{cache: cache} = state) do + {:reply, Cache.changes(cache, sha1, sha2, folder), state} + end + def handle_call({:fetch, %Service.Git{} = ref}, _, %State{cache: cache} = state) do case Cache.fetch(cache, ref) do {:ok, %Cache.Line{file: f}, cache} -> {:reply, File.open(f), %{state | cache: cache}} diff --git a/lib/console/deployments/git/cache.ex b/lib/console/deployments/git/cache.ex index 7e44cdc3b..72b86496f 100644 --- a/lib/console/deployments/git/cache.ex +++ b/lib/console/deployments/git/cache.ex @@ -65,6 +65,8 @@ defmodule Console.Deployments.Git.Cache do end end + def changes(%__MODULE__{git: g}, sha1, sha2, folder), do: file_changes(g, sha1, sha2, folder) + defp new_line(cache, repo, sha, path, filter) do with {:ok, _} <- git(repo, "checkout", [sha]), {:ok, msg} <- msg(repo), diff --git a/lib/console/deployments/git/cmd.ex b/lib/console/deployments/git/cmd.ex index 801429b3d..c5777cd61 100644 --- a/lib/console/deployments/git/cmd.ex +++ b/lib/console/deployments/git/cmd.ex @@ -27,6 +27,13 @@ defmodule Console.Deployments.Git.Cmd do do: {:ok, String.trim(sha)} end + def file_changes(repo, sha1, sha2, folder) do + case git(repo, "--no-pager", ["diff", "--name-only", "#{sha1}", "#{sha2}", "--", folder]) do + {:ok, res} -> {:ok, String.trim(res) |> Console.lines()} + _ -> {:ok, :pass} + end + end + def branches(%GitRepository{} = repo) do with {:ok, res} <- git(repo, "branch", ["-r"]) do split_and_trim(res) diff --git a/lib/console/deployments/git/discovery.ex b/lib/console/deployments/git/discovery.ex index 090503b88..5cd789bb7 100644 --- a/lib/console/deployments/git/discovery.ex +++ b/lib/console/deployments/git/discovery.ex @@ -26,6 +26,11 @@ defmodule Console.Deployments.Git.Discovery do do: Agent.sha(pid, ref) end + def changes(%GitRepository{} = repo, sha1, sha2, folder) do + with {:ok, pid} <- find(repo), + do: Agent.changes(pid, sha1, sha2, folder) + end + @spec docs(Service.t) :: {:ok, File.t} | error def docs(%Service{} = svc) do %{repository: repo} = Console.Repo.preload(svc, [:repository]) diff --git a/lib/console/deployments/pipelines/stage_worker.ex b/lib/console/deployments/pipelines/stage_worker.ex index b07b1cd73..fcbd1266a 100644 --- a/lib/console/deployments/pipelines/stage_worker.ex +++ b/lib/console/deployments/pipelines/stage_worker.ex @@ -5,11 +5,16 @@ defmodule Console.Deployments.Pipelines.StageWorker do alias Console.Deployments.Pipelines alias Console.Schema.PipelineStage + @poll :timer.minutes(2) + def start_link([shard]) do GenServer.start_link(__MODULE__, :ok, name: name(shard)) end - def init(_), do: {:ok, %{}} + def init(_) do + :timer.send_interval(@poll, :cleanup) + {:ok, %{}} + end def dispatch(shard, %PipelineStage{} = stage), do: GenServer.cast(name(shard), stage) @@ -34,4 +39,11 @@ defmodule Console.Deployments.Pipelines.StageWorker do end {:noreply, state} end + + def handle_info(:cleanup, state) do + Briefly.cleanup() + {:noreply, state} + end + + def handle_info(_, state), do: {:noreply, state} end diff --git a/lib/console/deployments/policies/rbac.ex b/lib/console/deployments/policies/rbac.ex index 773a2ad95..5d6f4b4a8 100644 --- a/lib/console/deployments/policies/rbac.ex +++ b/lib/console/deployments/policies/rbac.ex @@ -19,7 +19,8 @@ defmodule Console.Deployments.Policies.Rbac do PolicyConstraint, PinnedCustomResource, Stack, - StackRun + StackRun, + RunStep } def globally_readable(query, %User{roles: %{admin: true}}, _), do: query @@ -73,6 +74,8 @@ defmodule Console.Deployments.Policies.Rbac do do: recurse(stack, user, action, & &1.cluster) def evaluate(%StackRun{} = run, %User{} = user, action), do: recurse(run, user, action, & &1.stack) + def evaluate(%RunStep{} = step, %User{} = user, action), + do: recurse(step, user, action, & &1.run) def evaluate(%PinnedCustomResource{} = pcr, %User{} = user, action) do recurse(pcr, user, action, fn %{cluster: %Cluster{} = cluster} -> cluster @@ -81,19 +84,22 @@ defmodule Console.Deployments.Policies.Rbac do end def evaluate(_, _, _), do: false - def preload(%PipelineContext{} = ctx), do: Repo.preload(ctx, [pipeline: [:read_bindings, :write_bindings]]) - def preload(%PipelineGate{} = gate), do: Repo.preload(gate, [edge: [pipeline: [:read_bindings, :write_bindings]]]) - def preload(%Pipeline{} = pipe), do: Repo.preload(pipe, [:read_bindings, :write_bindings]) + @bindings [:read_bindings, :write_bindings] + @stack_preloads [:read_bindings, :write_bindings, cluster: @bindings] + + def preload(%PipelineContext{} = ctx), do: Repo.preload(ctx, [pipeline: @bindings]) + def preload(%PipelineGate{} = gate), do: Repo.preload(gate, [edge: [pipeline: @bindings]]) + def preload(%Pipeline{} = pipe), do: Repo.preload(pipe, @bindings) def preload(%Service{} = service), - do: Repo.preload(service, [:read_bindings, :write_bindings, cluster: [:read_bindings, :write_bindings]]) + do: Repo.preload(service, [:read_bindings, :write_bindings, cluster: @bindings]) def preload(%Cluster{} = cluster), - do: Repo.preload(cluster, [:read_bindings, :write_bindings]) + do: Repo.preload(cluster, @bindings) def preload(%RuntimeService{} = cluster), - do: Repo.preload(cluster, [cluster: [:read_bindings, :write_bindings]]) + do: Repo.preload(cluster, [cluster: @bindings]) def preload(%ClusterProvider{} = cluster), - do: Repo.preload(cluster, [:read_bindings, :write_bindings]) + do: Repo.preload(cluster, @bindings) def preload(%ProviderCredential{} = cred), - do: Repo.preload(cred, [provider: [:read_bindings, :write_bindings]]) + do: Repo.preload(cred, [provider: @bindings]) def preload(%DeploymentSettings{} = settings), do: Repo.preload(settings, [:read_bindings, :write_bindings, :git_bindings, :create_bindings]) def preload(%PrAutomation{} = pr), @@ -103,9 +109,11 @@ defmodule Console.Deployments.Policies.Rbac do def preload(%PinnedCustomResource{} = pcr), do: Repo.preload(pcr, [:cluster]) def preload(%Stack{} = stack), - do: Repo.preload(stack, [:read_bindings, :write_bindings, cluster: [:read_bindings, :write_bindings]]) + do: Repo.preload(stack, @stack_preloads) def preload(%StackRun{} = pcr), - do: Repo.preload(pcr, [stack: [:read_bindings, :write_bindings, cluster: [:read_bindings, :write_bindings]]]) + do: Repo.preload(pcr, [stack: @stack_preloads]) + def preload(%RunStep{} = pcr), + do: Repo.preload(pcr, run: [stack: @stack_preloads]) def preload(pass), do: pass defp recurse(resource, user, action, func \\ fn _ -> nil end) diff --git a/lib/console/deployments/pubsub/rtc.ex b/lib/console/deployments/pubsub/rtc.ex index a7b16e271..f549665af 100644 --- a/lib/console/deployments/pubsub/rtc.ex +++ b/lib/console/deployments/pubsub/rtc.ex @@ -1,5 +1,9 @@ ## I need to find a way to provide authorization for this that's not a major perf drag +defimpl Console.PubSub.Rtc, for: [Console.PubSub.RunLogsCreated] do + def deliver(%{item: item}), do: {item, :create} +end + # defimpl Console.PubSub.Rtc, for: [ # Console.PubSub.ServiceUpdated, # Console.PubSub.ClusterUpdated, diff --git a/lib/console/deployments/stacks.ex b/lib/console/deployments/stacks.ex index 5f8d15e75..d7aa6eebc 100644 --- a/lib/console/deployments/stacks.ex +++ b/lib/console/deployments/stacks.ex @@ -179,6 +179,7 @@ defmodule Console.Deployments.Stacks do |> RunLog.changeset(attrs) |> allow(cluster, :write) |> when_ok(:insert) + |> notify(:create) end @poll_preloads ~w(repository environment files)a @@ -190,11 +191,13 @@ defmodule Console.Deployments.Stacks do def poll(%Stack{delete_run_id: id}) when is_binary(id), do: {:error, "stack is deleting"} - def poll(%Stack{sha: sha} = stack) do + def poll(%Stack{sha: sha, git: git} = stack) do %{repository: repo} = stack = Repo.preload(stack, @poll_preloads) - case Discovery.sha(repo, stack.git.ref) do + case Discovery.sha(repo, git.ref) do {:ok, ^sha} -> {:error, "no new commit in repo"} - {:ok, new_sha} -> create_run(stack, new_sha) + {:ok, new_sha} -> + with {:ok, new_sha} <- new_changes(repo, git, sha, new_sha), + do: create_run(stack, new_sha) err -> err end end @@ -204,21 +207,31 @@ defmodule Console.Deployments.Stacks do case Discovery.sha(repo, ref) do {:ok, ^sha} -> {:error, "no new commit in repo for branch #{ref}"} {:ok, new_sha} -> - start_transaction() - |> add_operation(:run, fn _ -> - create_run(stack, new_sha, %{pull_request_id: pr.id, dry_run: true}) - end) - |> add_operation(:pr, fn _ -> - Ecto.Changeset.change(pr, %{ref: new_sha}) - |> Repo.update() - end) - |> execute(extract: :run) + with {:ok, new_sha} <- new_changes(repo, stack.git, sha, new_sha) do + start_transaction() + |> add_operation(:run, fn _ -> + create_run(stack, new_sha, %{pull_request_id: pr.id, dry_run: true}) + end) + |> add_operation(:pr, fn _ -> + Ecto.Changeset.change(pr, %{ref: new_sha}) + |> Repo.update() + end) + |> execute(extract: :run) + end err -> err end end def poll(_), do: {:error, "invalid parent"} + defp new_changes(repo, %{folder: folder}, sha1, sha2) do + case Discovery.changes(repo, sha1, sha2, folder) do + {:ok, [_ | _]} -> {:ok, sha2} + {:ok, :pass} -> {:ok, sha2} + _ -> {:error, "no changes within #{folder}"} + end + end + @doc """ Creates a new run for the stack with the given sha and optional additional attrs """ @@ -240,7 +253,7 @@ defmodule Console.Deployments.Stacks do |> Repo.insert() end) |> add_operation(:stack, fn %{run: run} -> - Ecto.Changeset.change(stack, %{sha: sha}) + Ecto.Changeset.change(stack, sha_attrs(run, sha)) |> Stack.delete_changeset(delete_run(stack, run)) |> Repo.update() end) @@ -248,6 +261,9 @@ defmodule Console.Deployments.Stacks do |> notify(:create) end + defp sha_attrs(%StackRun{dry_run: true}, _sha), do: %{} + defp sha_attrs(%StackRun{}, sha), do: %{sha: sha} + defp delete_run(%Stack{deleted_at: d}, %{id: id}) when not is_nil(d), do: %{delete_run_id: id} defp delete_run(_, _), do: %{} @@ -330,6 +346,8 @@ defmodule Console.Deployments.Stacks do defp notify({:ok, %StackRun{} = stack}, :create), do: handle_notify(PubSub.StackRunCreated, stack) + defp notify({:ok, %RunLog{} = log}, :create), + do: handle_notify(PubSub.RunLogsCreated, log) defp notify({:ok, %StackRun{} = stack}, :update), do: handle_notify(PubSub.StackRunUpdated, stack) defp notify({:ok, %StackRun{} = stack}, :delete), diff --git a/lib/console/graphql.ex b/lib/console/graphql.ex index bd5e8dc2a..5562bc9a0 100644 --- a/lib/console/graphql.ex +++ b/lib/console/graphql.ex @@ -84,6 +84,7 @@ defmodule Console.GraphQl do import_fields :kubernetes_subscriptions import_fields :build_subscriptions import_fields :user_subscriptions + import_fields :stack_subscriptions end def safe_resolver(fun) do diff --git a/lib/console/graphql/deployments.ex b/lib/console/graphql/deployments.ex index 971f02d57..912716839 100644 --- a/lib/console/graphql/deployments.ex +++ b/lib/console/graphql/deployments.ex @@ -168,4 +168,8 @@ safe_resolve &Deployments.enable/2 end end + + object :deployment_subscriptions do + import_fields :stack_subscriptions + end end diff --git a/lib/console/graphql/deployments/git.ex b/lib/console/graphql/deployments/git.ex index 2141a1d87..a7432a002 100644 --- a/lib/console/graphql/deployments/git.ex +++ b/lib/console/graphql/deployments/git.ex @@ -125,6 +125,17 @@ defmodule Console.GraphQl.Deployments.Git do field :cluster, :namespaced_name end + @desc "attributes for a pull request pointer record" + input_object :pull_request_update_attributes do + field :title, non_null(:string) + field :labels, list_of(:string) + field :status, non_null(:pr_status) + field :service_id, :id + field :cluster_id, :id + field :service, :namespaced_name + field :cluster, :namespaced_name + end + @desc "The attributes to configure a new webhook for a SCM provider" input_object :scm_webhook_attributes do field :hmac, non_null(:string), description: "the secret token for authenticating this webhook via hmac signature" @@ -546,5 +557,20 @@ defmodule Console.GraphQl.Deployments.Git do safe_resolve &Deployments.create_pr/2 end + + field :update_pull_request, :pull_request do + middleware Authenticated + arg :id, non_null(:id) + arg :attributes, :pull_request_update_attributes + + safe_resolve &Deployments.update_pr/2 + end + + field :delete_pull_request, :pull_request do + middleware Authenticated + arg :id, non_null(:id) + + safe_resolve &Deployments.delete_pr/2 + end end end diff --git a/lib/console/graphql/deployments/stack.ex b/lib/console/graphql/deployments/stack.ex index 4a6b2b3ad..52338fea7 100644 --- a/lib/console/graphql/deployments/stack.ex +++ b/lib/console/graphql/deployments/stack.ex @@ -205,6 +205,8 @@ defmodule Console.GraphQl.Deployments.Stack do connection node_type: :infrastructure_stack connection node_type: :stack_run + delta :run_logs + object :public_stack_queries do connection field :cluster_stack_runs, node_type: :stack_run do middleware ClusterAuthenticated @@ -306,4 +308,15 @@ defmodule Console.GraphQl.Deployments.Stack do resolve &Deployments.approve_stack_run/2 end end + + object :stack_subscriptions do + field :run_logs_delta, :run_logs_delta do + arg :step_id, non_null(:id) + + config fn %{step_id: step_id}, ctx -> + with {:ok, step} <- Deployments.resolve_run_step(step_id, ctx), + do: {:ok, topic: "steps:#{step.id}"} + end + end + end end diff --git a/lib/console/graphql/resolvers/deployments/git.ex b/lib/console/graphql/resolvers/deployments/git.ex index c256686b0..283dd346f 100644 --- a/lib/console/graphql/resolvers/deployments/git.ex +++ b/lib/console/graphql/resolvers/deployments/git.ex @@ -102,6 +102,12 @@ defmodule Console.GraphQl.Resolvers.Deployments.Git do def create_pr(%{attributes: attrs}, %{context: %{current_user: user}}), do: Git.create_pull_request(attrs, user) + def update_pr(%{attributes: attrs, id: id}, %{context: %{current_user: user}}), + do: Git.update_pr(attrs, id, user) + + def delete_pr(%{id: id}, %{context: %{current_user: user}}), + do: Git.delete_pr(id, user) + def create_webhook_for_connection(%{owner: owner, connection_id: conn_id}, %{context: %{current_user: user}}), do: Git.create_webhook_for_connection(owner, conn_id, user) diff --git a/lib/console/graphql/resolvers/deployments/stack.ex b/lib/console/graphql/resolvers/deployments/stack.ex index 0d54689a5..14b12ce25 100644 --- a/lib/console/graphql/resolvers/deployments/stack.ex +++ b/lib/console/graphql/resolvers/deployments/stack.ex @@ -46,6 +46,11 @@ defmodule Console.GraphQl.Resolvers.Deployments.Stack do |> allow(actor(ctx), :read) end + def resolve_run_step(step_id, %{context: %{current_user: user}}) do + Stacks.get_step!(step_id) + |> allow(user, :read) + end + def create_stack(%{attributes: attrs}, %{context: %{current_user: user}}), do: Stacks.create_stack(attrs, user) diff --git a/lib/console/graphql/topic.ex b/lib/console/graphql/topic.ex index 11c8b7e0f..9a9a102ca 100644 --- a/lib/console/graphql/topic.ex +++ b/lib/console/graphql/topic.ex @@ -27,3 +27,7 @@ end defimpl Console.GraphQl.Topic, for: Kazan.Apis.Core.V1.Pod do def infer(%{metadata: %{namespace: ns, name: name}}, _), do: [pod_delta: "pods:#{ns}:#{name}", pod_delta: "pods"] end + +defimpl Console.GraphQl.Topic, for: Console.Schema.RunLog do + def infer(%{step_id: id}, _), do: [run_logs_delta: "steps:#{id}"] +end diff --git a/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml b/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml index 116ed7394..34f214a92 100644 --- a/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_globalservices.yaml @@ -278,6 +278,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml b/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml index dad3526db..8ada0bafa 100644 --- a/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_managednamespaces.yaml @@ -190,6 +190,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm + chart from (useful if you're using a multi-source configuration + for values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/plural/helm/console/crds/deployments.plural.sh_servicedeployments.yaml b/plural/helm/console/crds/deployments.plural.sh_servicedeployments.yaml index 8cee44d2a..3dd15d4a3 100644 --- a/plural/helm/console/crds/deployments.plural.sh_servicedeployments.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_servicedeployments.yaml @@ -268,6 +268,52 @@ spec: - name - namespace type: object + repositoryRef: + description: reference to a GitRepository to source the helm chart + from (useful if you're using a multi-source configuration for + values files) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic values: description: arbitrary yaml values to overlay type: object diff --git a/schema/schema.graphql b/schema/schema.graphql index 9e307fb2c..61af06832 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -507,6 +507,10 @@ type RootMutationType { "just registers a pointer record to a PR after it was created externally be some other automation" createPullRequestPointer(attributes: PullRequestAttributes): PullRequest + updatePullRequest(id: ID!, attributes: PullRequestUpdateAttributes): PullRequest + + deletePullRequest(id: ID!): PullRequest + createCluster(attributes: ClusterAttributes!): Cluster updateCluster(id: ID!, attributes: ClusterUpdateAttributes!): Cluster @@ -727,6 +731,7 @@ type RootSubscriptionType { buildDelta(buildId: ID): BuildDelta commandDelta(buildId: ID!): CommandDelta notificationDelta: NotificationDelta + runLogsDelta(stepId: ID!): RunLogsDelta } input PolicyBindingAttributes { @@ -1244,6 +1249,11 @@ type StackRunConnection { edges: [StackRunEdge] } +type RunLogsDelta { + delta: Delta + payload: RunLogs +} + "A reference for a globalized service, which targets clusters based on the configured criteria" input GlobalServiceAttributes { "name for this global service" @@ -3762,6 +3772,17 @@ input PullRequestAttributes { cluster: NamespacedName } +"attributes for a pull request pointer record" +input PullRequestUpdateAttributes { + title: String! + labels: [String] + status: PrStatus! + serviceId: ID + clusterId: ID + service: NamespacedName + cluster: NamespacedName +} + "The attributes to configure a new webhook for a SCM provider" input ScmWebhookAttributes { "the secret token for authenticating this webhook via hmac signature" diff --git a/test/console/deployments/cron_test.exs b/test/console/deployments/cron_test.exs index 3eee2555b..77d4c0815 100644 --- a/test/console/deployments/cron_test.exs +++ b/test/console/deployments/cron_test.exs @@ -203,8 +203,12 @@ defmodule Console.Deployments.CronTest do describe "#poll_stacks/0" do test "it can generate new stack runs" do - stack = insert(:stack, environment: [%{name: "ENV", value: "1"}], files: [%{path: "test.txt", content: "test"}]) + stack = insert(:stack, + environment: [%{name: "ENV", value: "1"}], files: [%{path: "test.txt", content: "test"}], + git: %{ref: "main", folder: "terraform"} + ) expect(Console.Deployments.Git.Discovery, :sha, fn _, _ -> {:ok, "new-sha"} end) + expect(Console.Deployments.Git.Discovery, :changes, fn _, _, _, _ -> {:ok, ["terraform/main.tf"]} end) Cron.poll_stacks() diff --git a/test/console/deployments/pubsub/recurse_test.exs b/test/console/deployments/pubsub/recurse_test.exs index 1fed34ae0..81360c6cb 100644 --- a/test/console/deployments/pubsub/recurse_test.exs +++ b/test/console/deployments/pubsub/recurse_test.exs @@ -361,8 +361,9 @@ defmodule Console.Deployments.PubSub.RecurseTest do describe "StackCreated" do test "it will poll the stack" do - stack = insert(:stack) + stack = insert(:stack, git: %{folder: "terraform", ref: "main"}) expect(Discovery, :sha, fn _, _ -> {:ok, "new-sha"} end) + expect(Discovery, :changes, fn _, _, _, _ -> {:ok, ["terraform/main.tf"]} end) event = %PubSub.StackCreated{item: stack} {:ok, run} = Recurse.handle_event(event) @@ -377,8 +378,9 @@ defmodule Console.Deployments.PubSub.RecurseTest do describe "StackUpdated" do test "it will poll the stack" do - stack = insert(:stack) + stack = insert(:stack, git: %{folder: "terraform", ref: "main"}) expect(Discovery, :sha, fn _, _ -> {:ok, "new-sha"} end) + expect(Discovery, :changes, fn _, _, _, _ -> {:ok, ["terraform/main.tf"]} end) event = %PubSub.StackUpdated{item: stack} {:ok, run} = Recurse.handle_event(event) diff --git a/test/console/deployments/stacks_test.exs b/test/console/deployments/stacks_test.exs index fea82268b..bf177f12b 100644 --- a/test/console/deployments/stacks_test.exs +++ b/test/console/deployments/stacks_test.exs @@ -143,8 +143,13 @@ defmodule Console.Deployments.StacksTest do describe "#poll/1" do test "it can create a new run when the sha changes" do - stack = insert(:stack, environment: [%{name: "ENV", value: "1"}], files: [%{path: "test.txt", content: "test"}]) + stack = insert(:stack, + environment: [%{name: "ENV", value: "1"}], + files: [%{path: "test.txt", content: "test"}], + git: %{ref: "main", folder: "terraform"} + ) expect(Discovery, :sha, fn _, _ -> {:ok, "new-sha"} end) + expect(Discovery, :changes, fn _, _, _, _ -> {:ok, ["terraform/main.tf"]} end) {:ok, run} = Stacks.poll(stack) @@ -174,10 +179,16 @@ defmodule Console.Deployments.StacksTest do assert_receive {:event, %PubSub.StackRunCreated{item: ^run}} end - test "it can create a new run from a pr the sha changes" do - stack = insert(:stack, environment: [%{name: "ENV", value: "1"}], files: [%{path: "test.txt", content: "test"}]) + test "it can create a new run from a pr if the sha changes" do + stack = insert(:stack, + environment: [%{name: "ENV", value: "1"}], + files: [%{path: "test.txt", content: "test"}], + git: %{ref: "main", folder: "terraform"}, + sha: "old-sha" + ) pr = insert(:pull_request, stack: stack) expect(Discovery, :sha, fn _, _ -> {:ok, "new-sha"} end) + expect(Discovery, :changes, fn _, _, _, _ -> {:ok, ["terraform/main.tf"]} end) {:ok, run} = Stacks.poll(pr) @@ -200,6 +211,7 @@ defmodule Console.Deployments.StacksTest do assert second.index == 1 stack = refetch(stack) + assert stack.sha == "old-sha" %{environment: [_], files: [_]} = Console.Repo.preload(stack, [:environment, :files]) assert_receive {:event, %PubSub.StackRunCreated{item: ^run}} @@ -431,3 +443,26 @@ defmodule Console.Deployments.StacksTest do end end end + +defmodule Console.Deployments.StacksSyncTest do + use Console.DataCase, async: false + alias Console.Deployments.Stacks + + describe "#poll/1" do + test "it will create runs when it detects changes" do + git = insert(:git_repository, url: "https://github.com/pluralsh/console.git") + stack = insert(:stack, + repository: git, + git: %{ref: "master", folder: "charts"}, + sha: "e136726eb7f3ef1d3578b8b250b1fc1957331a84" + ) + + {:ok, run} = Stacks.poll(stack) + + assert run.status == :queued + refute run.dry_run + assert run.stack_id == stack.id + refute run.git.ref == stack.git.ref + end + end +end diff --git a/test/console/graphql/mutations/deployments/git_mutations_test.exs b/test/console/graphql/mutations/deployments/git_mutations_test.exs index 197390ab0..b17e51f12 100644 --- a/test/console/graphql/mutations/deployments/git_mutations_test.exs +++ b/test/console/graphql/mutations/deployments/git_mutations_test.exs @@ -219,6 +219,41 @@ defmodule Console.GraphQl.Deployments.GitMutationsTest do end end + describe "updatePullRequest" do + test "it will update an existing pr" do + pr = insert(:pull_request) + + {:ok, %{data: %{"updatePullRequest" => updated}}} = run_query(""" + mutation Update($id: ID!, $attrs: PullRequestUpdateAttributes!) { + updatePullRequest(id: $id, attributes: $attrs) { + id + status + title + } + } + """, %{"id" => pr.id, "attrs" => %{"status" => "MERGED", "title" => "new title"}}, %{current_user: admin_user()}) + + assert updated["id"] == pr.id + assert updated["status"] == "MERGED" + assert updated["title"] == "new title" + end + end + + describe "deletePullRequest" do + test "it will update an existing pr" do + pr = insert(:pull_request) + + {:ok, %{data: %{"deletePullRequest" => deleted}}} = run_query(""" + mutation Update($id: ID!) { + deletePullRequest(id: $id) { id } + } + """, %{"id" => pr.id}, %{current_user: admin_user()}) + + assert deleted["id"] == pr.id + refute refetch(pr) + end + end + describe "createScmWebhookPointer" do test "admins can create webhook pointers" do {:ok, %{data: %{"createScmWebhookPointer" => hook}}} = run_query(""" diff --git a/test/console_web/channels/graphql/deployment_subscriptions_test.exs b/test/console_web/channels/graphql/deployment_subscriptions_test.exs new file mode 100644 index 000000000..5722fe02e --- /dev/null +++ b/test/console_web/channels/graphql/deployment_subscriptions_test.exs @@ -0,0 +1,38 @@ +defmodule ConsoleWeb.GraphQl.DeploymentsSubscriptionTest do + use ConsoleWeb.ChannelCase, async: false + alias Console.{PubSub.Consumers.Rtc, PubSub} + + describe "runLogsDelta" do + test "new logs will broadcast deltas" do + user = insert(:user) + stack = insert(:stack, read_bindings: [%{user_id: user.id}]) + run = insert(:stack_run, stack: stack) + step = insert(:run_step, run: run) + logs = insert(:run_log, step: step) + + {:ok, socket} = establish_socket(user) + + ref = push_doc(socket, """ + subscription Logs($stepId: ID!) { + runLogsDelta(stepId: $stepId) { + delta + payload { + id + logs + } + } + } + """, variables: %{"stepId" => step.id}) + + assert_reply(ref, :ok, %{subscriptionId: _}) + + event = %PubSub.RunLogsCreated{item: logs} + Rtc.handle_event(event) + + assert_push("subscription:data", %{result: %{data: %{"runLogsDelta" => delta}}}) + assert delta["delta"] == "CREATE" + assert delta["payload"]["id"] == logs.id + assert delta["payload"]["logs"] == logs.logs + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index 29125db87..4cf59ddb8 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -549,6 +549,13 @@ defmodule Console.Factory do } end + def run_log_factory do + %Schema.RunLog{ + logs: "test logs", + step: build(:run_step) + } + end + def service_template_factory do %Schema.ServiceTemplate{ name: sequence(:service_template, & "tpl-#{&1}")