Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions lib/lightning/workflow_versions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -217,25 +217,34 @@ defmodule Lightning.WorkflowVersions do

## Parameters
* `workflow` — the workflow struct to hash
* `opts` — options:
* `hash: false` — return the joined pre-hash string instead of hashing it.
Useful for debugging what's fed into the hash. Defaults to `true`.

## Returns
* A 12-character lowercase hex string
* When `hash: true` (default): a 12-character lowercase hex string
* When `hash: false`: the joined pre-hash string

## Examples

iex> WorkflowVersions.generate_hash(workflow)
"a1b2c3d4e5f6"

iex> WorkflowVersions.generate_hash(workflow, hash: false)
"My Workflow{...}webhook..."
"""
@spec generate_hash(Workflow.t() | map()) :: binary()
def generate_hash(%Workflow{} = workflow) do
@spec generate_hash(Workflow.t() | map(), keyword()) :: binary()
def generate_hash(workflow, opts \\ [])

def generate_hash(%Workflow{} = workflow, opts) do
workflow = Repo.preload(workflow, [:jobs, :edges, :triggers])

workflow
|> Map.from_struct()
|> generate_hash()
|> generate_hash(opts)
end

def generate_hash(%{} = workflow) do
def generate_hash(%{} = workflow, opts) do
workflow_keys = [:name, :positions]

job_keys = [
Expand Down Expand Up @@ -320,9 +329,13 @@ defmodule Lightning.WorkflowVersions do
edges_hash_list
])

:crypto.hash(:sha256, joined_data)
|> Base.encode16(case: :lower)
|> binary_part(0, 12)
if Keyword.get(opts, :hash, true) do
:crypto.hash(:sha256, joined_data)
|> Base.encode16(case: :lower)
|> binary_part(0, 12)
else
joined_data
end
end

defp serialize_value(val) when is_map(val) do
Expand Down
75 changes: 75 additions & 0 deletions lib/mix/tasks/gen_workflow_hash.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
defmodule Mix.Tasks.Lightning.GenWorkflowHash do
@shortdoc "Generate the version hash for a workflow"

@moduledoc """
Generates a deterministic version hash for an existing workflow.

## Usage

mix lightning.gen_workflow_hash WORKFLOW_UUID [--no-hash]

## Arguments

* `WORKFLOW_UUID` - The UUID of the workflow to hash

## Options

* `--no-hash` - Print the joined pre-hash string instead of the hash.
Useful for debugging what's fed into the hash.

## Examples

mix lightning.gen_workflow_hash 550e8400-e29b-41d4-a716-446655440000
mix lightning.gen_workflow_hash 550e8400-e29b-41d4-a716-446655440000 --no-hash
"""
use Mix.Task

require Logger

alias Lightning.Workflows
alias Lightning.WorkflowVersions

@impl Mix.Task
def run(args) do
{opts, positional, invalid} =
OptionParser.parse(args, strict: [hash: :boolean])

cond do
length(invalid) > 0 ->
invalid_opts = Enum.map_join(invalid, ", ", fn {opt, _} -> opt end)
Mix.raise("Unknown option(s): #{invalid_opts}")

length(positional) != 1 ->
Mix.raise("""
Expected exactly 1 argument: WORKFLOW_UUID

Usage:
mix lightning.gen_workflow_hash WORKFLOW_UUID [--no-hash]
""")

true ->
[workflow_id] = positional
start_repo()
print_hash(workflow_id, opts)
end
end

defp start_repo do
Logger.configure(level: :error)
Mix.Task.run("app.config")
{:ok, _} = Application.ensure_all_started(:ecto_sql)
{:ok, _} = Lightning.Repo.start_link(pool_size: 1)
end

defp print_hash(workflow_id, opts) do
case Workflows.get_workflow(workflow_id) do
nil ->
Mix.raise("Workflow #{workflow_id} not found")

workflow ->
workflow
|> WorkflowVersions.generate_hash(opts)
|> IO.puts()
end
end
end