Create Microservices with Spring Boot and deploy them into Cloud Foundry

Time: 35 minutes.

Cloud Foundry

This lab is just to get familiar with some of the common Cloud Foundry command using the CF CLI.

  1. Make sure you have an account in PWS if not, create a trial account in Pivotal Web Services: https://run.pivotal.io

  2. Install Cloud Foundry CLI. You can get it from: https://github.com/cloudfoundry/cli#installers-and-compressed-binaries

  3. Login into Cloud Foundry

    cf login
    $ cf login -a api.run.pivotal.io

Useful CF CLI commands

cf help - Show help
$ cf help -a
marketplace - List available offerings in the marketplace
$ cf marketplace
services - List all service instances in the target space
$ cf services
create-service - Create a service instance
$ cf create-service cloudamqp lemur rabbitmq
bind-service - Bind a service instance to an app
$ cf bind-service myapp rabbitmq
push - Push a new app or sync changes to an existing app
$ cf push myapp
set-env - Set an env variable for an app
$ cf set-env myapp MY_VARIABLE ITS_VALUE

Deploy Spring Boot Microservices to Cloud Foundry

For this lab you will deploy two projects, the directory-web-security and the code-snippet-manager-security, both must be completed with OAuth2 implementation. Remember that the directory-web-security is the Authorization Server and the code-snippet-manager-security is the Resource Server. If you haven’t finish the challenges for both projects, the it’s time to do it.

Deploying directory-web-security project

  1. Open the direcoty-web-security app in your favorite IDE. Make sure your io.pivotal.workshop.directory.config.DirectorySecurityConfig class in the configure(HttpSecurity http) is based in the Http Basic Security (httpBasic()). This will allow our code-snippet-manager-security app to connect passing credentials using a basic authentication,

  2. Open a terminal window in the directory-web-security project and create the JAR file.

    maven
    ./mvnw clean package -DskipTests=true
    gradle
    ./gradlew build -x test
  3. Deploy the directory-web-security JAR.

    If you used Maven, it creates the JAR in the target/ folder. If you used Gradle, the JAR is in the build/libs folder.
    cf push directory-web -p target/directory-web-security-0.0.1-SNAPSHOT.jar --random-route -b java_buildpack
  4. Once deployed make sure is working by accessing the /api endpoint using the administrator credentials.

Deploying code-snippet-manager-security project

You will be using the code-snippet-manager-security project.

Remember that you had a Challenge for this project? Modify the code-snippet-manager-security project and use the directory-web-security project as authentication authority.

