Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add if: keyword to control when a task or command is ran? #608

Open
ProfessorManhattan opened this issue Nov 13, 2021 · 23 comments
Open

Add if: keyword to control when a task or command is ran? #608

ProfessorManhattan opened this issue Nov 13, 2021 · 23 comments

Comments

@ProfessorManhattan
Copy link

There are some cases where task: is called under cmds: and the task needs to be conditionally run. Take the following as an example:

---
version: '3'

tasks:
  python:requirements:
    deps:
      - :install:software:python
    run: once
    cmds:
      - task: python:requirements:poetry
      - task: :{{if eq .REPOSITORY_TYPE "ansible"}}common:python:requirements:ansible{{else}}donothing{{end}}
    status:
      - '[[ "${container:=}" == "docker" ]]'

I'm currently getting around the issue by having a task called donothing that gets called when the task should not run. It would be nice if we could conditionally run tasks somehow. Maybe something like this:

---
version: '3'

tasks:
  test:
    cmds:
      - task: anothertask
        when:
          sh: '[[ "$CONTAINER" == 'docker' ]]'
@ProfessorManhattan ProfessorManhattan added the type: feature A new feature or functionality. label Nov 13, 2021
@andreynering andreynering changed the title Ability to Run Tasks Conditionally Add if: keyword to control when a task or command is ran? Dec 30, 2021
@andreynering
Copy link
Member

Hi @ProfessorManhattan,

Something I had in mind for a while is that we could potentially have an if keyword for tasks and commands:

version: '3'

tasks:
  foo:
    cmds:
      - echo 'bar'
    if: '[[ "$ENV" == "bar" ]]'

or

version: '3'

tasks:
  foo:
    cmds:
      - cmd: echo 'bar'
        if: '[[ "$ENV" == "bar" ]]'

NOTE: We already have preconditions, which is similar but makes the call fail when an error is returned, which means if still makes sense to have for skipping without failing.

@ghostsquad
Copy link
Contributor

isn't that what status is supposed to do? decide to skip a step?

Unlike status which will skip a task if it is up to date, and continue executing tasks that depend on it, a precondition will fail a task, along with any other tasks that depend on it.

@andreynering
Copy link
Member

isn't that what status is supposed to do? decide to skip a step?

You have a good point, we'd have some intersection between these two features.

status currently prints Task "foo" is up-to-date messages, though. This is accurate for statuses but not for other reasons why you'd want to skip a task.

Also, if would be the opposite of status with regarding the expected status: if runs on zero, status runs on non-zero statuses.

So, I don't have a strong opinion, but maybe it would still make sense to have both if and status. Alternatively, we could add if: only to commands and not to the task itself.

@ProfessorManhattan
Copy link
Author

ProfessorManhattan commented Jan 16, 2022

Thanks @andreynering

Another thing we might want to keep in mind is handling the --force command line parameter. Currently, this will just ignore status which forces the user to wrap everything in ifs (even using status) for logic that doesn't relate to a particular project. In my use case, I share the same set of Taskfiles across hundreds of repositories of many different types - it is not feasible to generate the Taskfiles for each project type.. it would take too long.

I would like to see an if: that can be added at the least to the task and cmd level. It should also not be impacted by any --force logic. Take the following example:

---
version: '3'

