Repository: deis/deis

Issue:
Push configuration from existing environment file
it would be nice to have a command to push configuration from an existing `.env` file. There already appears to be a `config:pull` command to download the `.env` file. This is a little bit related to #1531 in that it would help configure existing Procfile based applications in one go.



Discussion:
+1

We could add an option to `config:set`:

```
$ deis config:set --from-file .env
```

Which would populate key/values from that file and push them to Deis. Does this seem reasonable?

That works. Though, I think the way the `config:set` command is documented, it shortcuts the syntax a bit. I expect `<var>=<value>` to be a required argument. Your syntax ignores the required arguments.

I wonder if you could get automagical and try loading the first argument from `<var>=<value>` and then from `<file>` if `<var>=<value>` is invalid?

The usage becomes something like:

```
Usage: deis config:set <var>=<value> [<var>=<value>...] | <file> [options]
```

Just ideas. Any way, as long is it was listed in `deis help config`, satisfies me :smile_cat: 

As a workaround to this:

```
$ deis config:set $(cat .env)
```

Works great and does the job.

+1 on this. Alternatively if you could document the workaround then I think that will work fine.

+1000

I think the `CLI` api should mimic [`ddolar/heroku-config`](https://github.com/ddollar/heroku-config) (a plugin for `heroku toolbelt`)

`deis config:pull` should keep it's functionality
`deis config:push` should push contents of a `.env` (if present) as if it were `deis config:set [..]`

`deis config:pull` is already implemented. Personally I'm interested why a `config:push` command is necessary when `deis config:set $(cat .env)` does the same thing... Any reasons why the latter won't work for the general use case?

tl;dr: I think most users would expect `config:push` to work if `config:pull` works.

I wouldn't call it 'necessary' as much as I would call it 'expected'. It's pretty safe to assume that over 80% of Deis implementors have been or still are Heroku users. With that in mind, the most sensible approach would be to mimic Heroku's API as much as possible, in order to provide what's expected by Deis' userbase. That being said, Heroku dosen't provide `config:pull` out of the box; that feature is instead provided by the `[heroku-config](https://github.com/ddollar/heroku-config)` plugin. But that plugin provides `config:push` as well, all based on the `.env` contents.

There are always workarounds for every CLI command, if you're very knowledgable of bash/shell scripting. But most devs aren't and are used to simple, intuitive, single-command, CLI tools. Moreso if they're based/inspired on other tools (in this case, the `heroku toolbelt`).

I understand the motivation behind convenience functions, but after giving this more thought on this I believe that they should be a plugin, much like Heroku's client. If there's a way we can make the client more pluggable, this functionality could easily be added into the client and convenience functions like `config:pull` and `config:push` could be moved out into a plugin. I'm all for it; I'd just prefer that we focus on the "core" functionality and allow users to extend the client the way they prefer.

Does that seem reasonable to you?

:+1: 
I agree on `config:push/pull` being plugins as a right approach. I just thought that as `pull` was part of the core, then `push` might be just as well, but I'm all for coherence :)

> I just thought that as pull was part of the core, then push might be just as well

That's a valid point. @bacongobbler we probably need to have a larger discussion around plugin architecture.


Resolving patch:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -984,6 +984,7 @@ def config(self, args):
         config:set         set environment variables for an app
         config:unset       unset environment variables for an app
         config:pull        extract environment variables to .env
+        config:push        set environment variables from .env
 
         Use `deis help [command]` to learn more.
         """
@@ -1184,6 +1185,30 @@ def config_pull(self, args):
         else:
             raise ResponseError(response)
 
+    def config_push(self, args):
+        """
+        Sets environment variables for an application.
+
+        Your environment is read from a file named .env. This file can be
+        read by foreman to load the local environment for your app.
+
+        Usage: deis config:push [options]
+
+        Options:
+          -a --app=<app>
+            the uniquely identifiable name for the application.
+        """
+        app = args.get('--app')
+        if not app:
+            app = self._session.app
+        # read from .env
+        try:
+            with open('.env', 'r') as f:
+                self._config_set(app, dictify([line.strip() for line in f]))
+        except IOError:
+            self._logger.error('could not read from local env')
+            sys.exit(1)
+
     def domains(self, args):
         """
         Valid commands for domains:


Test patch:
diff --git a/tests/config_test.go b/tests/config_test.go
--- a/tests/config_test.go
+++ b/tests/config_test.go
@@ -3,6 +3,7 @@
 package tests
 
 import (
+	"io/ioutil"
 	"testing"
 
 	"github.com/deis/deis/tests/utils"
@@ -20,6 +21,7 @@ var (
 func TestConfig(t *testing.T) {
 	params := configSetup(t)
 	configSetTest(t, params)
+	configPushTest(t, params)
 	configListTest(t, params, false)
 	appsOpenTest(t, params)
 	configUnsetTest(t, params)
@@ -69,8 +71,23 @@ func configSetTest(t *testing.T, params *utils.DeisTestConfig) {
 	utils.CheckList(t, appsInfoCmd, params, "(v6)", false)
 }
 
+func configPushTest(t *testing.T, params *utils.DeisTestConfig) {
+	if err := utils.Chdir(params.ExampleApp); err != nil {
+		t.Fatal(err)
+	}
+	// create a .env in the project root
+	if err := ioutil.WriteFile(".env", []byte("POWERED_BY=Deis"), 0664); err != nil {
+		t.Fatal(err)
+	}
+	utils.Execute(t, "config:push --app {{.AppName}}", params, false, "Deis")
+	utils.CheckList(t, appsInfoCmd, params, "(v7)", false)
+	if err := utils.Chdir(".."); err != nil {
+		t.Fatal(err)
+	}
+}
+
 func configUnsetTest(t *testing.T, params *utils.DeisTestConfig) {
 	utils.Execute(t, configUnsetCmd, params, false, "")
-	utils.CheckList(t, appsInfoCmd, params, "(v7)", false)
+	utils.CheckList(t, appsInfoCmd, params, "(v8)", false)
 	utils.CheckList(t, "run env --app={{.AppName}}", params, "FOO", true)
 }