One place for hosting & domains

      October 2019

      Using ldflags to Set Version Information for Go Applications


      Introduction

      When deploying applications into a production environment, building binaries with version information and other metadata will improve your monitoring, logging, and debugging processes by adding identifying information to help track your builds over time. This version information can often include highly dynamic data, such as build time, the machine or user building the binary, the Version Control System (VCS) commit ID it was built against, and more. Because these values are constantly changing, coding this data directly into the source code and modifying it before every new build is tedious and prone to error: Source files can move around and variables/constants may switch files throughout development, breaking the build process.

      One way to solve this in Go is to use -ldflags with the go build command to insert dynamic information into the binary at build time, without the need for source code modification. In this flag, ld stands for linker, the program that links together the different pieces of the compiled source code into the final binary. ldflags, then, stands for linker flags. It is called this because it passes a flag to the underlying Go toolchain linker, cmd/link, that allows you to change the values of imported packages at build time from the command line.

      In this tutorial, you will use -ldflags to change the value of variables at build time and introduce your own dynamic information into a binary, using a sample application that prints version information to the screen.

      Prerequisites

      To follow the example in this article, you will need:

      Building Your Sample Application

      Before you can use ldflags to introduce dynamic data, you first need an application to insert the information into. In this step, you will make this application, which will at this stage only print static versioning information. Let’s create that application now.

      In your src directory, make a directory named after your application. This tutorial will use the application name app:

      Change your working directory to this folder:

      Next, using the text editor of your choice, create the entry point of your program, main.go:

      Now, make your application print out version information by adding the following contents:

      app/main.go

      package main
      
      import (
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
      }
      

      Inside of the main() function, you declared the Version variable, then printed the string Version:, followed by a tab character, t, and then the declared variable.

      At this point, the variable Version is defined as development, which will be the default version for this app. Later on, you will change this value to be an official version number, arranged according to semantic versioning format.

      Save and exit the file. Once this is done, build and run the application to confirm that it prints the correct version:

      You will see the following output:

      Output

      You now have an application that prints default version information, but you do not yet have a way to pass in current version information at build time. In the next step, you will use -ldflags and go build to solve this problem.

      Using ldflags with go build

      As mentioned before, ldflags stands for linker flags, and is used to pass in flags to the underlying linker in the Go toolchain. This works according to the following syntax:

      • go build -ldflags="-flag"

      In this example, we passed in flag to the underlying go tool link command that runs as a part of go build. This command uses double quotes around the contents passed to ldflags to avoid breaking characters in it, or characters that the command line might interpret as something other than what we want. From here, you could pass in many different link flags. For the purposes of this tutorial, we will use the -X flag to write information into the variable at link time, followed by the package path to the variable and its new value:

      • go build -ldflags="-X 'package_path.variable_name=new_value'"

      Inside the quotes, there is now the -X option and a key-value pair that represents the variable to be changed and its new value. The . character separates the package path and the variable name, and single quotes are used to avoid breaking characters in the key-value pair.

      To replace the Version variable in your example application, use the syntax in the last command block to pass in a new value and build the new binary:

      • go build -ldflags="-X 'main.Version=v1.0.0'"

      In this command, main is the package path of the Version variable, since this variable is in the main.go file. Version is the variable that you are writing to, and v1.0.0 is the new value.

      In order to use ldflags, the value you want to change must exist and be a package level variable of type string. This variable can be either exported or unexported. The value cannot be a const or have its value set by the result of a function call. Fortunately, Version fits all of these requirements: It was already declared as a variable in the main.go file, and the current value (development) and the desired value (v1.0.0) are both strings.

      Once your new app binary is built, run the application:

      You will receive the following output:

      Output

      Using -ldflags, you have succesfully changed the Version variable from development to v1.0.0.

      You have now modified a string variable inside of a simple application at build time. Using ldflags, you can embed version details, licensing information, and more into a binary ready for distribution, using only the command line.

      In this example, the variable you changed was in the main program, reducing the difficulty of determining the path name. But sometimes the path to these variables is more complicated to find. In the next step, you will write values to variables in sub-packages to demonstrate the best way to determine more complex package paths.

      Targeting Sub-Package Variables

      In the last section, you manipulated the Version variable, which was at the top-level package of the application. But this is not always the case. Often it is more practical to place these variables in another package, since main is not an importable package. To simulate this in your example application, you will create a new sub-package, app/build, that will store information about the time the binary was built and the name of the user that issued the build command.

      To add a new sub-package, first add a new directory to your project named build:

      Then create a new file named build.go to hold the new variables:

      In your text editor, add new variables for Time and User:

      app/build/build.go

      package build
      
      var Time string
      
      var User string
      

      The Time variable will hold a string representation of the time when the binary was built. The User variable will hold the name of the user who built the binary. Since these two variables will always have values, you don’t need to initialize these variables with default values like you did for Version.

      Save and exit the file.

      Next, open main.go to add these variables to your application:

      Inside of main.go, add the following highlighted lines:

      main.go

      package main
      
      import (
          "app/build"
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
          fmt.Println("build.Time:t", build.Time)
          fmt.Println("build.User:t", build.User)
      }
      

      In these lines, you first imported the app/build package, then printed build.Time and build.User in the same way you printed Version.

      Save the file, then exit from your text editor.

      Next, to target these variables with ldflags, you could use the import path app/build followed by .User or .Time, since you already know the import path. However, to simulate a more complex situation in which the path to the variable is not evident, let’s instead use the nm command in the Go tool chain.

      The go tool nm command will output the symbols involved in a given executable, object file, or archive. In this case, a symbol refers to an object in the code, such as a defined or imported variable or function. By generating a symbol table with nm and using grep to search for a variable, you can quickly find information about its path.

      Note: The nm command will not help you find the path of your variable if the package name has any non-ASCII characters, or a " or % character, as that is a limitation of the tool itself.

      To use this command, first build the binary for app:

      Now that app is built, point the nm tool at it and search through the output:

      • go tool nm ./app | grep app

      When run, the nm tool will output a lot of data. Because of this, the preceding command used | to pipe the output to the grep command, which then searched for terms that had the top-level app in the title.

      You will receive output similar to this:

      Output

      55d2c0 D app/build.Time 55d2d0 D app/build.User 4069a0 T runtime.appendIntStr 462580 T strconv.appendEscapedRune . . .

      In this case, the first two lines of the result set contain the paths to the two variables you are looking for: app/build.Time and app/build.User.

      Now that you know the paths, build the application again, this time changing Version, User, and Time at build time. To do this, pass multiple -X flags to -ldflags:

      • go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

      Here you passed in the id -u -n Bash command to list the current user, and the date command to list the current date.

      Once the executable is built, run the program:

      This command, when run on a Unix system, will generate similar output to the following:

      Output

      Version: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy

      Now you have a binary that contains versioning and build information that can provide vital assistance in production when resolving issues.

      Conclusion

      This tutorial showed how, when applied correctly, ldflags can be a powerful tool for injecting valuable information into binaries at build time. This way, you can control feature flags, environment information, versioning information, and more without introducing changes to your source code. By adding ldflags to your current build workflow you can maximize the benefits of Go’s self-contained binary distribution format.

      If you would like to learn more about the Go programming language, check out our full How To Code in Go series. If you are looking for more solutions for version control, try our How To Use Git reference guide.



      Source link

      Defining Structs in Go


      Introduction

      Building abstractions around concrete details is the greatest tool that a programming language can give to a developer. Structs allow Go developers to describe the world in which a Go program operates. Instead of reasoning about strings describing a Street, City, or a PostalCode, structs allow us to instead talk about an Address. They serve as a natural nexus for documentation in our efforts to tell future developers (ourselves included) what data is important to our Go programs and how future code should use that data appropriately. Structs can be defined and used in a few different ways. In this tutorial, we’ll take a look at each of these techniques.

      Defining Structs

      Structs work like paper forms that you might use, for example, to file your taxes. Paper forms might have fields for textual pieces of information like your first and last names. Besides text fields, forms might have checkboxes to indicate Boolean values such as “married” or “single,” or date fields for birth date. Similarly, structs collect different pieces of data together and organize them under different field names. When you initialize a variable with a new struct, it’s as though you’ve photocopied a form and made it ready to fill out.

      To create a new struct, you must first give Go a blueprint that describes the fields the struct contains. This struct definition usually begins with the keyword type followed by the name of the struct. After this, use the struct keyword followed by a pair of braces {} where you declare the fields the struct will contain. Once you have defined the struct, you are then able to declare variables that use this struct definition. This example defines a struct and uses it:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy the Shark",
          }
          fmt.Println(c.Name)
      }
      

      When you run this code, you will see this output:

      output

      Sammy the Shark

      We first define a Creature struct in this example, containing a Name field of type string. Within the body of main, we create an instance of Creature by placing a pair of braces after the name of the type, Creature, and then specifying values for that instance’s fields. The instance in c will have its Name field set to “Sammy the Shark”. Within the fmt.Println function invocation, we retrieve the values of the instance’s field by placing a period after the variable where the instance was created, followed by the name of the field we would like to access. For example, c.Name in this case returns the Name field.

      When you declare a new instance of a struct, you generally enumerate the field names with their values, as in the last example. Alternatively, if every field value will be provided during the instantiation of a struct, you can omit the field names, like in this example:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      }
      
      func main() {
          c := Creature{"Sammy", "Shark"}
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output is the same as the last example:

      output

      Sammy the Shark

      We’ve added an extra field to Creature to track the Type of creature as a string. When instantiating Creature within the body of main, we’ve opted to use the shorter instantiation form by providing values for each field in order and omitting their field names. In the declaration Creature{"Sammy", "Shark"}, the Name field takes the value Sammy and the Type field takes the value Shark because Name appears first in the type declaration, followed by Type.

      This shorter declaration form has a few drawbacks that have led the Go community to prefer the longer form in most circumstances. You must provide values for each field in the struct when using the short declaration—you can’t skip fields you don’t care about. This quickly causes short declarations for structs with many fields to become confusing. For this reason, declaring structs using the short form is typically used with structs that have few fields.

      The field names in the examples so far have all begun with capital letters. This is more significant than a stylistic preference. The use of capital or lowercase letters for field names affects whether your field names will be accessible to code running in other packages.

      Struct Field Exporting

      Fields of a struct follow the same exporting rules as other identifiers within the Go programming language. If a field name begins with a capital letter, it will be readable and writeable by code outside of the package where the struct was defined. If the field begins with a lowercase letter, only code within that struct’s package will be able to read and write that field. This example defines fields that are exported and those that are not:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      
          password string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy",
              Type: "Shark",
      
              password: "secret",
          }
          fmt.Println(c.Name, "the", c.Type)
          fmt.Println("Password is", c.password)
      }
      

      This will output:

      output

      Sammy the Shark Password is secret

      We added an additional field to our previous examples, secret. secret is an unexported string field, which means that any other package that attempts to instantiate a Creature will not be able to access or set its secret field. Within the same package, we are able to access these fields, as this example has done. Since main is also in the main package, it’s able to reference c.password and retrieve the value stored there. It’s common to have unexported fields in structs with access to them mediated by exported methods.

      Inline Structs

      In addition to defining a new type to represent a struct, you can also define an inline struct. These on-the-fly struct definitions are useful in situations where inventing new names for struct types would be wasted effort. For example, tests often use a struct to define all the parameters that make up a particular test case. It would be cumbersome to come up with new names like CreatureNamePrintingTestCase when that struct is used in only one place.

      Inline struct definitions appear on the right-hand side of a variable assignment. You must provide an instantiation of them immediately after by providing an additional pair of braces with values for each of the fields you define. The example that follows shows an inline struct definition:

      package main
      
      import "fmt"
      
      func main() {
          c := struct {
              Name string
              Type string
          }{
              Name: "Sammy",
              Type: "Shark",
          }
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output from this example will be:

      output

      Sammy the Shark

      Rather than defining a new type describing our struct with the type keyword, this example defines an inline struct by placing the struct definition immediately following the short-assignment operator, :=. We define the fields of the struct as in previous examples, but then we must immediately supply another pair of braces and the values that each field will assume. Using this struct is now exactly the same as before—we can refer to field names using dot notation. The most common place you will see inline structs declared is during tests, as frequently one-off structs are defined to contain data and expectations for a particular test case.

      Conclusion

      Structs are collections of heterogenous data defined by programmers to organize information. Most programs deal with enormous volumes of data, and without structs, it would become difficult to remember which string or int variables belonged together or which were different. The next time that you find yourself juggling groups of variables, ask yourself if perhaps those variables would be better grouped together using a struct. Those variables may have been describing some higher-level concept all along.



      Source link

      How To Install Apache Tomcat 9 on Debian 10

      Introduction

      Apache Tomcat is a web server and servlet container that is used to serve Java applications. Tomcat is an open source implementation of the Java Servlet and JavaServer Pages technologies, released by the Apache Software Foundation. This tutorial covers the basic installation and some configuration of the latest release of Tomcat 9 on your Debian 10 server.

      Prerequisites

      Before you begin with this guide, you should have a non-root user with sudo privileges set up on your server. You can learn how to do this by completing our Debian 10 initial server setup guide.

      Step 1 — Install Java

      Tomcat requires Java to be installed on the server so that any Java web application code can be executed. We can satisfy that requirement by installing OpenJDK with apt.

      First, update your apt package index:

      Then install the Java Development Kit package with apt:

      • sudo apt install default-jdk

       

      Now that Java is installed, we can create a tomcat user, which will be used to run the Tomcat service.

      Step 2 — Create Tomcat User

      For security purposes, Tomcat should be run as an unprivileged user (i.e. not root). We will create a new user and group that will run the Tomcat service.

      First, create a new tomcat group:

      Next, create a new tomcat user. We’ll make this user a member of the tomcat group, with a home directory of /opt/tomcat (where we will install Tomcat), and with a shell of /bin/false (so nobody can log into the account):

      • sudo useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat

       

      Now that our tomcat user is set up, let’s download and install Tomcat.

      Step 3 — Install Tomcat

      The best way to install Tomcat 9 is to download the latest binary release then configure it manually.

      Find the latest version of Tomcat 9 at the Tomcat 9 Downloads page. At the time of writing, the latest version is 9.0.27, but you should use a later stable version if it is available. Under the Binary Distributions section, then under the Core list, copy the link to the “tar.gz”.

      Next, return to your SSH session and move to the /tmp directory on your server. This is a good directory to download ephemeral items, like the Tomcat tarball, which we won’t need after extracting the Tomcat contents:

      We’ll use the curl command-line tool to download the tarball. Install curl:

      Now, use curl to download the link that you copied from the Tomcat website:

      • curl -O http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.11/bin/apache-tomcat-9.0.11.tar.gz

       

      We will install Tomcat to the /opt/tomcat directory. Create the directory, then extract the archive to it with these commands:

      • sudo mkdir /opt/tomcat
      • sudo tar xzvf apache-tomcat-9*tar.gz -C /opt/tomcat –strip-components=1

       

      Next, we will set up the proper user permissions for our installation.

      Step 4 — Update Permissions

      The tomcat user that we created needs to have access to the Tomcat installation. We’ll set that up now.

      Change to the directory where we unpacked the Tomcat installation:

      Give the tomcat group ownership over the entire installation directory:

      • sudo chgrp -R tomcat /opt/tomcat

       

      Next, give the tomcat group read access to the conf directory and all of its contents, and execute access to the directory itself:

      • sudo chmod -R g+r conf
      • sudo chmod g+x conf

       

      Make the tomcat user the owner of the webapps, work, temp, and logs directories:

      • sudo chown -R tomcat webapps/ work/ temp/ logs/

       

      Now that the proper permissions are set up, we will create a systemd service file to manage the Tomcat process.

      Step 5 — Create a systemd Service File

      We want to be able to run Tomcat as a service, so we will set up systemd service file.

      Tomcat needs to know where Java is installed. This path is commonly referred to as JAVA_HOME. The easiest way to look up that location is by running this command:

      • sudo update-java-alternatives -l

       

      Output

      java-1.11.0-openjdk-amd64 1111 /usr/lib/jvm/java-1.11.0-openjdk-amd64

      Your JAVA_HOME is the output from the last column (highlighted above). Given the example above, the correct JAVA_HOME for this server would be:

      JAVA_HOME

      /usr/lib/jvm/java-1.11.0-openjdk-amd64

      Your JAVA_HOME may be different.

      With this piece of information, we can create the systemd service file. Open a file called tomcat.service in the /etc/systemd/system directory by typing:

      • sudo nano /etc/systemd/system/tomcat.service

       

      Paste the following contents into your service file. Modify the value of JAVA_HOME if necessary to match the value you found on your system. You may also want to modify the memory allocation settings that are specified in CATALINA_OPTS:

      /etc/systemd/system/tomcat.service

      [Unit]
      Description=Apache Tomcat Web Application Container
      After=network.target
      
      [Service]
      Type=forking
      
      Environment=JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
      Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
      Environment=CATALINA_HOME=/opt/tomcat
      Environment=CATALINA_BASE=/opt/tomcat
      Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
      Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'
      
      ExecStart=/opt/tomcat/bin/startup.sh
      ExecStop=/opt/tomcat/bin/shutdown.sh
      
      User=tomcat
      Group=tomcat
      UMask=0007
      RestartSec=10
      Restart=always
      
      [Install]
      WantedBy=multi-user.target
      

      When you are finished, save and close the file.

      Next, reload the systemd daemon so that it knows about our service file:

      • sudo systemctl daemon-reload

       

      Start the Tomcat service by typing:

      • sudo systemctl start tomcat

       

      Double check that it started without errors by typing:

      • sudo systemctl status tomcat

       

      You should see output similar to the following:

      Output

      ● tomcat.service – Apache Tomcat Web Application Container Loaded: loaded (/etc/systemd/system/tomcat.service; disabled; vendor preset: enabled) Active: active (running) since Thu 2019-10-24 17:18:11 UTC; 4s ago Process: 5962 ExecStart=/opt/tomcat/bin/startup.sh (code=exited, status=0/SUCCESS) Main PID: 5970 (java) Tasks: 44 (limit: 2377) Memory: 184.2M CGroup: /system.slice/tomcat.service └─5970 /usr/lib/jvm/java-1.11.0-openjdk-amd64/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.u Oct 24 17:18:10 tomcat systemd[1]: Starting Apache Tomcat Web Application Container… Oct 24 17:18:11 tomcat startup.sh[5962]: Tomcat started. Oct 24 17:18:11 tomcat systemd[1]: Started Apache Tomcat Web Application Container.

      This confirms that Tomcat is up and running on your server.

      Step 6 — Adjust the Firewall and Test the Tomcat Server

      Now that the Tomcat service is started, we can test to make sure the default page is available.

      Before we do that, we need to adjust the firewall to allow our requests to get to the service. If you followed the prerequisites, you will have a ufw firewall enabled currently.

      Tomcat uses port 8080 to accept requests. Allow traffic to that port by typing:

      With the firewall modified, you can access the default splash page by going to your domain or IP address followed by :8080 in a web browser:

      Open in web browser

      http://server_domain_or_IP:8080

      You will see the default Tomcat splash page, in addition to other information. However, if you click the links for the Manager App, for instance, you will be denied access. We can configure that access next.

      If you were able to successfully access Tomcat, now is a good time to enable the service file so that Tomcat automatically starts at boot:

      • sudo systemctl enable tomcat

       

      Step 7 — Configure Tomcat Web Management Interface

      In order to use the manager web app that comes with Tomcat, we must add a login to our Tomcat server. We will do this by editing the tomcat-users.xml file:

      • sudo nano /opt/tomcat/conf/tomcat-users.xml

       

      You will want to add a user who can access the manager-gui and admin-gui (web apps that come with Tomcat). You can do so by defining a user, similar to the example below, between the tomcat-users tags. Be sure to change the username and password to something secure:

      tomcat-users.xml

      <tomcat-users>
      . . .
          <user username="admin" password="password" roles="manager-gui,admin-gui"/>
      </tomcat-users>
      

      Save and close the file when you are finished.

      By default, newer versions of Tomcat restrict access to the Manager and Host Manager apps to connections coming from the server itself. Since we are installing on a remote machine, you will probably want to remove or alter this restriction. To change the IP address restrictions on these, open the appropriate context.xml files.

      For the Manager app, type:

      • sudo nano /opt/tomcat/webapps/manager/META-INF/context.xml

       

      For the Host Manager app, type:

      • sudo nano /opt/tomcat/webapps/host-manager/META-INF/context.xml

       

      Inside, comment out the IP address restriction to allow connections from anywhere. Alternatively, if you would like to allow access only to connections coming from your own IP address, you can add your public IP address to the list:

      context.xml files for Tomcat webapps

      <Context antiResourceLocking="false" privileged="true" >
        <!--<Valve className="org.apache.catalina.valves.RemoteAddrValve"
               allow="127.d+.d+.d+|::1|0:0:0:0:0:0:0:1" />-->
      </Context>
      

      Save and close the files when you are finished.

      To put our changes into effect, restart the Tomcat service:

      • sudo systemctl restart tomcat

       

      Step 8 — Access the Web Interface

      Now that we have create a user, we can access the web management interface again in a web browser. Once again, you can get to the correct interface by entering your server’s domain name or IP address followed on port 8080 in your browser:

      Open in web browser

      http://server_domain_or_IP:8080

      The page you see should be the same one you were given when you tested earlier:

      Tomcat root

      Let’s take a look at the Manager App, accessible via the link or http://server_domain_or_IP:8080/manager/html. You will need to enter the account credentials that you added to the tomcat-users.xml file. Afterwards, you should see a page that looks like this:

      Tomcat Web Application Manager

      The Web Application Manager is used to manage your Java applications. You can Start, Stop, Reload, Deploy, and Undeploy here. You can also run some diagnostics on your apps (i.e. find memory leaks). Lastly, information about your server is available at the very bottom of this page.

      Now let’s take a look at the Host Manager, accessible via the link or http://server_domain_or_IP:8080/host-manager/html/:

      Tomcat Virtual Host Manager

      From the Virtual Host Manager page, you can add virtual hosts to serve your applications from.

      Conclusion

      Your installation of Tomcat is complete! You are now free to deploy your own Java web applications.

      Currently, your Tomcat installation is functional, but entirely unencrypted. This means that all data, including sensitive items like passwords, are sent in plain text that can be intercepted and read by other parties on the internet. In order to prevent this from happening, it is strongly recommended that you encrypt your connections with SSL. You can find out how to encrypt your connections to Tomcat by following this guide (note: this guide covers Tomcat 8 encryption on Ubuntu 16.04).

      Source link