 In this lecture, I'm going to talk about cross-compiling and Seago. Go can cross-compile to any supported operating system and architecture. Cross-compiling means that I can compile on my Linux system a Go binary for Mac or a Windows system. Architecture means I can do that for AMD64, that's the Intel architecture, or I can also do that for ARM64, which is now used by MacBooks. It can also be another architecture that is used on embedded devices or on servers. You need to supply the Go operating system environment variable and the Go Arc environment variable during Go build to compile for another operating system or architecture. Go tool, this list shows you the support combinations. So when you want to cross-compile, you can use that command and then you can see the operating systems and the architecture that you can cross-compile for. When not cross-compiling, Seago will be enabled. When cross-compiling, it will be disabled. Seago allows you to run Seacode within Go. This is relevant even if you're not using this feature yourself because standard Go packages like Net can use Seago, for example for DNS resolving. Seago will link your binary to the current C library available on your operating system, but it will not work on an operating system with a different C library. So this is how it works when Seago is enabled. So Seago enabled equals one, Go build. This is the default, so you don't really have to supply this. The result is a dynamically linked binary will have your Go binary, which is linked to the Lib C system library. This can be G Lib C, which is GNU Lib C, just Lib C or muscle, which is used by Alpine. And this is a library that is installed on the operating systems. It's an external file. So it's not in your Go binary. It's in the library directory on a Linux system. And you will need those files to be able to run this Go binary. So if you would now want to run your Go binary on another system, you might not have these files and your Go program will not work. It will not execute it will execute an error that you cannot find this C library. And that's why you have the Seago enabled equals zero Go build that you can do. And then the result is a statically linked binary. So you're not linking to this C library anymore, rather than using these C functions, there's a pure Go implementation of the CV libraries, for example, in the package. And that results in one single binary. And now your binary is portable across all operating systems that it has been built for. So if it has been built for Linux, it will work on any Linux system. So this just creates one binary that is not dynamically linked, you would still need to cross compile if you want to run your binary on a Windows or Mac system. So if you want to run your binary on Linux, Mac and Windows, you would have three binaries. Those would then all be statically linked so that you don't need any external C libraries. If you want your Go library to just run on a single machine, then you can use Seago and then it will link to these C libraries because you have those available on your system. So enabling Seago when you're not cross compiling may lead to a binary smaller in size as those C bindings for DNS resolver and networking will be in libc or netglipc. So it's not only the DNS resolver, it is also other parts that are using Seago. You could even use Seago yourself. You already have the C libraries bundled with your operating system. So there's no need to have them included again in every binary. So that's actually how Linux works and Unix as well. If you execute something like LS or cat or any other Linux commands, those are dynamically linked so that you don't need all these libraries again in every single binary. Otherwise every binary would be pretty big and then it would take a lot of this capacity for every single binary to have them statically linked. Also if you have them statically linked, you would have to recompile everything. If you have a security bug in one of the libraries, if it's dynamically linked and there's a security bug, you just update the library, restart your binary and then you will be using the new libraries. So there are definitely benefits in enabling Seago. It will also lead to faster builds and that is why Seago is enabled by default if you are not cross compiling. Disabling Seago by default when you're cross compiling or with the flag is necessary when cross compiling and it's also necessary if your C library on the Disney system is different. For example, if you compile on a Bunto Linux, but you want to run on an Alpine Linux, you wouldn't think you would have to cross compile because it's still a Linux binary, but actually it will not work because Bunto Linux is using GNU, Lipsy and Alpine is using Muscle Lipsy. So let's have a quick look how this Seago and cross compiling works. So this is my Mac operating system, but I'm going to start a Linux Docker container just so that I can show you how the linking works. I'm going to use Docker for this. So I'm going to enter Docker run, RM, make sure that the container is removed after I exit the container, interactive mode and my container is going to be Alpine Linux. So now I'm in my container. So I downloaded this image earlier. That's why it's not downloading the image. It's cached and I'm going to install go first APK at go. And this is the package manager of Alpine. So you see here I'm running Alpine three dot 16. And this is going to install go. It also needs to install all dependencies. And then after this is complete, I will have the go command installed within this container. So now it has been downloaded. And I just need to create a test app in the directory app, main dot go. And I just pasted a listen serve application. So this is just an HP test server, go build, go build, main main dot go. So this is the standard command. And then main, this starts application. So this works. Main is the binary that has been created. Now I'm going to use a tool, LDD, which will show me how it is dynamically linked. And you can see that main is linked to slash lip LD muscle x 86. So if you would now copy this main to another Linux system, and it would not have muscle version one x 86 64, then it will not work. It will say, I cannot find this library. So what you can then do if this is the case, and we are not cross compiling yet, we are just creating a static binary is to use this flag. See go enabled equals zero, go build, oh, main, no, see go off main dot go, LDD main, no, see go. And main, no, see go is not a dynamic program. So what happened now is that it's not dynamic. It's a static binary that can now be copied to other Linux systems. It's now not using Cgo anymore. So it does need to be linked. So see go is also disabled if you cross compile. So if you do it again, go build main Darwin 64, which is for Mac Intel based Macs. Then go operating system is Darwin, and architecture is any 64. Then this will cross compile. What happens when I execute it? It doesn't know what to do with it because it's a binary of a different operating system. And it also says it's not a valid dynamic program. Through the defaults are most of the time okay, the only use case that I know of, where you would need to put Cgo enabled to zero is when you want to compile a binary that is still compatible, even though it doesn't have the same seed library on a different system. Otherwise, if you're in the same system, it's beneficial to have Cgo enabled. So if you want to see a list of everything that you can cross compile, you can use a command. Go to this list. And you can see here Darwin AMD 64, the one that I just built. But you have also Darwin arm 64. For example, when you need to build for those arm 64 based MacBooks. You have Linux, a lot of Linux, different architectures. So you have AMD 64, but you also have 32 bits. You have arm, arm 32 bit arm 64 bit. So you see you have a lot of different architectures that you could build for. And here, for example, you also have Windows. So this was a brief introduction into cross compiling and Cgo. If you run into any problems, cross compiling, you can always send me a message directly or put something on the Q&A, because there's a lot of cases where something could go wrong. But those cases are so specific that I cannot really cover them. This what I just explained is really the basics that you should know and that you can start from when cross compiling for different operating systems and architectures. In this demo, I want to show you how to package your code program in a Docker image. Once it is in the Docker image, you can launch it easily on any public cloud provider. Today, many SaaS applications are running based on Docker images. So it's very typical to ship your GoLang program as a Docker image, probably running an HP server in most cases. And then you can launch it easily using a cloud provider or a VM running somewhere. So I'm going to show you how to build this test server using a Docker file. So first, make sure you have Docker installed. We can just go to the Docker website and download it. I'm using Docker for Mac. There's also Docker for Windows. You can also download Docker for Linux. The only thing you really need to build a Docker image is a Docker file. And a Docker file is actually pretty easy to use. The first step is to say what your base image is. So I'm going to use from and there's a GoLang base image that you can use for compiling. So this GoLang 1.18 and I'm using Alpine Linux, because it has a smaller footprint as an Ubuntu image. So it's very well fit to run Go programs on. Remember what I said in my previous lecture, if you are building for Linux, you need the same libc libraries. So if you're building using Alpine, you also need to be running Alpine. So here I have my build container. So from GoLang Alpine as GoBuilder, and here I have the runtime container. So this is the actual container that will run when I use Docker run. Let's start with the build container. I have my base image that contains all the build tools. I have a Word directory. So this is the directory that I will build everything in. This is slash app. I'll copy everything from my local folder, this folder here into the Docker container into slash app, because that's my working directory. I then need to also add build tools curl git. So this can be necessary to build your executable. Most likely for this project, you only need git, you can just add some build tools. This container here, we will not ship. It's only for building. So it doesn't really exactly matter what you install, as long as you have a runtime container that is separate, where the build tools are not installed. So we add these build tools. APK add is a package manager in alpine Linux. You then do a go build of the go files to the executable server. And this will be dynamically linked to this lip muscle C library, because we are not cross compiling, and we are not specifying to disable C go. These lines are not really necessary. It just because I copy pasted them over from another project, where we just want to clean up after every comment. So then we have the runtime container, runtime container is going to be alpine, just the latest. So the C library needs to be the same. So we want to make sure that C library in this one, and this one are the same. Now this is the case. But if you get an error while executing your app, then you might have to change this latest into a specific version. It depends what alpine Linux is being used for Golan one dot 18. I also have a working directory slash app. I'm going to install CA certificates, which is not really necessary. If you only have a server, it's necessary when you want to make API calls. So if you make an API call to somewhere, you need CA certificates to be able to validate the server you're connecting to. So these are the root certificates. And then you can copy from the go builder, the app server and save it into app server. So this alpine Linux will only have the CA certificates installed and the binary in slash app server. We'll always expose port 8080 in the container. And then the entry point is slash app slash server. So that will be the process that starts when we start container. Let's try to build this. How do we build this with Docker build, Docker build, we are going to give it a tag test server. And then we need to specify the path that we want to build, which is the current directory. And you will see that the commands that we have listed in our Docker file will be executed. It will install git and some dependencies, fetch our core dependencies and then copy to our runtime container, the app server. So what is the image size of this Docker container? Let's have a look. Docker images filter on test server, 812.8 megabytes. So it's pretty small, it will obviously become bigger once you start making more complicated go programs and have more dependencies. How do we run this container? So we can either run it locally or on a cloud provider within Kubernetes or just with Docker on a VM, Docker run, or and make sure that when I actually continue that the containers also removed, interactive modes, not really necessary. It just I want to hit Ctrl C, once I want to exit a port. So the port that is exposed in the containers 8080, but I also want to have it bind it on my local machine. And I'm going to run the test server. Starting server from point 8080, curl local host 8080, the server is running. So that works. So we have a container running, we can also execute in this container to have a look in it. Docker exec, Docker PS, first Docker PS. Container ID is this one, Docker exec minus it for interactive mode. And I'm opening a shell. And then we are in the working directory app. app has a server binary is 6.5 megabytes. And if I do ldd of the server, you can see it is dynamically linked with our C library. What if you want to get it even smaller? That's also possible. So I'll exit this. And then I have a Docker file scratch that I created. So I'm going to close this one. Docker file scratch is very similar. The build is exactly the same. This is all the same. But instead of using a runtime container that is alpine Linux, I have scratch and scratch is a Docker maintained base image and that's really almost nothing in it. So from scratch, we're going to copy the app server. And we are not really installing the CA certificates anymore, because we don't have APK anymore, which means that we would have to also install CA certificates in our build image, if you want to have it, and then also do a copy from the CA path if you want to have the C is installed. But because it's not really necessary to run the server, I will just leave it like that. Just something to take into account. Docker build, Docker build minus the test server was our previous command. So I'll call this test server scratch. And then I want to specify the Docker file because it's not a default Docker file. If you don't specify minus F then it will look for Docker file. If you specify minus F you can specify any file. Docker file scratch is the one I'm going to build. And it's almost the same. It will do the same build. And it will just copy over the image to another runtime image. So let's first try to run it. Docker run test server scratch, starting server on port 8080. Okay, that works. Now let's compare Docker images filter for test server. And we still shaved off some megabytes. So test server is 12.8. And our test server scratch is 6.7. So if you really want the minimum, then test server scratch is also a good runtime image. It kind of depends on what you prefer. If you still need to do debugging at some point on the runtime container, like while it is running, because something went wrong, then often you want albion Linux a test server, and have maybe also curl installed. So here, sometimes I also install curl and bash, just so that I can do some debugging on the container itself of how it is running, if really, something goes wrong. For example, you want to do a curl localhost on a specific endpoint to see if this working. It's a little bit more difficult to debug with scratch because there is almost nothing in it. So if you run our test scratch again, and we look for the scratch image Docker exec. So if we want to open a shell, then it was to say, I don't have a shell, because bin SH is not installed. There is no shell, because it's really a minimal container image, there is really nothing in it. So you cannot even enter in it with a normal shell, you would have to install it first before you would be able to do some debugging. You can still make it smaller. The image if you want, there's also some flags that you can enter here, go build, you can remove debugging information stuff like that to get your goal and binary even smaller. If that really matters to you, then have a look at what flags are available, you can remove debugging information to get a smaller Docker image. Most of the time, I would say just using Alpine Linux already helps a lot in making sure that your Docker image stay small. So once you have a Docker image, you can really kind of deploy it everywhere you want. In this section, I'm going to talk about the AWS SDK. AWS stands for Amazon Web Services and is Amazon's cloud offering. In this section, we are going to use Go to make API calls to AWS. This is how it looks when you make an AWS API call. So it was has multiple endpoints that you can reach. For example, easy to is their virtual machine offering, where you can launch virtual machines. So they will have a separate AWS endpoint for that. You can hit that endpoint over HTTPS and add parameters to call different actions. Every call needs to be authenticated. So you would also have to supply off parameters. This makes it quite difficult to do these calls yourself. You could do them, for example, with curl or any other HP command line utility, or even with go with the HP get that we did, we can do these API calls. As a response to your API call, you will then get a reply as you see here at the bottom of our image. You then have to decode everything into a struct and every API call will have a different scheme. So you would have a scheme for every API call. There are thousands and thousands of API calls that you could make. So it would be quite a work to do this all by yourself. And that's why AWS has an SDK for goal that you can use. And it will have functions in Golang, like describe regions that you can just call. And then this SDK will take care of authentication and will also parse the output for you. So it's quite a no brainer to use the SDK for endpoints like this. So what do we need to do? First thing to open an AWS account if you don't have one already, then going to create an IM user, download the credentials, and then we're going to configure these credentials on our machine using the AWS command line utility or as environment variables. So you can configure them straight into Visual Studio code if you want, or you can use the AWS command line utility to configure them, and then you can use their command line utility together with the code that we're going to write. Let's start with configuring the AWS credentials of a user. If you don't have an AWS account yet, AWS amazon.com, create an AWS account and it will guide you through the steps to create an AWS account. Once you have your AWS account created, you can log in to the AWS account by signing in with your root account or with any administrator account. Then you will see a page like this one, very similar. We need to first create a user that we can use on our machine. So we're going to go to IM. You can just type IM, manage access to AWS resources. You go to users, add user username. You can call it go AWS SDK, for example. And we need an access key, programmatic access enables an access enables an access key ID and secret access key for the AWS API command line utility and SDK. So that's what we need. Permissions, you need to make sure that you have enough permissions to create an easy to instance. So you can give it easy to full access or just administrator permissions. You can click on attach existing policies directly. So we have here already policies created by AWS. The easiest is to give it administrator access. But there is also, for example, easy to full access that you could use to start with. I'm going to start with administrator access, because we will have other API calls to make as well. And then we can add text, which is optional. And then we can review. So we have the username go AWS SDK, which is going to be administrator, and we're going to get an access key. Make sure that you always protect these access keys. They're secret. Otherwise, someone else could create instances for you, or take over you AWS account, especially when you create an administrator. This is access key ID. So we can copy this. And then we have the secret access key. So if you click here, then you can show the secret access key is also just a string. And this we need to configure the easiest way to configure, I think is to use AWS command line utility. If you go to AWS Amazon.com slash CLI, then you can download the command line utility. So there's a windows installer, macOS installer Linux installer to download this AWS command line to download this AWS command line utility. I'll show you how to configure it with this CLI and how to configure it within visuals to the code. So you can pick either options. I would recommend the alias command line utility. But if you cannot download it for any reason, then you can still configure it directly in visual studio code. In visual studio code, if you have the alias command line utility installed, then you should be able to use alias as a command line command. To configure your credentials, you can do alias configure. And then it will ask for alias access key ID. And your secret key. This is my access key ID, my secret key. And then you can specify a region us one, or if you are in us us each one, default output format, you can leave to none. And then it should be configured. If I do alias STS, get color identity, it gives you the output and shows you that you are authenticated. So there is another way to do it. And this to edit the launch configuration. So if you go back to the configurations here, you can add here an environment variable. And as an environment variable, you can add the environment variables necessary for alias. And then visual studio code, when you run this, it will add these variables to the environment. And you will also be authenticated to your alias account. There's three environment variables that you would typically configure alias access key ID, alias secret access key. So this is the key ID and access key. So these are the two that are supplied. And then the alias default region, which can be us each one or any other region that you would like to launch in. So these you can also configure within your launch configuration. If you then launch your Golan application with SDK, it will be able to call the alias endpoints using this login information. In the next lectures, I will point out what part of the code will fail if you don't have your credentials properly configured. This is going to be our first LS SDK Golan demo. What you see here on screen is what we are going to build first. These are the steps that we need to take the functions that we need to call to launch an EC2 on alias. The first step is going to be that we need to load the config. This will load the credentials, the alias access key ID, and the secret key, whether it's in an environment variable, or in a file, if you configured it with a less configure, then it's going to be the file. Or if you even run it on an EC2 machine, it can be a role. So this load config is going to take care of that. And then we can do the EC2 API calls. First, we're going to do an API call to create an SSH key pair. In case our SSH key doesn't exist yet. If it exists already, then we're going to skip that. Then we're going to find the Ubuntu AMI. There is an API call to list the available images and AMI is an image. There's an Ubuntu image that we can search for. And that will then return AMI ID that we then going to use to launch our EC2. Then we can say launch EC2 using this Ubuntu image as a base image to start our EC2 machine. And then when everything goes right, then we can output our instance ID, our EC2 instance ID. So let's get started on that. For this demo, I'm using a new dietary, which I created and opened it in Visual Studio as a folder. I'm going to run go mode in it. Because now this will be important when we start using external modules. Let's start with a main function and then a function to create our EC2 instance. Fung main, this will be the entry point. And we need two variables, we're going to output the instance ID, which is a string, and we're going to use error. So these two we need. And then we're going to call a function, we're going to call our create EC2 function. And this will then return our instance ID and our error. We don't really know what yet to pass. So let me just write these function signatures here. String and error is what we return. And then we return nothing yet. What do we do? We need to check for errors. So let me just add an if statement here. If error is not equal to nil, then we're going to say create EC2 error. And then an error message. And we're also going to stop. Let's exit one. And if everything went fine, then we're going to printf instance ID, which is this instance ID. And that's why we declared it a little bit higher up. So that let's remove this colon because it's not necessary. So that the instance ID is assigned here, but declared here so you can still use it here. Save this backslash n. Oh, I did return. But I'm not going to return anything. I'm just going to print it. And here I need to print it instead of rf. So we print this, we print this. And then it runs a create EC2. We want to maybe pass one thing. You want to pass the region, the alias region, region so we can easily change it. So I'm going to launch in US East one. And I pass this. And now I can initialize the alias SDK. How do I do that? It's in the alias SDK go documentation. Let's have a look at it. alias SDK for go version two we are going to use is the GitHub repository. And you will find here the SDK documentation. But also you will find getting started, for example, here is the getting started. So the reference documentation is right here. So this is a developer guide with the getting started. And then you also find the services. Here's an example of the services with S3. There will also be an example of EC2, how to call them. So this is what you should read if you didn't have any idea how to get started. So let's just have a look at this getting started and they will get going. So we did a go mode in it. And then what you should do is go get need to go get these dependencies. These are external dependencies. So we are going to use config using the SDK default configuration from the environment variables or the shared credentials or the configuration files. So this is what we need. I'm going to copy this and we're going to change it a little bit. config undeclared. Because we need this one. And then we'll add this as an external dependency. And it says could not import this config quick fix go get that was the command that you saw in the getting started. You can also run it within visuals to the code. Or you can run it in the terminal. What we what happened then in go mode, you will now see that we are requiring this dependency. Once we start adding more dependencies, it will automatically add more to the go mode file. So this go get command adds dependencies, external dependencies to the go mode file. You will not find these internal dependencies because they just commit the goal line. So the config now works. And now we have contexts, contexts, we always need to provide a context with these load config. And context is something that is within goal line. So context to do means that we are supplying an empty context, which is fine. But what we also can do is we can pass this context if you want. And context can be useful in a way that it can contain variables is a context variable. So it can contain extra variables. But more importantly, you could actually cancel a context, which means these API calls can take some time to execute. If there's an API endpoint that is not working, and it would just time out, it would take 30 to 60 seconds. This context allows you to cancel this whole flow in another point in your program. Right now, you're not going to use it, but we are going to pass it. So I'm going to say context. And then gonna just define here context. CTX is context, background, background returns a non nil empty context, it's never canceled has no values and has no deadline. So you could then change this context if there's a deadline, or if it needs contain values that you want to pass between the functions, or if you want to cancel it later on. So we will use background, and then we'll just pass it around to our functions. This is of type context context. So let me have CTX context context. And then now you have context, conflict with region, and our region name. And we can use this conflict. And then let's just return an empty string with FMT RF unable to load SDK config. So what do we do next? We are going to initialize an easy to client easy to client equals easy to dot. And this is again something that we need to import now. So we'll import service to easy to I think it is. It's already in the help file service at easy to easy to and I would need to go get this go get. And now it's downloading this service easy to and now I'll be able to use it new from conflict. We can do easy to new from conflict. So all these services are very similar. So if you use another service within alias, you'll also do a new from conflict. And you pass this conflict. And now we have an easy to client. And this easy to client we can use to do API counts. easy to happens to be a really big package with lots of API commands. So it's very difficult to find in this list what exactly you need. So but there's a trick. I always have look at alias SDK to have to to see what is available. It just gives you a starting point to see. So we need to create first this key pair. So we have create key pair. Although we need to pass to create key pair, our context and the parameters, the parameters is of easy to create key pair input as well as we can supply input. So we have a context. And then we need to reference this variable, easy to create key pair input. And then there's options, but they're optional. key pair output and create key pair creep. And then we need to pass on variables. a key name. Let's pass a key name. Let's call it alias go demo. alias SDK demo or go alias demo. That's maybe better name. And what do we see here cannot use. So we have a pointer string, pointer string. So you could say I want to do it as a reference, but that actually doesn't work. That's why it was has some helper functions. I can use a less string. And string will convert a string to a pointer string. We then need to go get alias, which I already have. So now I have it was imported. If this shows an error for you, you still need to do go get or you have to click and click on go get. So what is a less string, go to definition, string returns a pointer value for the string value passed in. So we pass a string, and then it converts to a pointer string and returns a pointer string. So just some helper functions, you have the same for a string slice string map. It's because these alias variables always expect almost always expect pointers. So we have the key name. What's next on my list? I need to get the Ubuntu AMI image. Easy to client. And then if I just type image, what do we have? Create image, describe, describe images. Let's have a look what describe images says describes a specified images a mice available to you or all the images available to you. Sounds like right. Describe images, context and parameters. But this time it's of easy to describe images input. So we pause the context. Easy to describe image input. And what do we need here? We need a filter filters. And then we also would need the owner because the image that we are looking for is owned by Ubuntu. So we need to still find this owner ID, which we will find on Ubuntu.com is somewhere described in the documentation. And we need this filters, this filters. So this is those of type filters. And now sometimes it gets a bit tricky because types not filter, it says in documentation here. But what is types of filter, I can find this type of filter. So that can be sometimes a bit annoying to find. Let's have a look if we go to definition, then types of filter, right as github.com service easy to types. So we can add it to our imports, because otherwise it's difficult to find sometimes. Save this. Okay, now it's recognized. It's an array of name and values. So that's a filter. So if you have an array, then we need to do one more time curly brackets, because this is one element in our array or in high slice. Name is name, we need to filter on the name. So we are going to save the filter name and then we have the filter value. So this just happens to be coincidence that it's name and name, but its name refers to the name here, the name of the filter. And we're actually looking for the attribute within this scribe images of name, name, and again, a string because it needs to be a pointer here as well. And a comma at the end. Oh, this needs to be something else. It's a slice string. And what's going to be, we don't know yet. So there are multiple ways in finding finding this, you can do this in the alias web console, there is a describe images interface, where you can type Ubuntu to find the correct filters. I'm just going to give you the filter. Once you know what the filter is, you can easily change it a little bit. You can change the bit depending on your use case. So you can see Ubuntu images, HVM SSD, SSD, this HVM SSD is just a type of VM and then Ubuntu focal, but you can see now that you can easily changes if there's a new version of Ubuntu, you can change the name and the version. And that's how you would then find other versions. It's still showing values. Now it's showing problem. So again, if you log into your alias account, go to EC2, then images or am mines or find images, you can play around with that interface to to find these. It's just some AWS knowledge that you need to know to be able to filter images. There's another parameter that I want to add because we're going to ask alias to launch an HVM image. So I want to make sure that we filter only on HVM images could be a bit too much, but we just want to be sure. So it's going to be virtualization type and the value is going to be HVM, just so that we for sure have only images returned that are of type HVM within alias. And then we need the owners owners is again a string, string slice, and this we need to find online, like I said, if I type Ubuntu alias owner, then you have Ubuntu.com cloud images. And here is explained that you can find images using using alias commands. Or you can use it with the describe image, but then you need the owner. And this should be the owner 09972. And this we can then use. There's also an interface here on this website. There's an image locator that you can use. So this is very similar to be able to find these am I these. So what we are looking for is my ID like this. And our API call will return this for us. So you also see this instance type, the version, the name. So we are basically filtering on that plus than the owner. So that's what we need to input here. And this will give us an am my ID. What is the output describe images outputs, image outputs error. Oh, we are not catching our errors. So if you have an error, then we say create keep error and error message. And if we have an error here, for example, if nothing was returned, then we can say describe image error. Now we have the image output. Let's do some more checking if image output images, which is of type images, which is a slice. So if this is zero, this lines if the length of this line is zero, then we also want to return that we didn't find anything that means that something is wrong with our filter is empty. This was your length. If it is not of zero length, we can take the first element of this slice, which is going to be the most recent one, the zero, and then there's going to be the image ID. So let's keep this for now. We need to now launch our image, launch are easy to so easy to client launch. Now that's a bit run, run instances. So this API call is actually very old. It is already around for a very long time. You can see easy to classic easy to VPC. So it's definitely very, very old API call. easy to run instances, context and then easy to run instance input. And what does it return? An instance and error, I guess several look, run instance, output and error. So let's already capture our error. Run instances error. And then if we have our instance ID, instance, instances, first element, and then the instance ID. So we need to do another check. And this is going to be also pointer, because that's how illicit returns it. So every time you're assuming that there is a first element, make sure that you check it. So if land of instances is zero, then we are going to say instance instances is of zero length. And then we need to provide our run instance input. So there's a lot of input here that we can supply. First of all, our image ID, our image ID is this image ID right here from our image output, our key pair. And it's just a key name. So we can just say key name. And we just reuse the name itself. That should work. And then what else do we have? There's a min count and a max count that you always have to supply. It also needs to be a pointer. So we're just going to use a list in 32 as a helper function, key name, image ID. And then we also need a type. It's going to be an instance type. What type of instance I'm going to launch a T2 micro T3 micro or something else. We can again use the types package. And here everything is declared. So if I just type T3 micro instance type T3 micro, which gives you a very small image, but it's within the free tier. So if you just open your illus account, you have a free tier, so you don't have to pay for it. Just make sure that you shut it down after you tested it that it works. So that's it. I think if we're going to run this multiple times, what's going to happen then is that it's going to say my key pair already exists. So I want to capture that as well. But we don't have to really capture it straight away. We'll test it first. What is happening here? I don't need any outputs. So let's remove this variable. You're launching it US East one. Okay, I think we are ready to test this. Maybe it will just crash. And then we'll have to fix something. But we'll see. First of all, let me run this. And I haven't configured any illus credentials just to show you what the output would be. Go and go. And then we actually loaded a default config, but then failed to retrieve credentials failed to refresh cash credentials no easy to IMDS role found operation error. So it tries to fetch these credentials using something but there are no credentials. So what if I pass some credentials, it was access key ID, ABC, and it was secret access key XYZ. So now I pass credentials, but they're not valid. So I get states code 401, because there are credentials, but they're still not an indication failure, because they are not correct. So I'm gonna configure my credentials on my machine. And then I'm going to run this go run. So either you run the alias command line utility to have configured globally, or you configured in goaling itself in visual studio code itself. But then you have to use the run and started bugging or run without debugging to have visual studio code load these environment variables. I have configured my credentials now. I'm gonna run it again. Oh, and it works. instance ID is returned. And let's have a look at my alias console. So this is the alias console I want to easy to right here, click on it. And then you have instances on the left that you can click. It opens with a dashboard but then you can click on instances. And then you can refresh a few times. And now you can see it is any slicing. This is my instance ID. And here's my instance. And I have an Ubuntu AMI. Here's Ubuntu. Here's the MIT. Ubuntu focal. And then we have the key pair name go alias demo. So you could actually log into the instance. If you want to try to log into your instance using SSH, then have a look at your security group because your security group will be default. And what you want is that you can actually log in with your IP address. Because we don't have any security group supplied. You might have to edit this. So if you click on this security group, and then you do edit inbound rules, then you might have to add a rule for SSH. And you can type here your IP address, or you can just open it to everywhere. If you type your IP address is going to be 32. So if your IP address is 1234, then you need to add a slash 32 000 slash zero opens to anywhere for SSH. If you want to open everything, you can just say our TCP. So I'm not going to do that because I'm not going to log in at this point. But just for your information, if you want to be able to log in using SSH, that's how you should do it. So it's initializing now. Let's just delete it. We don't need it terminate. So make sure that you terminate always your instance, so it doesn't keep on running. Otherwise, you will start incurring costs if you run more than one T3 micro outside the free tier. If you're still in the free tier, you will not incur charges for T3 micro. But if you are running multiple ones, you will you will get charged. It just depends how many hours in a month that you run it. I think you have something like 730 hours of free or something. There's a page on Ilbas that describes you in detail how these work. So this is already shutting down. So what do I want to explain you now? Well, if I do it again, the go run dot go, it's going to say create key pair error. There's a duplicate. It already exists. How would you solve that? Well, we only need to create a key pair if the key pair doesn't exist. So there's also an easy to describe key pair easy to client. Describe key pairs as a context. Easy to describe key pairs input. And this outputs the key pairs and error describe key pairs error. What one does input key name. So I default describes everything. But I just want to have key names. And then the strings last, I think it is my key name is go illas demo. And then if it exists, then people and key pairs, key pairs. What is this? Yeah, key pair info. If it's zero, if the output is zero, then I want to create my key pair. And actually, something else that I forgot, because where's my key pair? Where's my private key? I still should output my private key. Otherwise, I cannot log into my machine. So new key pair or just key pair is gonna be here. And what I want is output it key pair. What does it have key pair? Can now I need to call on key pair dot key material is a string. And that's my PEM encoded file. And I should write this out to the current directory so that I have my key available. How do I do that? There's another function for that. Oh, as right file, right file, right data to a named file, creating it if necessary. I'm going to call it go illas easy to PEM file. And then it's a byte. So I need to convert my key material, which is a string pointer to a bind. So something like this. So I dereference it. So it becomes not a pointer anymore. When I put this star sign before it. So now it's a normal string that is being passed to the byte array. So now it becomes a byte array or byte slice, and then write file and then permission. So permissions, if I give it 0600, then it should only be readable and writable by the owner. So that's the best to do for something like a private key. So nobody else can read it. What is the output of this? An error. So I will just say error. Right file error. If something goes wrong. So what am I missing? Don't think that I'm missing anything. So let's go run it again. Okay, that works. But what if I delete my key file because I don't have my private key, so I still need to delete my key file. And then I'm sure that another error will pop up that we still need to fix. Let's first delete our instance. Then let's go to our key pairs. Key pairs are here. And here we have to go as demo action delete. So yeah, once you create the key pair didn't save your private key. There's no way to retrieve it. We have to delete it. And then recreate it. Otherwise, we will never be able to log into our instance. Although, just for completeness, there are other ways to look into an easy to instance nowadays. If you don't have an illness key pair, there's still something called the session manager that you could use to look into an instance. So clear, go run. Another error. Describe key pairs but our key pair is not found. So we return an error describe key pairs. It's just an API error occurred that we have our invalid key pair not found because we were asking for this go illness demo key pair to describe it. But it's not there. So it was gives an error how to solve that. There are ways to filter on this invalid key pair not found using a type in golang with the illness SDK. But I was looking and within this illness go illness SDK go v2. If you check the types, there's actually no type for this error. So there is another package that you could use. If you want to know more about it, you can search for a loss SDK go v2 and then error handling, it will explain you how you can handle errors. So I'm going to do something that is a little more straightforward, but maybe not the best way to do it. But I don't think we'll ever have issues doing this. So what I'm going to do is if the error is not nil, but we can only enter this conditional we can only return this error if the string does not contain. So this error is basically just a string if it does not contain this statement string strings. So if not contains invalid key pair not found. It's a little bit ugly. But I found an open issue in GitHub documentation is not completely up to date for the v2 golang SDK for AWS. And that not every single type is available. So there is a solution for it. There is a package that you can use. But that package might change over time. So this is code that should keep on working. When you want to try out this demo. So if there is if there is something better that comes out in the future, I will update my code in GitHub. But for now, I think this should be fine. So let's have a look. Go run go. Oops, another error. And, and this is always when you when you start coding against this AWS SDK, there are things happening that you just don't anticipate, you always have to check a lot. How certain variables are returned. And there's also tests that you can write. So now I'm checking for this string contains so we didn't go to so we didn't go to this error. But then what happened line 44, the length of this key pairs and key pairs is just not initialized. Key pairs is a pointer, which is not initialized always. So here we have to check if key pairs equals nil, or the length is zero. So if it's not defined, then we probably got an error here. And the error could have been in with key pair. If it was really an error, we return an error, if it was not really an error, but just not found, then we enter here and then we check if the key pair is nil if the key pairs nil, or the key pair is defined and we the length was still zero, then we're going to create this key pair. And this should work. Go run go. And it created another instance. So back to our instances. So this is the third one we created now. And then we can terminate it. We don't need it. Successfully terminate it. So we clean this up nicely. And that should be it. So we have a main function, the final context. So we could, we could have deadlines here in case it takes too long that we stop our program, for example, but we don't have anything. So we just apply an empty context, create easy to create easy to will load the configuration. We need to make sure that we have the environment variables. We will from the conflict create this easy to client describe key pairs. If it doesn't exist, the key pair, we're going to create a key pair. And then we didn't check whether we have the output file. And here we have the output file, which should be our private key. So if you want SSH to the machine, you can either use putty on Windows or the SSH command. SSH command is going to be SSH minus I off the go AWS PAM file. And then the login is always Ubuntu on these type of machines and the IP address. And then you should be able to log in create key pair, write file that worked. Then we describe the image, the image outputs in my ID. And this am ID we're going to use here, specify the key name, instance type, and we only go to launch one instance. And we're going to return the instance ID. And we output the instance ID. So that should be it to launch an AWS EC2 instance. In this demo, I want to show you how to upload a file to S3. But before we can upload a file to S3, we'll have to create a new S3 bucket. So let's get started on that. It's going to be very similar as the last demo. We just need to do a few things different. So this load config, I'm going to make a function for that. I'm going to call this function in its a streak lines. And we're still going to have the region which is string. And the reason why I'm going to do this is that we going to split up our functions, going to have multiple functions where we need the S3 client. If we had multiple functions where you would need the EC2 client in the previous lecture, then I would also have done it like that. So the S3 client is going to initialize a config. We're going to return an error to what else we're going to return the S3 client as well. And S3 client is going to be S3 client is going to be all this needs to be nil for sure. But what does need to be S3 dot new from config, and then CFG, and you from config replies, replies, then this S3 client. So we need to also pass a context. So we have cdx context, context, and then the region. So this, we still need import go get. So now we have a service S3 is now imported new from config. What does it apply? An S3 client. So that's okay. But we also need to reply that we don't have an error. And then in it S3 client, S3 client. So you would need to declare it here. S3 client is of type S3 client. In it S3 client or S3 client equals an error equals in it S3 client context. And the reason we use east one. And then we're going to make it if for that if S3 client, if error is not equals to nil, then we can return something fmt print f in it S3 client error and the explanation. And then we need to define our error here as well. S3 clients declare but not used. So I think we are good for now. We just need to write another function, write our function, create S3 buckets. And we also going to supply a context. But what else we're going to supply the S3 client, S3 client, and we can return an error. And then we just copy paste this a little bit here. Or in here we need an OS exit one. And here also we need an OS exit one. So we're going to create an S3 bucket. And we need the S3 client for that. S3 client, save this create S3 buckets. And then you just apply the error. So we have no errors now. Just that we have to return nil here. And I think we all green, we all green. So create a new bucket S3 client. Create bucket. That's an easy one. Supply the context. Supply the S3 create bucket input. And then we need to give the name. The bucket name. Bucket is a bucket name. A less string. Let's define bucket name for that. So the bucket needs to be unique in AWS. That means that if I create a bucket, bucket name, AWS demo, test bucket, then if you're going to execute this lab, it's going to say this bucket already exists, because I created it, because it needs to be unique. So I would say add a random string here, like something like something like this. And then if everyone that does this demo adds a unique string, it will always be unique. Oh, so I save this a less string bucket name. And then what happened here is it imported the older LS SDK, because if you use the older SDK previously, then sometimes Visual Studio can remember. But it's actually the wrong one, because this one is the V1. I need V2. It is possible to implement something using a service that is not completely implemented in V2, or feature that is not completely implemented in V2, and then you still have to use the V1. But in general, you should use V2. And I hope V3 never comes out, otherwise I would have to redo all these lectures. Create bucket. And then there is a lot of explanation here. And why is there a lot of explanation here? Because again, S3 exists already for a long time, has lots of features. And there is a possibility that you will get an error depending on what region you are using. By default, the bucket is created in the US, US East, North Virginia region. You can optionally specify a region in the request body. You might choose a region to optimize latency, minimize cost, and so on. For example, if you reside in Europe, you will probably find adventures to create a bucket there. And then you might have to specify something else. So let's just execute this and see how far we get. If I do go run.go, it seems to have created the bucket. That's no error. How can I know that? It was S3ls of this test bucket. Let's try that. Okay, no error, that means it has been created. Good. Let's just see if I would create this in Europe. You lost one, just gonna give it another name. If that would work. And if it doesn't, I will have to change something. Okay, that seems to have worked as well. No such bucket. US one. Oh, oh, I already see what is happening here. I need to specify error. And I don't really need outputs. So the first time they actually worked the second time it didn't work. So I'm gonna say create bucket error. And let's see. Let's see what we get back as an error when we run our goal and program in US one on this bucket. Create bucket error. The unspecified location constraint is incompatible for the region specific endpoint this request was sent to. So yeah, with S3 buckets that are not created in US East one, you might have some issues. And that's why I wanted to cover all the use cases here. If you're gonna create a bucket in another region, then US East one, you will need to add something. Let's try to fix that. So what do we have here? Create bucket input. Create bucket input configuration of types create bucket configuration, types create bucket configuration. And then you see now it's a street types and not easy to types anymore. And what do we have here location constraint specify the region where the bucket will be created. If you don't specify a region, the bucket is created in the US East region. So we also want to specify location constraint. And this is of times bucket location constraint. And what this is a string. So I can add a region here. And then we just need to pass the region again here. region string. And then we just add the region here as well. Okay, maybe I should make the region a constant. Why not region, region name us one because we're using it in multiple places. And then I don't have to pass it anymore. region name, remove this, remove this, and then region name that should work. And let's now see whether we can create a bucket in a different region. seem to have worked. And this doesn't give an error. All good. So I'm not going to use this bucket here. I'm going to use this first bucket in US East. So let me just bring it back to US East one. So we created our buckets, but we only have to do this once. Then again, we can do the scribe buckets or list buckets to check whether this bucket already exists. So how do you do that? as three client list buckets or describe buckets. There is no describe buckets, so it will need to be list buckets. What does list buckets say returns list of all buckets owned by authenticates and their touch it work list buckets. And then as three list bucket inputs, what's going to be our input? There's no input. It is going to list all the buckets. All buckets error. And then we will return an error. For example, these errors can still be hit as in we can still have an error if you don't have permissions for it. And that's what it says here. You will need to you will need to as three list all my buckets permission to be able to do this. And then we need to say that we find a bucket will start with false. And then we need to iterate over all these buckets. So let's have a look. Range buckets dots buckets. All buckets is the variable. But in all buckets, you have buckets, which is of types buckets, and then this will be done a bucket of types bucket. And if you have bucket name, we can compare the name, which is string. It's a pointer. So we need to make sure that we compare the actual names. So that's why we need to start. Otherwise, we are comparing the addresses. If bucket name equals to bucket name, which is our static variable, then font is true. If not found, then we're going to create this new bucket. And otherwise, we just gonna return nil. Whoops. Let's see that works. Save. Go run. And that seems to have worked. And we still have our bucket. We still have our bucket. Let's upload something now. We are going to pass this S3 client to another function. Upload to S3 buckets. And I'm gonna say upload to S3 bucket error. If we have an error, let's make a new function. Upload to S3 buckets. And return nil if you have no error. Okay, do we have still errors? No, no errors. What do we need to do to upload something to S3? We need our S3 client. And if you type upload, then you see upload part uploads a part in multi part upload. That's a really low level function. So this is not something that we are going to use. Amazon actually has other functions available for us to upload data to S3 with higher level functions. We have to do less. If you have a specific use case, and you cannot use those other functions, those high level functions, then you would have to use those. But for us, for a simple use case of uploading a simple file, github.com Ilvas feature S3 manager. So we're gonna get this package manager. And then once it imported, we'll be able to use it. Manager dot. Take some time to import it. If it takes some time, we can see there's no errors. And it seems github was just a bit slow. So now it's imported. Manager. And what did it say if I type upload, new uploader, new uploader creates a new upload instance to upload objects in S3. And this we should be able to use new uploader. And we need to pause our client as three clients. And this will give us a new uploader uploader equals manager new uploader. And then we can upload something to S3 with our high level function. Within the uploader package. What do we have here? Upload uploads an object to S3 context, and then S3 put what was that put object input, put object input. And that should be it. And this will give us an output, the output we probably don't need. It will return things like bytes, output and stuff like that. So we just need error. If there's an error, let me say upload error. What are we going to supply the key, the bucket, the key is just a file name, and the body, and the bodies of IO reader, which we have used previously. And do we need something else? You might need something else. For example, if you want to make an object public, you can add permissions, but we don't really need it now. So I'm going to keep it simple. bucket is the bucket name, convert it to pointer prefix is going to be the name of the file. So this can be a directory as well. So it can be directory slash test dot txt, something like that. And it will automatically create this directory, because there's not really directories that exist in S3 just a prefix. This doesn't need to work. unknown field prefix, key, key is an N. So the directory is called prefix, but it wants key. And then the body of it. So the body can be, you can maybe read a file first with the IO package to read a file, or we can just say, I'm going to create a new buffer, a new reader, which gives us a strings reader, which implements this IO reader interface. And it will just say hello world. So this also works. If you want to read from a file, you can also use the IO util reads file. This reads from a file. If you want to rather read from a file, a local file, we are using strings new reader, which gives us this body. And then it should upload a file. Let's have a look. We can output upload complete. Just so that we know the program executed, upload complete. Let's hope it is now in our bucket. And there is our test of the XT. Let's copy it locally to see if it worked. And then you can also use the iOS web console. If you want to download the file with the browser, or you can just read it here, hello world or on the console, this command will only work on Linux or macOS, it just outputs a file, hello world. So our upload of our test of the XT with our text hello world worked. So in this demo, I uploaded a file to S3 and I did it with a little bit of text. I'm sorry, if I'm going a little bit quick over this demo, but a lot of these code is just boilerplate code because you can see that it just this piece of code that is new. And this piece of code is new. And then here it is on for loop. But you can see that there's a lot boilerplate code involved to do these illness calls. I can still show you one more thing I can show you if you want to upload a file that is from our local drive. So like I said, this IO util we can use read file. And the file name is test.txt. And it returns bytes and error. So test file error. And then if we cannot read the file given read file error. And then we have a test file which is bytes. So what do we do then? We remove first this column and then bytes new reader bytes do buffer. Oh, there's new reader as well. New reader or new buffer. It's new reader. New reader from bytes test file. So this gives us a reader reader implements the interface IO reader. So this should also work. Go run upload complete. Let's now call test to the txt to see if the file matches. And yes, it is now a hello world with three explanation marks just like I had here in the test.txt. So if you have a local file that also works, if you do an API call like HTTP get and then you get the full body and then you upload it to S3 that also works. So where your source file comes from, that doesn't really matter. As long as you can give an IO reader, which can also stream this data, because it's a reader interface then you'll be able to upload a file to S3. If you want to delete your bucket afterwards, there will also be an S3 client delete bucket. If you want to delete your bucket manually afterwards, then you can do a less S3 delete bucket a less S3 delete bucket. And you can also do it over the interface. And a less S3 RM can remove the file. So if I do this, then the file is gone, because you will be charged for storage, for get requests and put requests. If you are in a free tier, you have some free usage, as long as you stay within the limits. But even then, if you have to pay for it, it's really only a few cents, as long as you keep your files very small. So that's it for this demo. So we uploaded a file into S3, and we created S3 bucket. Now that we uploaded a file to S3, let's try to download it. Next to our upload to S3 bucket, we're gonna have download from S3, pause S3 client. And most likely we'll get back the contents, which is going to be of bytes. So we're going to call it out. And I want to say download complete. And I'm gonna output it. And we just have to write this function. So our bind is how we typically transfer file contents with a slice of bytes download from S3, same signature as upload from S3, so I'm just going to quickly paste this function. And I'm going to call it download from S3. What I'm going to return is also bite. It's very small files. So I can just return the contents in one variable. I will then return empty bytes. So everything is green. We have here the new uploader manager. There's a new downloader. This new downloader accepts the S3 client and returns a downloader downloader equals this manager new downloader. And then we should be able to initiate a download function, download, and context and a writer at this writer at will be a buffer, we just then need to provide a variable of the writer at interface. And that will be the actual content of our file that we want to download. We will provide a variable of the writer at interface. And the downloader will then download the contents of this file of S3 into this buffer. S3 get object input. And this returns n and r. The n in 64 returned is the size of the object downloaded in bytes. I'm going to call this num binds and then error. If you have an error, then we say download error. And then we just need to define this buffer. Buffer equals manager new right at buffer. So they have a function we can use that returns a manager right at buffer and implements this writer at. We just need to provide an initial buffer, which we can just do by providing an empty byte. And then we can return here the buffer. And if you return the bytes, it will return a slice of bytes written to the buffer. So this just returns all the bytes that are in this buffer. So download will put everything in this buffer, and we will output the buffer. And if there's nothing to output, we can just output nil. We would still need to whether the num bytes matches the bytes in our buffer, just to be sure that we download it properly. If num binds equals to the length of buffer bytes. And if we give this a variable, then we can nicely give an error code with the correct variable. Num bytes received equals this length of the buffer bytes that we received. And if then the num byte is not equal to the number I received, then we're going to draw an error. Num bytes received doesn't match. And then we can just output it num bytes. And num bytes received. Num bytes is in 64. And num bytes received is int. So need to make sure that we are comparing the same things. We can just say that our int needs to convert in 64 first, so we can convert to the num bytes. So what are we doing here? downloading, checking for errors, checking for num bytes, and then returning the actual bytes. What are we downloading? That's what we need to provide here still get object input bucket and a string. Bucket can just copy it from here actually. Bucket and the key. Save this and then download complete here output it. Okay, let's have a look where this works. Go run. Go file. Upload complete as this one. Download complete that this one. And it's the hello world. So the hello world that we uploaded here from our test.txt. So if we now change it, this is a change that it should first upload this change and then download this change. Okay, that also seems to work. So our upload and download seems to be working. Also when you upload something to S3 and already just it is going to override it. So that's why we're not seeing an error. It first overwrites it and then in download it again. And we see that we can download something from S3 and output it. That's it for this demo. We have a simple upload and simple download function to write and read to and from S3. Now that we have completed our AWS upload and download industry. Let's also have a look how we can write tests for our new code. I'm going to create a new file main test.go. And what you could actually also do is you could move those functions to S3.go and then have a street test.go. That is probably a little bit more clean than what I'm doing. Package will still be main. Let's test our create S3 bucket. Just create S3 bucket. T testing T. Save testing is imported. And we want to call our create S3 bucket with a context and S3 client. Contacts background is a good one. Create S3 bucket error. If error is not nil, let's just exit create S3 bucket error context. And then now this S3 client. We don't really want to make real S3 calls in our testing. So we will have to write another mock S3 client. What should our mock S3 client implement? It should implement this list buckets and create buckets. How did we do that last time? Last time we created an interface. And now we will have to do the same. So list buckets is a function that will have to implement in our interface. So let's make a new type are S3 client, which is an interface, and it will have these two functions we are going to use list S3 bucket and create S3 buckets. And then we can change our function signatures, create S3 buckets, get the S3 client. And that should work. You can also return it here. But then we are going to get into some trouble if we are going to pass it to our upload S3 buckets. So let's just change it here do the test and then we'll continue explaining how we do the other functions. S3 client, if I save this, this will be still working. Looks okay. Just create a street bucket. And then we can create our mock client. So we're going to have type mock S3 client is going to be a struct. And then it's this struct will have these two functions. List bucket and create buckets. So this will be a function and mock S3 client twice. And we just need to create a function. And then we have the list. Then we have the list bucket output that we want to return and create bucket output. So we can just like last time we can say S3 list or just list bucket output. And this is of S3 list bucket output and create bucket output. And this is of S3 create bucket output. And then when we return your turn m dot list bucket output and no error. And here we return the create bucket outputs. List buckets output. That's better. And then he will pass our mock S3 client. What buckets are we going to output? We can output buckets of types buckets. Will these types be imported? Probably not. Let's copy it from here. This is going to be times buckets buckets. I need a colon here, comma here, another comma here. And we can say the name is test bucket. And it will still create our names undeclared. Oh, yeah, it's a slice. So we need to add one element. So this is one element. If you want to have two elements, it will be like that. That's bucket two, let's add two elements. And this will test our for loop here create buckets. So this will test our font. And then we will have the create bucket outputs of S3 create bucket outputs. And will we have anything in here location? But we're not using it. So you can just leave it empty as is. Let's put a breaking point and let's have a look as first run it without debugging. Okay, it runs. Let's now have a look with the debugging. So font is false. And actually what I wanted is I wanted to go in this. Okay. So we are comparing all the buckets and in buckets, we have two buckets test buckets. And our test bucket two. So this is how we can test our logic after an API call and then we create a bucket. And this returns no error. The error is nil. And we succeed. So this is how we can test API calls. And this we can do for every, every API call to a list we make. There's just one slide problem with our upload to S3 buckets. Because we are passing the S3 client. And then here we are initializing our new uploader. So this ideally, we would like to get out of the upload to S3 buckets. And if you don't have to pause S3 client we can then pause uploader. And if you pause upload, we can then implement an interface for S3 uploader. So the way we would do it is type S3 uploader interface upload. And we would also have S3 downloader interface. And they would put the download, we would changes into S3 uploader. Actually, no, one second, we would first change this uploader is the S3 uploader. And then we don't need this anymore. So we'll just take it away. But we still need to pause the uploader. And the uploader we can pause like this. Because the new uploader implements those two functions. Or this one upload function. And we can do the same for the downloader. So we can say here, manager new downloader, S3 client. And then here down, we can say the downloader is of S3 downloader and then we can also implement tests. Save this. And then there's still one problem, we are doing a read file. And we could just pause for example, the file name. Because even we don't test it, we might still want to read a file. But we might have another path to the read file. So I'm going to take this away, file name. Here's a well file name. And boss this right here, test.xt. And here, if we want to create an uploader, we have to make a type S3 mock S3 uploader, which is struct, implement this upload method. M mock S3 uploader. And this gives the upload output, upload output, return upload, M upload output and nil. Is it nil? Yeah, it's an error. Save this. And then when we create another function, test upload to S3 testing, what is it called, download from S3, upload to S3 buckets, test upload through S3 buckets. It doesn't need to exactly match, but it's nice to have it exactly match. And what do we need to pass context, uploader and file name. So here in our files, we could then make new folder test data. And test data, we could have new file test.xt. This is a test file. And it's not not going to really upload, but it's going to read it. Upload to S3 buckets. Contacts can be context background. And the uploader is going to be our mock uploader. And then we have a file name, which is going to be test data, test.txt. Let's call this mock uploader. And then we're going to define the mock uploader right here. What is going to be in the mock uploader, upload output, upload output, we're not really checking for it. So it can be empty. Upload output needs to be a manager, upload output. And it returns location upload ID. But if we are not really checking on it, if you are not really checking on it, we don't really need to add anything. We're just going to check if we have no errors. If you have no errors, then our test was successful. If there's not equals to no, then we have an error of the upload S3 buckets, we're not testing a lot of code here. But you could potentially have a lot of other code here in bigger applications, where you're going to do an upload, and you want to test your whole flow. So right now it's just small function. But you can easily see that it could get bigger. Let's put a breaking point. Now the file name is test data test.txt. And we read the data. And in test file, we now have this is a test file. And we have the bytes in there, which we are going to upload. And the uploads return nil. If we want that we could still write some more tests in our list bucket or create bucket or our upload to see if the arguments past are correct. For example, the put object input is this we could test basically on this where we are supplying a body. If you want to write more evolve tests. So this is how you typically test AWS endpoints, you write a mock uploader or a mock client for every service that you are using. And then you make sure that you pass this to your function. And as long as you implement the correct interfaces, then within the function, the client will always be able to do an upload or download a list bucket or create bucket. And then you can mock these functions in your testing file. In this section, I'll be covering Microsoft Azure. I'll show you how to use the Azure Go SDK to execute API calls on Microsoft Azure. What do we need to do before we can use the Azure Go SDK? First, you need to create an Azure account if you haven't done already. And you will have to download the Azure command line utility. Then you can run a Z login, which will authenticate you in the browser. And then the credentials will be configured in your home directory in the Azure directory. So Azure stores a token in this dot Azure directory in your home. And then when you use the Azure Go SDK, it cannot load these credentials from a token file in the directory and can then use the Azure API. In our first lecture, we want to create a virtual machine on Azure to show how the SDK works. To be able to create a virtual machine, we have to go through a few steps. The first step will be to create SSH keys. And I actually have a separate lecture on how to create SSH keys. So we are just going to use the code that already exists as a library in my GitHub repository. If you want to know the details about creating SSH keys in Go, you can have a look at that separate lecture. Then we're going to initialize the SDK, retrieve the token from our config, the token that has been stored by the Azure command line utility. And then we can start creating resources on Azure that are necessary to create our VM. First, we need a resource group, which is just a logical grouping. So that's pretty easy to create. We can then create the public IP, the vnets, the subnets and a network security group. We can then create a network interface that we can attach to this newly created virtual machine. So then this newly created virtual machine will then have a public IP address that we can then contact and SSH into it. To be able to SSH on port 22, we also need to create a network security group. So the network security group can allow access to port 22 based on an IP address, or it can also just allow access on port 22 so that we can test our newly created instance. That's what we are going to do in our first Azure Go SDK lecture. The first step before we can use the Azure Go SDK is that we need to install the Azure CLI if you haven't done already. So I just typed in Azure CLI in Google. And one of these Microsoft dot com links, the first one here will bring you to the correct website to be able to download this Azure CLI. So here are the instructions to install them windows on Mac on Linux. So if you are on Windows, we can just download the latest release or you can download a specific version on Mac. The easiest is to use brew. So I installed Azure CLI with brew. Once installed, you should have the AZ command available. And the first thing you have to do is to use AZ login. So if you open a new terminal, then you can type AZ login. And AZ login will open your browser, where you can then log into your Azure account with your Azure credentials. This is how it looks like, you'll be able to pick an account if you already have set up one, or you can use another account. And then once you give your credentials, it will say you have logged into Microsoft Azure. And then you can close this window. So make sure that you have opened an Azure account first, and then you just have to log in. And then the Azure command line utility will have saved the credentials in your home directory. Once your credentials are set up, you can use the AZ command to check whether your credentials are set up correctly. So if you use a command like AZ account list, it should output you a list of accounts that are available. So if this command works, and you don't get an error, your credentials are set up correctly on your own machine. Let's get started with our Azure instance demo. I'm going to try and create a virtual machine on Azure using the Azure Go SDK. First thing I did is I did a Go mod in of the Azure instances. And I already put a little bit of code in place, just three functions, the generate keys function, the get token function, and the launch instance function. So the generate keys is the only function that has some code in it, the other two have nothing in it yet. So what is this generate keys? This generate keys is going to use this SSH demo code to generate a private and a public key. So whenever we're going to launch our instance, we are going to create a my key dot PAM and my key dot PAP. So private and a public key, going to write this in this current directory. And then we're going to return the public key as a string. This we don't need to launch the instance because we're going to launch the instance with our public key. So we don't really have to worry how to generate these keys. These keys will be generated by this piece of code. If you want to know how this essay generate keys works, there's a separate lecture on SSH that you can have a look at. The next step is to get the token, the Azure token, so the initializing of the Azure Go SDK to get as a token that we can do API calls with. This we still need to write. And then we can try to launch the instance. So first we need to create a resource group, then all these other resources. And then finally, the virtual machine can be launched. So let's try to get started. I will first write this get token, try to execute that, see if we get an error. And if we don't get an error, we can start with a launch instance. So how do we initialize the token? Well, maybe let's have first a look at the Azure SDK. Let's Google for the Azure Go SDK. So here's the Azure SDK for Go on GitHub. So here's a lot of information about the Azure SDK. It says you can find the library folders grouped in a service called SDK directory. And you need Go 1.18 or later because they are using generics. And then there is also older packages that you can use, but you don't need to use if there would be something that is not implemented in this newer SDK. So if you have a look at SDK, here we have an AZ core, AZ identity, and the resource manager. So these are all packages that we can use. Let's have a look at this AZ identity. The Azure identity client module for Go provides Azure Active Directory token authentication. So we'll have to start with that. What do you need an Azure subscription, which you should have? It should be authenticated and Go 1.18. Here it is also explained what type of credentials you have, how you can get your credentials to the environment or to a managed identity. If you're on an Azure host, then you have a managed identity. We are not on an Azure host. So we want to use the command line utility. So let's have a look which one would be best for us. And here we see, for example, this new Azure command line interface credential. So now that we have the command line interface installed, we can then use this command to retrieve a token from our stored directory. And we also need to do the Go get of this AZ identity. So let's try that out. This is our get token AZ identity. And then the Go get is downloading. Let's handle our this error, return the error, and then otherwise we'll return the token. I am returning here a string, but I'm not going to return a string. I'm going to return whatever this credential is returning. So this has been downloaded. Save this AZ identity returns an Azure command line interface potential options. No, it returns the Azure command line interface credential. So let's see. But what do we need to actually invoke this API? We can have a look what we need to know what we have to return here, whether we return a string, if you do a get token, or whether we return this whole variable. Let's output right now an empty string. And then we can see when we are going to launch our instance, when we going to create our first resource, what exactly we need because it will be more clear. So I'm just going to save this. And I'm already going to run it to see if this new Azure command line interface credential actually works. So I do go run. Okay, that seems to work. My key has been created. And this get token was executed. No error so far. If you get an error, there's probably something wrong with your credentials. If you don't get an error, your credentials should be good. Let's now have a look. So we did the generate keys, get token, we are we are just sending an empty string, because we don't really know exactly what we need in our launch instance. But let's try to do the first step of our launch instance, creating the resource group to see what exactly the type is that we need. And I will tell you why I don't want to put the exact type here, because here we have the Azure command line interface credential. And that seems to me a variable that is not abstract enough, it should just be an Azure credential, rather than the Azure command line interface credential. And once we start using the function to create resources, we will see what type that we need. So let's start with the launch instance function. So remember the graph that I showed you in the previous lecture, the first step is to create a resource group, a logical grouping within Azure. So this is the identity we did. Let's go and have a look in SDK. And the resource group is part of the resource manager. So if you click on the resource manager, you will see that everything that is managed by the resource manager in Azure will be right here. There is also reference documentation where you can potentially more easily search for things. But then here, if I look for resource, resources, then within resources, we have the ARM resources. And this ARM resources can create for us a resource group. So this module provides operations for working with Azure resources. And here we have the reference documentation that I can also open. So do we have some examples here? The new client we can use. And we also need the subscription ID. So this subscription ID is also a concept in Azure, where we now have a token of an account, but an account can have multiple subscription IDs. So this subscription ID, you will have to retrieve still, and we are just going to pass this as an environment variable. Because once you have multiple subscriptions, then you will have to pick yourself in what subscription you want to launch your resources. So I'm going to pass this as an environment variable, but you can also pass this as a flag or hardcoded, but be careful when you hardcoded that you don't commit it to your public GitHub repository. So that's why you probably want it as an environment variable or as a flag. I'm just going to copy this, just to start with, I probably don't need options right now. And here is more sample code of a resource group. And then they actually have a main.go. So create resource group is the one we need. Create or update resource group. And we need a context resource group name, the location we are going to pass. So the location is the physical location, where you're going to launch. So they probably have here West US as a static variable declared. I'm also going to declare the location static. I'm going to do get enough of the Azure subscription idea as well. And that's how we're going to pass it. So we have a subscription ID. And then I'm going to initialize this client for a resource group. And I'm going to try and create a new resource group based on the same code here. So client is the new client and I need a subscription ID. So I'm going to say subscription ID. And then I can say string. Or if my type is the same as a next parameter, I can also remove it. So this is going to be the subscription ID, the credential and then the options. But if you don't need to pass any options, you can also pass nil. arm resources, we didn't do a go get of this. So I will need to go get of this. So this was in the resources. I think it was this one, not 100% sure. No, it's not that one. It's this one. That's it. So I'm going to save this one. Now it's imported the arm resources. And the new client is asking for a credential, an easy core token credential. So this is what we probably want to return here in our get token. So let's have a look what happens. Az core token credential, it's an interface of get token and our token here. Azure command line interface credential. It also had this get token. You see there's one function, the get token function. So if you reply to token and we have this interface get token, then we can nicely pass this credential. So we'll call this credential. So subscription is a string and the credential is an Azure core token credential. And I just need to change it right here as well. Our token is in token credential. And we still need to pass the subscription ID as well. So and the context. So let's try to install those context background and subscription ID is always get and subscription ID. So either we pass subscription ID here with our go run, or you do add configuration. And in this add configuration, go launch package, you also can have nf and nf is an object. And here you can then have the subscription ID if you want, like this. So if you use the run within Visual Studio code, you can define it here is subscription ID. Otherwise, what I'm going to do is I'm just going to export it. Export, which doesn't really work on Windows, you might still be able to use another way to set an environment variable, maybe something like set would work on Windows, export subscription ID, and then the subscription ID that I want to use. And then I will do this command here, and then I will do a go run. So this subscription ID needs to be set, because we are going to read the subscription ID. And also if you are Windows, I don't really want to set it as an environment, you can also pass this as a flag. We have some other lectures where we used flags, the parameters that we pass. So this subscription ID, we can actually say if one subscription ID is zero, then no subscription ID was provided. And then we exit. And then we're going to pass the context, the token, and the subscription ID, or actually subscription ID was first and then there was a token. Let's have a look. So subscription ID, first we have a context of context context, then we have a subscription ID, which is string, the credential and the public key. And that should be it. So we have the new client is of arm resources or arm resources client. Now we had our resource client. If there's an error, then we're just gonna return error. And then we can use this arm resource client to create a resource group. Let's have a look in this example again. So here we had this example. Oh, we need to create a new resource groups client. But if you did that new resource groups client, and then we can use the create or update of this resource groups client. And copy this resource groups client, new resource groups client. Parameters are the same. And then we can call this the resource groups client. And then the resource groups response is the resource group on the cloud group, not groups, resource group client, create or update, we pass the context, then the name, then the parameters, then the options and options can be nil. So resource group name, what should we name it? We can name it go demo. And actually, I like it more if you just put it online, because not that long. And then let's just say resource group params is these parameters. Just typically, the parameters are going to be longer. And then we can still put this on one line, resource groups, response, okay, and then the parameters, okay, and then the location. What is the location going to be? We can also use a static variable const location is West US. Do we now have everything? Oh, we have a comma too much here. This two is not declared. Okay, and I just saved it and it was declared. This two is also a package in the AZ core. So this two is a helper function, just like we had with the AWS SDK, we need to convert our string to a pointer. So to PTR actually takes a string converts to a pointer, returns a pointer to the provided value. And then we have our location to our location variable, but it just passes a pointer. So you will see this two pointer is used a lot just to convert it to a pointer. And this is actually the reason why you need go one of 18 for this SDK, because this pointer, and it's a function of T any, this pointer takes any type and will convert to a pointer. This implementation is done using generics. So it can take multiple types and then we will return you the type as a pointer. Now we have our resource group response. Do we need it? We probably need it a little bit later. Let me just put an underscore here, save this. And let's try to test it. So I'm first going to set my subscription ID. So how do you find your subscription ID? Easiest way is to log into your Azure portal, and then look for subscriptions. Click on subscriptions. And then here, I have only one subscription. And my subscription ID is this one. It's a UID. That's how it looks like. Export subscription ID. And then if you're actually on Windows, the Windows equivalent of export is set. You can also do set instead of export. And with this UID, if you want to go run, that should also work. You can also have a bash installed on Windows if you want to use a bash. That is also a very nice alternative. So we have exported our subscription ID. Go run. And they should then create our resource group. Oh, invalid resource group location. Oh, and I already have this resource group in my West Europe. So what happened here is we have an error that there is a conflict. I already have this resource in West Europe. And I'm trying to recreate this in West US. So I would have to first delete this resource, or I can just give it a different name. I will say go Azure demo. You can use Go demo, unless you also have used a name that is already in use. Go run. And that worked. You can always verify whether those resources have been created by checking the Azure portal. So we have resource groups. And then you can see our Go Azure demo. And if you click on it, we can see the resources within this resource group. And there's nothing yet. So we are going to now create the next resources the next resources that are necessary to launch our VM. Now that we have created our resource group, the next step would be to create our Vnet. Let's have a look again in the documentation. This was the arm resources. And if we have again a look at the resource manager, and then in network arm network, we should have the virtual networks. There are a lot of files here. Let's have a look what they say here. More sample codes, a virtual network, and a subnet. And to create a subnet, we also need a virtual network. So maybe I will just have a look at this example code, right here. Because here we also create a virtual network. And then we create a subnet. Let's have a look at create virtual network. We need the arm network for that. And the arm network is resource manager network, our network. I'll copy this already. And then I'm just going to copy paste this code. And this code is a little bit different than our resource group, because our resource group. And we have it still somewhere here, the resource group, the create resource group was just new resource group client and creator update. And the resource group is immediately created because it's a logical structure. It's not a resource like a network or a VM that you have to launch. So you just kind of create it, and then you immediately immediately get a response. But we does venus and with a lot of other resources, you actually do begin create or update. So it's not a create or update, it's a begin create or update, it just starts it and then you get a polar response. And then you can use poll until done to wait until this resource has been created, because it can take a few seconds, it can take a few seconds, it can take a few minutes before this resource is created. And only when it's created, you have the response. So that's a bit different than our resource group. And most resources are actually like this, where you create a resource, you have to wait a little bit, and then you get the response. So let's copy this code to create our virtual network client and then do a vNet creation. So I'll just copy this. So this was our create resource client. And now we're going to do a create vNet. And this needs the ARM network. Go get our network. And this also takes a subscription ID, the credential and options, which is null. And then we have the begin create or update the context, the resource group name, resource group response is here. And now we can use this resource group response, colon here. And we can use the name, the name of the resource group. It's basically the same as here. But then if you would like to change the name, you only have to change it in one place. This is the resource group name. And then we need the virtual network name. Go demo will call everything go demo from now on. And then we need the parameter parameter is of our network. So I'm just going to save so that our network is imported. And this is a string pointer. But what does it need? Just a string. So just going to put a star in front of it. So it's being passed as a string and as a pointer. And then we have the virtual network. So location we have. And then we can pass the properties. And one of the properties is the address space. So every Azure resource has properties. And because this parameter is just a struct, it is actually very easy to see what is required as properties, we have the virtual network, virtual network has a location that you need and has a properties of the virtual network and the properties is of a virtual network properties format, which is then this type, this struct type that we define here. And then we have here all these options. The main option that we have to pass here is address space because a Vnet needs an address space. So address space needs an address prefix and address prefix is a slice string slice as a pointer. And then we have our string. But because it expects pointers as elements, we do a two pointer. And then we can say 10, 10, 00 or 10, 116, it actually doesn't matter, it's just your preference, what you would like to have as your address space. And then if there's no error here, then we are going to pull until done. This is the polar response. So pull response pull until done, we pass our context gives us the response. And this response is our Vnet response. And this Vnet response, we can then use later, if you need to know the name, instead of using the hardcore at volume. So what is next then the create subnet. So the subnet works very similar, we have a subnet client. So we are going to inside a sub client, begin create or update, and then pull. So I'm going to copy this, put a comment here. Because here we are creating the subnet, the subnet, if there's an error, we're going to return error, the polar subnet client, polar response. So this is going to be a problem, I think. Can we reuse this polar response? Probably not. So let's call this Vnet polar response. And then let's call this subnets polar response, because these types are different. So we need to have different names for them. And there's going to be our subnet response. And we need a resource group name. So resource group response, resource group, response dot name, we need a virtual network name, vnet, response dot name, and we need a subnet name code demo. And then we're going to create this subnet. And then we have a subnet response. And then we should have our subnet. We can already try to run this. No errors. Let's do a go run. And this is going to take some time. But they should run without issue. Now, at some point, we will get errors when we try to execute it again and again and again. And then it's best to actually start checking whether something already exists. Because ideally, you want to be able to run it over and over again, even though some resources exist. So let's see what happens if we're going to run it again, now that our vnet and submit already exists. So that went actually fine. Because there are no dependencies yet. But once we start creating dependencies, there's going to be an error that we first have to remove the dependencies. And then we can only create this vnet. So let's continue a little bit. And then I will keep on executing this. And at some point, we will see that we will get an error and we'll have to add some extra logic to it. What is next, after we created the vnet and the subnet, we can create the public IP. And let's have a look how we can do that. Now we should be able to write ourselves without using an example. I think the arm network should also support a new IP address client. So let's try if we can have that. So it should be very similar as this IP client, new IP public IP address client. So you see there is actually a lot of resources here interface IP, load bands or frontend, virtual hub. If you check the Azure documentation, you can see what differences between all these services. You can also sometimes start from the UI, create a few resources and see then what resources in the end you would need. And then write is in Golan using the Azure SDK. Public IP address client. So it's going to be our public IP address client. And then you see the parameters are always the same. So you you get the client, same parameters, then use this public IP address client to begin or create something. And this and also has very similar parameters, it just a parameter of this resource that is then often difficult to know what to fill out. But then it should be the same what you fill out when you would create the resources using the portal. So if you don't really know what to fill out, I would create the resources first in the portal, see what the parameters are, then use those parameters in the SDK. So the context resource group name, public IP address name, and then parameters context, resource group name is the resource group response still, and then the name. And then we have the public address name code demo. And the parameters of our network, public IP address, and then options, which can be nil, then a comma, and then we just need to fill out the parameters here. And then we will have the polar response, the polar response, polar, public IP polar response, then the error. And then we can do the poll until done. And we also need to check on errors. So we can just go and paste this. So that's polar response, public IP polar response, poll until done. And begin create or update and we just need to do the parameters. What should be the parameter location, which we can copy paste from another one location, and then the properties. And the properties is of type, our network, public IP address properties format. You also see that the visuals to the code are to complete it to make it easier. And then what IP address do we want to associate? We don't really want hardcore IP address. We probably only want to define the allocation method, because we want a static IP address. Public allocation method. What does it accept the IP allocation method? And a comma. Oh, and this is a string. This is not a struct. It's a string. And it's a pointer. So two pointer. And the string is going to be the allocation methods. But normally, when it asks a string, and it's some kind of methods that there's only one or two options, there's going to be there's going to be variables declared within this package. So you cannot miss type is way about type arm network. And then I type in allocation, no allocation, there's nothing IP, IP allocation. And then here we have IP allocation method dynamic. And we need static. And this is of type IP allocation method. So sometimes it's a little bit difficult to look for it. You can also have a look at the reference documentation and then just look for this type IP allocation method to see what is available. But you can see if you look a little bit and you type in part of the name, you often can't find it like this. So we are going to create this IP address, we want a static IP address, we don't want a hard code IP address. So we're just going to pass this. So this is going to give the polar response and I will wait until we are done. What's next? I'm just going to add some comments so that I don't lose track. This was a public IP. What is next? Let's create a network security group. That's also going to be in this arm network. So let's copy this a little bit because we can start working from this new network security. If I type network, there's nothing. But if I type security groups, there's a new security group client creates a new instance of new security group client. This is going to be the one that we can use new security group client groups client, use a security groups client creates and or updates a network security group. So that's the one that we need. So again, it's not easy to always find the correct resource. Scrolling through the reference documentation and just having a look what is available. And in the GitHub repository, having a look what is available is often the way to go. This then has a network security group response that we can use. It's going to be the polar response that we can use right here. Network security polar response. Paul until done. And what does it need? The context, the resource group name, the network security group name, go demo, and then the security group as a parameter. The location is going to be the same. And then we have the properties. It's going to be of security group properties format. And then we're going to have a different contents here. A collection of security group rules. So now what we want to do is we want to allow access to port 22. You can allow access to port 22, just to your IP address, but to keep it easy, we are just going to allow our access to port 22. And then you can still change it if you only want to allow your IP address. I'm only going to run this instance for a couple of minutes. So it's easy to then just allow our traffic. So you see, we have all of read only stuff. So security rules will be the one to go with. So security group rules is new security group rule. And then we have our elements. And this needs to be of time arm network. Let's have a look what we need to put in here. A name of the security group. And then again properties. And then we have the rule itself. So we have a name. And this is going to be pointer, I guess. Pointer is the pointer. Yeah, it's a pointer. So name is going to be allow SSH. And then we have the, what was it? Properties. Properties is of arm network security rule format. Properties format. Let's see. Yeah, and then it added this ampersand here, because it needs to be a pointer. And then what do you have again, a description, destination address prefix, destination address prefixes. So one is just a string or pointer string. And then one is also a slice, destination port range, ranges, priority, priority is just a code. So these actually just reflects exactly what you see in the UI. If you have created a network security group, this is how it looks like. So the content is going to be exactly the same. So you always have a source and destination. So let's start with a source source address prefix, we only need one. So I'm going to say prefix to pointer. What is our prefix going to be? Just all IP addresses, source address, source port range, source port range can really be anything. So I'm going to say star, because we don't really know what the source is. The destination, though, is going to be 22. So we have destination address prefix, and this is going to be the same. We don't really know what it is. It doesn't really matter. But the destination port range is going to be 22. So we are going to allow access to port 22. We're going to have a protocol as a protocol going to be TCP. So we have Azure Firewall network rule protocol TCP. This is correct. We are looking for what protocol security rule protocol. That's not it, I think. What do we have our network? Something with TCP? Okay, now I get a lot more. Something with TCP network inbound security rules protocol TCP. I think that's it. No, because it's of inbound. I just need security rule security rule. There it is. If I type security rule TCP, it actually matches security rule protocol TCP. And this is of security rule. And this is of security rule protocol. And this is what I'm looking for. What you also have you have the protocol. What is the access? You're going to allow allow access allow security rule access allow. That also matches. And then what else do we have? Direction and description. Direction and description. Maybe first description. And you often only write these things once in your life and then you just copy paste it. That's how I do it. Allow SSH on port 22. Direction also to a pointer and the direction is going to be inbound security rule direction, our network inbound security rule inbound direction inbound security rule direction inbound. Yeah, that's it. So this rule is for inbound. And then we still need the priority. The priority is what? Int 32. And this value can between 146. So it only depends if you have multiple rules. I'm just going to say 1000 or 1000. I want to pointer cannot use value of ind need to be in 32. So if I say in 32 of this, then it will make it in 32 pointer and then it works. Okay, so this is the security group. And then we're just going to pull until done. Let me run it because I actually didn't rerun it for the IP address. So let's see if it is still working. This worked. So now we have an IP address allocated and a network security group. But if I run this again, will this still work? Because what happens is we are going to recreate some of these elements. Okay, that still works. So we still are going to do a creator update of all these elements. So I just want to see at some point, we'll get an error, but we are not there yet. So we can still run a go run as much as we want. Because when you are developing, that's quite important, I find that you don't always have to go in and remove all the resources that you can just run a go run over and over again, so that you can just keep on testing if there's one resource that doesn't want to be created because you have some mistakes in your properties, or you are just testing out the properties. What is next before we can launch our VM, our virtual machine is we need a network interface. And this network interface is really going to tie everything together like our subnet and our network security group and our IP address. First step is to create a new client for this network interface. It's still in our network, new, although we have here, you can create quite a few resources in this arm network, new interfaces client we have subscription ID, credential and no options. And then we're going to call this interface client, interface client, interface client, and what can our interface client create a new interface, interface client begin create or update, context, resource group name, network interface called demo parameters, arm network interface, and then the options. And then we have again this polar that I'm just going to copy paste public IP response polar, I'm going to copy paste here. So we have the network interface polar response and error. And that gives us no errors anymore, so that should work. But now we need all these parameters. Location. That's an easy one. Properties. Now it comes interface properties format. What do we need? Network security group. And typically, when you refer to it, you can refer to it with ID. I can say ID. And where is our network security group here? Network security group response. And network security response ID, we can put here. Does this work? Undeclared name, because we need to call on here. Now it works. Network security group. We also need the subnet. Do we have a subnet here? I don't see one immediately. But it's most likely going to be in this IP confrations. Because we have an IP conferation for a network interface. If you don't know immediately, you can have look at the example code that might give you some clues. Let's have a look what's in here. So we have a slice. So this is slice declaration. And then we have the elements. What do we have as elements? And name and properties. The name can be go demo. And then the properties name is to be a string, a pointer string. Properties needs to be interface configuration properties format of arm network. What's in there? A lot. We need a private IP address. But here we are not going to declare a fixed private IP address. We want to have a dynamic private IP address. We wanted to have a public static IP address, but the private IP address can actually be dynamic. So what do we need to do here? IP allocation method. So our network IP address allocation. And this time it's going to be dynamic. So very similar as we did with static but now it's dynamic for the private IP address. The subnet. Let's have a look subnet subnet. What's it subnet or subnet? Subnet. Arm network subnet. And what's going to go in here? Again, an ID. Where is our subnet? Where did we create our subnet? Right here we have a subnet subnet response. And then here we have the ID. So every resource that you create in Azure has an ID as well. And that we need to pass basically subnet response. But I also need this colon, which are keep on forgetting every time that you create a new variable, you need this colon, because the error was already defined. That's why you need now a colon. So we have all this. What is then the last that we are missing? Public IP address. And public IP address was right here. Public IP address response, a colon here. And now we link everything together in this network interface. Our network public IP address and the ID will be public IP address response ID. Looks good to me. Let's try to execute that. Do we have the poll until done? Yes, we have. And next will be the virtual machine, because then we link our network interface with our VM. So now I have this network interface. What happens if I execute the second time? And now we actually get this 400 batch requests that I was talking about. What does it want to do? There was an error into the put requests for the virtual networks. So when you have these virtual networks, when you have the begin creator update, it can actually not update it because now we have a network interface. So any updates are forbidden. So ideally, when we do this creation of this virtual network, what we want to check upon is does it already exist? And if it doesn't exist, we want to create it. But if it already exists, we want to skip it. Otherwise, we will always get this error and we have to remove our network interface every time we want to execute this again. So it's actually best to have also some code to check on that. So that's what I will do next. I will create some code to check whether our virtual network already exists. And if not, then we will create it otherwise will not create it. In this lecture, I want to check whether our virtual network already exists. And if not, we're going to create it. If it exists, we're going to skip it. So I'm going to write another function, find Vnet. If we find our Vnet, then we're going to skip it. So I'm going to say if not found, then we're going to create it. So this code all belongs in in here. But then I can already see that we need our Vnet response later on, because here we need the Vnet response name. So Vnet response is of type virtual networks client creator update response. So let's have a look how we can extract something out of it. You could just get a name, but then we don't have all the other attributes. So here we have virtual network, and that's probably a better choice, virtual network. So if we have a variable Vnet of this type, our network virtual network, then we can say Vnet equals and then our Vnet will be accessible right here. Because the scope is different. The scope of this Vnet response is only within this if and here outside this if we have declared the Vnet. So if we then say Vnet equals to Vnet response of virtual network, our Vnet will be accessible by other functions outside this if statement. Find Vnet, we still need to figure out what are we going to pass to this find Vnet? Well, maybe our virtual network client, because this virtual network client will need to check whether our Vnet already exists. Find Vnet. And then we probably also have an error here. So we are going to return font and error. Let's write this function, func find Vnet, and then Vnet client, and we return a Boolean and error. And this Vnet client is of type. Arm network, virtual network client, return false nil. So we couldn't find it by default. How do we find it? Vnet client, we can have this begin create function, begin delete, or we have a get to get, accept a context resource group name, virtual network name and options. So if it exists, then it will return this virtual networks client to get response. If it doesn't exist, it will return an error. But if the operation fails, it returns an easy core response error type. And this easy response error type, we can use to see what the error responses. So we need the resource group name, we're going to be string, and we need the Vnet name, which is also going to be string, and we also need the context. And we don't need to have this string here, because we already find here string, these will be automatically both of type string. So context, resource group name, Vnet name, and no options gives us the Vnet and the errors. If there is an error, then we want to check whether this is an error because it doesn't exist, or whether there is something else. We're going to have the Vnet as well. So if it exists, we also want to properly return it. But we want to make sure that type is also the same. So here we have a virtual networks client get response, that's not something that you want to return. We would like to return Vnet virtual network as well. And this is of type arm network virtual network, which is actually the same, which is actually the same as our Vnet right here. So we can actually remove this do Vnet is find Vnet. So this will be a Vnet. This will be of type arm network, virtual network. If it is not found, then we are going to create one, but this virtual network in this Vnet. And if we actually found one, then we're not going to execute this and then the Vnet that we found should be in this Vnet variable context. And then what else resource group response dot name, and the Vnet name, the name is going to be go demo. What is this? This needs to be a pointer. Okay. So find the Vnet, if it has not been found, we're going to create one, assign it to the Vnet variable. If we find one, we can just use the Vnet variable later on in our program, like here, for example, we need to return the Vnet. If we don't have an error, we found it. So it's going to be true. If we have an error, then we need to check whether the error was not found or something else. And the way you can check on this is there are some helper functions that we can use to see if the error is of this response error. So if the error is not nil, then if error is s, s finds the first error in the error chain that matches the target. And if one is found, set the target to that error value and returns true. So we pass the error as the first argument, and that the target and the target can be any and actually the way that is worse is like the same way that we do a JSON Marshall or unmartial, we're going to create a variable first. So we're going to have our error response of this az core response error. And then what it will do is if one is found, set the target to that error value and returns true. So if this is true, then our error response will have the error in it, and it will be of type az core response error. So here error is of type error. And if this is true, if this errors as is true, because this is a boolean, then this target will contain the error value of this type az response error. If you're confused by this, have a look at the error handling lectures that we had in the beginning of the course, where we had something similar going on. This is the Azure implementation of custom errors. So now that we have this error response, and if the error code is equal to not found, then we can return to our previous function that it's not found. And I think even that there's some other response codes that we could use status code is int. That's most likely the HTTP status code. So you could also just check on the 404. So 404 or not found is going to be the same. And then we return the virtual network, which can be empty. But we also say we didn't find it. So we will have to run the code to create a vnet. But if the error is not the not found error, then we want to return the vnet virtual network is also going to be empty. We're going to say false, but we're going to return an error because there's some other error that happened. So we still want to return an error. In case something went wrong here with the get that we stop the program. So that should be it really. Our vnet will only contain something if we are here, if we are returning true. In the other cases, it's not going to contain anything. And we need to check on this found variable. So let's have a look. So we already created our vnet. So let's do a go run. And now it will not create a vnet. Because it is already found. Will that work? Let's see. And also when you test these flows in your program, you have to test it both ways. Because now you see it actually works. But we didn't create it the vnet, right? Because we skipped it. So let's try to run it again. After we remove the vnet, just to see if our whole program would still work. So not creating the vnet actually worked. But let's just delete everything again. Delete and then yes. And let's see then if we can recreate everything nicely. So it's now doing this delete. It's running, it will take some time. But then once these elements, these resources are moved, then I'm going to try to execute it again and see if it still works. And then we will go to the flow where we still need to create a vnet. So succeeded. Go run. And now go run is going to create all these new resources. Oh, we got an error. We got a get error. Maybe I made a mistake here. Response for four not found. Oh, yeah, see I made a mistake. So we ended up right here. And because it's the error code is actually resource not found. And it's response for four not found. So either we can check on the status code or I think we can do resource not found. Let's try that and see if that works. So that's what I mean with it always important to check whether both flow still work because in our first flow, there was no error because we just found it. So we returned the virtual network. But now we are basically just testing whether this code actually works. And now it works. And if you refresh here, and then if we have a look again in our resource group, we again have those four resources. So then the next step is going to be to create our virtual machine. Finally, in this lecture, I'm going to now create the virtual machine. So let's have a look at examples because where would we start? It's a bit difficult to know if you don't even know what the package that we have to use. So this was our resource manager network example that we used earlier. Here are in the Azure samples. Let's first go back into the Azure SDK for go. I'm going to click on SDK, resource manager. And then we should have compute somewhere, resource manager, resources, compute, arm compute. Here we have again, lots of files. This is what you gonna need. Go get compute arm compute. And then we have samples. We have a sample of virtual machine that I'm going to use. So here, we can go to the main.go. And then we have the create VM function. Let's have a look. Create virtual machine is a function that they have. And then you can see we have a lot of parameters here actually. And that's why I wanted to copy paste it because these parameters, it's gonna be difficult to find without an example. So let me just import a package first. And then let's copy paste these parameters. Go get arm compute. All right. And then we have this arm compute new virtual machine client. This polar polar response exactly the same what we have been doing so far. I'm gonna copy this. And I'm gonna paste this right here create VM. And this can take a long time. So I'm gonna say creating VM just so we know that we started creating the VM in the code because it's gonna take easily a few minutes. And if I'm going to save now, we're gonna import this arm compute. We need to arm compute new virtual machine client. So it's gonna be the VM client. The SSH key we already have in this code. So we have the public key that we are passing the location, the identity. And then we have the storage profile image reference. And here we need an image. What are we going to launch a Windows server or Linux server? I'm gonna launch a Linux server. And we need SSH key for Linux. This is the 1804. But I don't really like the 1804. So I'm going to use another one. And again, you can then I think it was it said here, AZ VM image list, output table to find the images. So I already know which one I'm going to launch. I'm going to just change these variables a little bit to launch a 20 04. I actually found those in a GitHub issue on the Azure SDK, where they were saying that you can use actually those for 20 04. You can still use 18 04 if you want, or you can even launch a Windows server. It doesn't really matter at this point, you just want to have the latest LTS of Ubuntu in general. So the latest LTS is 20 04. If there's a new one that comes out then you can use the latest LTS. What do we have then so we have the image and we need a disk disk name can be go demo. And the rest I'm going to leave this just caching to manage disk. These are all Azure options like the way you want to have your VM. But I'm just gonna create a smaller 50 gigs disk for our virtual machine. What else do we have hardware profile? So yeah, what kind of instance do you want to launch? This determines how many CPUs and memory you will get in your instance. I'm going to launch instead of F2, a B1, which is very small. And it's a development instance. So it's very inexpensive to launch this one. Computer name, go demo admin username is going to be demo admin password. I don't need gonna use an SSH key. So then we are going to say Linux configuration disable password indication true. We don't need password authentication. We're going to use a public key. And we are going to write this public key to the demo user. So this is where we're going to write a tool in this VM. And this is our key. And our key we pass it in all the way in the beginning. Our key is public key. And it is actually asking for pointer strings. So what I could do is I could change this also in a pointer, not here, right here. So now it's a pointer. And if I go all the way back to the bottom, so that then I don't need these two pointer anymore, I can just say pop key, because it's already pointer. So we have our SSH configuration, our network profile. That's why we need our network interface. Network interface output. No response we call it. Copy this. And this is going to be the ID. And always is colon that I keep on forgetting the ID. So we have network interface, which is going to give us the interface we created. We want to configure the login and password. We are not using a password. We're using an SSH key. Standard B1 and an Ubuntu image. And we have a disk of 50 gigabytes. So this is enough to launch our first machine. Begin creator update, resource group, output, response dot name, the name of the VM alcohol to go demo, the parameters that we are passing. And then if it goes wrong, we return an error. And then we have the VM response. And I'm just going to output it when it is created. So I'm going to say printf VM created. And then I'm going to output the ID or the name. The ID is also possible. The ID will just be the full ID. That should work. Still have one error somewhere. Well, the subscription ID, I have a capital D here. And is it going to work? I think so. So let's try to run it. And then I'm going to open a second screen. And then I'm going to SSH into this machine. So let's have a look what this IP address was. So we could have either output the IP address, because this network interface would also have this public IP address. Or while this VM is now creating, we could have a look in the portal. So in the portal, we have this network interface. And this network interface has a public IP copy. Open a new bash prompt. And we can use SSH command. If you don't have the SSH command, then you can download open SSH for windows, or you can use putty, if you're more familiar with putty. But if you're using putty, you will still need to convert with putty gen this my key.pam. We also have an SSH client that we built in one of these lectures. So you could also SSH into this with an SSH client. So I have a look at SSH demo, which also includes an SSH client. Demo is our login. And then the IP address. And that should be it. So if you don't get this prompt, then you have to wait a little bit longer because it can take a few minutes before it is up and running. So now it's still booting. And here we have our code demo up for one minute. So this actually works. We have launched a VM. I'll log into the VM that we just created. And this is the output. This is the full ID of this VM that has been created. So we can just exit. And then if you don't want to keep the VM, make sure to delete it. So we can go back to the portal and then delete it. And then if you want to run it again, you can just go build this program again and run it or do a go run. So we have created quite a few resources. 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. Depending on what version you are using, what Kubernetes version you are using, you want a different Kubernetes go client. 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. Okay, so now that I did this go get Visual Studio actually updated this go mod file. 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 if we don't know, then we can give a clue and then we might have to add more cases. 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. Go get off this GitHub. 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. So we could put these three lines in a function and call this function or we are just going to be a little bit lazy and go be pasted. 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. Get files commit and commit is off had commit. 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. First, I'm going to create a web hook and then I'm going to create a file. 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 probably already have is quite powerful