Repository: conan-io/conan

Issue:
[bug] conf format isn't checked when creating a package
### Describe the bug

v2.17.1

I was playing with defining conf from a recipe and noticed error only when trying to consume such a recipe:

```
ERROR: cocoalumberjack/3.8.5: Error in compatibility() method, line 97
	print(f"{self.conf.get("user.myconf.asd")=}")
	ConanException: [conf] Either 'user.myconf.asd' does not exist in configuration list or the conf format introduced is not valid. Run 'conan config list' to see all the available confs.
```

In the [docs](https://docs.conan.io/2/reference/config_files/global_conf.html#user-tools-configurations) I found that `:` is required. But code to define the conf in `package_info()` didn't trigger such an error, I think it should.

### How to reproduce it

no error with `conan create .`
```py
def package_info(self):
    self.conf_info.define("user.myconf.asd", "whatever")
```

the above error with `conan install .` (consuming this dependency)
```py
def compatibility(self):
    print(f"{self.conf.get("user.myconf.asd")=}")
    ...
```


Discussion:
Thanks for the feedback @kambala-decapitator 

I am adding https://github.com/conan-io/conan/pull/18541 to check for ``conf_info`` incorrect definitions.



Patch:
diff --git a/conan/internal/graph/installer.py b/conan/internal/graph/installer.py
index fa09f90ba63..a2b6d0773fe 100644
--- a/conan/internal/graph/installer.py
+++ b/conan/internal/graph/installer.py
@@ -457,6 +457,10 @@ def _call_package_info(self, conanfile, package_folder, is_editable):
                 self._hook_manager.execute("post_package_info", conanfile=conanfile)
 
         conanfile.cpp_info.check_component_requires(conanfile)
+        try:
+            conanfile.conf_info.validate()
+        except ConanException as e:
+            raise ConanException(f"{conanfile}: Error in package_info() method:\n\t{str(e)}")
 
     @staticmethod
     def _call_finalize_method(conanfile, finalize_folder):
diff --git a/conan/internal/model/conf.py b/conan/internal/model/conf.py
index 8b5410bf842..0d7fd97da0b 100644
--- a/conan/internal/model/conf.py
+++ b/conan/internal/model/conf.py
@@ -150,8 +150,7 @@
 
 def _is_profile_module(module_name):
     # These are the modules that are propagated to profiles and user recipes
-    _profiles_modules_patterns = USER_CONF_PATTERN, TOOLS_CONF_PATTERN
-    return any(pattern.match(module_name) for pattern in _profiles_modules_patterns)
+    return TOOLS_CONF_PATTERN.match(module_name) or USER_CONF_PATTERN.match(module_name)
 
 
 # FIXME: Refactor all the next classes because they are mostly the same as
@@ -542,10 +541,12 @@ def set_relative_base_folder(self, folder):
 
     @staticmethod
     def _check_conf_name(conf):
-        if USER_CONF_PATTERN.match(conf) is None and conf not in BUILT_IN_CONFS:
-            raise ConanException(f"[conf] Either '{conf}' does not exist in configuration list or "
-                                 f"the conf format introduced is not valid. Run 'conan config list' "
-                                 f"to see all the available confs.")
+        if conf.startswith("user"):
+            if USER_CONF_PATTERN.match(conf) is None:
+                raise ConanException(f"User conf '{conf}' invalid format, not 'user.org.group:conf'")
+        elif conf not in BUILT_IN_CONFS:
+            raise ConanException(f"[conf] '{conf}' does not exist in configuration list. "
+                                 "Run 'conan config list' to see all the available confs.")
 
 
 class ConfDefinition:


Test patch:
diff --git a/test/integration/configuration/conf/test_conf.py b/test/integration/configuration/conf/test_conf.py
index 21343866141..35b3be2311e 100644
--- a/test/integration/configuration/conf/test_conf.py
+++ b/test/integration/configuration/conf/test_conf.py
@@ -150,7 +150,7 @@ def test_new_config_file(client):
             """)
     save(client.paths.new_config_path, conf)
     client.run("install .", assert_error=True)
-    assert "[conf] Either 'cache:read_only' does not exist in configuration list" in client.out
+    assert "[conf] 'cache:read_only' does not exist in configuration list" in client.out
 
 
 @patch("conan.__version__", "1.26.0")
@@ -307,10 +307,12 @@ def test_nonexisting_conf():
     c = TestClient()
     c.save({"conanfile.txt": ""})
     c.run("install . -c tools.unknown:conf=value", assert_error=True)
-    assert "ERROR: [conf] Either 'tools.unknown:conf' does not exist in configuration" in c.out
+    assert "ERROR: [conf] 'tools.unknown:conf' does not exist in configuration" in c.out
     c.run("install . -c user.some:var=value")  # This doesn't fail
+    c.run("install . -c user.some.var=value", assert_error=True)
+    assert "ERROR: User conf 'user.some.var' invalid format, not 'user.org.group:conf'" in c.out
     c.run("install . -c tool.build:verbosity=v", assert_error=True)
-    assert "ERROR: [conf] Either 'tool.build:verbosity' does not exist in configuration" in c.out
+    assert "ERROR: [conf] 'tool.build:verbosity' does not exist in configuration" in c.out
 
 
 def test_nonexisting_conf_global_conf():
@@ -318,7 +320,7 @@ def test_nonexisting_conf_global_conf():
     save(c.paths.new_config_path, "tools.unknown:conf=value")
     c.save({"conanfile.txt": ""})
     c.run("install . ", assert_error=True)
-    assert "ERROR: [conf] Either 'tools.unknown:conf' does not exist in configuration" in c.out
+    assert "ERROR: [conf] 'tools.unknown:conf' does not exist in configuration" in c.out
 
 
 def test_global_conf_auto_created():
diff --git a/test/integration/configuration/conf/test_conf_from_br.py b/test/integration/configuration/conf/test_conf_from_br.py
index a0d067526e7..42394c9eabe 100644
--- a/test/integration/configuration/conf/test_conf_from_br.py
+++ b/test/integration/configuration/conf/test_conf_from_br.py
@@ -293,3 +293,35 @@ def package_info(self):
     assert f'self.conf_info.{conf}' in c.out
     assert "Invalid 'conf' type, please use Python types (int, str, ...)" in c.out
 
+
+@pytest.mark.parametrize("conf", ["tools.myorg.myconf", "user.myorg.myconf"])
+def test_error_missing_colon_define(conf):
+    c = TestClient(light=True)
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            name = "tool"
+            version = "0.1"
+            def package_info(self):
+                self.conf_info.define("{conf}", 42)
+        """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .", assert_error=True)
+    assert "ERROR: tool/0.1: Error in package_info() method:" in c.out
+    if conf == "tools.myorg.myconf":
+        assert "[conf] 'tools.myorg.myconf' does not exist in configuration" in c.out
+    else:
+        assert f"User conf '{conf}' invalid format, not 'user.org.group:conf'" in c.out
+
+
+def test_error_missing_colon_consume():
+    c = TestClient(light=True)
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            def generate(self):
+                self.conf.get("user.myorg.myconf")
+        """)
+    c.save({"conanfile.py": conanfile})
+    c.run("install .", assert_error=True)
+    assert "User conf 'user.myorg.myconf' invalid format, not 'user.org.group:conf'" in c.out
diff --git a/test/unittests/model/test_conf.py b/test/unittests/model/test_conf.py
index a06707f6d77..dc6364422e7 100644
--- a/test/unittests/model/test_conf.py
+++ b/test/unittests/model/test_conf.py
@@ -389,8 +389,8 @@ def test_conf_scope_patterns_ok(scope, conf):
 
 @pytest.mark.parametrize("conf", ["user.foo.bar=1"])
 @pytest.mark.parametrize("scope, assert_message", [
-    ("", "Either 'user.foo.bar' does not exist in configuration list"),
-    ("pkg/1.0:", "Either 'pkg/1.0:user.foo.bar' does not exist in configuration list"),
+    ("", "User conf 'user.foo.bar' invalid format, not 'user.org.group:conf'"),
+    ("pkg/1.0:", "'pkg/1.0:user.foo.bar' does not exist in configuration list"),
 ])
 def test_conf_scope_patterns_bad(scope, conf, assert_message):
     final_conf = scope + conf