tasks:
  mytask:
    cmds:
      - gsettings set org.gnome.settings-daemon.plugins.color night-light-enabled true
    if:
      - ls /usr/bin/*session | grep gnome-session
    status:
      - [[ $(gsettings get org.gnome.settings-daemon.plugins.color night-light-enabled) == 'true' ]]

Both the if: and status: are used in this example. The if: parameter makes sure GNOME is being run on the system and the status: is used to achieve idempotency. This would make even more sense if we were running a command that requires a reboot. In this example, we would need the --force command to obey the if: or else we would get an error if GNOME is not on the system.

Currently, with any complex flows that rely on status, things can easily get messy when using --force but the --force command could potentially be pretty useful for cache busting. I see adding an if: parameter as a possible solution.

@ssbarnea
Copy link
Contributor

Probably the best use-case for if would be to add steps that depend only on specific platforms, like Windows, Linux, MacOS. Adding extra code for platform on each called shells script is a PITA, one that could be easily avoided with an "if".

@andreynering
Copy link
Member

andreynering commented Jan 5, 2023

For those interested, there's an ongoing discussion about a possible key specificly to select given OS/Arch on a task or command:

@max-sixty
Copy link

A +1 for if.

I really over-use status, which requires some mental gymnastics ("Only run this on ARM machines, which means status needs to be 0 on x86 machines, so raise an error on ARM"!), and isn't compatible with --force ("now we're running it on x86 machines?!")

Though I do empathize that it still has lots of overlap with status...

@titpetric
Copy link

@andreynering i have a couple of env vars where if would be useful. I'd be willing to open up a PR if there isn't anything in flight, going with your proposal in the reply. In combination with #1220 this would allow us to put most of our release pipeline into taskfiles, enabling matrix tests and targeting along with that "run offline" concept.

@andreynering
Copy link
Member

@titpetric Contributions are welcome! I would advise you to wait until #1220 is merged to avoid any conflicts. Also, that would allow you to test both features together.

@JonZeolla
Copy link
Contributor

FYI #1220 has been merged

@max-sixty
Copy link

FYI #1220 has been merged

Though IIUC this is a bit different — for rather than if?

@JonZeolla
Copy link
Contributor

Right; I'm just saying this is ready for development per this comment

@max-sixty
Copy link

Right; I'm just saying this is ready for development per this comment

Sorry, I didn't read up far enough. Thanks @JonZeolla

@thinkgos
Copy link

thinkgos commented Feb 6, 2024

if keyword have any progress?

@timrulebosch
Copy link

status kind of works, but its interactions with --force and sources make it unviable.

A when (more presence than if) that hooks to the same logic that status currently has, would be suitable. Any preference? If I can figure out the code, I might try create a PR.

@trulede
Copy link

trulede commented Mar 29, 2024

status kind of works, but its interactions with --force and sources make it unviable.

A when (more presence than if) that hooks to the same logic that status currently has, would be suitable. Any preference? If I can figure out the code, I might try create a PR.

After reading though the code, and considering the problem a bit more, there seem to be 3 possible paths:

  1. Add a new keyword like "if" or "when". I'm not convinced this is a good idea for the reasons; the calling task may need to know about the called task (and that might not be knowable until runtime) so that could end up being a mess, and as soon as there is an "if" ... we can be sure "else", "switch", "or" and friends will follow.
  2. Add a "skip" which is a duplication of the logic behind "status", but behaves independently of the "--force" flag. That gets the desired behaviour ... but ... conflicts a little with ...
  3. Add a "skip" attribute to the existing preconditions and change the behaviour of preconditions such that if all preconditions which fail have that skip attribute (set to true), then the task is skipped rather than erroring.

To my mind, adding a "skip" attribute to preconditions, and adjusting the behavior of precondition evaluation, is the better option.

@trim21
Copy link
Contributor

trim21 commented Jun 12, 2024

you can use go template in some case.

for example

  sync: 'poetry install --sync{{ if eq OS "linux" }} --only main {{end}}'

@ccxuy
Copy link

ccxuy commented Oct 11, 2024

you can use go template in some case.

Yes, but very urgly, and hard to maintain if the statement grows bigger.

@gedw99
Copy link

gedw99 commented Oct 18, 2024

use case for if else.

I have no idea how to do anything that reaches the same needs with task yet.

this checks if a binary is installed, and if not installs it into a special folder that is pathed in, to ensure we do not cross pollute projects. The ".dep" folder holds any binary dependencies I need , so that I can build a project.

I also has a OS and ARCH built in, called BASE_GOOS_NAME

GH_BIN_NAME=gh
ifeq ($(BASE_GOOS_NAME),windows)
	GH_BIN_NAME=gh.exe
endif
GH_BIN_VERSION=v2.59.0
GH_BIN_WHICH=$(shell $(WHICH_BIN_NAME) $(GH_BIN_NAME))

this-release-dep-del:
	rm -rf $(GH_BIN_WHICH)

this-release-dep: this-dep

ifeq ($(GH_BIN_WHICH), )
	@echo ""
	@echo "$(GH_BIN_NAME) dep check: failed"
	
	rm -rf $(BASE_DEP_DOWNLOAD_ROOT)
	mkdir -p $(BASE_DEP_DOWNLOAD_ROOT)
	@echo $(BASE_DEP_DOWNLOAD_ROOT_NAME) >> .gitignore

        # download the go, build it using a temp folder and copy the binary into the .dep folder, and delete the src. This Only happens ONCE so its fine.
	cd $(BASE_DEP_DOWNLOAD_ROOT) && git clone https://github.com/cli/cli -b v2.59.0
	cd $(BASE_DEP_DOWNLOAD_ROOT) && touch go.work
	cd $(BASE_DEP_DOWNLOAD_ROOT) && go work use cli

	mkdir -p $(BASE_DEP_ROOT)
	cd $(BASE_DEP_DOWNLOAD_ROOT)/cli && go build -o $(BASE_DEP_ROOT)/$(GH_BIN_NAME) ./cmd/gh
	rm -rf $(BASE_DEP_DOWNLOAD_ROOT)
else
	@echo ""
	@echo "$(GH_BIN_NAME) dep check: passed"
	@echo ""
endif

in case your wondering, "this-dep" installs the "which" that is cross platform:https://github.com/hairyhenderson/go-which. This is the type of Built in, that should be in task IMHO. "this-def" also employs the same "which" based file checking to see if it is needed to be installed or not.

So the best solution I know is to writer a golang based thing that does this which based checking , and installs the dep. then dep it at the start is a task run.

If anyone has any clues about how else I can do this would be awesome...

@trulede
Copy link

trulede commented Oct 18, 2024

I would not go down the path of IF and ELSE. If you really need such a complexity then just write a bash script and be done with it.

Better is to use STATUS to evaluate the condition for which a TASK should run. That effectively gives you the IF/ELSE logic you need, and you will get a better outcome. So, for you that might mean a TASK where the status checks for the absence of an installed dependency, and when missing, the commands install that dependency.

This is I think, not very complicated to try.

@gedw99
Copy link

gedw99 commented Oct 19, 2024

I agree that if / else is a bad pattern .

did not know about the STATUS keyword, but can I do conditionals of sorts with it ? I really need to solve this .

@gedw99
Copy link

gedw99 commented Oct 19, 2024

Like this ?

on mobile - not idea where those single quotes are on iOS mobile, so just raw formatting:

https://stackoverflow.com/questions/74863706/taskfile-how-to-run-only-if-binary-not-exists

tasks:
migrate:
desc: Get golang-migrate
cmds:
- echo running wget bla bla
status:
- test -f {{.MIGRATE_BIN}}
vars:
MIGRATE_BIN:
sh: which migrate || true

For every cmd that requires a binary , I just have the “install-binary-X”, as a dep .

@Firesphere
Copy link

I don't think status covers what an if would mean.
Aside from semantics, where using "status" as an "if" simply doesn't make sense, I think an if option would be very useful.

If I take the following:

tasks:
  generate:
    cmds:
      - git checkout branchname/{{.RELEASE}}
      - if: {{.TAG eq "true"}}
        cmd: git tag -a {{.RELEASE}} -m "Tagging {{.RELEASE}}
      - git push -a

That if construct is a lot more readable than using a status, which does a test, and furthermore, it allows me to run task generate TAG=true RELEASE=1.2.3

Currently, the task is constructed like so

tasks:
  generate:
    cmds:
      - git checkout branchname/{{.RELEASE}}
      - {{if .TAG | eq "true"}}git tag -a {{.RELEASE}} -m "Tagging {{.RELEASE}}{{end}}
      - git push -a

The tag command is a lot less readable, causing more chance of confusion and errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests