Skip to content
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
0dc8622
#697 Add fre workflow checkout tool and test
Jan 27, 2026
099342f
#697 Comment out non-existent tools for now
Jan 27, 2026
40d43a5
#697 Comment out things that aren't done yet
Feb 6, 2026
440a040
#697 Add validation and update output file name for resolved yaml
Feb 6, 2026
3249384
#697 Add assert to test
Feb 6, 2026
7c96c60
Add comma
singhd789 Feb 11, 2026
125b5e8
#697 Merge branch 'main' of github.com:NOAA-GFDL/fre-cli into 697.wo…
Feb 12, 2026
b217d5e
#697 Add comma
Feb 12, 2026
0694409
#697 Merge branch '697.workflow-checkout-tool' of github.com:NOAA-GF…
Feb 12, 2026
13ca73c
#697 Put file moving in checkout creation
Feb 12, 2026
70171a6
#697 See if pipeline run as root
Feb 12, 2026
c558027
#697 Fix stat
Feb 12, 2026
60e73cc
#697 make it fail for a sec
Feb 12, 2026
38accb6
test failure
Feb 18, 2026
06750f2
#697 Add some readme content
Feb 18, 2026
ecb3ed3
#697 Update comment
Feb 18, 2026
32d5906
#697 Remove click branch overwrite
Feb 18, 2026
881366e
#697 Comment out use of branch override
Feb 18, 2026
8bef3ee
#697 Fix file permission test
Feb 18, 2026
7d1eb4e
#697 Fix test
Feb 18, 2026
516cf5d
#697 hopfeully change file permissions correctly this time
Feb 18, 2026
95e25d8
#697 Nix file permission changes - doesn't work when running as root
Feb 18, 2026
e3c2452
#697 Address pylint messages
Feb 18, 2026
7b1f816
#697 Add fre workflow cli tests
Feb 18, 2026
c48728b
#697 Some clean up
Feb 19, 2026
817bf7a
#697 Add --force-checkout and --target-dir
Feb 20, 2026
09afc2d
#697 Update spacing to account for warning output
Feb 20, 2026
a29645a
#697 Update documentation
Feb 20, 2026
560093c
#697 Update doc
Feb 20, 2026
10ce009
#697 Fix tests
Feb 20, 2026
61be15c
#697 Fix --target-dir click value
Feb 20, 2026
04e8985
#697 Add default dir (see if pipeline passes)
Feb 20, 2026
9daa391
#697 update
Feb 20, 2026
14e87e9
#697 update
Feb 20, 2026
7af0425
#697 Fix tests and update argument order
Feb 25, 2026
c235bb4
#697 Merge branch 'main' of github.com:NOAA-GFDL/fre-cli into 697.wo…
Feb 25, 2026
5ee7f19
#697 fix exit code
Feb 25, 2026
007696d
#697 Adjust spacing
Feb 25, 2026
9ab07ca
#697 right spacing?
Feb 25, 2026
2d26bce
#697 set TMPDIR
Feb 25, 2026
1af0992
#697 Change warning to info
Feb 25, 2026
14c0d20
#697 Update help message and other output
Feb 25, 2026
ef0377b
#697 Uncomment validation
Feb 25, 2026
779b6ba
#697 Address ians comments
Feb 26, 2026
6ce4930
#697 Update
Feb 26, 2026
0bd5f60
#697 Update yaml workflow addition
Mar 5, 2026
e542b60
#697 Update readme
Mar 5, 2026
b1f4664
#697 Some documentation updates
Mar 9, 2026
fd672d3
#697 change repo to repository
Mar 9, 2026
98fafcb
#697 change repo to repository
Mar 9, 2026
7ad2290
#697 update test
Mar 9, 2026
f62089f
#697 Change `fre_logger.warning` to `fre_logger.info`
Mar 9, 2026
361f65f
#697 Remove envvar TMPDIR usage as default for now
Mar 9, 2026
a5e9314
#697 update doc
Mar 9, 2026
d59b3b6
#697 Address some of Mikyung's comments
Mar 16, 2026
2411f6e
#697 addressing comments pt. 2
Mar 16, 2026
37bc158
#697 address comments pt 3
Mar 16, 2026
b6f8d65
#697 update doc
Mar 19, 2026
63c92a8
#697 Merge branch 'main' of github.com:NOAA-GFDL/fre-cli into 697.wo…
Mar 19, 2026
e1459ef
#697 fix wording
Mar 20, 2026
cf09e3d
#697 Fix tests
Mar 20, 2026
ce50e1f
#697 archive the workflow directory instead, address comments
Mar 20, 2026
c2886ca
#697 update doc
Mar 20, 2026
2164715
#697 update doc
Mar 20, 2026
6923876
#697 Add fre workflow checkout tool and test
Jan 27, 2026
c7cbbfe
#697 Comment out non-existent tools for now
Jan 27, 2026
db193b9
#697 Comment out things that aren't done yet
Feb 6, 2026
96a1f7a
#697 Add validation and update output file name for resolved yaml
Feb 6, 2026
3bca7db
#697 Add assert to test
Feb 6, 2026
87b326f
#697 Add comma
Feb 12, 2026
1293aa8
#697 Put file moving in checkout creation
Feb 12, 2026
4a36008
#697 See if pipeline run as root
Feb 12, 2026
866b9d4
#697 Fix stat
Feb 12, 2026
c374c69
#697 make it fail for a sec
Feb 12, 2026
57452a3
test failure
Feb 18, 2026
341c7d8
#697 Add some readme content
Feb 18, 2026
6928357
#697 Update comment
Feb 18, 2026
f4b7a7d
#697 Remove click branch overwrite
Feb 18, 2026
6725ce8
#697 Comment out use of branch override
Feb 18, 2026
169f048
#697 Fix file permission test
Feb 18, 2026
1f84993
#697 Fix test
Feb 18, 2026
56b4741
#697 hopfeully change file permissions correctly this time
Feb 18, 2026
8997960
#697 Nix file permission changes - doesn't work when running as root
Feb 18, 2026
8a80800
#697 Address pylint messages
Feb 18, 2026
637e6ee
#697 Add fre workflow cli tests
Feb 18, 2026
318a426
#697 Some clean up
Feb 19, 2026
a1e96c1
#697 Add --force-checkout and --target-dir
Feb 20, 2026
322fb44
#697 Update spacing to account for warning output
Feb 20, 2026
78101e8
#697 Update documentation
Feb 20, 2026
560936d
#697 Update doc
Feb 20, 2026
9c23e71
#697 Fix tests
Feb 20, 2026
053c01a
#697 Fix --target-dir click value
Feb 20, 2026
a95469e
#697 Add default dir (see if pipeline passes)
Feb 20, 2026
6122a2c
#697 update
Feb 20, 2026
8c5a8c2
#697 update
Feb 20, 2026
ea0d90f
#697 Fix tests and update argument order
Feb 25, 2026
8ea6c34
#697 fix exit code
Feb 25, 2026
67c1544
#697 Adjust spacing
Feb 25, 2026
c619a25
#697 right spacing?
Feb 25, 2026
ca4c2e2
#697 set TMPDIR
Feb 25, 2026
4d19b2f
#697 Change warning to info
Feb 25, 2026
3ee11a4
#697 Update help message and other output
Feb 25, 2026
da10b19
#697 Uncomment validation
Feb 25, 2026
6507c01
#697 Address ians comments
Feb 26, 2026
1a0e830
#697 Update
Feb 26, 2026
964e31f
#697 Update yaml workflow addition
Mar 5, 2026
c348ceb
#697 Update readme
Mar 5, 2026
390d74d
#697 Some documentation updates
Mar 9, 2026
3edada1
#697 change repo to repository
Mar 9, 2026
8b94b3f
#697 change repo to repository
Mar 9, 2026
338893c
#697 update test
Mar 9, 2026
b1766d3
#697 Change `fre_logger.warning` to `fre_logger.info`
Mar 9, 2026
dfe8c38
#697 Remove envvar TMPDIR usage as default for now
Mar 9, 2026
e7e9f5f
#697 update doc
Mar 9, 2026
9aab444
#697 Address some of Mikyung's comments
Mar 16, 2026
a8eed8a
#697 addressing comments pt. 2
Mar 16, 2026
b1b5142
#697 address comments pt 3
Mar 16, 2026
7cffb66
#697 update doc
Mar 19, 2026
0230fd9
#697 fix wording
Mar 20, 2026
1e29e4e
#697 Fix tests
Mar 20, 2026
be774dc
#697 archive the workflow directory instead, address comments
Mar 20, 2026
7cf7523
#697 update doc
Mar 20, 2026
4a94f60
#697 update doc
Mar 20, 2026
f5bece4
#697 Merge branch 'main' of github.com:NOAA-GFDL/fre-cli into 697.wo…
Apr 16, 2026
375f833
#697 Merge branch '697.workflow-checkout-tool' of github.com:NOAA-GF…
Apr 16, 2026
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
2 changes: 1 addition & 1 deletion fre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

