Usando la variable env en la aplicación Spring Boot.

199

Estamos trabajando en una aplicación web Spring Boot y la base de datos que estamos usando es MySql ;

  • la configuración que tenemos es que primero lo probamos localmente (significa que necesitamos instalar MySql en nuestra PC);

  • luego empujamos a Bitbucket ;

  • Jenkins detecta automáticamente el nuevo impulso a Bitbucket y lo construye (para que pase la compilación mvn de Jenkins también necesitamos instalar MySql en las máquinas virtuales que ejecutan Jenkins).

  • Si la compilación de Jenkins pasa, enviamos el código a nuestra aplicación en OpenShift (usando el complemento de implementación Openshift en Jenkins).

El problema que tenemos, ya que es posible que ya lo haya resuelto, es que:

  • en application.propertiesno podemos codificar la información de MySql. Dado que nuestro proyecto se ejecutará en 3 lugares diferentes ( local , Jenkins y OpenShift ), necesitamos que el campo del origen de datos sea dinámico application.properties(sabemos que hay diferentes formas de hacerlo, pero estamos trabajando en esta solución por ahora).

    spring.datasource.url = 
    spring.datasource.username = 
    spring.datasource.password = 

La solución que se nos ocurrió es que creamos variables de entorno del sistema localmente y en la máquina virtual de Jenkins (nombrándolas de la misma manera que OpenShift las nombra) y asignándoles los valores correctos, respectivamente:

export OPENSHIFT_MYSQL_DB_HOST="jdbc:mysql://localhost"
export OPENSHIFT_MYSQL_DB_PORT="3306"
export OPENSHIFT_MYSQL_DB_USERNAME="root"
export OPENSHIFT_MYSQL_DB_PASSWORD="123asd"

Hemos hecho esto y funciona. También hemos comprobado Map<String, String> env = System.getenv();que las variables de entorno se pueden convertir en variables java como tales:

String password = env.get("OPENSHIFT_MYSQL_DB_PASSWORD");   
String userName = env.get("OPENSHIFT_MYSQL_DB_USERNAME");   
String sqlURL = env.get("OPENSHIFT_MYSQL_DB_HOST"); 
String sqlPort = env.get("OPENSHIFT_MYSQL_DB_PORT");

Ahora lo único que queda es que necesitamos usar estas variables de Java en nuestro application.propertiesy eso es con lo que estamos teniendo problemas.

En el que la carpeta, y cómo, qué necesitamos para asignar los password, userName, sqlURL, y sqlPortvariables para application.propertiespoder verlos y cómo podemos incluirlos en application.properties?

Hemos probado muchas cosas, una de ellas es:

spring.datasource.url = ${sqlURL}:${sqlPort}/"nameofDB"
spring.datasource.username = ${userName}
spring.datasource.password = ${password}

Sin suerte hasta ahora. Probablemente no estamos colocando estas variables env en la clase / carpeta correcta o las estamos usando incorrectamente application.properties.

¡¡Tu ayuda es altamente apreciada!!

¡Gracias!

SM
fuente
3
Lea @ConfigurationProperties para obtener más información. Sin embargo, este es un caso de uso perfecto para las propiedades de configuración específicas del perfil
Eddie B

Respuestas:

267

No necesita usar variables java. Para incluir variables de entorno del sistema, agregue lo siguiente a su application.propertiesarchivo:

