nebius/SWE-bench-extra huggingface dataset:
Repository: canonical/charmcraft

Issue:
reactive plugin charm tools version validation code is britle
The charm tools may soon remove the now redundant legacy charm store code, when that happens the output from the version command may no longer use the format expected here:
https://github.com/canonical/charmcraft/blob/dbf82a0fbaa6633248bb213d3943603c1e7c50a6/charmcraft/reactive_plugin.py#L64-L65

I would recommend either using the `--format json` argument or looking for the `charm-tools` string in the output rather than relying on there being a newline character there.

Ref: https://github.com/juju/charm-tools/blob/17812f0e00ad634f8440053d43d1a5370a71fa50/charmtools/version.py#L15-L25

Comments:

Patch:
diff --git a/charmcraft/reactive_plugin.py b/charmcraft/reactive_plugin.py
index bc50b20..337ac49 100644
--- a/charmcraft/reactive_plugin.py
+++ b/charmcraft/reactive_plugin.py
@@ -14,6 +14,7 @@
 
 """Charmcraft's reactive plugin for craft-parts."""
 
+import json
 import shlex
 import subprocess
 import sys
@@ -61,14 +62,19 @@ class ReactivePluginEnvironmentValidator(plugins.validator.PluginEnvironmentVali
         :raises PluginEnvironmentValidationError: If the environment is invalid.
         """
         try:
-            output = self._execute("charm version").strip()
-            _, tools_version = output.split("\n")
-
-            if not tools_version.startswith("charm-tools"):
+            version_data = json.loads(self._execute("charm version --format json"))
+
+            tool_name = "charm-tools"
+            if not (
+                tool_name in version_data
+                and "version" in version_data[tool_name]
+                and "git" in version_data[tool_name]
+            ):
                 raise PluginEnvironmentValidationError(
                     part_name=self._part_name,
-                    reason=f"invalid charm tools version {tools_version}",
+                    reason=f"invalid charm tools version {version_data}",
                 )
+            tools_version = f"{tool_name} {version_data[tool_name]['version']} ({version_data[tool_name]['git']})"
             emit.debug(f"found {tools_version}")
         except ValueError as err:
             raise PluginEnvironmentValidationError(


Test patch:
diff --git a/tests/test_reactive_plugin.py b/tests/test_reactive_plugin.py
index 8e12027..bea2715 100644
--- a/tests/test_reactive_plugin.py
+++ b/tests/test_reactive_plugin.py
@@ -36,7 +36,7 @@ def charm_exe(tmp_path):
     charm_bin = pathlib.Path(tmp_path, "mock_bin", "charm")
     charm_bin.parent.mkdir(exist_ok=True)
     charm_bin.write_text(
-        '#!/bin/sh\necho "charmstore-client 2.5.1"\necho "charm-tools version 2.8.2"'
+        '#!/bin/sh\necho \'{"charm-tools": {"version": "2.8.4", "git": "+git-7-6126e17", "gitn": 7, "gitsha": "6126e17", "pre_release": false, "snap": "+snap-x12"}}\''
     )
     charm_bin.chmod(0o755)
     yield charm_bin
Repository: canonical/charmcraft

Issue:
The presence of CHARMCRAFT_AUTH should be logged
Currently we're logging all CHARMCRAFT_* environment variables, but specifically excluding `CHARMCRAFT_AUTH` to not leak secrets.

However, it's very useful, for debugging purposes, know that `CHARMCRAFT_AUTH` is present or not, so we should log it with a different value, something like...

```
System details: (...); Environment: DESKTOP_SESSION='plasma', CHARMCRAFT_AUTH='<hidden>', ...
```

Comments:

Patch:
diff --git a/charmcraft/main.py b/charmcraft/main.py
index 716cc14..bf24e73 100644
--- a/charmcraft/main.py
+++ b/charmcraft/main.py
@@ -102,16 +102,20 @@ EXTRA_ENVIRONMENT = ("DESKTOP_SESSION", "XDG_CURRENT_DESKTOP")
 
 def _get_system_details():
     """Produce details about the system."""
-    # prepare the useful environment variables: all CHARMCRAFT* (except AUTH keys)
+    # prepare the useful environment variables: all CHARMCRAFT* (hiding AUTH keys)
     # and desktop/session
-    useful_env = {name for name in os.environ if name.startswith("CHARMCRAFT")}
-    useful_env.discard(ALTERNATE_AUTH_ENV_VAR)
-    useful_env.update(EXTRA_ENVIRONMENT)
-
-    os_platform = utils.get_os_platform()
-    env_string = ", ".join(f"{k}={v!r}" for k, v in sorted(os.environ.items()) if k in useful_env)
+    useful_env = {
+        name: value
+        for name, value in os.environ.items()
+        if name.startswith("CHARMCRAFT") or name in EXTRA_ENVIRONMENT
+    }
+    if ALTERNATE_AUTH_ENV_VAR in useful_env:
+        useful_env[ALTERNATE_AUTH_ENV_VAR] = "<hidden>"
+    env_string = ", ".join(f"{name}={value!r}" for name, value in sorted(useful_env.items()))
     if not env_string:
         env_string = "None"
+
+    os_platform = utils.get_os_platform()
     return f"System details: {os_platform}; Environment: {env_string}"
 
 


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index 5c5aa3c..48202da 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -334,8 +334,8 @@ def test_systemdetails_charmcraft_environment():
     )
 
 
-def test_systemdetails_no_auth():
-    """System details specifically excluding secrets."""
+def test_systemdetails_hidden_auth():
+    """System details specifically hiding secrets."""
     with patch("os.environ", {ALTERNATE_AUTH_ENV_VAR: "supersecret"}):
         with patch("charmcraft.utils.get_os_platform") as platform_mock:
             platform_mock.return_value = utils.OSPlatform(
@@ -344,7 +344,7 @@ def test_systemdetails_no_auth():
             result = _get_system_details()
     assert result == (
         "System details: OSPlatform(system='test-system', release='test-release', "
-        "machine='test-machine'); Environment: None"
+        f"machine='test-machine'); Environment: {ALTERNATE_AUTH_ENV_VAR}='<hidden>'"
     )
Repository: canonical/charmcraft

Issue:
Clarify `charmcraft status --help` text for `--format`
#784 implements structured output of `charmcraft status`, but the help text on it is a bit misleading:
```
base ❯ time charmcraft status --help                               
Usage:
    charmcraft status [options] <name>

...

Options:
...
             --format:  Produce the result formatted as a JSON string
```

The above makes it seem like `charmcraft status charmname --format` will export in JSON, but really it wants `--format json`.  Easy to fix by changing the option string to say it needs an extra arg, one of `json` or whatever else is available.

Comments:

Patch:
diff --git a/charmcraft/cmdbase.py b/charmcraft/cmdbase.py
index 6106a7f..8009afc 100644
--- a/charmcraft/cmdbase.py
+++ b/charmcraft/cmdbase.py
@@ -21,6 +21,7 @@ import json
 import craft_cli
 
 JSON_FORMAT = "json"
+FORMAT_HELP_STR = "Produce the result in the specified format (currently only 'json')"
 
 
 class BaseCommand(craft_cli.BaseCommand):
@@ -50,5 +51,5 @@ class BaseCommand(craft_cli.BaseCommand):
         parser.add_argument(
             "--format",
             choices=[JSON_FORMAT],
-            help="Produce the result formatted as a JSON string",
+            help=FORMAT_HELP_STR,
         )


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index 48202da..feb1304 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -34,7 +34,7 @@ from craft_cli import (
 from craft_store.errors import CraftStoreError
 
 from charmcraft import __version__, env, utils
-from charmcraft.cmdbase import BaseCommand, JSON_FORMAT
+from charmcraft.cmdbase import BaseCommand, JSON_FORMAT, FORMAT_HELP_STR
 from charmcraft.commands.store.client import ALTERNATE_AUTH_ENV_VAR
 from charmcraft.main import COMMAND_GROUPS, main, _get_system_details
 
@@ -484,7 +484,7 @@ def test_basecommand_include_format_option(config):
     assert action.dest == "format"
     assert action.default is None
     assert action.choices == [JSON_FORMAT]
-    assert action.help == "Produce the result formatted as a JSON string"
+    assert action.help == FORMAT_HELP_STR
 
 
 def test_basecommand_format_content_json(config):
Repository: canonical/operator

Issue:
Container.start() silently accepts no arguments and does nothing
`Container.start()` takes a variable number of strings as arguments, each specifying a service to be started. The issue is that it also accepts zero arguments without complain, even though it clearly indicates a mistake on the user.

I believe it should raise some sort of exception in the case of no arguments.

Applying this patch to current master (`18477647`) demonstrates the issue:

```diff
diff --git a/test/test_model.py b/test/test_model.py
index ed64add..564681a 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -824,6 +824,10 @@ containers:
             ('start', ('foo', 'bar')),
         ])
 
+    def test_start_no_argument(self):
+        with self.assertRaises(Exception):
+            self.container.start()
+
     def test_stop(self):
         self.container.stop('foo')
         self.container.stop('foo', 'bar')
```

Comments:

Patch:
diff --git a/ops/model.py b/ops/model.py
index b58d729..d10c3f0 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1043,10 +1043,14 @@ class Container:
 
     def start(self, *service_names: str):
         """Start given service(s) by name."""
+        if not service_names:
+            raise TypeError('start expected at least 1 argument, got 0')
         self._pebble.start_services(service_names)
 
     def stop(self, *service_names: str):
         """Stop given service(s) by name."""
+        if not service_names:
+            raise TypeError('stop expected at least 1 argument, got 0')
         self._pebble.stop_services(service_names)
 
     # TODO(benhoyt) - should be: layer: typing.Union[str, typing.Dict, 'pebble.Layer'],


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index ed64add..030393a 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -824,6 +824,10 @@ containers:
             ('start', ('foo', 'bar')),
         ])
 
+    def test_start_no_arguments(self):
+        with self.assertRaises(TypeError):
+            self.container.start()
+
     def test_stop(self):
         self.container.stop('foo')
         self.container.stop('foo', 'bar')
@@ -832,6 +836,10 @@ containers:
             ('stop', ('foo', 'bar')),
         ])
 
+    def test_stop_no_arguments(self):
+        with self.assertRaises(TypeError):
+            self.container.stop()
+
     def test_type_errors(self):
         meta = ops.charm.CharmMeta.from_yaml("""
 name: k8s-charm
Repository: canonical/operator

Issue:
Failing to fetch a resource does not raise the expected exception 
According to the doc string and, as a consequence, the rtfm docs, `charm.model.resources.fetch` is expected to throw a `ModelError`. In reality, it's a `RuntimeError`. We should probably change either the docs or the exception raised.

https://github.com/canonical/operator/blob/9baf2ab3deafcaff5d9c6679cdf1d11fc88e3fa3/ops/model.py#L950-L960

Comments:

Patch:
diff --git a/ops/model.py b/ops/model.py
index ec99173..c20d883 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -951,10 +951,10 @@ class Resources:
         """Fetch the resource from the controller or store.
 
         If successfully fetched, this returns a Path object to where the resource is stored
-        on disk, otherwise it raises a ModelError.
+        on disk, otherwise it raises a NameError.
         """
         if name not in self._paths:
-            raise RuntimeError('invalid resource name: {}'.format(name))
+            raise NameError('invalid resource name: {}'.format(name))
         if self._paths[name] is None:
             self._paths[name] = Path(self._backend.resource_get(name))
         return self._paths[name]


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index d243df3..01325d6 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -490,7 +490,7 @@ class TestModel(unittest.TestCase):
         self.harness.add_resource('foo', 'foo contents\n')
         self.harness.add_resource('bar', '')
 
-        with self.assertRaises(RuntimeError):
+        with self.assertRaises(NameError):
             self.harness.model.resources.fetch('qux')
 
         self.assertEqual(self.harness.model.resources.fetch('foo').name, 'foo.txt')
Repository: canonical/operator

Issue:
Harness.Container.get_services() does not seem correctly mocked
I think this test should work, but it does not with `ops 1.3.0`:

```
    def test_mocked_get_services(self):
        self.harness.begin()
        traefik_container = self.harness.charm.unit.get_container("traefik")
        traefik_container.add_layer("traefik_layer", {
            "summary": "Traefik layer",
            "description": "Pebble config layer for Traefik",
            "services": {
                "traefik": {
                    "override": "replace",
                    "summary": "Traefik",
                    "command": "/usr/bin/traefik",
                },
            },
        })

        assert traefik_container.get_service("traefik")  # So far, so good
        assert "traefik" in traefik_container.get_services()  # FAILS, `traefik_container.get_services()` returns {} 
```

Comments:

Patch:
diff --git a/ops/model.py b/ops/model.py
index 9974af9..e627951 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1194,6 +1194,7 @@ class Container:
         If no service names are specified, return status information for all
         services, otherwise return information for only the given services.
         """
+        service_names = service_names or None
         services = self._pebble.get_services(service_names)
         return ServiceInfoMapping(services)
 


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index c8e890f..5004d88 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -772,9 +772,26 @@ class TestApplication(unittest.TestCase):
             resources:
               foo: {type: file, filename: foo.txt}
               bar: {type: file, filename: bar.txt}
+            containers:
+              bar:
+                k: v
         ''')
         self.peer_rel_id = self.harness.add_relation('db2', 'db2')
         self.app = self.harness.model.app
+        self.addCleanup(self.harness.cleanup)
+
+    # Tests fix for https://github.com/canonical/operator/issues/694.
+    def test_mocked_get_services(self):
+        self.harness.begin()
+        c = self.harness.charm.unit.get_container('bar')
+        c.add_layer('layer1', {
+            'summary': 'layer',
+            'services': {"baz": {'override': 'replace', 'summary': 'echo', 'command': 'echo 1'}},
+        })
+
+        s = c.get_service('baz')  # So far, so good
+        self.assertTrue(s)
+        self.assertTrue('baz' in c.get_services())
 
     def test_planned_units(self):
         rel_id = self.peer_rel_id
@@ -1062,7 +1079,7 @@ containers:
         self.assertEqual(services['s2'].current, ops.pebble.ServiceStatus.INACTIVE)
 
         self.assertEqual(self.pebble.requests, [
-            ('get_services', ()),
+            ('get_services', None),
             ('get_services', ('s1', 's2')),
         ])
Repository: canonical/operator

Issue:
secret id is not always canonicalized before calling secret_get
Secret canonicalizes the ID it receives upon initialization.
However, before the Secret object is initialized, the ID is used to call secret_get. This results in the ID being sometimes canonicalized, sometimes not, depending on the caller.

For example:
```
self.model.get_secret(id="foo").get_content()
# resulting call: secret-get --id foo

self.model.get_secret(id="foo").peek_content()
# resulting call: secret-get --id secret:foo --peek
```

This is not a problem per se, but it might mean headache in the future. 

Comments:

Patch:
diff --git a/ops/model.py b/ops/model.py
index 141bd4f..2ab4ff8 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -276,6 +276,9 @@ class Model:
         """
         if not (id or label):
             raise TypeError('Must provide an id or label, or both')
+        if id is not None:
+            # Canonicalize to "secret:<id>" form for consistency in backend calls.
+            id = Secret._canonicalize_id(id)
         try:
             content = self._backend.secret_get(id=id, label=label)
             return Secret(self._backend, id=id, label=label, content=content)


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index 976fb77..35df7ea 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -2841,7 +2841,18 @@ class TestSecrets(unittest.TestCase):
         self.assertEqual(secret.get_content(), {'foo': 'g'})
 
         self.assertEqual(fake_script_calls(self, clear=True),
-                         [['secret-get', '123', '--format=json']])
+                         [['secret-get', 'secret:123', '--format=json']])
+
+    def test_get_secret_label(self):
+        fake_script(self, 'secret-get', """echo '{"foo": "g"}'""")
+
+        secret = self.model.get_secret(label='lbl')
+        self.assertIsNone(secret.id)
+        self.assertEqual(secret.label, 'lbl')
+        self.assertEqual(secret.get_content(), {'foo': 'g'})
+
+        self.assertEqual(fake_script_calls(self, clear=True),
+                         [['secret-get', '--label', 'lbl', '--format=json']])
 
     def test_get_secret_id_and_label(self):
         fake_script(self, 'secret-get', """echo '{"foo": "h"}'""")
