Repository: dbt-labs/dbt-core

Issue:
dbt ls should return a zero exit code when no models are selected
### Describe the bug
`dbt ls` returns an exit code of `1` when no models are selected.

### Steps To Reproduce
Run `dbt ls` with a model selector which returns no models, e.g. `my_model,config.materialized:incremental` where `my_model` is not incremental.

### Expected behavior
I'd expect a warning message, and an exit code of `0`. `--warn-error` could be used to opt back in to the current behaviour.

### Log output

```
The selector 'current_concepts+,config.materialized:incremental' does not match any nodes and will be ignored
No nodes selected!
```

JSON (separate run):

```
{"timestamp": "2020-11-12T18:40:54.899662Z", "message": "No nodes selected!", "channel": "dbt", "level": 13, "levelname": "WARNING", "thread_name": "MainThread", "process": 58352, "extra": {"run_state": "internal"}}
```

### System information
**Which database are you using dbt with?**
n/a

**The output of `dbt --version`:**
```
0.18.1
```

**The operating system you're using:**

n/a

**The output of `python --version`:**

n/a

### Additional context

We're using this to programatically inspect the DBT graph and would expect an empty list rather than an error here. An error means we need to either string match against the output or ignore any errors, neither of which are desirable.

Slack thread I raised this in: https://getdbt.slack.com/archives/CJARVS0RY/p1605206141123400



Discussion:
Agree! I don't think it makes sense for `dbt ls` to ever return a nonzero exit code.

Resolving patch:
diff --git a/core/dbt/task/list.py b/core/dbt/task/list.py
--- a/core/dbt/task/list.py
+++ b/core/dbt/task/list.py
@@ -190,4 +190,5 @@ def get_node_selector(self):
             )
 
     def interpret_results(self, results):
-        return bool(results)
+        # list command should always return 0 as exit code
+        return True


Test patch:
diff --git a/test/integration/047_dbt_ls_test/test_ls.py b/test/integration/047_dbt_ls_test/test_ls.py
--- a/test/integration/047_dbt_ls_test/test_ls.py
+++ b/test/integration/047_dbt_ls_test/test_ls.py
@@ -436,7 +436,7 @@ def expect_select(self):
         results = self.run_dbt_ls(['--resource-type', 'test', '--select', 'outer'])
         self.assertEqual(set(results), {'test.schema_test.not_null_outer_id', 'test.schema_test.unique_outer_id'})
 
-        self.run_dbt_ls(['--resource-type', 'test', '--select', 'inner'], expect_pass=False)
+        self.run_dbt_ls(['--resource-type', 'test', '--select', 'inner'], expect_pass=True)
 
         results = self.run_dbt_ls(['--resource-type', 'test', '--select', '+inner'])
         self.assertEqual(set(results), {'test.schema_test.not_null_outer_id', 'test.schema_test.unique_outer_id'})
@@ -450,7 +450,7 @@ def expect_select(self):
         results = self.run_dbt_ls(['--select', 'config.incremental_strategy:delete+insert'])
         self.assertEqual(set(results), {'test.incremental'})
 
-        self.run_dbt_ls(['--select', 'config.incremental_strategy:insert_overwrite'], expect_pass=False)
+        self.run_dbt_ls(['--select', 'config.incremental_strategy:insert_overwrite'], expect_pass=True)
 
 
     @use_profile('postgres')
diff --git a/test/integration/062_defer_state_test/test_modified_state.py b/test/integration/062_defer_state_test/test_modified_state.py
--- a/test/integration/062_defer_state_test/test_modified_state.py
+++ b/test/integration/062_defer_state_test/test_modified_state.py
@@ -54,7 +54,7 @@ def setUp(self):
 
     @use_profile('postgres')
     def test_postgres_changed_seed_contents_state(self):
-        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=False)
+        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=True)
         assert len(results) == 0
         with open('data/seed.csv') as fp:
             fp.readline()
@@ -106,12 +106,12 @@ def test_postgres_changed_seed_contents_state(self):
         with open('data/seed.csv', 'a') as fp:
             fp.write(f'{random},test{newline}')
 
-        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=False)
+        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=True)
         assert len(results) == 0
 
     @use_profile('postgres')
     def test_postgres_changed_seed_config(self):
-        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=False)
+        results = self.run_dbt(['ls', '--resource-type', 'seed', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=True)
         assert len(results) == 0
 
         self.use_default_project({'seeds': {'test': {'quote_columns': False}}})
@@ -123,7 +123,7 @@ def test_postgres_changed_seed_config(self):
 
     @use_profile('postgres')
     def test_postgres_unrendered_config_same(self):
-        results = self.run_dbt(['ls', '--resource-type', 'model', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=False)
+        results = self.run_dbt(['ls', '--resource-type', 'model', '--select', 'state:modified', '--state', './state'], strict=False, expect_pass=True)
         assert len(results) == 0
 
         # although this is the default value, dbt will recognize it as a change