در حال بارگذاری ...

Defining Continuous Integration0:00

Continuous integration is one of those terms, as with many programming terms, cooked up in some mad science lab somewhere that has very little meaning for you and I as developers getting into the game. So let me lay it out in plain language. All it means is that instead of you manually performing checks before you merge code, perhaps linting, running your tests, running type checks like phpstan, rather than you doing that manually, you can get a computer somewhere in the cloud on a server to do the checks for you. Happens every single time you create a pull request. You don't have to think about it.

Happens every single time you create a pull request. You don't have to think about it. It's automated and it's continuous, hence continuous integration. Now the majority of us will use GitHub as our platform to host code. So what we're going to do in this series is take a look at GitHub Actions, how you can create the .yaml files necessary to build and run those actions, and we'll start with some fun stuff just to get us off the ground and used to the syntax, and then we'll build to more complex but altogether more useful things for actually automating these processes in your code. Let's go have fun.

Planning the PR Comment Action1:16

in your code. Let's go have fun. For our first exploration into GitHub Actions, if you go to Laravel Framework and search no plans to merge, you'll find many, many pull requests where if you jump into the pull request itself and you scroll down, sure enough, Taylor will have made a comment, no plans to merge, no plans to merge right now, or something along those lines, and then he closes the PR. Now, that's absolutely fine, but it has become a little bit of a meme in the Laravel community. Of course, we want to do the same thing, right? We want to be the same as Taylor so that we can own Lamborghinis.

Of course, we want to do the same thing, right? We want to be the same as Taylor so that we can own Lamborghinis. So of course, when we close a pull request, we should put no plans to merge, but we don't want to do that by hand. What are we, animals? No, instead, wouldn't it be cool if we could create a GitHub Action that whenever we close a PR, it automatically adds the comment, no plans to merge. I think that would be great, and it will give us a good taste of how we set up a GitHub Action from scratch. So let's go ahead and create that in this episode.

Creating the Workflow File2:13

Action from scratch. So let's go ahead and create that in this episode. To get started, I'm going to head to the Actions tab on my GitHub repository, and I'm going to set up a workflow myself. You can choose from many different workflows, and the suggested ones usually actually get you pretty far, but for this particular instance, we'll set up a workflow ourselves. Now GitHub actually has a built-in editor. I'm not going to stay here for long, but I will just to get us off the ground. In fact, if you click Control Space, it will even do auto-completion for you, which is pretty cool.

In fact, if you click Control Space, it will even do auto-completion for you, which is pretty cool. And the first thing I'm going to provide for this GitHub Action is a friendly name. So perhaps for the name, we'll just say, no plans to merge, like so. And with that, I'm going to commit the changes using this green button here, and I'll commit directly to the main branch. And if we pull the changes in our IDE, yep, sure enough, here's our .github folder. Inside there, we have workflows and then main.yaml with no plans to merge. Right, I'm going to right-click and refactor rename this. I'm actually going to call it no plans to merge in kebab case.

Right, I'm going to right-click and refactor rename this. I'm actually going to call it no-plans-to-merge in kebab case. And yes, it's written in YAML. In case you're not familiar, YAML is just a markup language, same as JSON, right? But it uses indentation to be able to define groups of data. It's great for stuff like this, where you're essentially just creating sets of instructions. It takes five minutes to get used to. You'll be up and running with it in no time. Don't worry too much. So as with most programming tasks, let's break this down into small chunks.

Configuring Trigger and Jobs3:42

Don't worry too much. So as with most programming tasks, let's break this down into small chunks. We've said when a pull request is closed, that's known as the trigger. What should happen for GitHub to actually execute this script? When a pull request is closed, then we want to create a comment that says no plans to merge. So let's start with our trigger. We define triggers in GitHub Actions using the on root keyword. And if you use autocomplete in phpStorm in the online workflow editor, you'll see there are many different events that we can hook into. For example, any time someone deletes a branch or tag,

you'll see there are many different events that we can hook into. For example, any time someone deletes a branch or tag, any time someone forks your repository, or for 99% of continuous integration, any time something happens on a pull request. Now, there are many things that can happen on a pull request. It can be opened. It can be updated. It could be deleted. Or in our case, it could be closed. So underneath, you can actually look for a certain type,

Or in our case, it could be closed. So underneath, you can actually look for a certain type, and you can see all the different types available here. We're obviously interested in when a pull request is closed. We only have to type this to get the workflow executing at the right time. So all we've had to do is say that any time a pull request is closed, run this script. Now, to actually run the actions in our script, we need to define another root keyword called jobs. Jobs can have as many different jobs as make sense for this particular task, but we're only interested in one.

Jobs can have as many different jobs as make sense for this particular task, but we're only interested in one. Let's give it a unique ID. We'll say no plans to merge. Again, I'll use kebab-case here. And then the first thing I want to say is the operating system that this runs on. The GitHub documentation for workflows is excellent. You can pretty much work everything out just by reading through it. The runs on documentation has a nice little table down here that tells us exactly which operating systems are supported by GitHub Actions.

