If you would continue to write on this program, I would definitely recommend to split this trade VM out in multiple functions, maybe one function for every client, that's going to keep the overview a little bit better than what I did. I was just focusing on explaining how to launch these resources. So I didn't want to split everything in functions. So let's try to remove this VM and then we are finished with this demo. So if I go back to the resource group, go Azure demo, I'll refresh this and now I have this virtual machine. And you can remove this virtual machine. And you should probably also remove the network interface and the public IP address and the disk just to make sure that you are not incurring any charges because the disk is going to cost you money, the virtual machine, but also the public IP address. If it's not in use, there's a very small fee to pay every month. If it is in use, you don't have to pay. But if it's not in use, you will have to pay for it. So make sure that those are for sure deleted. The network security group, you don't incur any charges for that and the virtual network neither. But to make everything clean, you could actually remove this whole resource group. If you're done testing, I will just remove this whole resource group. So that's it for this lecture. That was quite a lot to get this VM launched. But at least you got some training in launching all these different resources. So now if you want to launch another resource like a database, just have a look at the reference documentation at the SDK. And it should be fairly straightforward, very similar to what we did to launch other resources on Azure. In the next coming lectures, I'm going to show you how to use Go with Kubernetes. If you're not familiar with Kubernetes yet, I would recommend you to first take a Kubernetes course. I have another Kubernetes course on Udemy, for example, that you can take. What are we going to do in this first lecture is I'm going to show you how to use the Kubernetes client in Go. Kubernetes has an API. It's a well-documented API that you can call using REST calls. So just over HTTP, using X519 certificates for authentication. So we have certificates considered in our kube config configuration file. And using those certificates, you can authenticate to this HTTP REST API, the Kubernetes API, and interact with it. You could do this with curl, you could do this with the HTTP client in Go. But it would be, again, very evolved to do this all yourself. So there is a Kubernetes Go client available that will do all the heavy lifting for us. And we just have to supply the config file and the API calls that you want to make. The Kubernetes API will then reply with a JSON response when we do API calls. We do an API call and the Kubernetes API response with JSON. This Kubernetes API is the same for any Kubernetes distribution, whether you run MiniCube or on AWS using cops or the Google Kubernetes engine or the Azure Kubernetes engine or anything else, it should always be the same. There are differences, but mainly in the API version of the Kubernetes API. So depending on what Kubernetes version you're running, you will need a different Go client. The version doesn't need to exactly match. But on their GitHub page, they have a matrix to see what client version is compatible with what server version. To make these demos work, you need a Kubernetes cluster. The easiest way to have a Kubernetes cluster running on your machine is using MiniCube. So let's have a look at MiniCube. This is the Kubernetes MiniCube GitHub page. So it's GitHub.com Kubernetes MiniCube. And here you can download MiniCube either from the release or you could actually go to the documentation. There is the documentation here that has an installation page to get started page. And here it explains what you need. All you need is Docker or similarly compatible or a virtual machine environment. What does that mean? You need either Docker installed, like just Docker for Windows, Docker for Mac, or you need virtualization support, hyperkit, hyperv, parallels. VirtualBox is a free that you can download if you don't have Docker installed or it's not compatible with your system. Then your operating system, I'm on macOS. But if you're Windows, then it's just a few commands that you have to enter to download the installer and to add it to your path. If you're Windows, I also like this one. Chocolate tea. It's a package manager. You can just install the package manager and then do Choco install MiniCube and then you'll do everything for you. I am on macOS. I will execute these commands or you can use brew as well. And then you can start your cluster with MiniCube Start. If you have any problem with MiniCube Start, it'll most likely be that you have an issue with any of these virtualization machine managers. So for example, if you run VirtualBox, there's a VirtualBox page that can have a look how to use VirtualBox. In the case of VirtualBox, you first need to download it and then MiniCube Start driver VirtualBox to run MiniCube on VirtualBox. The easiest way I find is to use Docker. I have Docker installed. So MiniCube will run within a Docker container on my laptop. So when I'm going to do the demo in the next course, I will have my MiniCube cluster running. And the only thing that I did is just MiniCube Start. In this demo, I will try to deploy a new container packaged in a deployment on our Kubernetes cluster that is running locally. So I started in a new directory, Kubernetes demo. I had to go mod in it. I have a main function. And the first thing I need to do is I need to read about how the go client for Kubernetes works. So let's go to GitHub. Invite app, Kubernetes go client in Google, then GitHub.com, Kubernetes client go. This is it. And here it says what I need to do. First, it says have a look at the compatibility matrix.  And how to get it? Go get client.go together with the version number. So also they say we recommend using the V0 XY tax for Kubernetes releases 1170. So I am on 124. So I should use V0 24 and then the minor version. So I'm on 20. So I'm going to use the 024 one. So go get Kubernetes client go. This is what I copied from the main page 20.4. So I am on 24.1. So you can know that by doing Qubectl version, or just have a look at your output of mini cube started to also tell you what communities version you are on. Other versions will also work. So if you have a newer version, it most likely will also work. It just for best compatibility, you should use one that matches. It's just that there are new API calls introduced in later versions. So if you want to use those new API calls, then you need to use the correct version for the API calls that we are going to use. You could even use an older version. So you could even potentially use this version. It just depends whether these API calls of this deployment are going to change. If they change, you will need a new version for a newer Kubernetes cluster. And when we do this go get, we get an error. So it is, it requires this Google cloud Google.com, this package and this package. And when it arrives at GitHub, it will come calling mock 143. Then it says cannot authenticate record data in server response. It's possible that when you do go get a new version that you don't get an error, it just, there is something wrong currently in this mock repository. For some reason, I cannot get a one for three version. But then when I tried earlier, I can get the one for four version. So this is an excellent opportunity to show you how to force a certain version. So we can say replace and then this package without the version. So when we see GitHub.com go like mock replace it in still the same package, we could also change another package. For example, if you would fork it on your own repository, make a fix, you could also do a replace and then this would be instead of going, it would be your GitHub account. But we are going to keep it. We're going to keep it on this goal and mock. We're just going to force it to a different version. So we're going to say I want to use the one for four instead one for three. And it actually works. So now we are using the one for three. V zero 24 one Kubernetes client. And we're forcing it to use the mock version from one for four. So just like our other packages, we first need to initialize it. We need to make sure that we provide the correct config. So let's have another look at this client go. They have an example somewhere. How to use it. If your application runs in a pot in the cluster, please refer to our in cluster example. Otherwise, please refer to the out of cluster example. So there are two ways of authenticating in Kubernetes. If you're within the cluster, it's easier because then the API server is already there. You just have to contact it locally. And it knows that you are coming from within a pot. But if you're externally, then you need to do authentication, you need your certificates. So it needs a cube config file. So let's have a look at this example, main go. And then here we have some code that we can use, create the client set. So this client that we need, and with this client that we can then do API calls. So we can do core v1, bots, list the bots of my Kubernetes cluster or create a deployment. So I'm going to copy this client set, because this is how we're going to get our Kubernetes config. Actually, I'm also going to copy this. Let me just copy everything. I will remove this flag parse because we are not going to work with flags. It's always good to start from an example. So we are going to call this our get client function. And we are going to return. We are going to return whatever this new for config is going to return. We don't know yet because we need to save it and import some variables. So let me just put this first all in the function. So this is just the code that we copy pasted. And I'm not gonna work with this flag. So I'm going to remove this first. And what am I going to do? I'm not going to check whether a home directory was supplied. I'm gonna straight to this home directory. And that's going to be my path where I'm going to look for this cube config file. So if you want to supply another cube config, you can just hard code it here because build config from flanks. The second parameter here is just the path to your config file. The path to your config files will be your home directory. So this till design is my home directory and cube config is then your Kubernetes configuration on Windows. It should also be in a home directory in the cube directory. And you should have the config file. If your config file is somewhere else, then you could override it. You could just say my cube config is for example, just a file called config in the directory where I am executing this goal and program. So let's save this. It's not automatically adding on these these things, these imports. So I will have to end them myself, or I can copy paste them from the example. Let's just copy paste everything that I found here, save this, it will remove the ones that I'm not using. And we might have to import some more. Let me try to see if I need to import this. Go get this Kubernetes is already imported. Go get this is also already imported. Yeah, because I did import of this. And it also has all the sub directories.  So now I have the correct packages in my go mod file. What do I get here? I get a Kubernetes client set. So what I'm going to return is these Kubernetes client sets and potentially an error. And then I can say again, var client is my Kubernetes client set. And I'm going to do if a client an error, but then they will have to define the error as well. This is the error. Get the clients. If error is not equal to nil, then I'm going to print the error. And I'm going to always exit one. Save this. Okay, I need to remove this colon. Client is not used. And here I still need to return either an error or nil and the client set client set nil is nil the errors nil client set is here. And I could already test this. So print F tests. I have no idea what is in this client. It just functions that we can use. So I just try to print it, which will give us some useless information, I guess. But I want to see if this actually works. I want to clear go run. Go. What does it do? No configuration has been provided. Try setting Kubernetes master environment variable. So it's actually not working as because my Kubernetes cluster is not running. So if I do cube CTL get pots, it says local host refused. So I need to still run my mini cube cluster. And it's going to say that it detected the Docker driver. And now it's going to create this Docker driver. So now it needs to run this Kubernetes cluster. So I'm sorry about the noise is going to make the background noise because this is all very heavy. And then my MacBook starts using its fine as well. So actually, I'm not on 124. I'm on 123. But 124 will will come out very early. I think I just have an older mini cube version right now installed. And one 124 will be out very soon. And this 124 this 024 that I'm running here will work with the V 23 as well. Let's try to run our program again. Okay, so now I have the client ready. So now we can do the actual deploy code. And I'll just pause for a second because this fan noise will stop once everything is booted up. Now that we have the client we can actually connect to our Kubernetes cluster. So let's try to run a deployment. I have here an Apple demo file of type deployment. This is the API version. It has a name hello deployment and specification. We are going to run one replica. So one instance of our deployment. And then we have a selector which is going to select the template with the app. Hello label. So we have here the template and the specification of our container. So this specification will run the Kubernetes demo from my Docker Hub repository. And it will expose a port for 3000. So this runs on Port 3000. So these label. We'll also see in a pot definition. So later on we can filter on the pods that have this label to see whether our pot is running. What do we need to do first? We need to do the deployment. So let's create a function. Deploy. And what are we going to return? We'll see that later we'll start with an error. And then we can we need to pause the client for sure. So we need to have the client Kubernetes client set. And then here. Let's start with an error. Deploy. And we're going to pass this client. So and this we can remove all we can say. Just deploy it. Deploy finished just so that we have something that we know that the deploy has finished. So how how do we do this API call? Well, the API version is apps V1. So we will find this also in this client. You see apps V1 is an endpoint on this rest API. So you will see within this client package, we also have this division the same as the API endpoints. So apps V1 deployments. And here we have to pass something. Name space, which is a string. So either we can say empty, which is going to then use the default namespace, or we can specify the default namespace or we can create a namespace. So I'm going to say default, or you could pass this parameter, or you could just hardcord it. Where if you want to deploy to another one than default default is the one that is there by default in mini cube. And we can do apply create the leads get list patch update. Let's start with create because that's the easiest one. Create we need to pass a context. So let me pass again a context here. And then I will have a new context here, context background, and I will pass it as well. Context. So we have a context. And then we have a deployment V1 deployment. So this V1 deployment is actually all this. But it needs to be in this V1 deployment type, and it's still in YAML. So we need to parse this. And actually this, these Kubernetes packages, they have something to parse it. So let's like not let's not do it like that. Let's pass deployment. And then we're gonna say far deployment is a new pointer to V1 deployment. Then we need to add another argument, our create options, create options, which doesn't seem to work. Because what is V1 here? Apps V1. Let's have a look in this create definition. Meta V1 case, we can just copy paste Meta V1. So these are the create options that we can pass Meta V1 Meta V1. And then here this also needs to be called Meta V1. And it doesn't work. Meta V1 create options works, but it's not a pointer. So Meta V1, this is how we should do it. What does it return? A deployment. Look out, deployment response, the error. And then if the error is not null, we can return the error. Deployment error like this. Once we have deployment response, what we actually want is we want to return these labels. We're going to assume that whenever we do a deploy, we're gonna have these labels. And in the next function, we want to filter on these labels when we get the pots, just to see if the pots are running. So if you do, if you reply from this template, the labels, then we can later on use them. So if I say deployment, response, and then we have what do we have here, the specification. We have this here as well, specification. Now we should have a template. And then we have labels. And then we have no error. But we need to change our function signature, because now we are returning a map. This will be a map app. We will be the key and hello world value. And we just need to make sure that we also returns something here. So the map will be nil. But we still have an empty deployment objects. We cannot deploy an empty deployment object, because it will not work. It will give us an error. We still need to convert this YAML file into this deployment struct. And now you could use some YAML unmartial package to marshal this into this specification. I actually try that and that doesn't work 100%. And when I was looking into the documentation, I found that this go Kubernetes client has its own functions to do the unmartialing. So it's some kind of helper functions that can help you. It's not very well documented at this point in time. So I just found it in a GitHub issue. And I'm just going to copy paste the code that I found in this GitHub issue. And then I will try to explain it. So these are the two lines scheme codex universal DC Lizer. So they call it the serializing your YAML into the community scheme. And this is the decode function that we are then going to call. And the only thing that we need to supply into this decode function is the contents of our Apple YAML. And it will magically make a Kubernetes object from it. So that's all good. But I already saved it and couldn't find the scheme. So we'll need to help Visual Studio code a little bit. And it's going to be in this Kubernetes package in this client Kubernetes package for a go. And it's scheme. And now it found it. So Apple is then by it. So this is actually a function. So we could actually make this a bit shorter because we're only going to use it once. So we could also just say scheme codex universal serializer decode. And this is our function, our decode function decode attempts to deserialize the provider data, which is YAML. But I think it can also be something else can probably be also JSON. That's why it's really a decoder and not just an un-martialer because it just accepts bytes and YAML and JSON are compatible. So it says the object is not guaranteed to be populated. We need to still this runtime object. We still need to figure out whether it's a deployment object. Let's start with reading our file. So up file error equals IO read file. It's IO util read file and read file expects a file name. A file name is ample demo read file error in case it has an error. So you read the ample demo file, pass it here to the decode function and decode function gives us a runtime object. This object is going to be of type v one deployment, but we don't know yet, because this is a universal function for all the communities objects. It just gives us a runtime object and we need to test whether this could be of type v one deployment. And if it is of type v one deployment, we want to put it in this deployment variable and then do this create. So how do we do that? We can use switch object type. And if the type is v one deployment, then we can say case v one deployment, then we know it's this type. What if it is not this type, then we can make a catch all default so that we can say return an error, unrecognized type, and the type is of group version kinds. So it also returns the type, this universal serializer.  So v one deployment, if it's v one deployment, then deployment equals the object, but with the v one deployment type. So we're just going to change this object type to v one deployment, save this. And then we could actually test it. So let's try to test it. Let's have a look whether we have deployments qtl get deployments. No, we don't have any deployments. We don't have any pots. Okay, let's run this deploy. Oops, I still have an error somewhere. Oh, yeah, I didn't change this code here. So we will say deployment labels is a map string string. And then deployment labels deploy finished. Did a deploy with labels. I never used that for output. It's deploy labels. All right, let's try again. Let's check our deployment. Cube cdl get deployments. And we have the hello world deployment. And we have the pot running. Let's try to do another go run. And this is a bit annoying. You can only run it once because it already exists. So let's try to change our quote a little bit so that we can make change to our up yaml and then it will still apply the changes. What I want to do now is I want to check whether the deployment already exists. If not, I want to create it. If it exists, I want to update it. So instead of create, we can also do a get. I'm going to do a get context than the name of deployment and then the get options. So context, the name deployment name, which shouldn't be hello world deployment. And then the meta V1 get options. What is returned a deployment. But I actually only want to know whether it exists or not. And if it doesn't exist, it will throw an error. So I will just check if there's an error. If error is not equals to nil, and I want to split it out, I want to say if there is an error and it is not found, then I will do something. If there's an other error, I will not return an error. And there is a package within the Kubernetes client that we can use for that. There's an errors package that can check whether something is not found. So I'm going to add the Kubernetes IO API machinery and errors package. And you might have to do a go get of this package. If you don't have it yet, what you can then do is if there is not nil and errors is not found over the error. So if this error includes is not found, is not found returns true if the specified error was created by a new not found. So if this deployment get returns is not found, then we go in this conditional. Else if there is not nil and the errors is not found, but we are going to reverse this. So if it's not, is not found, then it's a real error. Let me say deployment get error. And otherwise, if it's not found, then we just move this code in here. Because if it's not found, then we need to create it. If it is found, then we're going to update it. We need to have the update options. That way we can override our deployment, just like that, if it is already created. So there's a deployment. So let's test this. No deployment. Deploy finished the deploy with labels. Map hello world. One replica. Let's now put three replicas. And then instead of an error, we should get the deploy is finished the deploy. And now you have three replicas and three pots running. So and if you put it back to one, then it should still work. So this now will do the update if it already exists and I create if it doesn't exist. Now next, what we can also do is we can have another function to wait for the pots, whether they are running or not. Here they are quickly running because we already have downloaded the image and there's no health checks on it. But other pots, it can take a couple of minutes before they are in the running state. So we want to check on that. Now that we have our deploy running, let's have a look at how we can wait after our deploy. So we do our deploy. But ideally, we want to make sure that all the pots are running, because the deploy only creates a deploy object, a deploy resource in Kubernetes. It doesn't mean that our pots are already running. So ideally, and that's why we are getting these deployment labels, these deployment labels are going to be pushed to our pots. So our pots will also have the labels app and hello world. So we can filter on those as long as those are unique to see whether our pots are running. So I'm going to make another function, deploy client and I'm also going to pass deployment labels, which is a map string string. So it's going to be the key is going to be the app and the value the hello world. Wait for pots. I'm going to call it. And I'm just going to copy this. This is going to be called wait for pots. We're going to return an error in case something goes wrong. And we're going to pass this, these labels, which is a map string string. And at the end, we're going to return now. What are we going to do? We're going to block our goal and program with an indefinite loop. And to make an indefinite loop, you just write for curly brackets. And then we will stay for having this for loop until we hit the break point. Once we hit break, then the loop is over. In this loop, we are going to list the pots. And only when they are all running, we're going to break. And otherwise, we're just going to keep our program running. Or we could say also that we are only running it for 10 checks. So after 10 times, we check it, whether the pots are running, we abort or we could just keep it running and you can abort with control C. So we are going to use client, client apps V1 again. Oh, it's not going to be apps V1 because it's not going to be a deployment or a demon set or replica set. It's going to be a pot and a pot is in a different function. It in, let's have a look what we have core V1. So you see you have auto scaling batch certificates. There's a lot of API endpoints that you see here that are typical incriminates. Core V1 contains the pots, your core V1 and then pots, pots accepted and namespace, namespace can be default. You can make a static variable of this. Or you could even pass it as a parameter instead of repeating it. And what are we going to do? We're going to list the pots, lists accept the context and meta V1 list options. And it returns a pot list, an error, pot list error. If there's an error, then pot list error. And now we can iterate over the pots. So we are looking for pots not running. So pots not running is zero. Then we're going to do a for loop for key value range pot list. Pot list is not an array. It's a struct. Pot list items is an array. So we're going to iterate over that and then we get a pot. We don't need the key. So we use underscore, which means that we don't need this variable. And it's a pot. Then we can say if pot what do we have here? Pot status. What is status? Pot status. Pot status face is not equal to running. So it's not equal to running. Then we say pot is not running. Plus plus. So plus one. And then if our pot's not running is zero, then we can do the break. But not running. Let's make this pot's running actually. And then if it's equal to pot's running, and we're going to increment this because we need at least one running otherwise, otherwise we're just going to exit. So if pot's running, and then we'll have also all pots. Or I can use the length of the items. Pot's running. If pot's running is greater than zero, then we have pot's running. But pot's running also needs to be equal to the length of the pot list item. So because all our pots needs to be running. And then we can break. What is not happening still is that we are not passing our label. So let's also pass our label labels. And we can pass our labels in the list options. We have a label selector. Selector to restrict the list of returned objects by their labels. Defaults to everything. Say label selector and going to be a string. And we can again use some helper functions if you want. So we already use this errors errors helper functions of the API machinery. And we have some more helper functions in there. We have the labels. And we just need to remove this API because it's not an API. It just PKG labels. And if we didn't have look at the labels and just comment this out. Labels. That doesn't seem to work. Labels. I'm just going to save. No. Sometimes it's a bit difficult. If you haven't imported package yet to get the visual studio code to auto complete. So this is clearly labels. But it doesn't want to complete. Labels. So if I command click on this, then I get the API machinery labels documentation. And then I can also have a look here. So there is a validate selector from set that I'm going to use. Validated selector from set returns a selector. Labels validate. Oh, and now I see what is going wrong. So if you use your labels as a map string string, you cannot use labels from the import. So I'm just neat. I just need to rename this one. Labels. Deployment labels. And now I will be able to use labels from my import validate. And then I can pass a labels set. And this label set is a map string string. So I can just pass this deployment labels in this validated selector from set. And it will reply a selector. I'll say parse labels or validated labels error. So if my labels are invalid, it will also return error. So it's a very nice helper function just to make sure that your labels are correct. And he then has a function to pass to a label selector, just a string function. And then it will be 100% correct what we pass to our list options. So now we do the wait for pods. We have a for loop. I'm just a little bit scared that we are going to breaking out of the for loop too quickly. Because not all pods might be created at the same time. So what we could do is we could also reply the replication amount. If we say deployment response spec replicas, then we could use also the amount of replicas that we expect in our wait for pods function just 100% sure. For our simple use case, I don't think we need it. We could also just do a time sleep here, which is not very nice. But we could also just wait a little bit before we are going to check whether this deployment is created. In general, when we are using mini cube, the pods are pretty quickly created. So for our use case, it shouldn't really be a problem. But if you want to be sure you want to check off all the edge cases, you will need to also make sure that the pods that are running is equals to the replication factor. So if we are checking, we can say waiting for pods to become ready, running D out of D, and we can say pods running, there's the ones that we found. And the total is the length of the pod list items. And that's just N. And then we go to a time sleep. Five seconds. And then we will just sleep five seconds and then go again in this indefinite loop until we break the loop. I think that's it. I would like to test this. Let's already spin up our mini cube cluster. And we just go over the code one more time. So we have the pods running zero. We do pods running plus plus if the face is running, only if the pods running is greater than zero and is equal to the total of the pods that are being returned, then we're going to break sounds. Okay. Let's test this. Go around and go. Uh huh. Waiting for pods to become ready. So you see, so that's why we need this zero because when we check the first time, there's no pods. And if you wouldn't check for this greater than zero, it will already have kicked us out. Okay, deploy finished. Did deploy with labels map hello world. So we could actually put this FMT higher so that it's also outputting one out of one. So we do a cube CTL get pods. We have one. Let's try to do this with three. Not sure if it's still going to work because when we change it, it's already it's already going to see it's already going to have a pod running. So that's why we need this deployment response. Spec replicas so that we can pause it as well. Expected pods is an int was a replicas and int into 32. De-reference it and then int. I'll just need to add change all returns and zero everywhere. I'll make it in 32 and make it here also in 32. Still have an error. That's it. And then we can add expected pods and we can pause it here. And then we add a parameter and we just need to compare and what's running equals to expected pods. Okay. And also because what's running is int. We need to change the type from interd to int. We could have also done that earlier and we didn't have to pause in 32. We could have done that here as well. Could have done that right here as well if you want that. Okay, let's try this now. Let's now start five pots waiting for pots to become ready. And now it works. Yeah. First we have three out of three and now we have five out of five. So this is how we can also have a function to wait for the pot. And then we can have another function after wait for pots where we do something with those pots. Maybe we want to update another system or you want to execute something in them or we want to verify something. It's always good to have a function to wait for a certain state. And this is where we wait for a certain state. And this is typically implemented with a for loop that is going to be running forever until we break the for loop. So that's it for this demo. We have created our first goal line program that can interact with our Kubernetes cluster. And your API calls are very, very similar. So once you know how to work with pots, you can use this client set to interact with all the other APIs as well. In this demo, I'm going to rework the Kubernetes demo. The deploy that we did in the previous demo. And I want instead of to supply this this ability ML, I want to have this ability on my GitHub. I will never make a change in my GitHub repository. I want to have it updated on my Kubernetes cluster. So I'm going to create a new GitHub repository at the webhook. And when this webhook is invoked, I want to have it deploy on the Kubernetes cluster. So what do I need to do for that? Let's have a look. I first don't need these. So I can remove those. And then the cat client, I'm going to change a little bit. Because I want to eventually run this in my cluster. So I'm going to say in cluster bool. And then if in cluster, then we are going to do something else than than not in cluster. So the config is going to be different if he are in the cluster because now we have rust in cluster config. And this returns also rust config just like this one. So I'm going to move this in my if right here. I'm going to declare the variables error and config right here. This is also accepts an error. So now we have to get client that is going to create a client set, depending whether we can pause in cluster or not. We are first going to say false because I want to test it first outside the cluster. Then when it works, I want to move it in the cluster. So we might not need everything here anymore. Let me clean up a little bit. And what we are going to do is we're going to create a server, an HTTP server that is going to be able to receive our web hooks from GitHub. So GitHub every time there's a push is going to send us a web hook. So we need to be able to capture those web hooks. GitHub is just going to send us a post request. We just need to capture that post requests. HTTP listen and listen and serve. This is how you can listen on a port in go. The address can just be 8080 so that it listens on port 8080. And we don't need a handler. We don't need this handler. We are just going to define one HTTP handler here. We are going to say that if we hit web hook, then we are going to invoke an HTTP function. So we need to write our server and our server will have an HTTP function. So let's create a file server.go. Still going to be package main, but you could perhaps move this to a separate package if you later want to make it a bigger program. Then we're going to have a function or a web hook. And I'm just going to copy this function signature from here. So I handle funk accept a function. This is the function that I'm going to write. And it's going to be our requests and our writer. That's HTTP. And then we're going to write a struct that is going to keep all the information that we need type is called server struct. And what information are we going to keep this client? That's the first thing we are going to keep. And now we can define it as is a new server. And our client is the client. And then we can pass the function. But if we're going to pass this function web hook, this is going to work. It's going to compile. But this web hook doesn't have access to our client. So if you make this function part of our struct, then we will be able to reach a client using s.client. Going to save this. And then we just need to do s.webhook because now the webhook is within the struct. Going to save this and have to print f tests. Just to see if it works. Will it work? No, it doesn't work because my Kubernetes cluster is not running. Let's maybe just comment this and this. And let's try again. Okay, seems to be working. Her local host 8080 page not found. Okay, and now with webhook it output tests. So this is working. All good so far. Can uncomment this. Our server is working. What do we need to do when we hit this webhook? We need to do the deploy on the Kubernetes cluster. Where do we get this deployment file from this Apple TML from Git. So the webhook will supply us some information, some information in the JSON from the push notification. And we can analyze that. There's a GitHub SDK for goal line. So let's have a look at that. GitHub goal line SDK. I typed this in Google. And we have to go GitHub. So we can do go get GitHub.com Google slash go GitHub V 45. And then how does it work? Can construct a new client? We can do authentication. So we can pass our access token in case our post raise private. Then we can supply this access token. And then we can use this client. And this client has several ways of then querying the GitHub API. And it's all described in the Godok reference. So for example, we want to download a file from our GitHub proposed from from our GitHub repository. So if I just look for download here, download artifact, download contents, artifact is probably something else. Download contents is download a file, we can pass an owner repo file path. So we can try to download our file using this function from the repository service in this GitHub SDK. So GitHub will send us a push. You just have to analyze this push. It's a JSON in this push request. It will tell us what files are updated and added. And then for every file that is updated and added, we can invoke this download contents. And then we just need to do the deploy on Kubernetes to send the YAML definition that we basically put on our Git repository. So this webhook, let's have a look how this works. There's a webhook section here. Go GitHub provides structs for almost all GitHub webhook events. How do they look like? We need a push request. So if I look in the list, there's a push. And there's an example payload. So we already see here commits an area of commit objects describing the pushed commits. And here's an example. So this is how it looks like. We will extract from the repository the name. And we have the full name. And we have repository owner the name. So we could use name plus repository name, for example. And then we just have to extract the files that are added, send it to our Kubernetes server. This is the event. And we can parse this event using this GitHub SDK. So we don't need the structs ourselves. We don't need to create these structs to translate JSON into a Golan. This is all done for us. So we could basically copy this code and make an event for a GitHub push and extract the parts that we need. So let's try that out. Let's first initialize the client. And we are going to support authentication. So let's copy this. So we could create another GitHub.go. We could just do it here. We have the get client and we do get GitHub client. What will it return? We don't know yet. It's going to return this new client from GitHub. So let's make sure that we do a go get.  We added this. Let's add this here. Go GitHub and call this GitHub. And GitHub new client. Let's save this. Now that I look at it, there's a slash GitHub that we need to add. It's going to be like this package GitHub. And then new client gives us a GitHub client so we can do a return of the GitHub client. And we can pass access token. And what if we don't have an access token? What if the GitHub repository is public? Then we pass nil. So we can actually say if access token is empty, then we are just going to return a client with no authentication. And otherwise. Add the context here. And otherwise then we are going to use a static token access token and the new client. So it uses this two package to make sure that every time we do a get or post request that this includes this token. So there's almost nothing we have to do ourselves. We just have to glue on this code together. Get GitHub client returns a new GitHub client. We need to store that again. I can say GitHub client and then here we can say get hit up get GitHub client. Also context. Context is a new context. And the second parameter is our token. I will just read a token from our environment. GitHub token. Does it all work? What does it reply? String. What does this reply? The GitHub client. Is there no error? Okay, there cannot be any error. So we don't need this error. Then we have GitHub client and then we're going to use the client is GitHub client. We need to add it here as well. The client is a GitHub client. And actually, maybe we don't even need this intermediate variables. We could also remove this. Say as is a new server and then still do as the client and as GitHub client. And this is the same. And then we need a little bit less of code, which is always beneficial. Okay, so far so good. GitHub, GitHub client. I'm going to save this. This is going to be imported. Still an error here. Not sure why. Probably have to. Okay, I need it to know to go get and then maybe my go month was not updated or something like that. So what do we need to do now? Let's have a look at this web hook code. Can just copy paste this to start with. What is our payload? Validate payload. And then we can just pass the request. HTTP requests. So if you pass the request. Then it will extract the payload itself. So this JSON, it knows how to extract it as long as we pass this request. And then we have the web hook at secret key. So we need to have a web hook secret key, which is going to be of type string. And we also need to get this from somewhere. So I'm also going to provide this web hook secret. As an environment variable. Or as get an web hook secret. And this is a secret that we can define when we created the web hook, just as a validation, whether it's our own web hook or it's someone else web hook, making these requests, so that if you don't have the secret that you cannot impersonate that you are GitHub. If error is not equal to nil, then what should we do then? We should probably send an error 500 back to the client. We can do that by doing right header, header, right, header, right 500, which is gives which it gives us a 500 error. But then we also would like to print the error on our screen or in our logs eventually. And then we say, validate payload error and error. And then we return. So they stopped the function.  First is a validate payload that we're going to pass the payload and then if it's passed it's going to be in event and that event we can again check to see what type of event it is. And these are types in go from this SDK. And we should have a push event type. And this is the event type that we are going to use. We probably want the default as well to just say that we don't have this kind of event event not found event not found and then the event type. What is this 500? I'm probably using the wrong function. All right. What is this? HTTP. Request. Oh, I see what I did. This is the response writer and this is the request. And this webhook secret key should be a bind. Bind. And then this writer is going to be right header 500. So now write the header which is off code 500. And that should all work. We save it. Is also going to be request. Save it. Looks like it all works. So the webhook comes in. We validate it. We parse the webhook and then what do we do with it? So now that we have access to this push event, we could extract some information and mainly we're going to have to extract the files. So this is going to contain the commits. So let me write a function for that. I'm going to pass this commits to a function. Files is get files commits. And then I will write this separately because this is going to take some space.  Commits is off. GitHub had commits. Get files. What do we return? A string slides. We will need to. Loop over these commits. This is one commit range commit. So let's loop over this. And then let's have a look in this commit. What is in this commit? We have removed. We have added. And we also have modified. So let's say that we have. All files is a string slice. Now we want to append to it. But this added is also a slice. So we basically want to merge to slice, which we can also do it up and. We just need to add three dots after our slice. And then these two slices will be merged and we will be put in all files. We can do the same for the modified files. But what if a file then is duplicated? Let's try to remove all the duplicates as well. How to do that easily? We can put all the files in a map and then put the map back into a slice. So we're going to have all unique files. He's a map string bull. And then we're going to go iterate over these all files. File. File name. Range all files. And then what we are just going to say all unique files. Is file name. And it's true. All unique files. File name true. And we still need to convert it back into a slice. And now we can say for. File name. Range all unique files. And we can say all unique files slice is not a slice. I'm probably doing it in too many steps because I could have put it in. I could have put it in. A map already here. But yeah, now we're already started. What are we going to do then? Append this. File name here. And I'm going to return. All unique files slice. When I write something like this, I always want to test it. So let's write a server test go and quickly write us a test. Test get files. T. That's the team. And how do we test this? We get the files. Files equals get files. And then we have to supply GitHub. Had to commit. Save it first. So the GitHub is important. And the had commit is an array. So we need an extra set of curly brackets. And then we added a file. Added string. Test dot txt. And then we have no modified files. If files if lamb files is not equals to one, then you're going to say log fatal F. No, not log t fatal F. Expected. This one needs to be modified. Expected only one file. And I have more than one file. Let's try to test this. Files declared but not used. Oh, it will be here. Files declared and not used. And then commit is not found because it needs to be event commits. And let's just print this out. Founds files. Yes. And I'm going to join them together. Files and a comma. Okay, that should work. Run test. What does this do? Okay, it passes. And then back to the server. Now we have the files. What do we need to do now? Now we need to download the contents of the file. We need to download the contents for every file from GitHub. So another loop. File name, range files. And then we have this GitHub client. Repositories. And what was it? It's only with download download contents and download contents. Gives us a read closer. The response and error downloaded file. I don't think we need a response. And the error. If the error is not nil, then again, we'll do this. Download contents error. What goes in it? The context. We have context. No, we don't have a context. We only need one. So I will say context background. Context background. And then, oh, my MacBook seems to not be able to follow for a second. It's because I'm making it a bit messy here. Context owner repo file path and options. Contents. And what was it? Repo repo is I can find this in this event. Event repo. And then first I need the owner name is going to be the GitHub username and event repo name. And then the file path is file name. And then GitHub repository contents cat options, which is probably going to be empty. And I think that these are pointer strings. So I want to make sure that we pass them. We pass actual value. To them. Save this. And now I have this downloaded file is of IO read closer. So I need to make sure that I also close it. Close, but with defer. And then we can download it just like we did with our HTTP streams. We can say IO read all downloaded file. And this is the body file body error. Again, error handling. Read all. Now that we have the file body. We can do to deploy. Deploy. We need a context. So let me just use the same context again. Context is context background. And context background. And then the context here. The client. We could also move this deploy into this server struct. And then we wouldn't have to supply the client, but it doesn't really matter here. And then we get back the map. The pots. And whether there was an error. I don't think we need those first two. But we need to pass this file body somehow. So I'm going to pass this file body, which is of binds. And I'm going to add a parameter app file. And I don't need to read app file because this also reads everything in binds. Going to save this. This works. This works. Save this deploy error. So what else do we need? So we are first going to test this outside our cluster. And it's going to then do what? Start a server. Start a server here. It will initialize my Git of clients and my Git client. It will then wait for the web hook. If there's a web hook, it will validate it. Pass it. We'll get some information out of it like the owner of the repo, the name of the repo, the files. And then for every unique file we have, we'll do a deploy. And then we can just say deploy of file name finished. And then file name is the file name is the file name. All right. So let's try to get this operational. We need a web hook secret. We're going to enter the environment variables here. If you are on windows, I think what is easiest is to add a configuration go launch package and then you have the end of here. And that you can use. So here you can then say web hook secret and so on. And then you do run run without debugging and then the environment variables are injected. I'm not going to do that. I'm just going to put them on screen here. Web hook secret, my secret, I can choose it myself. So I'm just going to keep it simple and the GitHub token. So we still need to create a GitHub token. So let me create a GitHub token for my own GitHub account. So this is my account. And on the right top, I'll go to settings and then there's developer settings here at the left bottom. And then I'm going to create a new personal access token. You can click generate new token. You'll have to enter your password. We are doing a Go demo only to be valid for seven days. And then we will need to have repo control that should allow us to access the repositories and then download the data. Generate token, this is a token. I'll copy it and then I can enter it here, the GitHub token. And I'm going to do go run.go, which will not work unless I'm going to start my mini cube cluster. But how can GitHub access my server if it's running on my computer? Well, either you have a public IP address, which I don't have because this is just a residential connection. I mean, I have a public IP address, but this port 80 is not going to be accessible on this public IP address. Another one do some complicated port forwarding. So the easiest way to have the forwarding set up is to use something called Ngrok. It's another piece of software that you can download. So if you go to ngrok.com, serve web apps with one comment. Sign up for free. So this you can download, brew, install Ngrok or with Windows zip file, or you can install first chocolate, the package manager and then install Ngrok. Or there's also a docker. So it's the fastest way to put anything on the internet. So you install this and then when you have it installed, you will have to run Ngrok. So I already have it set up. So I'm just going to type Ngrok and then it will give me the help. Ngrok. So Ngrok will tunnel local ports to public URLs. So if I do Ngrok HTTP 80, it will make 80 public, but I'm going to write on 8080. So I will do Ngrok HTTP 80 80 and the clock. So I'm online. My session will expire in two hours. There's terms of service that you can read. And I should be able to access my server at this URL. So let me do another bash here. Curl of this URL. It's not working because I get like a whole page that is not working because my server is not running. OK, we need to go run just a dot because we also have server test. Allow. And now I can go to my bash screen and I get a not found. Which is also not only showing here, but this window is too small. I think you see foreign foreign not found. So this was the first one that it was not running. And now I get a force here for not found. So this is working. So now I just need to use this URL slash web hook and set up the web hooks in GitHub. So I'm going to go to my repository that I created. I created a separate repository for this. The go deploy Kubernetes GitHub and this one is private so that we can immediately also test this token.  So what books. And web hook slash web hook. So this URL comes from and Grog the secret, which I supply as an environment variable. My secret enable as a verification should work because and Grog is doing the SSL for us, which events you would you like to trigger this workbook, just a push event. So if there's a push, then it should trigger this web hook and web hook. Now the web hook is active should have this check mark next to it. And I can add a file. Add file, create new file. And I'm going to use this app.yaml that I used in the previous lecture. So this one is the Kubernetes deploy app.yaml. Just going to copy paste this one. This is the one I want to deploy. I'll first give it a name. And let me just check that my Kubernetes cluster is empty, that there is no deployment running. Cube CDL get deploying. There is no deployment running. I'm not cheating. Going to test this web hook. And I'm not sure even if it's going to work. I hope it will work. Oh, there is already something that happened here. Event not found. Oh yeah, so when you create a web hook, it creates an event. So maybe it would be good to also capture this. I didn't really think about that one. Case github hook. Oh yeah, there's a hook. And this is when the hook is created. So then we can just say, hook is created. And then it will not say event not found. Hook is created. And you would have to restart your server actually for this to work. And then you would have to recreate the web hook if you want to have this nice output. But just pushing the file now should trigger the web hook as well. Google deploy Kubernetes github. So this is where I configured the web hook. This is a special repo that is empty. Best to keep it separate because otherwise you will just randomly push files to your Kubernetes cluster. You could add a filter on YAML and all these things if you want some more complexity. And yeah, this should be it. Commit. Let's try. And let's have a look. Found files, Apple TML. It's an extra return here. Deployer of Apple TML finished. Wow, and we have now the 200 OK here. Let's have a look. Oops, I'm opening too many windows. Oh, and the deploy is there. No, it's not. No, it's POTS. And the POT is also there. That's good news. Let's try to update our... Let's try to update our file and see that if we do an update that it also takes into account the update. Three replicas. Commit. Found files, Apple TML. Deployed Apple TML finished. So now we have the second webhook. And keep CTL. Get POTS. We have three POTS. So I kind of wrote a whole CI CD here in just a few lines because you also have CI CD systems that kind of take your Git repository and then ingest these files and put them in Kubernetes. That's kind of what we did now. So we rewrote some of these systems that do that for you in Kubernetes. So this is our working version. But then in the next lecture I'm going to have to move this within the cluster because it's still running outside our cluster with this ngrok. If you then put it in the cluster then you can make it a state service itself that runs within your Kubernetes cluster and monitors your Git repository, your GitHub repository to see if there are any files that needs to deploy. Now that we successfully tested our GitHub deploy locally I'm going to run it within a deployment on my Kubernetes cluster. So here we have another look to our main.yml This Get Client had the parameter files because we wanted to be not within the cluster. So I'm going to change it to true and now it will expect us to be within the cluster. So this code will be executed instead of the kube config. Another approach could be instead of this flag to check whether this file exists and if this file exists then we are outside the cluster if it doesn't exist, then we are probably in the cluster and then you should load and it would also work. So I'm just going to keep it simple now so Get Client true. We are within the cluster gorun.go or gorun. We'll hopefully say something unable to load in cluster configuration, Kubernetes service host Kubernetes service port is not defined so these are the environment variables that it looks for and these are defined within the pod to then contact the Kubernetes service. It will contact the Kubernetes service and if you contact the Kubernetes service it's just the REST API and this REST API if you just contact it like that then you don't have any permissions to create pods or do anything so we will still need to give our pod permissions to create deployment and we can do that using a service account. So let's have a look at this deploy.yaml this yaml definition that I created to have our github deploy application the application that we wrote in the previous lecture to have deployed on a Kubernetes cluster. So if we start from the top we also have deployment I call it github deploy so we have the label app is github deploy one replica for now and we have a service account name so I will create a service account called github deploy and I will attach the permissions to it we have a container the name is github deploy and the image is wordfiana github deploy so if you want to deploy your own github deploy you will need a docker container repository you could use docker hub that's what I'm using and then this is your login of docker hub and then this is the repository so create this first this docker hub account and then you can build and push it to your docker hub account to have it available I'm going to show you in a second how we're going to build this docker container but you will need your own docker hub account to be able to push this image the port I give the name it's called htp port you don't really have to give it a name but I gave it a name and the container port is 8080 because we run on 8080 I specify two environment variables because we don't really want to have it in our code that we're going to push to github so I set github token comes from a secret so we'll still have to create this secret the secret name is going to be github deploy and the key github token and here the key will be webhook secret so what Kubernetes is going to do is it's going to have a look in the secret and inject these values in our pots so this is basically our deployment we would still need some other resources to get this working so we have another resource a service also called github deploy we say it needs to be on port 8080 and this is going to be how we're going to contact our application our github deploy outside our cluster because it is of type load balancer if you deploy this on AWS or Google Cloud with their Kubernetes offerings then this load balancer will in case of AWS creating a load balancer and then you will have a public hostname that you can then configure as a webhook I'm going to use minicube because that's the easiest way to run a development environment and you can still use load balancer in minicube you just have to enter a command to tunnel this you can enter minicube tunnel which I also will show you that will then create a link between your local machine and this service and then you can still use ngrok to expose it then we have the service account github deploy and then we have a role so this role is necessary to give us the correct permissions so we have this api group apps if you scroll down to our deploy you see client apps v1 this is actually apps api group and then the resource is deployments these are deployments here so this follows nicely the same structure and then the verbs that we are going to use get list watch create update patch delete it's a bit too much for what we need but then we are sure here we use create update and also in this wait for pots we do a list but as an on the pots which I didn't give permissions for because we are not using it here but you could add it as well in that case the api group is empty and then we have the role binding so the role binding is going to link this role with this service account it's also called github deploy so it says the subject is the service account and the role is the github deploy so that's it so we have the role binding to link those two service account and the role we have a service and then we have the deployment that also contains this service account name with the service account and then we have the secrets so what do we still need to do we need to push our container and we need to create our secrets let's try to start with this container I have created a docker file here so this is a pretty generic docker file that is going to package our go lang program into a docker image so you need to have docker installed to be able to build this there's docker available for windows mac and linux so you can just download and install it if you don't have it yet what are we going to do we're going to take the official go lang image we're going to download it we're going to use this as the go builder we're going to create a new directory and cd into this directory it's the github deploy directory we are going to use this is just to compile our program so to compile our program we need to copy everything from our current working directory everything that is right here we're going to install our container and we also have a docker ignore file and it's going to ignore the yaml definition and git so we don't have to upload the git into our docker container then we're going to install curl and git because we need it for the go gets to work and then we're going to build it because we are on alpine which is a slim down version it's still linux but we need to disable cgo so we just have a few flags here so that it works on alpine linux because this is the 118th alpine if you don't want to use alpine you can just remove this this alpine but then you wouldn't be able to use apk add because this apk add is specific to alpine if you remove this alpine and you have a normal linux distribution i think it's a debian based so then you would have to use upcat so we here we build it we build our golang into a single binary using this line go build and our output will be github deploy so in the github deploy working directory we have file github deploy which is our binary then we're going to start another container just an alpine container so it's very minimal there's nothing in it and we're going to add cr certificates bash and curl so these things are very handy for debugging and also cr certificates is necessary if you want to make certain calls and verify tls certificates then we're going to say copy from the go builder this github deploy executable into slash github deploy so we will just have one image with one file extra this file which is our executable and then when we start our container we'll execute this executable github deploy let's try to build this docker build i'm going to give it a tag and this tag is necessary to push it to my docker hub account let's have a look this needs to be the same docker build t warthiana github deploy and what do i want to build i want to build my current directory so i'm just going to put the dot now it's building and the build is finished so you see all the steps here my steps are cached because this is not the first time that i run it i don't want to use a cache no cache which you don't need but i just want to show you the full output now you see downloading dependencies and installing everything and now the build will also take a little bit longer than the cached build and then if i would do a docker build again without this flag then it will go quicker again because now i already built it the next step is to push it to our docker hub this is docker hub docker.com and then if you don't have an account yet you can create a new account here or you can sign in to sign in with your own account my account is hub docker.com slash u slash warthiana and here you can see i pushed this github deploy i'll push it again just to make sure that i have the latest version and then we can start using it so how do i push this image to run docker login docker login will ask you the credentials for docker hub so that you can push your docker image to your own account so i do docker push and then the name of my image it will take this docker image that i just built and send it to github and now within our Kubernetes cluster we can use it so this will take a few seconds to get pushed the next step is to create these secrets to create these secrets you can use a kubectl command i rather use this github create secrets command than having them hardcoded in here because this deployed the demo i'm then going to push to github so that way we can enter a command that is not visible in gith and then have still our definition in gith so this is finished let's try to start our minicube cluster and then create this secret i need to start this minicube cluster always again because what i do is i do a minicube delete to then start from a clean minicube cluster so my minicube is running i'm going to create this secret with kubectl create secret it's a generic secret so if you have a look at the help then you can create a docker registry a generic or tls so i'm going to create a generic one and then i'll make sure that this command is also in a readme in my github for this demo i need to add these flags from literal and then key equals value so the webhook secret is my secret and the github token is this token that i just created again and then you also need to add a name so kubectl create secret generic and then the name is the github deploy because we refer here to the name and then it created a secret kubectl get secret if you then have a look at this secret then you see the actual input the data key value and the value is base64 encoded webhook secret and then again the value so this should work let's try to deploy this kubectl apply of this deployable this creates deployment the service account the role and the role binding one deploy one pot and one service how do we then access this service from our host system because this is of type loadblender we can use minicube tunnel and minicube tunnel will create a tunnel between my host system and the service that is of type loadblender so if you are using minicube these are the same steps if you are on a real cluster then using this service loadblender will spin up a cluster that also gives you a host name that should be accessible starting tunnel for github deploy we need to keep this window open so I'm just going to open another bash window curl localhost 8080 page not found so this is our service within our Kubernetes so let's now run ngrok again ngrok.hp8080 and now ngrok instead of going to this local instance it will go to our minicube tunnel and our minicube tunnel will go to the service service will go to the pot and in the end we get to our real application this URL changed so I need to copy paste this again as a webhook go deploy Kubernetes github settings I can delete my old webhook actually I didn't have to delete it I could have just updated it actually but it doesn't matter webhook secret is my secret just send a push event and then add the webhook and now I don't get the correct now I don't get the correct oh it just took some time so I did a post and yeah now the last delivery was successful let's have a look in the logs to be sure I'm going to open another window oh look we did our post gave us a 500 kubectl log get pots let's have a look in the logs there's nothing in the logs but the delivery was correct so let's continue and see what happens when we do the webhook there's still a possibility that I didn't that I didn't properly use this github hook or that we get a validated payload error because there is also backslash and missing here so let's just continue for now let's see how it goes code m.dml so if we trigger here a change then it will trigger our webhook so I made a change let's go and have a look in our webhook webhook you can click on it, recent deliveries ok so we have two and we can see the event you see so we have the repository the owner and so we have this commits array in here here this commits where we have the modified apple.tml and we can then send this apple.tml to our Kubernetes cluster so let's have a look whether this worked or not kubectl get pots and yes I have my pot running kubectl get deploy and I have my deploy running there's just one pot let's try to run three of them then I just need to make one change in my code m.dml three replicas commit and now we have three and three pots let's now have a look at the logs kubectl get logs of this oh we still get this event not found that's why we had this 500 error GitHub hook was the event not found maybe there's still something wrong here most likely the type is still of a different version so that's why it's not working and also it was not displaying it immediately because I also don't have a backtrash in here these logs might not have shown if there was no new align but then when the webhook triggered we found the files and then we did the deploy so this is the most important here we definitely want to capture this first event even if we don't capture this first event nothing is really going to happen I will try to update the correct code in my github repository so that you also have the correct code there it's probably going to be something small so now we have this working so we have these logs here the deploy the webhook works we have ngrok that we have two times the states okay and we have the minicube tunnel that we set up for github deploy and we have our application running within Kubernetes and you can also go and have a look inside this pod by using kubectl-exec and then because we installed bash you can also enter bash and then we can have a look what is running here so we have this github deploy running and that's really our only binary that we have put in this image and the reason why it can contact our Kubernetes server is because we have somewhere these Kubernetes service defined and then if we curl to this IP address using htps then you can see that we are actually well just clear the screen for a second we can actually contact this API server and you see if we don't use any user then we have anonymous and then we get forbidden and that's why we needed this service account and this service account is then somewhere mounted within our pod run secret Kubernetes IO service account that's probably going to be it and then here we have the CA certificate and also we're going to have something to authenticate a token exactly or another certificate so that we can assume this service account we can connect to this API and then we have the role to do this get list watch create update patch or delete and that's what this go SDK does for us so this get client here this rest in cluster config is going to take this environment variable and see if we can contact the Kubernetes cluster using this secret of our service account that's why we don't have to do all these steps ourselves so that's it for this lecture if you're deploying this in a real cluster you're going to have a real host name that you can then use and that you can expose your application and then you don't have to use ngrok so the only downside here with Minicube is that you have to use all of steps to get to your actual pod but if you would use a real production cluster you don't need all these steps this was quite a long demo I hope when you try this demo yourself it also works for you if not reach out to me in the Q&A or send me a message in this lecture I'm going to show you how to create SSH keys you can and probably already created SSH keys using either puttygen in Windows or in Linux or Mac but you can actually have Goalign also create your SSH keys for you and that's what I'm going to show you in this lecture I'm going to create it as a package so that we later on can use this package in other lectures because some cloud APIs like AWS allow you to create a key with their SDK or their API calls but that is not the case with every SDK so you might want to know how to create those SSH keys in Goalign itself I already wrote my main function so we don't have to go over that anymore what do I do here I have a private key, public key of a type byte and I'm going to call that generate keys and the generate keys function is going to return a private key and public key and then I'm just going to write the mykey.pam in my current directory so that I have those keys ready to later on do something with it I'm using this package which is also defined in my Goal mod so whatever I put in my directory here will then be within the package so it's a very simple layout I just have my current directory that will contain my Go files so here I have package SSH, generate keys and it will then return this private key and public key so here I have SSH which is this package which I initialized right here so I did a Go mod in it of my current directory so this current directory here is the package and this is called package SSH I don't have this cmd keygen where it may not go in but this is just as a test to have an executable if you only wanted a package then you could even remove this cmd just have keygen, just have this function generate keys and then you could import this from any external package and just use it the way you would like so generate keys and then I'm going to write it to my local files to make this work I still need to write this generate keys so what is an SSH key this SSH key that we want to create is of format RSA we actually have an RSA package in Golang that can generate this key for us so from crypto RSA we have this generate key we need to supply a random source and we need to say how long it needs to be and then it will reply this private key so this is fairly easy I can just say run reader and then it will import from crypto the run reader so make sure that you are using crypto run and not any other run package because the crypto run is actually the safe package to cryptographically secure generate a random number you can choose how long it is I'm going to pick 4096 which should be enough currently to have a decent key size this returns an RSA private key an error so our private key error is RSA generate key if there is an error we can return an error generate key error and then we have this private key but this private key is of RSA private key we need a PEM format key to be able to load it using the SSH command or to import it as an open SSH key in Putty so with Putty you would have to use PuttyGun to import an open SSH format key here we will be able to use the SSH command because the SSH command supports open SSH format it supports this PEM format with Putty you would need an extra step private key so this is just the bind but we still need to convert into PEM format so there is also a PEM package in Golang so we can say private key PEM equals and then we have PEM and here we can generate a PEM block and then it will import the encoding PEM package this block is a struct which has a type headers and bytes so the type is going to be RSA private key we use curly bracket because it's not a function it's a struct type is going to be RSA private key and then we have the bytes the headers are optional so we have the bytes the decoded bytes of the contents typically a DER encoded ASN1 structure so what does that mean that we still need to convert our private key which is just bytes into a format that PEM would accept so we have the bytes and to convert this to the format that is acceptable for PEM we can use the x509 package and this x509 package has a Marshall pkscs1 private key converts an RSA private key to the pkcs format so this is the ASN1 DER format and this is the kind of key commonly encoded in PEM blocks of type RSA private key so to know all these things you can read the documentation or you can just find some examples on the internet and read what all these functions do so we pass the RSA private key here and it will return bytes so now we have a PEM block that we can already return so this is private key PEM block and if you would do private key PEM.bytes types or header then it would just only return one of these parts the bytes or the types or the header that is empty so we need another function to return this to parse this whole PEM block and then return it as bytes and this function is called the PEM encode to memory and this accepts a PEM block and returns then bytes and we apply our private key and then we still need to get a public key and the public key needs to be in a format that open SSH can work with so we are going to use a SSH package for that the SSH package is not included by default so we need to import it using goget goget will download it and then we can add it here colang.org the crypto SSH package and we should be able to use the SSH dot and we need a public function new public key new public key takes an rsa public key which we have in the private key so we have private key and in the private key we have a public key because when you generate a new key it generates a private and a public key it's a pointer so let's pass it as a reference and then we get back an SSH public key that we can then use public key and the error and then if there is an error we return the error new public key error and public key is of type SSH public key so again we need to parse this public key into a format that we can use like the authorized keys format if you are familiar with that so what we are going to do is SSH package has a martial authorized key and this accept the public key save this so these are the lines to generate a key create this pmbloc this is how the pmbloc looks like but it will have the rsa private key here in the top and then from this private key that also is associated with the public key will do the new public key from the public key which will then give us the authorized key format so let me just show you how that looks like we save this go run cmd key gen may not go going to run the generate keys and then we are going to write my key my key pup so now I should see those two appearing the my key.pump is the begin rsa private key then you have the key here and end rsa private key the public key that is associated with it starts with SSH rsa and then is the public key is the public SSH key for this private key so now we can use this format for our authorized keys when we need to work with SSH so this pump format when you have this and your windows you need putty gen to convert this to a putty private key I actually have putty gen here so this is putty gen for Mac on windows you have UI but what you can do here is then convert a private key to the putty private key format so putty gen and then my key.pump which is the input the output is a putty private key so it's minus capital O private and then you need to specify the output file my key.ppk and this would then convert my open SSH format my key.pump from the open SSH format or the they call it open SSH format in putty I think but it's just the rsa private key the pump format into the putty format and this is the putty user key file that you can then use in putty in the next few lectures I'm going to show you how to use SSH in go so in the previous lecture we just create the SSH keys which was fairly simple you just output the private key and the public key now I want to show you how the interaction between the client and the server works to fully comprehend how SSH works it is actually better to first write the server and then the client to see all the complexities SSH itself is quite complex and even with the library there are a lot of things to show you that you have to take into account even when writing the client how does the connection work between our client server we are going to do authentication using a server key the mykey.pump and our server will then have the mykey.pump the public key of our private key which is also called the address keys if you have a server running with SSHD then you will for sure came across this so this is the authentication part I have the private key and the server has the public key to verify that I have the private key and then we can start communicating using the SSH v2 protocol so it is not going to use those keys anymore it is going to create new keys to do the actual encryption the keys that we are going to use is only for authentication and also on the client side is also going to verify whether the server is really the server that we expect if we have the server.pump the public key of the server and this is typically the known host if you are using putty or the SSH client the first time you connect you will be prompted whether you trust this server which is basically asking whether you want to save this public key locally and from that point on you have this public key and if then the server would change its private certificate or it would be another server then you would know so if you are going to write our client in SSH in Go then we can already supply this server.pump because we know this upfront which is also the most secure way to verify whether the server is a really server it claims so this is the authentication in nutshell I am first going to start to build this SSH server and then we will have a look at the client so in the meantime that we don't have the Go client you will have to use an SSH client I am just going to use SSH in macOS which is open SSH which you can also download on Windows if you don't have it so try it out SSH command if not you can download open SSH for Windows as well you can also use Putty but for Putty as I explained in the previous lecture you would have to convert the private key to be able to login to the server this demo of this SSH server brings together lots of different concepts like how to do networking and how to work with asynchronous calls so it is actually quite an interesting demo that brings together a lot of things I already wrote my main function the server main.go and my main function will read the mykey.pub which is necessary to allow the client certificate and the server.pum which is a server certificate that we still need to create so let me start by creating these certificates so if I do go run cmd keygen keygen.go generate the mykey.pum and mykey.pub so this one I don't need anymore and this I'm going to rename into the server.pum and then always you need to rename the public key as well because the public key and the private key need to match they go together and that's going to be our server.pum and I'm just going to run this keygen again and then we're going to have our mykey.pum and pub which is going to be used by the client eventually and then I'm going to include this package the SSH package in the SSH package we have the start server and the start server is defined in the server.go going to pass this private key and the authorized keys so the authorized keys is basically one or more public keys to give access to in our SSH server so how do we start writing this SSH server well again there's an SSH package so let's have a look at this SSH package so this is the SSH package and there are a lot of functions if you think about on a high level what we need to do is we need to pass our public key or our public keys we need to pass our private key and then we need to build a config and then we need to start a server so here you also have the client implementation but we are really looking for the server implementation so the best way to figure out I find is to have a look at the examples here you have a lot of examples we have the client example dial example which is also going to be for the client and then we have that new server connection so this is going to be accepting new connections so this is what we are going to need and here we already have quite an evolved example with different examples you have a password callback if you want to do password authentication and then you have a public key callback and then then it opens a port and then you have this new server connection this SSH new server connection and then you have the code to handle your request so let's try to write this from scratch and we start by parsing our authorized keys so let's take this bit first because this example we can already use we need to import our authorized keys and this code block will make sure that we can do that so we have the authorized keys which is a byte and then authorized keys map so we are going to store it in a map and these are authorized keys so we are just going to replace this authorized keys we are not going to do a fatal so we are going to change this into a return of an error for authorized keys error and then the way this works is let me try to save this first I just need to add the error here so we are going to declare a map and the key is going to be our authorized key and the bool just that we have it so we are going to put everything to true that way we also don't have duplicates so as long as there is data as long as there is bytes in authorized keys then we are going to parse the authorized key using these authorized keys and this is going to be our public key that we extract and in the rest there might be more public keys we only passed one but we could pass more so this example actually already takes into account that you could have more and as long because this is a for loop as long that you have more keys in there then we are going to parse more keys and then put these keys in our authorized map so you see here this is our public key this is the rest of what is left over we put a string of this public key in our map we assign it through to it and then we say what is left over we put in our authorized keys so if you are only using one key it will do one iteration then rest will be empty we assign empty to authorized keys and then we are finished then we can already start building our SSH config so this is the SSH config the SSH server config that we are going to use and let's copy paste this and let's have look what we need to change here so we don't want password authentication we rather just want to use keys so we can remove this and it actually says in the comments remove this to disable password auth and we are just going to use public key authentication so the public key callback is a function and this is the function that is going to validate whether our public key is correct and we have this address keys map that contains all our public keys so we are going to return this SSH permission and then we can add this extension if you want so record public key use for authentication so then later on you could perhaps use it so then we return this SSH permission if our authentication is granted because if this key exists in this map then it's granted and otherwise we are going to say it's an unknown public key so this is our authentication part we have this SSH package taking care of all these things and now we would like to start our server and to start our server the server itself to create the networking socket is not implemented in this package which is actually quite smart because then they don't have to take care of that it actually uses the implementation of the networking in Goalang itself so it's going to use a net package and that you see here so here we have this net listen and this will open port 2022 or you can give it another port and then we can accept connections we still need to parse our private key as well, we don't need to read it anymore so we don't need this code but we need the parse private key so parse private key we're going to add this private key to our config we're going to start the server and then if there's a new connection then we're going to accept it and then once it is accepted we still need to do something with this connection we need to make sure that the SSH package is going to send the correct data and respond to the correct data using the SSH protocol so that's why we need this new server connection and this new server connection uses this listener that accepts connections and also needs this config and then it gives us a lot of variables that we can then work with to communicate with our clients so let's copy all this paste this and then we need to maybe change a few lock fatal apps let's start from the top, parse private key private key so now we have a SSH signer type that we need then we add the host key which accepts the SSH signer now we load the server.pam the private key in our config and then once the server connection has been configured the connection can be accepted NAT needs to be imported I'm going to save this now, NAT is imported you can put it on a different port if you like and then we'll say parse private key error listen error and then we'll also say accept error listener listener accept error and then this is the most important function this new server connection new server connection starts a new SSH server with C as underlying transport so it does the handshake and if the handshake is unsuccessful it will close and return an error the request and new channels must be serviced or the connection will hang so that will do afterwards let's just see if he would be able to login so I'm just going to remove these variables for now and see if he can start a server failed to handshake or I'll just call it new server con error and then we're going to print something if it's successful ok go run server.go so now our server is running and then like I said you can download SSH for windows if you don't have the SSH command or you can use putty localhost and then you need to pass the p flag before or after localhost to indicate what port you want to connect to and then you might actually get this error remote host identification has changed so if you already have for localhost on port 2022 you already have a public key in your system then you might want to remove that if you ever connected to localhost on this port it will already have a private key and then it will say ok you already have a public key in your system and this is going to be in the known host files because it tries to connect and it wants to check the public key of the server and the public key of the server is different what we already have in a known host and if it's different it's just going to say that there's a problem so you want to either remove this port so I'm going to remove this so I now removed the last line of this file and then I'm going to try again connection refused because what happened our server stopped working because we are not really in a loop here we are just only accepting one single connection and if that connection fails or that connection is in use we are not doing anything anymore so we will need to add a for loop here to make to make sure it keeps on running so maybe let's do that first so we listen to this port this only needs to happen once but then we want to keep on accepting we want to keep on accepting clients so we are just going to make a for loop of it and then we also need to make sure that we don't exit just like that so our returns might actually just need to be printf printf here if we have an error our server will just print it instead of exiting and then the server can just run forever basically until we hit ctrl c or something and then we don't need this return anymore so this will run forever until the process gets skilled or we hit ctrl c or something let's try it again go run and what is happening now is that it's not iterating of this loop constantly this is actually blocking so as long as we don't have anyone connecting to our server we are just stuck on this line just waiting waiting until someone connects if someone connects then we will do the next lines so now we cannot establish the authenticity of this host so we are going to say yes we would like to do that and then we will not get this warning anymore because then the public key will be saved within our known host ok what happened here we didn't supply private key yet so our server crashed somewhere oh yeah it crashed here because we don't have these extensions pubkey because we are not logged in because these extensions is only set when we are logged in so let's try to check for this if permissions is not equal to nil then we can output this I guess let's try it out let's try to connect again that didn't work either because our connection is probably nil so if connection is not equal to nil and connection permissions is not equal to nil then we are going to output it ok that's much better new server connection SSH no auth pass yet unknown public key for Edward Viana which is my login here so this SSH client uses Edward Viana by default as my login unknown public key for Edward Viana let's try to pass this key the mykey.pam which is our private key and then we are logged in logged in with key sha256 and then this fingerprint so we cannot do anything it is even blocked even Ctrl C doesn't work anymore so you would have to probably click this trash to get rid of it if you would like to close it now so it's completely blocked because we are not doing anything yet we need to handle this connection we need to do still with something with this connection we would like to probably give the user a terminal to then type something so let's close this the server and now the client is also closed and let's try to see if we can do something within the connection there is still some code that we can use from the example so we need to define here the channel, the requests and then we can use those variables the incoming request channel must be serviced so that was already something that this new server connection was saying that the once a client is connected then we need to service this connection and that we do with go which means it will run in the background SSH discard request rex so let's add that and then service the incoming channel and this is where we get into the protocol details channels have a type depending on the application level protocol intended in the case of a shell the type is session and the server shell may be used to present a simple terminal interface so we are indeed going to present a simple terminal interface to our SSH client because that's what our SSH client will ask for so let's copy paste this and let's start with that so I copy pasted this and I need an extra curly bracket here at the end need to Chen Chen and rex needs to be defined again so let's try to identify what is really happening here so the new server connection replies us these new channel and new request and they are in goal line of type channel and when it is defined like this channel with a less than and a dash then it's a read only channel so this is a read only channel that we are getting back and this is not a read only channel so we cannot write to this channel but we can listen to this channel so what's going to happen here the incoming request channel must be serviced so that is the package that asks us that whenever we do a new service connection then we need it needs to be serviced so this servicing we do with go SSH this card requests and this card requests in the background will consume and reject all requests from the past in channel I don't know myself exactly all the details why this is necessary but it's in the package documentation so we're just going to use what they tell us to use and this card requests will listen to what is happening on this request channel and then it's going to do what it needs to do to make the protocol work and it will do that in the background what are we going to do we are going to listen on the incoming channel so in SSH you have a concept channel and in goal line you have a concept channel so that's why it's a bit confusing this channel is of new channel so for every new channel in SSH you're going to listen to this go channel so this go channel basically means that we are going to wait here you're going to listen to this channel in go and if there's a new variable in it the variable of type SSH new channel we're going to go into this loop and then we're going to have this new channel variable so for every new channel that is created in this SSH connection we're going to have an iteration in this loop then channels have a type and if the channel type is not a session then we are just going to reject it because we are only going to implement these session types and when we connect with the RSSH client we only need this session channel type to respond to because the session channel type that's what's going to open a terminal for us or we're going to implement a terminal that is going to open when the client is asking for this new channel of type channel type so if it's of type channel we're going to accept it here we rejected it if it's not session then we reject it otherwise we're going to accept it and once we accept it then we have the channel the requests and the errors so here again I'm going to just print this if we cannot accept the channel and what is then going to happen we then can respond to requests so within a session channel we can then respond to requests so we can have a shell request or you can have a pty request or you can have an nth request and then in the example it's only going to handle shell requests and we are going to change it a little bit so this runs in the background because this function has a keyword go so it's going to run in the background and then it's going to go again to the for loop and then here it's going to wait for if there's a different channel so if there's only one channel we're only going to iterate it once and then it will just be kind of stuck here but if it's stuck here then we will never be able to accept another connection while we are dealing with our current connection so ideally we would run this also in the background so we can make another asynchronous function go handle connection and this handle connection will need the channel but probably also the connection so this chance is of new channel so I'm going to pass this and then I'm also going to pass a connection because we might need that later oh but this needs to be just chance and then and then I just need this in the function signature of handle connection so I'm going to create handle connection handle connection connection is of ssh servercon and then we have this chance chance and this is of new channel so I'm going to put this here this is going to be my new function that runs asynchronous and then all this stuff here this for loop this is going to be in my function and here it will go all the way back to this for loop and then it will accept new connections so this is how this asynchronous works so now we have this in the function I'm going to save this so then this runs in the background for every connection that we have and it will iterate over the multiple channels so if there's a new channel it will just iterate over the next one this we are not using yet so I'm just going to put an underscore here and then I want to know what requests that this ssh client is going to make so let's do the following for requests I'm going to say requests made by client and then the request type request type made by client and then we can see what we have to implement let's see if this works wow we are logged in and then when we are making our requests then there's a request made by client request and request and shell request and only the shell request was accepted because here we do a request reply so here we have this request ssh request we do a reply and if it's type shell then we are going to say true so we are going to reply through here this is a boolean reply sender response to a request to a request so if it is shell then we are going to say ok you can get a shell but then we get this error pti allocation request failed on channel 0 so ideally we also want to reply yes if we have a request for a pty rec so again this is like ssh protocol so you see you make a connection it asks 3 things so you would have to implement those 3 so what did I maybe not explain yet so this function here so we have to go this runs asynchronous so that we can go to the next channel if there is one this has a function and the function input is the ssh request so this is the request request is of type channel so this is another read only channel this function will be executed straight away because this anonymous function runs in the background will be requests will be passed so here we have request is then called in and then when there is a new request when there is a new request ssh request that is being put on the channel then we are going to run this for loop so this for loop will run indefinitely within this function in the background for every new request that is coming in so here we have 3 requests so we run it 3 times this loop for every request that was being put on the channel the next step would be that if we get this request of pti request or shell that we provide are client here with a terminal and a terminal is basically comes from a long time ago in unix where you would have terminals but this is still implemented within ssh and other programs where you have a terminal and in a terminal you open a shell for example these are also 2 terminals here and they support things like resizing and your cursor can basically move so to implement this ourselves would be quite some work so in goline we also have a terminal package that we can use let's try first with a switch right here on rack type so if rack type oh and you can see there was a request made for a window change so you see depending on what we do there might be some more requests being made you can implement all of them if you would like but that's not really the goal of this demo the goal is to have just something working so that we can experiment with sending commands because what do you do typically between servers you often just want to connect to a server execute some commands see what is happening and then exit and this is typically what you need within automation jobs you just need to send a command or you might even have to run a server like an SSH server that can receive commands so that's what really we want to build here using go and within our function where we here get the request to open a shell we kind of want to give a shell and then maybe implement some commands so first we need to open this terminal so we don't use this terminal package for it if we receive shell then actions if there is pti request there will be certain actions and then there will be a default where we don't really implement that and then we can just say we don't implement it so we say we reply false and here we reply through so here in pti request I'm going to open my terminal and then even nf you might capture and say we implement this there's also payload by the way so even we could capture this payload which is bytes so in case of nf then there might be some environment variables being passed let's keep it simple for now let's try to implement this terminal and for this start terminal we need the new package so I'm going to go get go lang xterm and we're going to use this x term package term provides support functions for dealing with terminals as commonly found on unix systems let's try to implement that and we're going to put in a new function create terminal and we're going to pass our connection and our channel channel was the one that we got returned here so channel channel connection it just depends what you need but we need for to create a terminal we need this connection and channel so I'm going to say connection channel although we go to return maybe not yet anything maybe an hour later connection is of ssh server connection and channel is of type ssh channel term dot new terminal and where are we going to open this new terminal so this channel is actually a read or writer so this channel this ssh channel influence read write and close so we can use this interface read writer that can read and write to this variable channel so this is a ssh channel so this is not a go lang channel anymore this is just an ssh channel that we can write to send data and receive data so we are going to say open me a channel using this term package and we're going to have a prompt this is going to be a prompt then we have terminal instance that is being returned the term terminal then this channel also needs a defer channel close so when the function is finished we are going to close our terminal and then we want to do something with this terminal this terminal instance also has a function read line so read line whenever our user using our ssh client is going to type something then we want to read this line read line and error so read line is going to be blocking so we want to do that in a separate go function and we want to keep on doing this so we are going to make a loop so we are going to have this loop here and then as long as our channel is open we are going to wait for new lines if there is an error what should we do when there is an error maybe escape the function or maybe just print it let's print it read line error just so we have all the error logs and let them break the function break the for loop and then the function will close then we have line let's use another switch and what should we check for case I will just try to come up with something who am I which is not the same as the who am I command in Linux but we can just use it as an example command so who am I then it returns a login here we can say who am I and then we can send a response default we can say that we didn't understand this and then we can send it back to the client so how do you return something let's have a look in this term instance and there is term instance.write and we can just write a buffer and this will turn the amount of bytes written and an error but yeah let's just keep it simple for now we could capture all the output and all the errors but let's just try to fire and forget to see if there is anything working UR and then that's why I also send this connection UR and we need to do an S print maybe so that it's nicely formatted URS connection.connection.user and if it's default we will say command not found and if we just press enter then the line is going to be empty we don't do anything we just keep it empty so we have a switch close our for loop and then we have a go function and our go function needs to be executed as well but there's no argument so I'm just going to put these brackets here and then our go function will automatically be executed immediately if error is not equal to 0 this looks pretty good so let's try to see what's happening so we are in a channel new channel is being created of type session we accept the new channel then we get SSH channel we return and this request so this request we can then use so if there's a new request then we'll go in here then we'll be waiting here for those new requests this in is an SSH request if there's a new request then this will fire and we'll say request type made by client and if the request is PTI REC then we create a terminal and actually because this is in the background it will also just continue here all the way to the beginning and then it will wait until there's a new channel to be opened so everything is pretty asynchronous and we could even have more code here if you want to execute it just when the client connects and it didn't make any requests so then it will create a terminal term instance is a terminal so this will start a terminal and then it will give us like like here we have the dollar sign it will give us a greater than sign we'll make sure the channel is closed at some point read line who am I how is this a sprint f it is and then if we don't understand the command we'll say command not found so then we have a little shell that we can try out so this is not really a shell comparable with bash like we have here so the user cannot really do anything it just has this little shell that we interpret the commands and then execute something so this can be nice when you have a server that users need to login to certain actions server is running and connection is immediately closed and I get a read line error because it's end of oh and I see what I did I put the deferred channel close here and then it's executed when this function is finished because this create terminal function will finish but then this go function here is separate and it will finish later so this defer I want to have it in my go function which will keep on running forever so only when we have an error for example a read line error we will break and then when we come here at the end then the channel will close that's what we want let's try that again allow this seems to be working better we made this request three times we say ok we can make request but only once we executed something and then we have a shell and what does that mean to have a shell I can type something I can go left I can go right I can remove my letters I can say ls ok command not found so all these commands are not found because I am just checking here with my read line what commands I am sending if I press enter then it's a case of empty let's now try who am I you are at the word piano because that's the user that I connected with how do I exit here ctrl c and then you see the read line error and then you go out or I can also make another case for quit and then I can say goodbye on the terminal goodbye and then we are going to close it and how do we close it channel close we are going to close this channel let's try this again allow nice and we try who am I quit says goodbye close so that also works that's pretty nice so we now have created with go with just two packages that are from golang.org which means that they are from golang but they just don't follow the stability promise that they have because these are very stable here the apis could change a little bit but those packages are very reliable still we only use two packages that's sage package and the term package one is for the SSH client server and one is for the terminal support so you see using the default packages and just some easy to use concepts we have written a complete SSH server that opens a terminal where you can enter some commands interpret those commands and then execute some code you could also execute commands in the background based on what you are getting here or you could even type everything to a shell but that's not the point of this server we just want to have a shell where you can just type some commands so this is nice to have to then test our golang client when we are going to use our golang client we just want to send one single command we don't really want to have our full shell and this is also supported within the SSH client because you can also add commands here you can also add commands and those commands will be executed but the way that the SSH protocol is implemented this SSH client will not ask for a PTI and a shell it will just ask for exec so and if it is still there it will ask to execute something and then it says exec request failed on channel 0 so in the next lecture what I want to show you for the client is how do we execute single commands over SSH which then uses the request type exec and then I think we are ready to start implementation of a client let's pick up where we left we do the SSH command and instead of opening a shell we want to send a command so then we did an exec request so what did we really hit here that we have this exec request failed we have here the default which captured the exec but we need to have an exec for this request type and what do we then need to do we need to execute our WMI if what we supply is WMI so let's just see what happens now now that we have this exec here we reply it through instead of false well we reply it through now nothing happened nothing really happened so let's have a look what a payload is payload and then rack.payload contains a payload rack type is a type, rack payload is a payload and this payload is what we will have to analyze payload is WMI so that's good that we know this WMI so that we can now use to send something back let's make a function of this execute something and we're going to pass a payload the payload and then what we can do is we can make a function execute something payload is bind and then let's give maybe a string back and then we can reuse this and we can say exec something payload but payload is just going to be WMI because here we already know what it is and it should be bind exec something and then we return this but when do we return this only if the payload is only if the payload contains WMI and then we have a default what could be the default just not found maybe command not found not sure if this N needs to be there probably yes just to make it nice case WMI let's convert this to a string command not found and then let's also output the command command not found and what is the command payload we need the connection and still connection is of type SHH server connection and then we also need to pass it twice here connection and the connection here and then so this returns a string so then how do we apply this back let's have a look what's in REC not much what else do we have here channel channel write we can write something back to a channel channel write exec something and then we just need to convert it back to bytes because channel write expect bytes and then we have the reply true we can reply that we can handle this type of exec and then we also write the response let's try that out sounds logical command not found WMI and I was expecting this because when I was preparing this lecture I figured out something interesting that is not easy to debug immediately because it has command not found WMI and I'm just checking here for WMI but how does this WMI really looks like if we translate this to bytes so if we have exec and then we will say event the print f let's say v and v and the first one will be byte WMI and the second one will be the payload so I want to see in bytes what WMI is in bytes and what the payload is in bytes and then I'm going to get a difference and the first WMI is just a string so 119 100x4 and here I have just in the payload some data before this WMI I'm not 100% sure what this prefix is here it's something in the SH protocol that I'm unaware of that I haven't read about yet and I'm just going to strip it because I don't need it but if I would need it or if I'm doing something wrong I would have to correct it later on in my GitHub repository unfortunately but for me it works and for the goal client it also works so hopefully it will work for all the clients so how do you strip this I can say payload bytes trim trim left so there's also I can trim my bytes from the left to remove this 0006 so this 0 is actually 0 bytes so this doesn't really mean anything but I still need to get this 6 away as well bytes trim left of the WAC payload which is our bytes and then the string trim left and is there stream left bytes maybe let's have a look trim ok, trim prefix accepts bytes easier to deal with so I'm going to say 0006 I want to remove that from the beginning of my bytes and then I'm going to send this payload save this I can remove let's try to run it again and let's try to run my command again ok who am I, you are Edward Vienna but what's happening here it is keeping the command open it's not closing it so there's still a reason why it is waiting for input from my channel I can just close the channel after I get the exec I guess because if it sends the exec maybe you don't need the channel anymore and this is just how I want to do the implementation basically not 100% sure if it's all rfc compatible but if I'm writing my own client and it's only my own client connecting I don't really need to be fully rfc compatible in my own script if I just want to do it quick and dirty if you would want to write a real ssh server and for people to download it you probably want to be rfc compatible so that every client can connect but there are already a lot of ssh servers out there I don't think you're going to release a new ssh server that I am typically building are really for internal use to give a shell or be able to send some commands over encrypted using ssh keys so that we still have a skewer connection or you might even use sshd and only write the client so I'm going to save this go run exit again and in here I closed my channel after I send the reply and then I get the correct output now there is something to add to make sure that you're compatible with multiple clients because the go client is going to want to see an exit code so an exit code is if you aren't the exit code of the previous program then the exit code was 255 which is an error code if you just do voie mine code then I get 0 which means it is successfully exited and this is something that the client will look for so we can actually send another reply to our client and just like we have request here we can also send request to our client we can say channel send the request send a channel request and it will wait for reply if we want to reply send the request we can say exit status do we want to reply no and what is our payload we are just going to say our payload is 4x0 which will then translate in an exit code 0 and our go run client will look for that so if you don't have this line then our go run client in next lecture might complain so we just want to make sure that we have it here as well so that's it let me just test this another time to make sure that it is still working and it is still working and also if I now check for the exit code I see 0 instead of 255 so that's probably better that we send this along also something that we are not doing is this nth we could also have a case nth and use this payload to set the environment variables or have a look what it is sending you are free to improve this SSH server I was even thinking of maybe making a separate directory in my github repository so that I have the server that I covered during lecture and maybe a separate one for everyone who wants to do PRs to have some improvements because there can definitely be some more improvements and then in the next lecture we can work on a go run client that we could use with this server but maybe also with other go run servers so let's start now with our client so I created the main go in cfd client and I just have a few lines that are already wrote here just to make it easy to read the mykey.pam and the server.pub this mykey.pam is my private key that I would need to log into the sh server otherwise I wouldn't get in this is the same that I pass here and the server.pub is the public key of a server this is the public key that is now stored in known hosts if I use SSH because the first time I connected it asked do you want to save this host and then it saves a fingerprint of this public key now here what I do is I already pass the server.pub so that I'm sure that I'm connecting to the right server then we need to parse these to the private key we parse with the ssh parse private key and we parse then the bytes of the private key and then we have the public key parsed which is being parsed by the ssh parse artist key so this artist key is the public key and this artist key gives back the public key the comments potential options any other keys that would be in this public key so I'm just ignoring this variable and a potential error and then how would I connect to this ssh server I already showed it to you in the previous demo there's a dial function so let's have a look at the documentation there's the dial function so again the crypto ssh documentation and here we have the dial function either you go to the dial function or you go to the examples there's also an example so let's have a look we have the dial client dial and here's an example dial starts a client connection to the given ssh server it's a convenience function that connects to the given network address which is a string it's a handshake and set up the client for access to incoming channels and requests use the dial with new client connection instead so this is a higher level dial function that we can use if we don't want to deal with channels and requests like in the server so in the server we have to define it here in the client we can just use this higher level function that we don't really need to deal with channels and requests and we can use some higher level functions that will then initiate the request that we need just like the server we need a client config we need a public key so let's just copy paste this client config then we're going to change this because the authentication method that we are going to use is not password but is key based and then we're going to dial protocols tcp this is going to be localhost 2022 and then the config so I'm just going to copy paste this config the username can be username you're not really checking on it and the authentication method is going to be public keys so public keys has the correct return method which is out method we are looking for out method so we can actually have multiple out methods if you wanted but we are only going to use the keys SSH signer the signer is this one private key parsed this is the private key that we supply and then the host key callback is the public key that we need so it's a fixed host key it's a public key parsed because this is the SSH public key so this is our SSH config localhost 2022 I think it was and let's see go run seem declined go I think it connected did it connect maybe not let's clear this just to be sure it logged in with key so it connected but it didn't do anything there's no requests going so how do we make sure there are requests going so we can execute something or you could also start a shell if you wanted to but starting a shell you could as well use the SSH command so what I'm going to do is I'm just going to execute who am I just to see how that works because typically when you write a client that does something with SSH you're going to just execute a command show the output and then exit client new session client new session opens a new session for this client so let's try this new session is session and error session new session and then we need to do something with this session and just see what this function does so go run what does session do nothing yet ok well let's continue session now that we have this session keyword we just need to add a colon here now that we have this session keyword we can do something with it so we have run shell wait we have output output runs a cmd on the remote host and returns it returns its tenant output that's kind of what we are looking for we just want to send a command and show the output so this session we also need to close when we are finished so we're going to say the first session close is a byte and an error so out and then out error equal session output what are we going to execute where am I and actually if I just define it like this then I don't need this output variable ok if there is an error then session output error session output error and if there is a new client error new session error if there is a new session error then new session error and then we can just output out I guess output is out so let's see what this is going to do it's not doing any request right now but I expect that this output is then going to do an exec request with the payload who am I and the output will then be outputted here on screen let's have a look go run client.go and we see that the only request that is made is the exec request because the session output uses the exec request and will then collect this output and you are username and vice is not Edward Viana because the username that we supply this time is username and not Edward Viana you have to supply a username but we are not really using the username in our server we are just checking on this key if you wouldn't supply this key then you wouldn't be able to login so if I would not supply this key what would happen then handshake failed no authentication passed what would happen if I would pass a password it would be the same it would also give us a handshake fail because we are not supporting any password method if you just want to use password you could change the server and then implement the password method check on login and password maybe even with a third party database and then allow users that way so this is how we can implement a client so this client is pretty straightforward thanks to this high level function that we have dial we don't really need to deal with these sessions and requests because our session output is doing that for us we just pass a command it sends a command using the exec just like we did with our SSH command that was being passed here and then it just outputs the outputs or we have the output in this variable that we can then use so this is actually quite interesting because we could have like a CICD pipeline where we have this client for example that would make a connection to an SSH server and execute something show it on screen and exit and this all you could potentially integrate with other Go packages to integrate this with an API or with a product in AWS or with something in Kubernetes so just being able to programmatically make a secure connection to a server using the keys that you p