Patch Deprecating save-state and set-output commands in GitHub Workflows using sed command.
Tags: bash, githubactions, automation, sed
Published: 2023-05-06 13:54:04

## Introduction

GitHub had announced that the `set-output` and `save-state` workflow commands will be deprecated from 31st May 2023. 
More details on it [here](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/).

Let’s cover the changes required to patch this.

**Old Syntax**

```
- name: Save state
  run: echo "::save-state name={name}::{value}"

- name: Set output
  run: echo "::set-output name={name}::{value}"
```
**New Syntax**

```
- name: Save state
  run: echo "{name}={value}" >> $GITHUB_STATE

- name: Set output
  run: echo "{name}={value}" >> $GITHUB_OUTPUT
```

---

## Automating syntax update using sed.

If you’re new to sed. You can read my [introductory blog on sed](https://dev.to/cloudsky13/eliminate-the-monotony-automation-with-sed-4ond) to get started.

Since syntax for both `set-output` and `save-state` are identical we’ll be able to use the same logic for building the command.

First, we need to identify a pattern which we can pass in the **sed command to mimic the old syntax**.

```
sed -i 's|"::set-output name=\([^"]*\)::\([^"]*\)"|"\1=\2" >> $GITHUB_OUTPUT|g'
```
1. Firstly, we notice how `::set-output name=` will always be constant throughout all usages of the set-output, so we can we it as a **search element**.

2. Now we observe the {name} and {value} are reusable entities and also these are separated by `::`

3. This means we can **leverage capture groups to temporarily store these entities**.

4. Once done with the above steps we can simply append ` >> $GITHUB_OUTPUT` as per the new syntax.

## GitHub Workflow to commit changes in another repo

The below workflow takes the `repository name` as input and replaces any occurrence of the old syntax of `set-output` with the new syntax.

```
name: Update-target-repo-with-new-syntax
run-name: "Auto-patching"

on: 
  workflow_dispatch:
    inputs:
      Target-repo-name:
        description: "Repository name to be updated"
        required: true  

env:
  GITHUB_TOKEN: ${{ secrets.TARGET_REPO_TOKEN }}
jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Repo
        uses: actions/checkout@v3
        with:
          path: main

      - name: Checkout Target Repo
        uses: actions/checkout@v3
        with:
          repository: organisation-name/repo-name
          token: $GITHUB_TOKEN
          path: my-target-repo-path
          ref: main
          
      - name: Commit changes to Target Repo and create pull request
        run: |
          cd $GITHUB_WORKSPACE/my-target-repo-path/
          
          git config --global user.email "<github_email>"
          git config --global user.name "<github_username>"
          git branch standard-branch-name
          git checkout standard-branch-name
          workflowList=$(ls ./.github/workflows/*)
          for workflow in $workflowList
          do
            if grep -q -e ::save-state -e ::set-output $workflow; then
              sed -i 's|"::set-output name=\([^"]*\)::\([^"]*\)"|"\1=\2" >> $GITHUB_OUTPUT|g' $workflow
              sed -i 's|"::save-state name=\([^"]*\)::\([^"]*\)"|"\1=\2" >> $GITHUB_STATE|g' $workflow
            fi
          done

          set -e
          if ! { git commit -am "Replacing-deprecated-syntax-in-workflows"; }; then
              echo "Nothing to commit"
              exit 0
            else
              git push -u origin `standard-branch-name
              gh pr create -B main -H standard-branch-name --title 'Updating-deprecated-synatx' --body 'Created by Github action' --repo organisation-name/repo-name
          fi
          
```

---

## Workflow steps in detail

 `- name: Checkout Source Repo`

The workflow first checks out to the current repository using GitHub Action `actions/checkout@v3`.

`- name: Checkout Target Repo`
It then checks out to the target repository using GitHub PAT Token or personal access token. 

_Note that `path: my-target-repo-path` creates a directory in the PWD (Present Working Directory) and checks out to the target repo code in the `ref` branch i.e. main._

Learn more on `actions/checkout@v3` [here](https://github.com/marketplace/actions/checkout#checkout-v3).

`- name: Commit changes to Target Repo and create pull request`

In this step the workflow first changes the working directory using the `cd` command. $GITHUB_WORKSPACE stores the default working directory of the GitHub runner.

Using git commands, it then creates a new branch with a standard name in the target repository.

After that it stores all the file names present in the workflow directory of the dot GitHub folder into a variable called `workflowList`. 

The for loop then iterates on each file name and use the grep command to check if the file returns any output for the search elements we discussed earlier.

_Note we use `-q` or `--quiet` flag with the `grep` command is used to suppress all normal outputs of the command._

---

## Error Handling

If a match is found for the search element, the sed commands we orchestrated are executed on that workflow file to update it with the new syntax.

If no match is found the control simply proceeds to check the next file until all the files are checked.

Once all files are checked we need to commit and push our changes. But what if there weren't any changes, you get an error as below

![Exit code 1, error message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sw5y9qgv5n2hozdtsqge.png)

To avoid this error, we need to make sure that git commit is executed only if there are any changes made in the working tree.

To check if working tree is clean or not I have added this if block, which checks the exit code from the git commit command.

If the **exit code is non-zero** it simply prints "Nothing to commit" and **changes the exit code to 0**. Thereby preventing the workflow from unwanted failures.

![Solution to above error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/haheavpp1mmebu10yy98.png)

And in the else condition the changes are pushed to the new branch created by the workflow. Then a PR is created using the GitHub CLI command `gh pr create`. Read about the command [here](https://cli.github.com/manual/gh_pr_create).

---

## Pull Request snapshot

![Pull request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9xpz4bfwqxv1fyhz1x6.png)

---

## Closing remarks

Thank You for reading my blog post.

Tried to cover an example of a real-world problem statement in this blog along with basic error handling.

If you liked my approach to this problem, do share it the with your peers.

Do share any ideas that pop up in your head which revolves around the use of bash and GitHub to solve a real-world problem.

Drop your comments, feedback and questions below. Have a good day. Cheers!