spring.datasource.url = ${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/"nameofDB"
spring.datasource.username = ${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password = ${OPENSHIFT_MYSQL_DB_PASSWORD}

Pero la forma sugerida por @Stefan Isele es más preferible, ya que en este caso se tiene que declarar sólo una variable de entorno: spring.profiles.active. Spring leerá el archivo de propiedades apropiado automáticamente por application-{profile-name}.propertiesplantilla.

Ken Bekov
fuente
12
Este método es más conveniente para vincular docker. Por ejemplo:docker run --name my-tomcat -p 127.0.0.1:8080:8080 -e APP_DB_DB=mydb -e APP_DB_USER=dbuser -e APP_DB_PASS=dbpass --link mongo-myapp:mongo -v /path-to/tomcat/webapps:/usr/local/tomcat/webapps -d tomcat:8-jre8-alpine
Fırat KÜÇÜK
17
Esta es absolutamente la mejor manera de hacerlo. El uso de variables de entorno significa que no necesita enumerar secretos en texto plano junto con su aplicación. Esto es significativamente más seguro y reduce la dependencia de sus medidas de seguridad de acceso al código fuente para proteger todo su patrimonio. Una publicación SO accidental con propiedades incluidas no da como resultado que se filtre información.
kipper_t
51
Quería agregar a esto y mencionar que si está utilizando Spring Boot (no comprobó si funciona sin arranque), cualquier propiedad puede ser anulada a través de una variable de entorno automáticamente sin modificar su application.properties. es decir, si usted tiene una propiedad llamada spring.activemq.broker-urlentonces la variable de entorno correspondiente sería: SPRING_ACTIVEMQ_BROKER_URL. los puntos y guiones se convierten automáticamente en guiones bajos. Esto es extremadamente conveniente cuando se trabaja con contenedores / botas de resorte.
abe
15
Si diseña para la nube, no es una forma preferible de usar los perfiles Spring. El estándar de aplicación de 12 factores recomienda el uso de variables de entorno: 12factor.net/config
Mikhail Golubtsov
66
Sé que este tema es un poco viejo. Pero puede combinar tanto la configuración de variable de entorno como la configuración de perfil de resorte. Su perfil de desarrollador debe tener información estática, mientras que su perfil de producción puede hacer uso de las variables de entorno. De esta forma, los desarrolladores ya no necesitan definir variables de entorno en su máquina si solo quieren implementar el perfil de desarrollo.
underscore_05
72

La forma más fácil de tener diferentes configuraciones para diferentes entornos es usar perfiles de resorte. Ver configuración externalizada .

Esto te da mucha flexibilidad. Lo estoy usando en mis proyectos y es extremadamente útil. En su caso, tendría 3 perfiles: 'local', 'jenkins' y 'openshift'

A continuación, tiene archivos de propiedades específicas del perfil 3: application-local.properties, application-jenkins.propertiesyapplication-openshift.properties

Allí puede establecer las propiedades para el entorno correspondiente. Cuando ejecuta la aplicación, debe especificar el perfil para activarlo de esta manera: -Dspring.profiles.active=jenkins

Editar

Según el documento de Spring, puede configurar la variable de entorno del sistema SPRING_PROFILES_ACTIVEpara activar los perfiles y no es necesario pasarla como parámetro.

¿Hay alguna forma de pasar la opción de perfil activo para la aplicación web en tiempo de ejecución?

No. Spring determina los perfiles activos como uno de los primeros pasos, al construir el contexto de la aplicación. Los perfiles activos se utilizan para decidir qué archivos de propiedades se leen y qué beans se instancian. Una vez que se inicia la aplicación, esto no se puede cambiar.

Stefan Isele - prefabware.com
fuente
44
Me gusta esta respuesta, pero ¿qué pasa si quieres que el nombre del perfil provenga del entorno? Intenté -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES, y configuré el entorno del sistema operativo var en /etc/profile.d/myenvvars.sh, pero Spring Boot no lo recoge
Tom Hartwell
1
SPRING_PROFILES_ACTIVE funciona debido a la característica de encuadernación relajada de spring boot docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
55
gracias por esta respuesta Stefan, funcionó para mí, pero con un cambio: la propiedad es en realidad spring.profiles.active y no spring.active.profiles
Rudi
10
Si bien los perfiles Spring pueden ser muy útiles, en relación con el OP no son adecuados. Esto se debe a cómo se almacena el código fuente y a la sensibilidad de la información de propiedades almacenada con eso. El contexto OP se basa en el acceso a la base de datos. Para esa situación, no desea detalles de producción en texto plano en la fuente. Esto significa que si la fuente se ve comprometida, la base de datos también se ve comprometida. Es mejor usar variables env o herramientas secretas para esto, como Vault. Prefiero env. También haría que todos los entornos funcionen de la misma manera a este respecto para garantizar la coherencia. Evita accidentes en el futuro.
kipper_t
2
Puede usar un archivo de propiedades de perfil de Spring Boot externo a la aplicación JAR. Este archivo específico del entorno, por ejemplo, application-production.propertiesse implementaría en la máquina de producción de forma segura y, por lo general, no estaría en el repositorio de código fuente de la aplicación.
Colin D Bennett
13

Esto es en respuesta a una serie de comentarios, ya que mi reputación no es lo suficientemente alta como para comentar directamente.

Puede especificar el perfil en tiempo de ejecución siempre que el contexto de la aplicación aún no se haya cargado.

// Previous answers incorrectly used "spring.active.profiles" instead of
// "spring.profiles.active" (as noted in the comments).
// Use AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME to avoid this mistake.

System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, environment);
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml");
gthazmatt
fuente
12

Flayway no reconoce las variables de entorno directas en application.properties (Spring-Boot V2.1). p.ej

spring.datasource.url=jdbc:mysql://${DB_HOSTNAME}:${DB_PORT}/${DB_DATABASE}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASS}

Para resolver este problema, hice estas variables de entorno, generalmente creo el archivo .env:

SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/place
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=root

Y exportar las variables a mi entorno:

export $(cat .env | xargs)

Y finalmente solo ejecuta el comando

mvn spring-boot:run

O ejecuta tu archivo jar

java -jar target/your-file.jar

Hay otro enfoque aquí: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html

Felipe Girotti
fuente
1
¿Qué es env-vars? ¿Cómo se usan? Su respuesta se refiere a cosas sin una descripción completa y no incluye ningún enlace. Casi lo rechacé, pero veo que su representante tiene 21, así que usted es nuevo y una persona encontró útil su respuesta, así que lo dejé pasar, pero trate de proporcionar más información en futuras respuestas, y bienvenido a SO (desbordamiento de pila). Espero que lo disfrutes tanto como yo.
PatS
2
Gracias @PatS, agregué más detalles, espero que sea útil.
Felipe Girotti el
1
Excelentes cambios. Gracias actualizando tu respuesta.
PatS
9

Aquí hay un código de fragmento a través de una cadena de archivos de propiedades de entornos que se están cargando para diferentes entornos.

Archivo de propiedades bajo los recursos de su aplicación ( src / main / resources ): -

 1. application.properties
 2. application-dev.properties
 3. application-uat.properties
 4. application-prod.properties

Idealmente, application.properties contiene todas las propiedades comunes que son accesibles para todos los entornos y las propiedades relacionadas con el entorno solo funcionan en entornos específicos. por lo tanto, el orden de carga de estos archivos de propiedades será tal:

 application.properties -> application.{spring.profiles.active}.properties.

Fragmento de código aquí: -

    import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;

    public class PropertiesUtils {

        public static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

        public static void initProperties() {
            String activeProfile = System.getProperty(SPRING_PROFILES_ACTIVE);
            if (activeProfile == null) {
                activeProfile = "dev";
            }
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer
                    = new PropertySourcesPlaceholderConfigurer();
            Resource[] resources = new ClassPathResource[]
                    {new ClassPathResource("application.properties"),
                            new ClassPathResource("application-" + activeProfile + ".properties")};
            propertySourcesPlaceholderConfigurer.setLocations(resources);

        }
    }
Ajay Kumar
fuente
2
¿Spring Boot no maneja este escenario fuera de la caja? Consulte la documentación de configuración externa aquí
ChickenFeet,
4

Tal vez escribo esto demasiado tarde, pero he tenido un problema similar cuando he tratado de anular los métodos para leer propiedades.

Mi problema ha sido: 1) Leer la propiedad de env si esta propiedad se ha establecido en env 2) Leer la propiedad de la propiedad del sistema si esta propiedad se ha establecido en la propiedad del sistema 3) Y, por último, leer de las propiedades de la aplicación.

