Utilisation de la variable env dans l'application.properties de Spring Boot

224

Nous travaillons sur une application Web Spring Boot et la base de données que nous utilisons est MySql ;

  • la configuration que nous avons est que nous la testons d'abord localement (cela signifie que nous devons installer MySql sur notre PC);

  • puis nous poussons vers Bitbucket ;

  • Jenkins détecte automatiquement le nouveau push vers Bitbucket et fait une compilation dessus (pour que Jenkins mvn build passe, nous devons également installer MySql sur les machines virtuelles qui exécutent Jenkins).

  • si la construction de Jenkins réussit, nous transmettons le code à notre application sur OpenShift (en utilisant le plugin de déploiement OpenShift sur Jenkins).

Le problème que nous avons, comme vous l'avez peut-être déjà compris, est que:

  • dans application.propertiesnous ne pouvons pas coder en dur les informations MySql. Étant donné que notre projet sera exécuté à 3 endroits différents ( local , Jenkins et OpenShift ), nous devons rendre le champ de source de données dynamique application.properties(nous savons qu'il existe différentes façons de le faire, mais nous travaillons sur cette solution pour le moment).

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

La solution que nous avons trouvée est de créer des variables d'environnement système localement et dans la VM Jenkins (en les nommant de la même manière qu'OpenShift les nomme) et en leur attribuant les bonnes valeurs respectivement:

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"

Nous avons fait cela et cela fonctionne. Nous avons également vérifié Map<String, String> env = System.getenv();que les variables d'environnement peuvent être transformées en variables java en tant que telles:

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");

Maintenant, la seule chose qui reste est que nous devons utiliser ces variables java dans notre application.propertieset c'est ce avec quoi nous avons des problèmes.

Dans ce dossier, et comment, avons-nous besoin d'assigner les password, userName, sqlURLet sqlPortvariables pour application.propertiesêtre en mesure de les voir et comment pouvons-nous les inclure dans application.properties?

Nous avons essayé de nombreuses choses, dont:

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

Pas de chance pour l'instant. Nous ne mettons probablement pas ces variables d'environnement dans la bonne classe / dossier ou les utilisons de manière incorrecte dans application.properties.

Votre aide est très appréciée!!

Merci!

SM
la source
3
Lisez @ConfigurationProperties pour en savoir plus. Cependant, ceci est un cas d'utilisation parfait pour les propriétés de configuration spécifiques au profil
Eddie B

Réponses:

305

Vous n'avez pas besoin d'utiliser des variables java. Pour inclure des variables d'environnement système, ajoutez les éléments suivants à votre application.propertiesfichier:

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}

Mais la manière suggérée par @Stefan Isele est plus préférable, parce que dans ce cas , vous devez déclarer une seule variable d'env: spring.profiles.active. Spring lira automatiquement le fichier de propriétés approprié par application-{profile-name}.propertiesmodèle.