The runs on documentation has a nice little table down here that tells us exactly which operating systems are supported by GitHub Actions. And, well, all of them are essentially Linux, Windows, and macOS. I would recommend going for Linux if at all possible. It's likely that your server runs Linux. The majority of the examples and documentation you'll find online will assume you're using Linux, and it's the cheapest to run if ever you do have to pay for GitHub Actions. So let's go ahead, copy this Ubuntu latest. No prizes for guessing that that's the latest version of Ubuntu.

So let's go ahead, copy this Ubuntu latest. No prizes for guessing that that's the latest version of Ubuntu. And then we're going to go back to our IDE, and we just drop that in as a string like so. And now any time this noPlansToMergeJob is executed, well, GitHub is going to fire up an instance, an image of Ubuntu to run the job on. With our metadata defined, we can now turn our attention to the job steps, small tasks that we need to complete in order to actually have a finished workflow. Well, in our case, we just have one step, right? Create a Comment on the PR.

Well, in our case, we just have one step, right? Create a Comment on the PR. So let's give it a name, create a Comment on the PR. And then there are two different ways we can run a step. One is by using the run command, which as you can see, runs a command line program. So for example, we could say echo hello, and that would echo hello out inside the GitHub Action. You can basically do anything that you're able to do through the command line in Ubuntu, but we're not going to use that in this case. We're actually going to make use of users,

but we're not going to use that in this case. We're actually going to make use of users, which allows you to tap into a huge marketplace of pre-made GitHub Actions. If you head to github.com/marketplace, you can search through all available actions. I'm going to search for script, and one of the first results will be GitHub script. It's first party, and it essentially allows us to run JavaScript and make requests to GitHub's API, which for creating a comment sounds like exactly what we want.

and make requests to GitHub's API, which for creating a comment sounds like exactly what we want. In fact, if we scroll down a little to the examples in the documentation for this action, we have comment on an issue. Now in GitHub, every pull request is an issue, but not every issue is a pull request. In other words, this example should work absolutely fine for our use case. Let's take a quick look at the syntax. We pass the name of the action along with the version we want to use to that users key we already talked about.

We pass the name of the action along with the version we want to use to that users key we already talked about. So let's copy that value, and then we'll jump into our IDE, and we'll drop it in place. We then have this little with parameter, which is going to allow us to pass custom input to any given action, and the action will define the custom input that you're allowed in its documentation. In the case of GitHub script, it expects a script parameter where we define that piece of JavaScript we actually want to make use of. Okay, so let's copy that, and then we'll paste that into our action,

where we define that piece of JavaScript we actually want to make use of. Okay, so let's copy that, and then we'll paste that into our action, and I'll tell you what, why don't we update the body right away? We'll say no plans to merge, and we'll be a little bit friendly and put a thumbs up emoji in there as well. You might be thinking, well, where's GitHub coming from, or where's context coming from? Well, let's go back to the browser. If we head to the top of the documentation here, you can see that the following arguments are provided to your script.

If we head to the top of the documentation here, you can see that the following arguments are provided to your script. GitHub, which is a pre-authenticated, that's wonderful because it means we can make requests to the API without worrying about tokens and keys, a pre-authenticated REST.js client with pagination plugins. In fact, if we open that up, octokit/rest.js, and let's jump into, hmm, issues should be here, right? Yep, issues, create an issue comment. Here we go, you can see that this is what's actually happening under the hood.

Yep, issues, create an issue comment. Here we go, you can see that this is what's actually happening under the hood. We pass in an owner, a repo, an issue number, and a body, and sure enough, that's exactly what we're passing in here, issue number, owner, repo, and body. So let's go back to the documentation because the other thing we talked about was context. We have this context object, which is an object containing the context of the workflow run. This is quite complex when you first get started with GitHub Actions,

which is an object containing the context of the workflow run. This is quite complex when you first get started with GitHub Actions, but essentially, GitHub is going to pass you metadata about the workflow, which contains tons of information you can use in order to actually be able to execute the job. It's an object, and if we take a look at the example we've been given, that object includes the issue number, the repository owner, the name of the repository, and we can use these things. In fact, we need these things to be able to complete many, many, many different actions that we want to take in workflows.

Testing the Workflow10:23

In fact, we need these things to be able to complete many, many, many different actions that we want to take in workflows. Okay, we'll leave off that for now and come back to it in just a moment. I think we're about ready to test this workflow file. So I'm going to Command-K on phpStorm in order to commit and push this file back up into my main code base, and then let's go ahead and create a new branch, my bad PR, and we'll jump into one of these actions here. I've made use of Jetstream, and I'm going to do something stupid. This is a random comment, and I've not even spelled it right, right?

I've made use of Jetstream, and I'm going to do something stupid. This is a random comment, and I've not even spelled it right, right? You get these kind of contributions all the time in open source, usually from me. So we'll go ahead, commit and push this, and then let's go and create a pull request in the GitHub client. The pull request is so bad, I'm not even going to give it a proper title and description, and of course, as soon as myself or someone else reviews this, we realize it's terrible, and we go straight to close the pull request.