fre_logger = logging.getLogger(__name__)

FORMAT = "[%(levelname)5s:%(filename)24s:%(funcName)24s] %(message)s"
FORMAT = "[%(levelname)7s:%(filename)24s:%(funcName)24s] %(message)s"
logging.basicConfig(level = logging.WARNING,
format = FORMAT,
filename = None,
Expand Down
3 changes: 2 additions & 1 deletion fre/fre.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
# click and lazy group loading
@click.group(
cls = LazyGroup,
lazy_subcommands = {"pp": ".pp.frepp.pp_cli",
lazy_subcommands = {"workflow": ".workflow.freworkflow.workflow_cli",
"pp": ".pp.frepp.pp_cli",
"catalog": ".catalog.frecatalog.catalog_cli",
"list": ".list_.frelist.list_cli",
"check": ".check.frecheck.check_cli",
Expand Down
6 changes: 3 additions & 3 deletions fre/tests/test_fre_cmor_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def test_cli_fre_cmor_help_and_debuglog():
assert result.exit_code == 0
assert Path("TEST_FOO_LOG.log").exists()

log_text_line_1='[ INFO: fre.py: fre] fre_file_handler added to base_fre_logger\n' # pylint: disable=line-too-long
log_text_line_2='[DEBUG: fre.py: fre] click entry-point function call done.\n' # pylint: disable=line-too-long
log_text_line_1='[ INFO: fre.py: fre] fre_file_handler added to base_fre_logger\n' # pylint: disable=line-too-long
log_text_line_2='[ DEBUG: fre.py: fre] click entry-point function call done.\n' # pylint: disable=line-too-long
Comment thread
singhd789 marked this conversation as resolved.
with open( "TEST_FOO_LOG.log", 'r', encoding='utf-8') as log_text:
line_list=log_text.readlines()
assert log_text_line_1 in line_list[0]
Expand All @@ -89,7 +89,7 @@ def test_cli_fre_cmor_help_and_infolog():
assert result.exit_code == 0
assert Path("TEST_FOO_LOG.log").exists()

log_text_line_1='[ INFO: fre.py: fre] fre_file_handler added to base_fre_logger\n' # pylint: disable=line-too-long
log_text_line_1='[ INFO: fre.py: fre] fre_file_handler added to base_fre_logger\n' # pylint: disable=line-too-long
with open( "TEST_FOO_LOG.log", 'r', encoding='utf-8') as log_text:
line_list=log_text.readlines()
assert log_text_line_1 in line_list[0]
Expand Down
103 changes: 103 additions & 0 deletions fre/tests/test_fre_workflow_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
CLI Tests for fre workflow *
Tests the command-line-interface calls for tools in the fre workflow suite.

Each tool generally gets 3 tests:
Comment thread
singhd789 marked this conversation as resolved.
Outdated
- fre workflow $tool, checking for exit code 0 (fails if cli isn't configured right)
- fre workflow $tool --help, checking for exit code 0 (fails if the code doesn't run)
- fre workflow $tool --optionDNE, checking for exit code 2; misuse of command (fails if cli isn't configured
right and thinks the tool has a --optionDNE option)
"""
import os
from pathlib import Path
from click.testing import CliRunner
from fre import fre

runner = CliRunner()

## fre workflow subtools search for if TMPDIR is set, specifically for fre workflow checkout --target-dir
# If a value for --target-dir is not passed --> TMPDIR will be used for --target-dir
# If TMPDIR is not set --> a default location will be used for --target-dir
#-- fre workflow
def test_cli_fre_workflow(monkeypatch, tmp_path):
''' fre workflow '''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow"])
assert result.exit_code == 2

def test_cli_fre_workflow_help(monkeypatch, tmp_path):
''' fre workflow --help '''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow", "--help"])
assert result.exit_code == 0

def test_cli_fre_workflow_opt_dne(monkeypatch, tmp_path):
''' fre workflow optionDNE '''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow", "optionDNE"])
assert result.exit_code == 2

#-- fre workflow checkout
def test_cli_fre_workflow_checkout(monkeypatch, tmp_path):
''' fre workflow checkout'''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow", "checkout"])
assert result.exit_code == 2

def test_cli_fre_workflow_checkout_help(monkeypatch, tmp_path):
''' fre workflow checkout --help '''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow", "checkout", "--help"])
assert result.exit_code == 0

def test_cli_fre_workflow_checkout_opt_dne(monkeypatch, tmp_path):
''' fre workflow checkout optionDNE '''
monkeypatch.setenv("TMPDIR", tmp_path)
result = runner.invoke(fre.fre, args=["workflow", "checkout", "optionDNE"])
assert result.exit_code == 2

def test_cli_fre_workflow_checkout_target_dir_set(tmp_path):
"""
Test checkout in target directory if --target-dir is explicitly set.
"""
experiment = "c96L65_am5f7b12r1_amip_TESTING"
result = runner.invoke(fre.fre, args=["workflow", "checkout",
"--yamlfile", "fre/workflow/tests/AM5_example/am5.yaml",
"--experiment", experiment,
"--application", "pp",
"--target-dir", tmp_path])
assert result.exit_code == 0
assert Path(f"{tmp_path}/cylc-src/{experiment}").exists()

def test_cli_fre_workflow_checkout_TMPDIR_set(tmp_path, monkeypatch):
"""
Test checkout if TMPDIR environment variable is set and --target-dir has no
Comment thread
singhd789 marked this conversation as resolved.
Outdated
specified value
"""
experiment = "c96L65_am5f7b12r1_amip_TESTING"
Path(f"{tmp_path}/env_var").mkdir(parents=True)
monkeypatch.setenv("TMPDIR", f"{tmp_path}/env_var")

result = runner.invoke(fre.fre, args=["workflow", "checkout",
"-y", "fre/workflow/tests/AM5_example/am5.yaml",
"-e", experiment,
"-a", "pp"])
assert result.exit_code == 0
assert Path(f"{os.environ['TMPDIR']}/cylc-src/{experiment}").exists()

#def test_cli_fre_workflow_checkout_default_dir():
# """
# Test checkout if TMPDIR and --target-dir is not set;
# use the default location: ~/.fre
# """
experiment = "c96L65_am5f7b12r1_amip_TESTING"
monkeypatch.setenv("TMPDIR", "")

result = runner.invoke(fre.fre, args=["workflow", "checkout",
"-y", "fre/workflow/tests/AM5_example/am5.yaml",
"-e", experiment,
"-a", "pp"])
#default cylc-src location
default_dir = os.path.expanduser("~/.fre")
assert result.exit_code == 0
assert Path(f"{default_dir}/cylc-src/{experiment}").exists()
19 changes: 19 additions & 0 deletions fre/workflow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# FRE workflow
`fre workflow` provides subtools that help to clone, install, and run a workflow from a repository.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm... maybe we can discuss the README in person? 😁

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is fre workflow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited slightly to define a "toolset"


## Quickstart
From the root of the fre-cli repository, run:
Comment thread
singhd789 marked this conversation as resolved.
Outdated
```
# Checkout/clone the post-processing workflow repository
fre workflow checkout -y fre/workflow/tests/AM5_example/am5.yaml -e c96L65_am5f7b12r1_amip_TESTING -a pp
```

## Subtools
- `fre workflow checkout [options]`
- Purpose: Clone the workflow repository/branch, depending on the application passed.
Comment thread
singhd789 marked this conversation as resolved.
Outdated
- Options:
- `-y, --yamlfile [model yaml] (str; required)`
- `-e, --experiment [experiment name] (str; required)`
- `-a, --application [ run | pp ] (str; required)`
- `--target-dir [target directory where workflow will be cloned] (str; optional; default is ~/.fre-workflows`
- `--force-checkout (bool; optional)`
Empty file added fre/workflow/__init__.py
Empty file.
159 changes: 159 additions & 0 deletions fre/workflow/checkout_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
""" Workflow checkout """
import os
import subprocess
from pathlib import Path
import logging
import shutil
import json
from jsonschema import validate, SchemaError, ValidationError
from typing import Optional

import fre.yamltools.combine_yamls_script as cy
from fre.app.helpers import change_directory

fre_logger = logging.getLogger(__name__)

######VALIDATE#####
def validate_yaml(yamlfile: dict, application: str):
"""
Validate the format of the yaml file based
Comment thread
singhd789 marked this conversation as resolved.
Outdated
on the schema.json held in [gfdl_msd_schemas](https://github.com/NOAA-GFDL/gfdl_msd_schemas).

:param yamlfile: Model, settings, pp, and analysis yaml
Comment thread
singhd789 marked this conversation as resolved.
Outdated
information combined into a dictionary
:type yamlfile: dict
:param application: type of workflow to check out/clone
:type application: string
:raises ValueError:
- if gfdl_mdf_schema path is not valid
Comment thread
singhd789 marked this conversation as resolved.
Outdated
- combined yaml is not valid
Comment thread
singhd789 marked this conversation as resolved.
Outdated
- unclear error in validation
Comment thread
singhd789 marked this conversation as resolved.
Outdated
"""
schema_dir = Path(__file__).resolve().parents[1]
schema_path = os.path.join(schema_dir, 'gfdl_msd_schemas', 'FRE', f'fre_{application}.json')
fre_logger.info("Using yaml schema '%s'", schema_path)
# Load the json schema: .load() (vs .loads()) reads and parses the json in one)
try:
with open(schema_path,'r', encoding='utf-8') as s:
schema = json.load(s)
except:
fre_logger.error("Schema '%s' is not valid. Contact the FRE team.", schema_path)
raise

# Validate yaml
# If the yaml is not valid, the schema validation will raise errors and exit
try:
validate(instance = yamlfile,schema=schema)
fre_logger.info(" ** COMBINED YAML VALID ** ")
except SchemaError as exc:
raise ValueError(f"Schema '{schema_path}' is not valid. Contact the FRE team.") from exc
except ValidationError as exc:
raise ValueError("Combined yaml is not valid. Please fix the errors and try again.") from exc
except Exception as exc:
raise ValueError("Unclear error from validation. Please try to find the error and try again.") from exc

def workflow_checkout(target_dir: str, yamlfile: str = None, experiment: str = None, application: str = None, force_checkout: Optional[bool] = False):
"""
Create a directory and clone the workflow template files from a defined repository.
Comment thread
singhd789 marked this conversation as resolved.
Outdated

:param yamlfile: Model yaml configuration file
:type yamlfile: str
:param experiment: One of the postprocessing experiment names from the
yaml displayed by fre list exps -y $yamlfile
Comment thread
singhd789 marked this conversation as resolved.
Outdated
(e.g. c96L65_am5f4b4r0_amip), default None
:type experiment: str
:param application: Which workflow will be used/cloned
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. not sure what this means.. maybe list the workflows avaialble?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is my change better?

:type application: str
:param target_dir: Target/base directory used for cylc-src/<workflow> creation
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this means either..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is my change better here too?

:type target_dir: str
:param force_checkout: re-clone the workflow repository if it exists
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overwrite instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the force-checkout behavior might be changing slightly, this was a good reminder to not forget about changing the docs with it 😄

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it got it

:type force_checkout: bool
:raises OSError: if the checkout script was not able to be created
Comment thread
singhd789 marked this conversation as resolved.
Outdated
:raises ValueError:
- if the repository and/or tag was not defined
- if the target directory does not exist or cannot be found
- if neither tag nor branch matches the git clone branch arg
Comment thread
singhd789 marked this conversation as resolved.
Outdated
"""
# Used in consolidate_yamls function for now
platform = None
target = None

if application in ["run", "pp"]:
fre_logger.info(" ** Configuring the resolved YAML for the %s **", application)
yaml = cy.consolidate_yamls(yamlfile=yamlfile,
experiment=experiment,
platform=platform,
target=target,
use=application,
output="config.yaml")

validate_yaml(yamlfile = yaml, application = application)

# Reset application for pp to make it discoverable in yaml config
if application == "pp":
application = "postprocess"

workflow_info = yaml.get(application).get("workflow")

repo = workflow_info.get("repository")
tag = workflow_info.get("version")
fre_logger.info("Defined tag ==> '%s'", tag)

if None in [repo, tag]:
raise ValueError(f"One of these are None: repo / tag = {repo} / {tag}")

fre_logger.info("(%s):(%s) check out for %s ==> REQUESTED", repo, tag, application)

# Create src_dir if it does not exist
if not Path(target_dir).exists():
Path(target_dir).mkdir(parents=True, exist_ok=True)

# Define cylc-src directory
src_dir = f"{target_dir}/cylc-src"
Comment thread
singhd789 marked this conversation as resolved.
# workflow name
workflow_name = experiment

# create workflow in cylc-src
try:
Path(src_dir).mkdir(parents=True, exist_ok=True)
except Exception as exc:
raise OSError(
f"(checkoutScript) directory {src_dir} wasn't able to be created. exit!") from exc

if Path(f"{src_dir}/{workflow_name}").is_dir():
fre_logger.info(" *** PREVIOUS CHECKOUT FOUND: %s/%s *** ", src_dir, workflow_name)
if force_checkout:
fre_logger.warning(" *** REMOVING %s/%s *** ", src_dir, workflow_name)
shutil.rmtree(f"{src_dir}/{workflow_name}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hoarder in me is hesitant about "rm -rf"s. How about renaming it with a date stamp instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, valid point. I'm just worried the checkout directory might get flooded with workflows if the user forgets to remove them

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pathway will only exist if the checked-out location is now inconsistent with the fre workflow checkout instructions.

Normally, the checked-out workflow template will be completely satisfactory, and unchanged since the last call.

So I think in general, the old cylc-src locations won't accumulate too much, and if they do, well, users can delete it themselves or we can change our mind.

Copy link
Copy Markdown
Contributor Author

@singhd789 singhd789 Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That almost makes me want to go back to the the default cylc-src location clearly being HOME though - so it will be more discoverable than ~/.fre-workflows. I mean, --force-checkout isn't required still. So if there is a checked out template that exists, that is unchanged AND --force-checkout is not used, that existing checkout will be used.

The way I see it, --force-checkout is optional and will probably mainly be used if the user wants it gone, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is ~/cylc-src more discoverable than ~/.fre-workflows?

Some users might want the "rm -rf" but it's safer to let the user do "rm -rf"s. Plus, force-checkout has a cultural meaning from Bronx, which moved it aside.

If users complain about the extra directories we'll give them what they want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Thanks for that clarification from the Bronx side. Currently, with fre make --force-checkout, the source directory is just being removed. I wonder if that needs to be redone. 🤔

Ok, if we're saving directories, I think I like the idea of creating a "logged_workflows" or "archived_workflows" directory in ~/.fre-workflows

Oh also, for the "discoverable" comment - I just meant in the fact that .fre-workflows would technically be a "hidden" folder, right?

Copy link
Copy Markdown
Contributor Author

@singhd789 singhd789 Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going back to this - I still kind of like the removal of the workflow. I understand there is cultural meaning to force-checkout though.

In my own case, sometimes I would change/fix something in the yaml and since the yaml combining is baked into the checkout here (as it's done inf fre make), I would be able to use --force-checkout to get that yaml update in the resolved yaml (if there are no other workflow changes). I mean, maybe I'll just add in a check to compare the resolved yamls and error if the yamls are different (just like how the workflow comparison is happening).

But besides that, when we move the previous workflow to a different location, based off my example, someone could have a few similar workflows just for yaml changes, if --force-checkout was used

else:
with change_directory(f"{src_dir}/{workflow_name}"):
# capture the branch and tag
# if either match git_clone_branch_arg, then success. otherwise, fail.
current_tag = subprocess.run(["git","describe","--tags"],
capture_output = True,
text = True, check = True).stdout.strip()
current_branch = subprocess.run(["git", "branch", "--show-current"],
capture_output = True,
text = True, check = True).stdout.strip()

if tag in (current_tag, current_branch):
fre_logger.warning("Checkout exists ('%s/%s'), and matches '%s'", src_dir, workflow_name, tag)
else:
fre_logger.error(
"ERROR: Checkout exists ('%s/%s') and does not match '%s'", src_dir, workflow_name, tag)
fre_logger.error(
"ERROR: Current branch: '%s', Current tag-describe: '%s'", current_branch, current_tag)
raise ValueError('Neither tag nor branch matches the git clone branch arg')
if not Path(f"{src_dir}/{workflow_name}").is_dir():
fre_logger.info("Workflow does not exist; will create now")
clone_output = subprocess.run( ["git", "clone","--recursive",
f"--branch={tag}",
repo, f"{src_dir}/{workflow_name}"],
capture_output = True, text = True, check = True)
fre_logger.debug(clone_output)
fre_logger.info("(%s):(%s) check out ==> SUCCESSFUL", repo, tag)

## Move combined yaml to cylc-src location
current_dir = Path.cwd()
shutil.move(Path(f"{current_dir}/config.yaml"), f"{src_dir}/{workflow_name}")
fre_logger.info("Combined yaml file moved to %s/%s", src_dir, workflow_name)
41 changes: 41 additions & 0 deletions fre/workflow/freworkflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
''' fre workflow click interface for fre workflow subcommands'''
import os
import click
import logging
fre_logger = logging.getLogger(__name__)

#fre tools
from . import checkout_script
#from . import install_script
#from . import run_script

@click.group(help=click.style(" - workflow subcommands", fg=(57,139,210)))
def workflow_cli():
''' entry point to fre workflow click commands '''

@workflow_cli.command()
@click.option("-y", "--yamlfile", type=str,
help="Model yaml file",
Comment thread
laurenchilutti marked this conversation as resolved.
required=True)
@click.option("-e", "--experiment", type=str,
help="Experiment name",
required=True)
@click.option("-a", "--application",
Comment thread
singhd789 marked this conversation as resolved.
type=click.Choice(['run', 'pp']),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about leaving off the enumeration here, to let 'fre workflow checkout' use the workflow name in the yaml?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean like fre workflow checkout --workflow-name c96L65_am5f7b12r1_amip_TESTING? If so, my follow-up question would be, if there is a "workflow" section under both the "postprocess" and future "run" sections (like how I'm doing it in this PR), how would we distinguish between the repos the checkout step will grab? Currently, either -a pp and -a run is passed and used in combining yamls and parsing the yaml to find the right workflow/repo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Dana. Great question, and I see what you mean. I think the full "fre workflows checkout" call has to accept at least experiment name and "application"/"workflow".

Let's take the following resolved yaml:

postprocess:
    workflow:
        repository: "https://github.com/NOAA-GFDL/fre-workflows.git"
        version: "main"
    settings:
       history_segment:    "P1Y"
        site:               "ppan"
        pp_start:   *ANA_AMIP_START
        pp_stop:    *ANA_AMIP_END
        pp_chunks:  [*PP_AMIP_CHUNK96]
        pp_grid_spec: *GRID_SPEC96
run:
    workflow:
        repository: "https://github.com/NOAA-GFDL-WORKFLOWS/esm-run.git"
        version: "2026.02"
    settings:
        ...
download_data:
      workflow:
          repository: "https://github.com/ceblanton/download_data.git"
          version: "testing"
      settings:
           ...

And let's assume we have a model yaml that resolves to the above. fre workflow checkout accepts a model yaml and experiment name, which gives us the above resolved yaml approximately.

fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=postprocess

That will checkout out the postprocessing workflow definition, to

~/.fre-workflows/c96L65_am5f7b12r1_amip_TESTING/postprocessing

(I think). Then for the run case:

fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=run

will checkout to

~/.fre-workflows/c96L65_am5f7b12r1_amip_TESTING/run

That gives us the flexibility to "discover" other workflows, and install and run them too.

fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=download_data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe we could checkout to these locations instead, which would reduce the volume. The checked out location could instead be the workflow definition name as seen by git:

fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=postprocess

would checkout to

~/.fre-workflows/fre-workflows

fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=run

would checkout to

~/.fre-workflows/esm-run

and fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING --application=download_data

would check out to

~/.fre-workflows/download_data

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AH, I see what you're saying now. We would still have --application but it wouldn't strictly be set in the click options. Got it! Yeah, that makes sense (with having a check to make sure that "application" passed exists somewhere is the yamls).

Regarding where the workflow will be checked out, I suppose that makes sense since only one checked out version will exist unless --force-checkout is used. I feel like following the workflow definition as seen by git, fre-workflows should eventually become something more specific like to postprocessing like fre-postprocess-workflow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent. I think I'm following you. Right- "fre-workflows" as we have it is actually (one of) the postprocessing workflow definitions, so in the future it should be renamed accordingly, as it's somewhat misleadingly named right now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok looping back to this real quick, thinking about this more, I think we should still include the experiment name in the workflow checkout location in the case that different experiments are being checked out. If someone is doing fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING1 --application=pp that is different than say fre workflow checkout -y model.yaml -e c96L65_am5f7b12r1_amip_TESTING2 --application=pp, but they'll both want to check out the same repo. So, we could have ~/.fre-workflows/[application]-[experiment_name]?

help="Type of workflow to check out/clone",
required=True)
@click.option("--target-dir",
type=str,
default=os.path.expanduser("~/.fre-workflows"),
help=f"""Target directory for the workflow to be cloned into.
If not defined, a default location of ~/.fre-workflows
will be used""")
@click.option("--force-checkout",
is_flag=True,
default=False,
help="If the checkout already, exists, remove and clone the desired repo again.")
def checkout(target_dir, yamlfile, experiment, application, force_checkout):
"""
Checkout/extract fre workflow
"""
checkout_script.workflow_checkout(target_dir, yamlfile, experiment, application, force_checkout)
Loading
Loading