Ken Bekov
la source
12
Cette méthode est plus pratique pour la liaison de docker. Par exemple: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
19
C'est absolument la meilleure façon de procéder. L'utilisation de variables d'environnement signifie que vous n'avez pas besoin de lister les secrets en texte brut avec votre application. Ceci est beaucoup plus sécurisé et réduit la dépendance à l'égard de vos mesures de sécurité d'accès au code source pour protéger l'ensemble de votre patrimoine. Un message SO accidentel avec des propriétés incluses n'entraîne pas de fuite d'informations.
kipper_t
55
Je voulais ajouter à cela et mentionner que si vous utilisez Spring Boot (je n'ai pas vérifié si cela fonctionne sans démarrage), alors toute propriété peut être remplacée automatiquement via une variable d'environnement sans modifier votre application.properties. à savoir, si vous avez une propriété appelée spring.activemq.broker-urlalors la variable d'environnement correspondant serait: SPRING_ACTIVEMQ_BROKER_URL. les points et les tirets sont automatiquement convertis en traits de soulignement. Ceci est extrêmement pratique lorsque vous travaillez avec des conteneurs / bottes à ressort.
abe
16
Si vous concevez pour le cloud, ce n'est pas un moyen préférable d'utiliser les profils Spring. L'utilisation de variables d'environnement est recommandée par la norme d'application à 12 facteurs: 12factor.net/config
Mikhail Golubtsov
6
Je sais que ce sujet est un peu vieux. Mais vous pouvez combiner à la fois la configuration des variables d'environnement et la configuration du profil de ressort. Votre profil de développement doit avoir des informations statiques tandis que votre profil de production peut utiliser les variables d'environnement. De cette manière, les développeurs n'ont plus besoin de définir des variables d'environnement sur leur machine s'ils veulent simplement déployer le profil de développement.
underscore_05
82

Le moyen le plus simple d'avoir différentes configurations pour différents environnements est d'utiliser des profils de ressort. Voir configuration externalisée .

Cela vous donne beaucoup de flexibilité. Je l'utilise dans mes projets et c'est extrêmement utile. Dans votre cas, vous auriez 3 profils: 'local', 'jenkins' et 'openshift'

Vous avez alors 3 profil des fichiers de propriétés spécifiques: application-local.properties, application-jenkins.propertiesetapplication-openshift.properties

Vous pouvez y définir les propriétés de l'environnement concerné. Lorsque vous exécutez l'application, vous devez spécifier le profil à activer comme ceci: -Dspring.profiles.active=jenkins

Éditer

Selon la documentation de printemps, vous pouvez définir la variable d'environnement système SPRING_PROFILES_ACTIVEpour activer les profils sans avoir besoin de la passer en paramètre.

existe-t-il un moyen de transmettre l'option de profil actif pour l'application Web au moment de l'exécution?

Non. Spring détermine les profils actifs comme l'une des premières étapes, lors de la création du contexte d'application. Les profils actifs sont ensuite utilisés pour décider quels fichiers de propriétés sont lus et quels beans sont instanciés. Une fois l'application démarrée, cela ne peut pas être modifié.

Stefan Isele - prefabware.com
la source
4
J'aime cette réponse, mais que faire si vous voulez que le nom du profil provienne de l'environnement? J'ai essayé -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES, et j'ai défini la var d'environnement du système d'exploitation dans /etc/profile.d/myenvvars.sh, mais Spring Boot ne le prend pas
Tom Hartwell
2
SPRING_PROFILES_ACTIVE fonctionne grâce à la fonction de reliure relâchée de spring boot docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
5
merci pour cette réponse Stefan, cela a fonctionné pour moi, mais avec un changement - la propriété est en fait spring.profiles.active et non spring.active.profiles
Rudi
12
Si les profils Spring peuvent être très utiles, ils ne sont pas adaptés à l'OP. Cela est dû à la façon dont le code source est stocké et à la sensibilité des informations de propriétés stockées avec cela. Le contexte OP concerne l'accès à la base de données. Pour cette situation, vous ne voulez pas de détails de prod en texte brut dans la source. Cela signifie que si la source est compromise, la base de données est également compromise. Il est préférable d'utiliser des variables d'environnement ou des outils secrets pour cela, tels que Vault. Je préfère env. Je ferais également que tous les environnements fonctionnent de la même manière à cet égard pour la cohérence. Cela évite les accidents dans le futur.
kipper_t
2
Vous pouvez utiliser un fichier de propriétés de profil Spring Boot externe au JAR de l'application. Ce fichier spécifique à l'environnement, par exemple, application-production.propertiesserait déployé sur la machine de production de manière sécurisée et ne se trouverait généralement pas dans le référentiel de code source de l'application.
Colin D Bennett
17

Flayway ne reconnaît pas les variables d'environnement directes dans l' application.properties (Spring-Boot V2.1). par exemple

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

Pour résoudre ce problème, j'ai fait ces variables d'environnement, généralement je crée le fichier .env:

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

Et exportez les variables vers mon environnement:

export $(cat .env | xargs)

Et enfin, lancez simplement la commande

mvn spring-boot:run

Ou exécutez votre fichier jar

java -jar target/your-file.jar

Il existe une autre approche ici: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html

Felipe Girotti
la source
1
Qu'est-ce que env-vars? Comment sont-ils utilisés. Votre réponse fait référence à des choses sans description complète et vous n'incluez aucun lien. J'ai presque voté contre cela, mais je vois que votre représentant a 21 ans, donc vous êtes nouveau et une personne a trouvé votre réponse utile, alors je l'ai laissée aller, mais essayez de fournir plus d'informations dans les réponses futures, et bienvenue dans SO (Stack Overflow). J'espère que vous l'apprécierez autant que moi.
PatS
2
Merci @PatS, j'ai ajouté plus de détails, j'espère que cela vous sera utile.
Felipe Girotti
1
Excellents changements. Merci de mettre à jour votre réponse.
PatS
13

C'est en réponse à un certain nombre de commentaires car ma réputation n'est pas assez élevée pour commenter directement.

Vous pouvez spécifier le profil au moment de l'exécution tant que le contexte d'application n'a pas encore été chargé.

// 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
la source
9

Voici un extrait de code à travers une chaîne de fichiers de propriétés d'environnements en cours de chargement pour différents environnements.

Fichier de propriétés sous les ressources de votre application ( src / main / resources ): -

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

Idéalement, application.properties contient toutes les propriétés courantes qui sont accessibles pour tous les environnements et les propriétés liées à l'environnement ne fonctionnent que sur l'environnement spécifié. donc l'ordre de chargement de ces fichiers de propriétés sera de telle manière -

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

Extrait de code ici: -

    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
la source
3
Spring Boot ne gère-t-il pas ce scénario hors de la boîte? Voir la documentation External Config ici
ChickenFeet
4

Peut-être que j'écris cela trop tard, mais j'ai eu le même problème lorsque j'ai essayé de remplacer les méthodes de lecture des propriétés.

Mon problème a été: 1) Lire la propriété de env si cette propriété a été définie dans env 2) Lire la propriété de la propriété système si cette propriété a été définie dans la propriété système 3) Et enfin, lire à partir des propriétés de l'application.

