Giter Club home page Giter Club logo

godotenv's Introduction

GoDotEnv CI Go Report Card

A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file).

From the original Library:

Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.

But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.

It can be used as a library (for loading in env for your own daemons etc.) or as a bin command.

There is test coverage and CI for both linuxish and Windows environments, but I make no guarantees about the bin version working on Windows.

Installation

As a library

go get github.com/joho/godotenv

or if you want to use it as a bin command

go >= 1.17

go install github.com/joho/godotenv/cmd/godotenv@latest

go < 1.17

go get github.com/joho/godotenv/cmd/godotenv

Usage

Add your application configuration to your .env file in the root of your project:

S3_BUCKET=YOURS3BUCKET
SECRET_KEY=YOURSECRETKEYGOESHERE

Then in your Go app you can do something like

package main

import (
    "log"
    "os"

    "github.com/joho/godotenv"
)

func main() {
  err := godotenv.Load()
  if err != nil {
    log.Fatal("Error loading .env file")
  }

  s3Bucket := os.Getenv("S3_BUCKET")
  secretKey := os.Getenv("SECRET_KEY")

  // now do something with s3 or whatever
}

If you're even lazier than that, you can just take advantage of the autoload package which will read in .env on import

import _ "github.com/joho/godotenv/autoload"

While .env in the project root is the default, you don't have to be constrained, both examples below are 100% legit

godotenv.Load("somerandomfile")
godotenv.Load("filenumberone.env", "filenumbertwo.env")

If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)

# I am a comment and that is OK
SOME_VAR=someval
FOO=BAR # comments at line end are OK too
export BAR=BAZ

Or finally you can do YAML(ish) style

FOO: bar
BAR: baz

as a final aside, if you don't want godotenv munging your env you can just get a map back instead

var myEnv map[string]string
myEnv, err := godotenv.Read()

s3Bucket := myEnv["S3_BUCKET"]

... or from an io.Reader instead of a local file

reader := getRemoteFile()
myEnv, err := godotenv.Parse(reader)

... or from a string if you so desire

content := getRemoteFileContent()
myEnv, err := godotenv.Unmarshal(content)

Precedence & Conventions

Existing envs take precedence of envs that are loaded later.

The convention for managing multiple environments (i.e. development, test, production) is to create an env named {YOURAPP}_ENV and load envs in this order:

env := os.Getenv("FOO_ENV")
if "" == env {
  env = "development"
}

godotenv.Load(".env." + env + ".local")
if "test" != env {
  godotenv.Load(".env.local")
}
godotenv.Load(".env." + env)
godotenv.Load() // The Original .env

If you need to, you can also use godotenv.Overload() to defy this convention and overwrite existing envs instead of only supplanting them. Use with caution.

Command Mode

Assuming you've installed the command as above and you've got $GOPATH/bin in your $PATH

godotenv -f /some/path/to/.env some_command with some args

If you don't specify -f it will fall back on the default of loading .env in PWD

By default, it won't override existing environment variables; you can do that with the -o flag.

Writing Env Files

Godotenv can also write a map representing the environment to a correctly-formatted and escaped file

env, err := godotenv.Unmarshal("KEY=value")
err := godotenv.Write(env, "./.env")

... or to a string

env, err := godotenv.Unmarshal("KEY=value")
content, err := godotenv.Marshal(env)

Contributing

Contributions are welcome, but with some caveats.

