Repository: dbt-labs/dbt-core
Pull request: Add more package validation
resolves #9985

### Problem

dbt hangs when packages have empty values.  This is generally not intentional but the result of empty env-vars.

### Solution

Add validation

### Checklist

- [ ] I have read [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md) and understand what's expected of me  
- [ ] I have run this code in development and it appears to resolve the stated issue  
- [ ] This PR includes tests, or tests are not required/relevant for this PR
- [ ] This PR has no interface changes (e.g. macros, cli, logs, json artifacts, config files, adapter interface, etc) or this PR has already received feedback and approval from Product or DX
- [ ] This PR includes [type annotations](https://docs.python.org/3/library/typing.html) for new and modified functions


Problem statement:
[Bug] dbt hangs when packages have empty values
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

When a package is defined as 

```
packages:
    - local: ""  
```

Running `dbt deps` results in dbt hanging.

### Expected Behavior

Get an error that the value is am empty string

### Steps To Reproduce

Define a packages.yml in a dbt project as
```
```
packages:
    - local: ""  
```

run `dbt deps`

### Relevant log output

_No response_

### Environment

```markdown
- OS:
- Python:
- dbt:
```


### Which database adapter are you using with dbt?

_No response_

### Additional Context

_No response_

Hints from issue discussion:

Commit message: Merge branch 'main' of https://github.com/dbt-labs/dbt-core into er/9986-packages-validation

Patch to review:
diff --git a/.changes/unreleased/Fixes-20240422-152244.yaml b/.changes/unreleased/Fixes-20240422-152244.yaml
new file mode 100644
index 00000000000..869d152fda9
--- /dev/null
+++ b/.changes/unreleased/Fixes-20240422-152244.yaml
@@ -0,0 +1,6 @@
+kind: Fixes
+body: Validate against empty strings in package definitions
+time: 2024-04-22T15:22:44.575999-05:00
+custom:
+  Author: emmyoop
+  Issue: "9985"
diff --git a/core/dbt/contracts/project.py b/core/dbt/contracts/project.py
index 46c9742952f..9c99fd3bfec 100644
--- a/core/dbt/contracts/project.py
+++ b/core/dbt/contracts/project.py
@@ -101,13 +101,26 @@ class PackageConfig(dbtClassMixin):
     @classmethod
     def validate(cls, data):
         for package in data.get("packages", data):
+            # This can happen when the target is a variable that is not filled and results in hangs
+            if isinstance(package, dict):
+                if package.get("package") == "":
+                    raise ValidationError(
+                        "A package is missing the value. It is a required property."
+                    )
+                if package.get("local") == "":
+                    raise ValidationError(
+                        "A local package is missing the value. It is a required property."
+                    )
+                if package.get("git") == "":
+                    raise ValidationError(
+                        "A git package is missing the value. It is a required property."
+                    )
             if isinstance(package, dict) and package.get("package"):
                 if not package["version"]:
                     raise ValidationError(
                         f"{package['package']} is missing the version. When installing from the Hub "
                         "package index, version is a required property"
                     )
-
                 if "/" not in package["package"]:
                     raise ValidationError(
                         f"{package['package']} was not found in the package index. Packages on the index "
diff --git a/tests/functional/dependencies/test_simple_dependency.py b/tests/functional/dependencies/test_simple_dependency.py
index de06452ec30..2f8c1201307 100644
--- a/tests/functional/dependencies/test_simple_dependency.py
+++ b/tests/functional/dependencies/test_simple_dependency.py
@@ -434,3 +434,44 @@ def test_malformed_tarball_package_causes_exception(self, project):
         ) as e:
             run_dbt(["deps"])
             assert e is not None
+
+
+class TestEmptyDependency:
+    def test_empty_package(self, project):
+        # We have to specify the bad formatted package here because if we do it
+        # in a `packages` fixture, the test will blow up in the setup phase, meaning
+        # we can't appropriately catch it with a `pytest.raises`
+        empty_hub_package = {
+            "packages": [
+                {
+                    "package": "",
+                    "version": "1.0.0",
+                }
+            ]
+        }
+        write_config_file(empty_hub_package, "packages.yml")
+        with pytest.raises(DbtProjectError, match="A package is missing the value"):
+            run_dbt(["deps"])
+
+        empty_git_package = {
+            "packages": [
+                {
+                    "git": "",
+                    "revision": "1.0.0",
+                }
+            ]
+        }
+        write_config_file(empty_git_package, "packages.yml")
+        with pytest.raises(DbtProjectError, match="A git package is missing the value"):
+            run_dbt(["deps"])
+
+        empty_local_package = {
+            "packages": [
+                {
+                    "local": "",
+                }
+            ]
+        }
+        write_config_file(empty_local_package, "packages.yml")
+        with pytest.raises(DbtProjectError, match="A local package is missing the value"):
+            run_dbt(["deps"])


Code review comments:
diff_hunk: @@ -101,13 +101,26 @@ class PackageConfig(dbtClassMixin):
     @classmethod
     def validate(cls, data):
         for package in data.get("packages", data):
+            # This can happen when the target is a variable that is not filled and results in hangs
+            if isinstance(package, dict):
+                if package.get("package") == "":
+                    raise ValidationError(
+                        "A package is missing the value. It is a required property."
path: core/dbt/contracts/project.py
text: @user1:
Should we add some more detailed message here to help folks narrow down the problem since we already know what type is missing value here?

diff_hunk: @@ -101,13 +101,26 @@ class PackageConfig(dbtClassMixin):
     @classmethod
     def validate(cls, data):
         for package in data.get("packages", data):
+            # This can happen when the target is a variable that is not filled and results in hangs
+            if isinstance(package, dict):
+                if package.get("package") == "":
+                    raise ValidationError(
+                        "A package is missing the value. It is a required property."
path: core/dbt/contracts/project.py
text: @author:
```suggestion
                        "A hub package is missing the value. It is a required property."
```