@@ -2852,7 +2863,7 @@ class TestSecrets(unittest.TestCase):
         self.assertEqual(secret.get_content(), {'foo': 'h'})
 
         self.assertEqual(fake_script_calls(self, clear=True),
-                         [['secret-get', '123', '--label', 'l', '--format=json']])
+                         [['secret-get', 'secret:123', '--label', 'l', '--format=json']])
 
     def test_get_secret_no_args(self):
         with self.assertRaises(TypeError):
Repository: carsonyl/pypac

Issue:
"TypeError: putenv() argument 2 must be string, not None" Error when using pac_context_for_url
pypac version: 0.9.0 (retrieved from pip)
Python version: Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32

When the configured pac file returns "DIRECT" for a URL then the function will fail with the following error:
```
  File "file.py", line 27, in get_job_info
    with pac_context_for_url('http://' + self.host):
  File "C:\Python27\lib\contextlib.py", line 17, in __enter__
    return self.gen.next()
  File "C:\Python27\lib\site-packages\pypac\api.py", line 305, in pac_context_for_url
    os.environ['HTTP_PROXY'] = proxies.get('http')
  File "C:\Python27\lib\os.py", line 422, in __setitem__
    putenv(key, item)
TypeError: putenv() argument 2 must be string, not None
```

In this case the pac file will return "DIRECT" for the url - the code gets to line 303
`proxies = resolver.get_proxy_for_requests(url)`
which will result in the code going to pypac/resolver.py:133  and the function  proxy_parameter_for_requests("DIRECT") will be called. This will then set proxy_url_or_direct to None which will be used for the returns values in the dictionary for 'http' and 'https'. These value of None are used in
```
os.environ['HTTP_PROXY'] = proxies.get('http')
os.environ['HTTPS_PROXY'] = proxies.get('https')
```
Resulting in the error putenv() argument 2 must be string, not None