and of course, as soon as myself or someone else reviews this, we realize it's terrible, and we go straight to close the pull request. Now, if we jump up to the Actions panel, we should see, here we go, see that this action has started. If we jump in and we click the actual job itself, you can see that the job is running, and it's gone ahead and executed the run actions GitHub script at v7 to create a comment on the PR. It didn't fail, so I'm guessing if we go back to pull requests, and let's head into that work in progress pull request we've just closed,

It didn't fail, so I'm guessing if we go back to pull requests, and let's head into that work in progress pull request we've just closed, sure enough, we have a comment on the PR, no plans to merge. This worked. Our workflow executed at the right time, and it did exactly what we'd expect, but there is a slight issue with our current implementation. Let's open up another pull request, but this time instead of closing the pull request, I'm going to go ahead and merge it.

but this time instead of closing the pull request, I'm going to go ahead and merge it. Now, if we jump back to the Actions tab once again, you'll note that we kicked off another workflow here that no plans to merge workflow, which is interesting. Let's go back to pull request number seven, and note that even though we merged it, we still get this comment, no plans to merge. What's going on? A quick look at the documentation reveals that when a pull request merges,

Adding Merge Condition12:40

What's going on? A quick look at the documentation reveals that when a pull request merges, the pull request is automatically closed. So that essentially means that this trigger not only fires when we manually close a pull request because we don't want to merge it, but also when the pull request was successfully merged into our code base. It's firing at the wrong time, and there's no way in the trigger to actually stop that from happening. The documentation tells us that we can actually use a conditional check on a job to only perform it under certain circumstances.

The documentation tells us that we can actually use a conditional check on a job to only perform it under certain circumstances. So let's go ahead, copy that code, and then under runsOn, I'm going to paste that in. The key is if, and note that for the value, we're making use of those context objects again, github.event.pullrequest.merged, which is a Boolean, and then we use a double equal sign to perform an equality check, and obviously we want the opposite here. We don't want to say it was merged.

and obviously we want the opposite here. We don't want to say it was merged. We want to say it wasn't merged. So let's set that to false. All right, I'm going to commit this, and then we'll see if it's actually fixed our problem. So we'll hit merge pull request and confirm, and then I'm going to head up to the actions panel, and yeah, you'll note that the action has kicked off. We would expect that because that event still triggers,

and yeah, you'll note that the action has kicked off. We would expect that because that event still triggers, but then we get this new indicator when the action finishes, and if we actually jump into the action, you'll see that the status has been skipped. So that did work exactly as expected. It didn't run the task, and if we were to go back to the pull request in question, let's head to closed, find the PR. Sure enough, there was no comment that said no plans to merge.

Debugging with GitHub Context14:16

let's head to closed, find the PR. Sure enough, there was no comment that said no plans to merge. Now at the moment, all of the objects we're making use of in this if check or perhaps down here where we have context.issue.number, for example, they're a bit of a black box. We've taken it as gospel that they exist and we can use them, but it would be nice to see everything that's available to us in an action. So I'm going to give you a piece of debugging code that you can make use of in any action to have a peek inside. Let's add a new step for our job.

that you can make use of in any action to have a peek inside. Let's add a new step for our job. I'll give it a name of outputContext, and then I'll use that run key, which again allows us to execute a shell script, and I'm going to echo out an environment variable called GITHUB_CONTEXT. That environment variable doesn't exist yet, but we can create it using a little & on our step. So we'll define GITHUB_CONTEXT,

but we can create it using a little end key on our step. So we'll define GitHub_context, and the value is going to be, well, a little bit of special GitHub workflow syntax. So $ and then two opening curly braces, and then we'll use the toJSON function provided by GitHub, and we'll pass in that little GitHub object. Then we can close this out with two closing curly braces, and essentially this is going to turn that object into a JSON string that will be set as GitHub context,

and essentially this is going to turn that object into a JSON string that will be set as GitHub context, and then obviously echoed out inside the output of our GitHub action. All right, let's commit this, and then we can go ahead and close a PR to hopefully see that object inside GitHub. Now when our action runs, you'll see that we do output this massive GitHub context object. Let's go ahead and copy it. There's plenty to be going with here,

Let's go ahead and copy it. There's plenty to be going with here, and then once we've got it copied, we'll head to our IDE, paste it in a JSON file, and then reformat it so that we can see what's going on. So if we go back to our workflow, we said if GitHub.event.pull_request.merged, well, let's find that inside that Scratch file. So I'm looking for merged, here we are, and of course, as you'd expect, that value is set to false.

So I'm looking for merged, here we are, and of course, as you'd expect, that value is set to false because we did indeed close the pull request. So you can use that little bit of debug code to see exactly what GitHub has made available to you depending on the trigger type you're currently using, and you'll quickly realize looking through this that the sky truly is the limit. There is so much you can do with all of this context. So you've now dipped your toes

There is so much you can do with all of this context. So you've now dipped your toes into the world of GitHub Actions. You understand the basic syntax, you understand how to put a workflow file together, but, I mean, creating a comment on a PR isn't exactly continuous integration. So let's take things to the next level in the following episode.

in the following episode.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