Well, this is the solution to that Challenge!!

  1. Open your code-snippet-manager-security project and open the io.pivotal.workshop.snippet.config.SnippetConfiguration class. Modify it to look like the following code:

    src/main/java/io/pivotal/workshop/snippet/config/SnippetConfiguration.java
    package io.pivotal.workshop.snippet.config;
    
    import io.pivotal.workshop.snippet.domain.Code;
    import io.pivotal.workshop.snippet.domain.Language;
    import io.pivotal.workshop.snippet.domain.Snippet;
    import io.pivotal.workshop.snippet.repository.SnippetRepository;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    @Configuration
    public class SnippetConfiguration {
    
    
            @Bean
            @Profile("local")
            public CommandLineRunner runner(SnippetRepository snippetRepo) {
                    return args -> {
                            @SuppressWarnings("serial")
                            List<Snippet> snippets = new ArrayList<Snippet>() {
                                    {
                                            add(new Snippet("Hello World", new Language("HTML", "xml"),new Code(new String(Files.readAllBytes(Paths.get("code/html-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("C#", "c#"),new Code(new String(Files.readAllBytes(Paths.get("code/cs-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Pascal", "py"),new Code(new String(Files.readAllBytes(Paths.get("code/pas-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Erlang", "erl"),new Code(new String(Files.readAllBytes(Paths.get("code/erl-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("JavaScript", "js"),new Code(new String(Files.readAllBytes(Paths.get("code/js-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Groovy", "groovy"),new Code("println 'Hello World'")));
                                    }
                            };
    
                            snippetRepo.saveAll(snippets);
    
                    };
            }
    
            @Bean
            @Profile("cloud") //(1)
            public CommandLineRunner runnerCloud(SnippetRepository snippetRepo) {
                    return args -> {
                            @SuppressWarnings("serial")
                            List<Snippet> snippets = new ArrayList<Snippet>() {{
                                    add(new Snippet("Hello World", new Language("JavaScript", "js"),new Code("console.log(\"Hello World\");")));
                                    add(new Snippet("Hello World", new Language("Groovy", "groovy"),new Code("println 'Hello World'")));
                            }};
                            snippetRepo.saveAll(snippets);
                    };
            }
    }
    1. As you can see we are using the @Profile annotation, and we adding it to the runner method, making it a local profile, but also we are adding a new method runnerCloud and using the cloud profile. Why do we need to do this? well we are not deploying the code/ folder, so there are no initial snippets.

    Tip
    By default Cloud Foundry activates the cloud profile.
  2. This is part of the Challenge: Use the directory-web-security project as authentication-authority for the code-snippet-manager project. Here we are going to see the solution. Create the io.pivotal.workshop.snippet.config.SnippetSecurityConfig class, this class will reach out to the directory-web-security app.

    src/main/java/io/pivotal/workshop/snippet/config/SnippetSecurityConfig.java
    package io.pivotal.workshop.snippet.config;
    
    import io.pivotal.workshop.snippet.domain.Person;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.actuate.autoconfigure.security.EndpointRequest;
    import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.hateoas.MediaTypes;
    import org.springframework.hateoas.Resource;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.RequestEntity;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.util.UriComponentsBuilder;
    
    import java.net.URI;
    import java.util.Collections;
    
    @EnableConfigurationProperties(SnippetProperties.class)
    @Configuration
    public class SnippetSecurityConfig extends WebSecurityConfigurerAdapter {
    
        private final Logger log = LoggerFactory.getLogger(SnippetSecurityConfig.class);
        private RestTemplate restTemplate;
        private UriComponentsBuilder builder;
        private SnippetProperties properties;
    
        public SnippetSecurityConfig(RestTemplateBuilder restTemplateBuilder,SnippetProperties properties){
            this.restTemplate = restTemplateBuilder.basicAuthorization(properties.getAuthenticationUsername(),properties.getAuthenticationPassword()).build();
            this.properties = properties;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .requestMatchers(EndpointRequest.to("status", "info"))
                    .permitAll()
    
                    .requestMatchers(EndpointRequest.toAnyEndpoint())
                    .hasRole("ACTUATOR")
    
    
                    .requestMatchers(StaticResourceRequest.toCommonLocations())
                    .permitAll()
    
                    .antMatchers("/api/**").hasRole("ADMIN")
                    .antMatchers("/").hasRole("USER")
    
                    .and()
                    .httpBasic();
        }
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception { //(1)
            auth.userDetailsService(new UserDetailsService(){
    
                @Override
                public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    
                    try {
                        builder = UriComponentsBuilder.fromUriString(properties.getAuthenticationUri())
                                .queryParam("email", email);
    
                        log.info("Querying: " + builder.toUriString());
    
                        ResponseEntity<Resource<Person>> responseEntity = restTemplate.exchange(  //(2)
                                RequestEntity.get(URI.create(builder.toUriString()))
                                    .accept(MediaTypes.HAL_JSON)
                                    .build()
                                , new ParameterizedTypeReference<Resource<Person>>() {
                                });
    
                        if (responseEntity.getStatusCode() == HttpStatus.OK) {
    
                            Resource<Person> resource = responseEntity.getBody();
                            Person person = resource.getContent();
                            return User.withDefaultPasswordEncoder().username(person.getEmail()).password(person.getPassword()).roles(person.getRole()).build();
                        }
    
                    }catch(Exception ex) {
                        ex.printStackTrace();
                    }
    
                    throw new UsernameNotFoundException(email);
    
    
                }
            });
        }
    
    }
    1. We are configuring the AuthenticationManagerBuilder and the UserDetailsService that will use a restTemplate to contact the directory-web-security-app.

    2. We are going to reach the /api/persons/search/findByEmailIgnoreCase endpoint. This endpoint returns a HAL+JSON, meaning that we need a Resource that will get email we are looking for.

  3. From the previous class, you’ve already saw that we need the SnippetProperties. Create/Modify the io.pivotal.workshop.snippet.config.SnippetProperties class.

    src/main/java/io/pivotal/workshop/snippet/config/SnippetProperties.java
    package io.pivotal.workshop.snippet.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @ConfigurationProperties(prefix = "snippet")
    public class SnippetProperties {
    
            private String path;
            private String authenticationUri;
            private String authenticationUsername;
            private String authenticationPassword;
    
            public String getPath() {
                    return path;
            }
    
            public void setPath(String path) {
                    this.path = path;
            }
    
        public String getAuthenticationUri() {
            return authenticationUri;
        }
    
        public void setAuthenticationUri(String authenticationUri) {
            this.authenticationUri = authenticationUri;
        }
    
        public String getAuthenticationUsername() {
            return authenticationUsername;
        }
    
        public void setAuthenticationUsername(String authenticationUsername) {
            this.authenticationUsername = authenticationUsername;
        }
    
        public String getAuthenticationPassword() {
            return authenticationPassword;
        }
    
        public void setAuthenticationPassword(String authenticationPassword) {
            this.authenticationPassword = authenticationPassword;
        }
    }
  4. Open the src/main/resources/application.properties file and add the following configuration.

    src/main/resources/application.properties
    ## Web App Port
    server.port=${port:8081}
    
    ## JPA
    # spring.jpa.show-sql=true
    spring.jpa.generate-ddl=true
    spring.jpa.hibernate.ddl-auto=create-drop
    
    ## REST
    spring.data.rest.base-path=api
    
    ## ACTUATOR
    management.endpoints.web.base-path=/admin
    endpoints.default.web.enabled=true
    
    ## Snippet
    snippet.path=/tmp/snippets
    
    ## URL
    snippet.authentication-uri=http://localhost:8585/api/persons/search/findByEmailIgnoreCase
    snippet.authentication-username=admin
    snippet.authentication-password=admin

    As you can see we are adding the snippet.authentication* properties, with the credentials and uri of the directory-web-security app.

  5. Next, open a terminal window in the code-snippet-manager-security project and create the JAR file.

    maven
    ./mvnw clean package -DskipTests=true
    gradle
    ./gradlew build -x test
  6. Deploy the code-snippet-manager-security JAR but don’t start the application just yet.

    If you used Maven, it creates the JAR in the target/ folder. If you used Gradle, the JAR is in the build/libs folder.
    cf push code-snippet-manager -p target/code-snippet-manager-security-0.0.1-SNAPSHOT.jar --random-route -b java_buildpack --no-start
  7. Remember that this application relies on the application.properties because it was added the credentials and the uri, so we need to alter at least the snippet.authentication-uri, so we can add the directory-web's uri. to Modify this we are going to use environment variables:

    cf set-env code-snippet-manager SNIPPET_AUTHENTICATION_URI http://directory-web-unapprehended-pluralism.cfapps.io/api/persons/search/findByEmailIgnoreCase

    The URI is the one from the directory-web application you have just deployed.

  8. Start the code-snippet-manager application.

    cf start code-snippet-manager
  9. Once started make sure is working by accessing the code-snippet-manager app /api using the credentials.

Challenges

  • Add the code from the code-snippet-manager-amqp to use a RabbitMQ service.

  • Instead of using an H2 as persistence engine, use a MySQL service, what changes do you need to do in your code?