Comments:

Patch:
diff --git a/pypac/api.py b/pypac/api.py
index 6b8c089..4541be4 100644
--- a/pypac/api.py
+++ b/pypac/api.py
@@ -301,8 +301,9 @@ def pac_context_for_url(url, proxy_auth=None):
     if pac:
         resolver = ProxyResolver(pac, proxy_auth=proxy_auth)
         proxies = resolver.get_proxy_for_requests(url)
-        os.environ['HTTP_PROXY'] = proxies.get('http')
-        os.environ['HTTPS_PROXY'] = proxies.get('https')
+        # Cannot set None for environ. (#27)
+        os.environ['HTTP_PROXY'] = proxies.get('http') or ''
+        os.environ['HTTPS_PROXY'] = proxies.get('https') or ''
     yield
     if prev_http_proxy:
         os.environ['HTTP_PROXY'] = prev_http_proxy


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index 2adb5fc..255da3b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -368,3 +368,11 @@ class TestContextManager(object):
                 assert os.environ['HTTP_PROXY'] == fake_proxy_url
                 assert os.environ['HTTPS_PROXY'] == fake_proxy_url
         assert os.environ['HTTP_PROXY'] == 'http://env'
+
+    def test_pac_direct(self, monkeypatch):
+        monkeypatch.setenv('HTTP_PROXY', 'http://env')
+        with _patch_get_pac(PACFile(proxy_pac_js_tpl % 'DIRECT')):
+            with pac_context_for_url(arbitrary_url):
+                assert os.environ['HTTP_PROXY'] == ''
+                assert os.environ['HTTPS_PROXY'] == ''
+        assert os.environ['HTTP_PROXY'] == 'http://env'