This library has been declared feature complete (see #182 for background) and will not be accepting issues or pull requests adding new functionality or breaking the library API.

Contributions would be gladly accepted that:

  • bring this library's parsing into closer compatibility with the mainline dotenv implementations, in particular Ruby's dotenv and Node.js' dotenv
  • keep the library up to date with the go ecosystem (ie CI bumps, documentation changes, changes in the core libraries)
  • bug fixes for use cases that pertain to the library's purpose of easing development of codebases deployed into twelve factor environments

code changes without tests and references to peer dotenv implementations will not be accepted

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Releases

Releases should follow Semver though the first couple of releases are v1 and v1.1.

Use annotated tags for all releases. Example git tag -a v1.2.1

Who?

The original library dotenv was written by Brandon Keepers, and this port was done by John Barton based off the tests/fixtures in the original library.

godotenv's People

Contributors

2tef avatar adamsbite avatar adombeck avatar alexandear avatar alexquick avatar bochenski avatar buddhamagnet avatar dependabot[bot] avatar djherbis avatar doarakko avatar dreygur avatar dvrkps avatar egorse avatar hairyhenderson avatar jmervine avatar joho avatar lotusirous avatar lucastetreault avatar matiasanaya-ffx avatar mattn avatar mdanzinger avatar mmilata avatar mniak avatar orxobo avatar pda avatar renovate[bot] avatar santosh653 avatar statik avatar wolfeidau avatar ys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

godotenv's Issues

Unknown import path

go mod encounters an error when trying to load one of the packages. I think the package location ("github.com/rogpeppe/go-internal/modfile") might have been moved.

go: finding github.com/rogpeppe/go-internal/modfile latest
../../../go/pkg/mod/github.com/gobuffalo/[email protected]/envy.go:26:2: unknown import path "github.com/rogpeppe/go-internal/modfile": cannot find module providing package github.com/rogpeppe/go-internal/modfile

Escaping the # character

If I have for example a variable in the .env file:
Password=good#password
The library will only read good
I tried escaping the # with one or more / symbols, doesn't work. Couldn't find a way to make it work.

Won't read from variable when it contains $ character

go version go1.9.2 windows/386

When I am reading from variable in my .env file which has $ in string:
MAIL_SECRET=xTvDqw$27

os.Getenv("MAIL_SECRET")
gives xTvDqw7 back, ignoring $2

why is that, have $ sign some special meaning?

Support for long lived applications

I am looking to have the .env file read more as a stream. Basically I just want the environment variables to update on the fly as I modify the .env file. This would allow for zero-downtime when modifying .env variables. My application is intended to run as a daemon on a system, never shutting down unless asked to.

Proper error when it can't find or parse the dotenv file

In the source code we have

// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
func Load(filenames ...string) (err error) {
	filenames = filenamesOrDefault(filenames)

	for _, filename := range filenames {
		err = loadFile(filename, false)
		if err != nil {
			return // return early on a spazout
		}
	}
	return
}

the return // return early on a spazout behavior is questionable, in my opinion. I think it should show a normal error somehow, since it makes things easier to debug.

In my case it turned out to be an error with env file being parsed, since I had an accidental newline in one of the variables. However I couldn't figure this out without going into the dotenv source code and adding some logger statements.

Please Provide Annotated Release Tags

This package is used by Gitea which is a package I'm currently working to get into Debian. This means I get to do a review of all build dependencies. While working through your project, I saw that tags are used to mark releases, but they are not being annotated.

Unannotated release tags end up causing some headaches for packaging systems that monitor upstream activity--mostly for new releases--because the information is missing from 'git describe'. To annotate a tag, it just needs the -a flag passed. (git tag -a).

If you're willing to, it's possible to update the current tags (or just latest) with annotation. I've included some links [1] [2] that explain the process.

If you choose not to update tags, it would still be hugely appreciated if you could use annotated tags in the future.

[1] http://sartak.org/2011/01/replace-a-lightweight-git-tag-with-an-annotated-tag.html
[2] http://stackoverflow.com/questions/5002555/can-a-lightweight-tag-be-converted-to-an-annotated-tag

Go modules and API lockdown

It's time to support go modules. Because I don't love having to change import path on breaking changes there's a bit of a TODO list.

Must do

  • Fix #66
  • Fix #64
  • Add #10
  • Actually make the go module file

Maybe

  • Change behaviour in #37
  • Add #38

Do support opting out for 'value quoteing"

Right now, when you open a file with godotenv and manipulate it, write it back, the values are all quoted.

In fact, that does not work out, since e.g. docker-compose does not allow those values to be quoted. So e.g.

That will work

COMPOSE_FILE=docker-compose.yml:docker-compose-dwcm.yml

while

COMPOSE_FILE="docker-compose.yml:docker-compose-dwcm.yml"

errors with
ERROR: .IOError: [Errno 2] No such file or directory: u'./"docker-compose.yml'

Any way to opt out of "quoting" values? Thanks!

Integrate with Kubernetes

Very useful, thank you!

I discovered godotenv through a Go sample that uses it. I'm working on running that solution on Kubernetes. One way to solve this with Kubernetes would be to add the .env file to the container but this seems to defeat the principle of keeping config externalized.

I used ConfigMaps and am sharing it here for consideration:

There's a limitation (!) in ConfigMaps which prevents using them to map a source file containing environment variables or other properties directly into container environment variables.

The workaround is to surface the ConfigMap's contents as a volume in the container but this then requires moving the .env (or similarly named file) to a separate directory.

  1. Move .env. to its own directory and revise the Golang reference. NB for convenience I'm using a relative reference here without "./" and I use an absolute reference in the container spec (step 3)
err := godotenv.Load("config/.env")
  1. Convert an existing .env to a ConfigMap
kubectl create configmap env --from-file=./config/.env
  1. The container spec must then reference the ConfigMap (env) through a volume mount:
spec:
  containers:
  - image: dazwilkin/configmaptest:rpi3
    imagePullPolicy: Always
    name: configmaptest
    ports:
    - name: http
      containerPort: 8080
      protocol: TCP
    volumeMounts:
    - name: config-volume
      mountPath: /config
  volumes:
    - name: config-volume
      configMap:
        name: configmaptest

This results in a container that will run on Kubernetes deriving its env settings from the pod during deployment. The same image may be used in multiple pods each using a different configuration.

Thoughts?

Variables expansion only using values from the file, not os.Getenv

Is there a particular reason for NOT falling back to os.Getenv, when a variable to be expanded within another variable in the file DO NOT exist in that file?

I saw the history on issues and the PR implementing the current expansion logic, and I guess this should be following the Ruby lib behaviour - which I am not familiar with.

So I would like to know if that makes any sense, and we could think about adding such feature to use pre-existing environment variables, or not.

BTW: thanks for the package, very helpful!

Path to .env, different path for compiled package.

When I am working on my project I am happy having my .env in the root of my git repo, however, when I run go install which points to /srv in my GOPATH environmental variable I want it to read the .env from /srv/.env. Right now I am just sym linking and this works fine, but feels a little hacky. Thoughts?

What do you think about own variable reading functions?

// now
_test := os.Getenv("TEST_INT")
# _test = "5" || _test = ""

// feature
_test := godotenv.GetPanic("TEST_INT")
# _test = "5" || panic("TEST_INT")

// int value
_test, err := godotenv.GetInt("NATS_test")
// _test = 5
// err = nil

env TEST_INTS=1 2 3 5
// list value
_test := godotenv.GetsPanic("TEST_INTS")
# _test =["1", "2", "3", "5"] || panic("TEST_INTS")

_test := godotenv.GetIntsPanic("TEST_INTS")
# _test = [1, 2, 3, 5] || panic("TEST_INTS")

https://github.com/alexsuslov/godotenv/blob/master/load.go

Proposal: add an "import" statement to the .env syntax

Problem: My projects usually have a stack of .env files, all of which are optional. For example, most of my projects have a .env, and allow an option .env.local, which overrides .env. And when I run tests, the tests load .env.local, then .env.test, then .env.

It would be nice to run the godotenv command with a single file argument, like when running tests: godotenv -f .env.test go test ./..., while still loading the other .env files in the right precedence order.

Proposal:

If a .env file contains a comment line like:

# import: .env.local

When encountered, godotenv would essentially Load() that other file. File not found errors would be ignored. (a "must-import" directive could be added to require the presence of the other file).

So .env files could put "overrides" up top, and "defaults" at the bottom. For example, the .env.local use case could be handled with a .env file like:

# load local developer overrides
# import: .env.local
key1=val
key2=val

The .env.test case could be handed by defining a .env.test file like:

# load local developer overrides
# import: .env.local
testkey1=value
# load defaults
# import: .env

Or, if a project wanted to have separate local overrides for both the default .env file and the .env.test file, the .env.test file could be:

# load local developer overrides
# import: .env.test.local
testkey1=value
# load defaults (which in turn loads .env.local overrides)
# import: .env

Issues: files could be loaded more than once. This shouldn't pose an issue. Circular references could occur, but could be detected by the loading code and error out.

Bool as a value of env variable?

I want to have bools inside of my .env file, right now when adding via os.Setenv() only strings are added, is this a limitation of go? I am having to strconv.PraseBool() my env variables just to get simple checks throughout my application.

Option to read env files from multiple folders.

I think having an option of passing folder name to Load(folders ...string) or Overload(folders ...string) will help tremendously in loading all env files found. This is extremely beneficial when working with different packages where each package has its own env file within it.

Supporting lists

I often find myself wanting to provide a set of values via the environment.
So far I've been using comma-separated values, like so

MY_LIST=val1,val2,val3

And then splitting that (strings.Split(os.Getenv("MY_LIST"), ",")).

Recently I had a bunch of values which could include commas.

Would it be possible to include some sort of support for list values? Syntax suggestion

MY_LIST[]=val1
MY_LIST[]=val2
MY_LIST[]=val3

`Read()` default behaviour works more like `Overload` than `Load()`

When reading a series of files to a map, successive .env files which define a variable already set by files earlier in the list will overwrite the value of that variable. Since this is different from the behaviour of Load() (which is consistent with Load() from the reference dotenv ruby implementation), this difference should be documented.

Here's a gist for demonstration purposes

Quotes dropped at the end of a value

When having a value which includes quotes, godotenv seems to drop them automatically:

TEST = "echo 'asd'"

results in

echo 'asd

Short term solution by adding a space after it works for our usecase, however I doubt that this is the case in general
example that works:

TEST = "echo 'asd' "

results in

echo 'asd'

In hindsight after reading

The parser itself is pretty stupidly naive and I wouldn’t be surprised if it breaks with edge cases.

it is not particularly surprising.
Maybe this can be used as an example to make it a bit smarter?

Capability to check for .env file not found error and other possible errors

Description

When running gotdotenv.Load:

func Load(filenames ...string) (err error) {

Theres no way to check for a "FileNotFound" error.
godotenv panics as follows:

➜  server git:(master) ✗ go run main.go
panic: open .env: no such file or directory

goroutine 1 [running]:
main.main()
	.../server/main.go:12 +0xa9
exit status 2

Possible Solution

A possible solution would be to enumerate errors:

var (
        // EnvFileNotFound returns a "env file not found". Happens when godotenv is not able to find a .env file in the cwd
	EnvFileNotFound = errors.New("env file not found")
)

Then provide a ErrorIs like function, for example:

	err := godotenv.Load()

	if err != nil {
		if godotenv.ErrorIs(godotenv.EnvFileNotFound, err) {
                    // Do something
                } else {
		    return nil, err
                }
	}

Thanks in advance!

Load real env vars in production and use a .env locally

This is pretty common in Node. You load a .env locally to simulate env vars, but when deploying to production or dev server (Heroku, AWS, etc) env vars are real and are only available to the person that configured the server.

It's dangerous to store critical data in the repo such as database users, password hashes, etc.

Add support for fallback if env variable is missing

Would be nice if there was option for fallback option if a variable is missing

Something similar to:

func env(key, fallback string) string {
value, exists := os.LookupEnv(key)
if !exists {
value = fallback
}
return value
}

Specify path to .env

Nice work here. I have my .env in the directory website/. I run go install from src/ like this

go install github.com/audrey/website

How do I run this without having .env in the root directory ie. src/ but in website/?

proposal: make env file optional

When you call Load() or Overload() without args it returns an error open .env: no such file or directory. There is a situation where .env file is not necessary like deploying with docker or running tests using CI tools like CircleCI where the environment can be set in other ways.
as such, it will be good if .env file is made optional so the app can use the existing os Env or get env through os.Args
EDITED:
check this comment for more #99 (comment)

Empty env var are not allowed anymore

Before PR #25 it was possible to have empty variables like

MYVAR=somevalue
MYOTHERVAR=

As of current version I'm receiving a panic with panic: runtime error: slice bounds out of range. Is this by design? I like to leave empty variables as placeholders and to let others know which variables can be used.

I can send the PR if necessary.

Thanks!

why only upper case variable substitution

Commit 992ab0e added a regexp for variable substitution but the pattern only matches digits and upper case letters. Why not lower case letters too? (I see earlier code only supported upper case too.)

I added a-z to the pattern and it made the package work on my env variables and did not break any of the unit tests. Turns out all the unit tests are for upper case variables so far. The go package doesn't have problems with lower case or mixed case variables, nor does bash.

If this is intentional, perhaps a comment in the README stating only upper case are supported for canonical reasons or something like that.

Should go without saying but just in case ... nice package. Thanks for making it available to the go community!

Load() with no args doesn't work

I get the error (EXTRA *os.PathError=open .env: no such file or directory) when I don't include any arguments in Load(). My env file is called local.env. It works if I hardcode Load("local.env")

I stick my local.env in the same directory as my main.go. Any ideas on what I'm doing wrong? Thank you.

signal passing

my app does a graceful shutdown when it receives sigterm or sigint, which might take a couple seconds. But godotenv ends immediately I think.

Seems like godotenv should pass signals to the child and then wait.

Not parse yml lists

Hello, testing the format .env yml the code created not read that particular variable .

INTERFACES :

  • eth0
  • eth2

Can't use godotenv when running tests

I've added godotenv to my project and it works fine when running the compiled binary, but it fails when running my tests:

$ go test ./...
2017/10/13 16:22:37 Error loading .env fileopen .env: no such file or directory

This is happening because the tests are being run in the following temp dir location:

/var/folders/yh/7x8fqv696sb1673313nk8_m80000gn/T/go-build041463486/github.com/myrepo/identity/server/_test

and it can't find the .env file. Can anyone tell me how to fix this issue? Do I need to specify the absolute path to the .env file? I'm using the following code to load the .env file without specifying a path:

err = godotenv.Load()

if err != nil {
	log.Fatal("Error loading .env file", err)
}

Marshal option that accept non-string values in the map

I think ints and floats should not be quoted, but since all the values are strings in the Marshal method, everything is quoted.
Maybe there could be a method that accepts map[string]interface{} and then converts accordingly

.env file output

Hi, I've got a use case where it would be beneficial to be able to output .env files as well as read them. Is that something you'd be interested in a PR for or is it out of scope for this project?

if env var is set to an empty value in the environment, godotenv will overwrite it

let's say I have an empty environment, and a .env file containing:

COLOR=blue

then:

$ godotenv env
COLOR=blue

Now I set COLOR to an empty value:

$ COLOR= godotenv env
COLOR=blue

I don't think it should override my setting. I tested with the ruby dotenv, it respects the env var being set, even to an empty value:

$ COLOR= dotenv env
COLOR=

New semver release tag

Seems like the last release tag was v1, there have been a few years worth of commits since, it would be nice if a newer release tag could be created (semver style) thanks. It makes vendoring a lot easier.

Any key starting lowercase `export` fails to return value.

Any key starting lowercase export fails to return value.

All the following will fail to return value:

export export_ export_x export_xx export_xxx export_zzzz export_env export-env export_envx exportabc export_env EXPORT_ENV export_env_settings export_env_

expor or EXPORT are fine.

I note export BAR=BAZ is valid usage.

Changing the if to check 'export ' rather than 'export' and testing specifically for key of export in godotenv.go line 256 gives the expected behaviour for me.

// Parse the key
	key = splitString[0]
	if strings.HasPrefix(key, "export") {
		key = strings.TrimPrefix(key, "export")
	}
	key = strings.Trim(key, " ")

change to

// Parse the key
	key = splitString[0]
	if strings.HasPrefix(key, "export ") && string.Trim(key, " ") != "export" {
		key = strings.TrimPrefix(key, "export")
	}
	key = strings.Trim(key, " ")

Doesn't support multiline vars

If I set something like this:

KEY='this is
a multiline
variable'

godotenv returns:

Can't separate key from value

Even though that's a valid variable assignment.

I'm currently at work; will attempt a fix later this week.

How to prevent reversing when using godotenv.Write()?

So I currently have this code:

// Env - populate app's .env file
func Env(app string, appType string) {
    // Define app's variables before writing
    envMap := map[string]string{
        "FOO" : "FOO's value",
        "BAR" : "BAR's value",
    }

    // Write to .env
    err := godotenv.Write(envMap, "/some/path/.env")
    if err != nil {
        panic(err)
    }
}

The order is not kept and it's reversed once written, the output is this:

BAR="BAR's value"
FOO="FOO's value"

Now how do you prevent it from reversing? Or will I have to write up a custom function to reverse it? I will have almost 100 lines in that map so I really don't wanna start typing up the map in reverse if I could help it. godotenv.Write() still reverses it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.