Entonces, para resolver este problema, voy a mi clase de configuración de bean

@Validated
@Configuration
@ConfigurationProperties(prefix = ApplicationConfiguration.PREFIX)
@PropertySource(value = "${application.properties.path}", factory = PropertySourceFactoryCustom.class)
@Data // lombok
public class ApplicationConfiguration {

    static final String PREFIX = "application";

    @NotBlank
    private String keysPath;

    @NotBlank
    private String publicKeyName;

    @NotNull
    private Long tokenTimeout;

    private Boolean devMode;

    public void setKeysPath(String keysPath) {
        this.keysPath = StringUtils.cleanPath(keysPath);
    }
}

Y sobrescribir fábrica en @PropertySource. Y luego he creado mi propia implementación para leer propiedades.

    public class PropertySourceFactoryCustom implements PropertySourceFactory {

        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            return name != null ? new PropertySourceCustom(name, resource) : new PropertySourceCustom(resource);
        }


    }

Y creó PropertySourceCustom

public class PropertySourceCustom extends ResourcePropertySource {


    public LifeSourcePropertySource(String name, EncodedResource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(EncodedResource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, Resource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(Resource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException {
        super(name, location, classLoader);
    }

    public LifeSourcePropertySource(String location, ClassLoader classLoader) throws IOException {
        super(location, classLoader);
    }

    public LifeSourcePropertySource(String name, String location) throws IOException {
        super(name, location);
    }

    public LifeSourcePropertySource(String location) throws IOException {
        super(location);
    }

    @Override
    public Object getProperty(String name) {

        if (StringUtils.isNotBlank(System.getenv(name)))
            return System.getenv(name);

        if (StringUtils.isNotBlank(System.getProperty(name)))
            return System.getProperty(name);

        return super.getProperty(name);
    }
}

Entonces, esto me ha ayudado.

Maksym Galich
fuente
4

Usando Spring context 5.0, logré cargar correctamente el archivo de propiedades correcto basado en el entorno del sistema a través de la siguiente anotación

@PropertySources({
    @PropertySource("classpath:application.properties"),
    @PropertySource("classpath:application-${MYENV:test}.properties")})

Aquí, el valor MYENV se lee del entorno del sistema y si el entorno del sistema no está presente, se cargará el archivo de propiedades del entorno de prueba predeterminado, si proporciono un valor MYENV incorrecto, no se podrá iniciar la aplicación.

Nota: para cada perfil, desea mantener - necesitará hacer un archivo application- [profile] .property y, aunque utilicé Spring context 5.0 y no Spring boot , creo que esto también funcionará en Spring 4.1

Abdeali Chandanwala
fuente
3

Me enfrenté al mismo problema que el autor de la pregunta. Para nuestro caso, las respuestas en esta pregunta no fueron suficientes, ya que cada uno de los miembros de mi equipo tenía un entorno local diferente y definitivamente necesitábamos .gitignoreel archivo que tenía la cadena de conexión db y las credenciales diferentes, por lo que las personas no confirman el archivo común por error y romper las conexiones db de otros.

Además de eso, cuando seguimos el siguiente procedimiento, fue fácil de implementar en diferentes entornos y como un bono extra , no necesitábamos tener ninguna información confidencial en el control de versiones .

Obteniendo la idea del framework PHP Symfony 3 que tiene un parameters.yml(.gitignored) y unparameters.yml.dist (que es una muestra que crea el primero composer install),

Hice lo siguiente combinando el conocimiento de las respuestas a continuación: https://stackoverflow.com/a/35534970/986160 y https://stackoverflow.com/a/35535138/986160 .

Esencialmente, esto da la libertad de usar la herencia de las configuraciones de resorte y elegir los perfiles activos a través de la configuración en la parte superior más cualquier credencial extra sensible de la siguiente manera:

application.yml.dist (muestra)

    spring:
      profiles:
        active: local/dev/prod
      datasource:
        username:
        password:
        url: jdbc:mysql://localhost:3306/db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d en el servidor de desarrollo)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: verysecretpassword
    url: jdbc:mysql://localhost:3306/real_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d en la máquina local)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: rootroot
    url: jdbc:mysql://localhost:3306/xampp_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application-dev.yml (propiedades específicas del entorno adicionales no sensibles)

spring:
  datasource:
    testWhileIdle: true
    validationQuery: SELECT 1
  jpa:
    show-sql: true
    format-sql: true
    hibernate:
      ddl-auto: create-droop
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL57InnoDBDialect

Lo mismo se puede hacer con .properties

Michail Michailidis
fuente