Donc, pour résoudre ce problème, je vais à ma classe de configuration 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);
    }
}

Et écrasez l'usine dans @PropertySource. Et puis j'ai créé ma propre implémentation pour lire les propriétés.

    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);
        }


    }

Et créé 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);
    }
}

Donc, cela m'a aidé.

Maksym Galich
la source
4

En utilisant Spring Context 5.0, j'ai réussi à charger le fichier de propriétés correct en fonction de l'environnement système via l'annotation suivante

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

Ici, la valeur MYENV est lue à partir de l'environnement système et si l'environnement système n'est pas présent, le fichier de propriétés de l'environnement de test par défaut sera chargé, si je donne une valeur MYENV erronée - il ne parviendra pas à démarrer l'application.

Remarque: pour chaque profil, vous voulez maintenir - vous devrez créer un fichier application- [profile] .property et bien que j'aie utilisé Spring context 5.0 et non Spring boot - je pense que cela fonctionnera également sur Spring 4.1

Abdeali Chandanwala
la source
3

J'ai été confronté au même problème que l'auteur de la question. Pour notre cas, les réponses à cette question n'étaient pas suffisantes car chacun des membres de mon équipe avait un environnement local différent et nous devions absolument.gitignore du fichier qui avait la chaîne de connexion et les informations d'identification différentes, afin que les gens ne commettent pas le fichier commun par erreur et rompre les connexions db des autres.

De plus, lorsque nous avons suivi la procédure ci-dessous, il était facile de déployer sur différents environnements et, en prime, nous n'avions pas du tout besoin d'informations sensibles dans le contrôle de version .

Obtenir l'idée du framework PHP Symfony 3 qui a un parameters.yml(.gitignored) et un parameters.yml.dist(qui est un exemple qui crée le premier à travers composer install),

J'ai fait ce qui suit en combinant les connaissances des réponses ci-dessous: https://stackoverflow.com/a/35534970/986160 et https://stackoverflow.com/a/35535138/986160 .

Essentiellement, cela donne la liberté d'utiliser l' héritage des configurations de ressort et de choisir les profils actifs via la configuration en haut, ainsi que les informations d'identification supplémentaires sensibles comme suit:

application.yml.dist (exemple)

    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 sur le serveur de développement)

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 sur la machine locale)

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 (propriétés supplémentaires spécifiques à l'environnement non 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

La même chose peut être faite avec .properties

Michail Michailidis
la source
1

Si les fichiers de propriétés sont externalisés en tant que variables d'environnement, la configuration de l'exécution peut être ajoutée dans l'EDI:

--spring.config.additional-location={PATH_OF_EXTERNAL_PROP}

Mahesh K
la source