saltstack / salt

Software to automate the management and configuration of any infrastructure or application at scale. Get access to the Salt software package repository here:
https://repo.saltproject.io/
Apache License 2.0
14.1k stars 5.47k forks source link

[BUG] state.sls_exists not working with salt-ssh #66893

Open shtrom opened 2 days ago

shtrom commented 2 days ago

Description

Similar to #29253, state.sls_exists doesn't work with salt-ssh. Likely just needs an implementation in the ssh client.

I need it to implement conditional state inclusion, e.g., #16687 or #20010. I guess I could work around the issue bit creating a deterministic id in the state to detect, and use state.sls_id instead, but it's a bit more involved.

Setup (Please provide relevant configs and/or SLS files (be sure to remove sensitive info. There is no general set-up of Salt.)

Please be as specific as possible and give set-up details.

With a simple state file, e.g., test.sls in a local setup as follows:

/srv/salt $ cat conf/master
root_dir: ./
cachedir: ./cache/salt/master
pki_dir: ./conf/pki/master
gpg_keydir: ./conf/gpgkeys
log_level: info

file_roots:
  base:
    - /srv/salt/states

pillar_roots:
  base:
    - /srv/salt/pillar
    - 
/srv/salt $ cat states/test.sls
{% do salt.log.warning('blam') %}

/srv/salt $ cat states/test2.sls
test:
  test.nop:
    - name: {{ salt['state.sls_exists']('test') }}

Checking for state existence locally works.

/srv/salt $ sudo -E salt-call state.sls_exists test
[INFO    ] Loading fresh modules for state activity
[WARNING ] blam
local:
    True
/srv/salt $ sudo -E salt-call state.apply test2                                                                        
[INFO    ] Loading fresh modules for state activity                                                                                                                     
[INFO    ] Fetching file from saltenv 'base', ** done ** 'test2.sls'                                                                                                    
[INFO    ] Loading fresh modules for state activity                                                                                                                     
[WARNING ] blam                                                                                                                                                         
[INFO    ] Running state [test] at time 20:41:06.443709                                                                                                                 
[INFO    ] Executing state test.nop for [test]                                                                                                                          
[INFO    ] Success!                                                                                                                                                     
[INFO    ] Completed state [test] at time 20:41:06.444889 (duration_in_ms=1.18)                                                                                         
local:                                                                                                                                                                  
  Name: test - Function: test.nop - Result: Clean - Started: 20:41:06.443709 - Duration: 1.18 ms                                                                        

Summary for local                                                                                                                                                       
------------                                                                                                                                                            
Succeeded: 1                                                                                                                                                            
Failed:    0                                                                                                                                                            
------------                                                                                                                                                            
Total states run:     1                                                                                                                                                 
Total run time:   1.180 ms    

But using the same setup via salt-ssh doesn't, and raises an exception when called from another state.

/srv/salt $ grep -A 3 remote roster
remote:
  host: 192.2.0.2
  thin_dir: /srv/salt
/srv/salt $ sudo -E salt-ssh remote state.sls_exists test
remote:
    False
/srv/salt $ sudo -E salt-ssh remote state.apply test2
[INFO    ] Fetching file from saltenv 'base', ** done ** 'test2.sls'                                                                                                    
[ERROR   ] Rendering exception occurred                                                                                                                                 
Traceback (most recent call last):                                                                                                                                      
  File "/opt/salt/lib/python3.10/site-packages/salt/utils/templates.py", line 469, in render_jinja_tmpl                                                                 
    output = template.render(**decoded_context)                                                                                                                         
  File "/opt/salt/lib/python3.10/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/opt/salt/lib/python3.10/site-packages/jinja2/environment.py", line 939, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 3, in top-level template code
  File "/opt/salt/lib/python3.10/site-packages/jinja2/sandbox.py", line 394, in call 
    return __context.call(__obj, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/client/ssh/wrapper/__init__.py", line 226, in caller
    return parse_ret(stdout, stderr, retcode, result_only=True)
  File "/opt/salt/lib/python3.10/site-packages/salt/client/ssh/wrapper/__init__.py", line 331, in parse_ret
    raise error(
salt.client.ssh.wrapper.SSHCommandExecutionError: The command resulted in a non-zero exit code: {
    "local": {
        "jid": "20240916104119100206",
        "return": false,
        "retcode": 1,
        "id": "remote",
        "fun": "state.sls_exists",
        "fun_args": [
            "\"test\""
        ]
    }
}

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/salt/lib/python3.10/site-packages/salt/utils/templates.py", line 210, in render_tmpl
    output = render_str(tmplstr, context, tmplpath)
  File "/opt/salt/lib/python3.10/site-packages/salt/utils/templates.py", line 491, in render_jinja_tmpl
    raise SaltRenderError(
salt.exceptions.SaltRenderError: Problem running salt function in Jinja template: The command resulted in a non-zero exit code: {
    "local": {
        "jid": "20240916104119100206",
        "return": false,
        "retcode": 1,
        "id": "remote",
        "fun": "state.sls_exists",
        "fun_args": [
            "\"test\""
        ]
    }
}; line 3

---
test:
  test.nop:
    - name: {{ salt['state.sls_exists']('test') }}    <======================

---
[CRITICAL] Rendering SLS 'base:test2' failed: Problem running salt function in Jinja template: The command resulted in a non-zero exit code: {
    "local": {
        "jid": "20240916104119100206",
        "return": false,
        "retcode": 1,
        "id": "remote",
        "fun": "state.sls_exists",
        "fun_args": [
            "\"test\""
        ]
    }
}; line 3
---
test:
  test.nop:
    - name: {{ salt['state.sls_exists']('test') }}    <======================

---
remote:
    - Rendering SLS 'base:test2' failed: Problem running salt function in Jinja template: The command resulted in a non-zero exit code: {
          "local": {
              "jid": "20240916104119100206",
              "return": false,
              "retcode": 1,
              "id": "remote",
              "fun": "state.sls_exists",
              "fun_args": [
                  "\"test\""
              ]
          }
      }; line 3

      ---
      test:
        test.nop:
          - name: {{ salt['state.sls_exists']('test') }}    <======================

      ---

Both the local and remote machines are running ArchLinux.

Steps to Reproduce the behavior

See above for reproduction setup, debug output below.

sudo -E salt-ssh -l debug remote state.sls_exists test ``` [INFO ] Loading Saltfile from '/srv/salt/Saltfile' [DEBUG ] Reading configuration from /srv/salt/Saltfile [DEBUG ] Reading configuration from /srv/salt/conf/master [WARNING ] Insecure logging configuration detected! Sensitive data may be logged. [DEBUG ] Configuration file path: /srv/salt/conf/master [DEBUG ] The functions from module 'flat' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded flat.targets [DEBUG ] The functions from module 'jinja' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded jinja.render [DEBUG ] The functions from module 'yaml' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded yaml.render [DEBUG ] compile template: ./roster [DEBUG ] Jinja search path: ['/srv/salt/cache/salt/master/files/base'] [PROFILE ] Time (in seconds) to render './roster' using 'jinja' renderer: 0.0031299591064453125 [PROFILE ] Time (in seconds) to render './roster' using 'yaml' renderer: 0.0005714893341064453 [DEBUG ] The functions from module 'roster_matcher' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roster_matcher.targets [DEBUG ] The functions from module 'roots' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roots.envs [DEBUG ] The functions from module 's3fs' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available. [DEBUG ] The functions from module 'local_cache' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded local_cache.prep_jid [DEBUG ] Adding minions for job 20240916103040419257: ['remote'] [DEBUG ] The functions from module 'state' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'cmd' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'config' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'cp' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'defaults' are being loaded by dir() on the loaded module [DEBUG ] Override __grains__: [DEBUG ] The functions from module 'grains' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'log' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'mine' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'pillar' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'publish' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'saltcheck' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'slsutil' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad state.sls_exists: 'state.sls_exists' is not available. [DEBUG ] Performing shimmed, blocking command as follows: state.sls_exists test [DEBUG ] Executed SHIM command. Command logged to TRACE [DEBUG ] Child Forked! PID: 1084526 STDOUT_FD: 9 STDERR_FD: 11 [DEBUG ] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE [DEBUG ] RETCODE 192.168.103.24: 1 [DEBUG ] The functions from module 'nested' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded nested.output remote: False ```
sudo -E salt-ssh -l debug remote state.apply test2t ``` [INFO ] Loading Saltfile from '/srv/salt/Saltfile' [DEBUG ] Reading configuration from /srv/salt/Saltfile [DEBUG ] Reading configuration from /srv/salt/conf/master [WARNING ] Insecure logging configuration detected! Sensitive data may be logged. [DEBUG ] Configuration file path: /srv/salt/conf/master [DEBUG ] The functions from module 'flat' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded flat.targets [DEBUG ] The functions from module 'jinja' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded jinja.render [DEBUG ] The functions from module 'yaml' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded yaml.render [DEBUG ] compile template: ./roster [DEBUG ] Jinja search path: ['/srv/salt/cache/salt/master/files/base'] [PROFILE ] Time (in seconds) to render './roster' using 'jinja' renderer: 0.003270387649536133 [PROFILE ] Time (in seconds) to render './roster' using 'yaml' renderer: 0.0006649494171142578 [DEBUG ] The functions from module 'roster_matcher' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roster_matcher.targets [DEBUG ] The functions from module 'roots' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roots.envs [DEBUG ] The functions from module 's3fs' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available. [DEBUG ] In saltenv 'base', looking at rel_path '_modules/munin.py' to resolve 'salt://_modules/munin.py' [DEBUG ] In saltenv 'base', ** considering ** path '/srv/salt/cache/salt/master/files/base/_modules/munin.py' to resolve 'salt://_modules/munin.py' [DEBUG ] In saltenv 'base', looking at rel_path '_modules/utils.py' to resolve 'salt://_modules/utils.py' [DEBUG ] In saltenv 'base', ** considering ** path '/srv/salt/cache/salt/master/files/base/_modules/utils.py' to resolve 'salt://_modules/utils.py' [DEBUG ] In saltenv 'base', looking at rel_path '_modules/yay.py' to resolve 'salt://_modules/yay.py' [DEBUG ] In saltenv 'base', ** considering ** path '/srv/salt/cache/salt/master/files/base/_modules/yay.py' to resolve 'salt://_modules/yay.py' [DEBUG ] The functions from module 'local_cache' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded local_cache.prep_jid [DEBUG ] Adding minions for job 20240916105303800684: ['remote'] [DEBUG ] The functions from module 'state' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded state.apply [DEBUG ] Performing shimmed, blocking command as follows: test.opts_pkg [DEBUG ] Executed SHIM command. Command logged to TRACE [DEBUG ] Child Forked! PID: 1095258 STDOUT_FD: 9 STDERR_FD: 11 [DEBUG ] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE [DEBUG ] RETCODE 192.168.103.24: 0 [DEBUG ] The functions from module 'roots' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roots.envs [DEBUG ] The functions from module 's3fs' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available. [DEBUG ] The functions from module 'jinja' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded jinja.render [DEBUG ] The functions from module 'yaml' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded yaml.render [DEBUG ] compile template: ./pillar/top.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/top.sls' using 'jinja' renderer: 0.006115436553955078 [PROFILE ] Time (in seconds) to render './pillar/top.sls' using 'yaml' renderer: 0.0008871555328369141 [DEBUG ] The functions from module 'confirm_top' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded confirm_top.confirm_top [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? * [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "*" => "True" [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? *.oz.narf.ssji.net [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "*.oz.narf.ssji.net" => "False" [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? *.cav.narf.ssji.net [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "*.cav.narf.ssji.net" => "False" [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? supahwinch.narf.ssji.net [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "supahwinch.narf.ssji.net" => "False" [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? emile.narf.ssji.net [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "emile.narf.ssji.net" => "False" [DEBUG ] The functions from module 'compound_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded compound_match.match [DEBUG ] compound_match: remote ? PC-de-Martine.narf.ssji.net [DEBUG ] The functions from module 'glob_match' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded glob_match.match [DEBUG ] compound_match remote ? "PC-de-Martine.narf.ssji.net" => "False" [DEBUG ] compile template: ./pillar/console.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/console.sls' using 'jinja' renderer: 0.001466989517211914 [PROFILE ] Time (in seconds) to render './pillar/console.sls' using 'yaml' renderer: 0.00026345252990722656 [DEBUG ] compile template: ./pillar/ddns.sls [DEBUG ] The functions from module 'gpg' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded gpg.render [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/ddns.sls' using 'jinja' renderer: 0.001268625259399414 [PROFILE ] Time (in seconds) to render './pillar/ddns.sls' using 'yaml' renderer: 0.0003466606140136719 [DEBUG ] The functions from module 'config' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded config.get [DEBUG ] Reading GPG keys from: ./conf/gpgkeys [PROFILE ] Time (in seconds) to render './pillar/ddns.sls' using 'gpg' renderer: 0.21248221397399902 [DEBUG ] compile template: ./pillar/firewall.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/firewall.sls' using 'jinja' renderer: 0.0036504268646240234 [PROFILE ] Time (in seconds) to render './pillar/firewall.sls' using 'yaml' renderer: 0.0033550262451171875 [DEBUG ] compile template: ./pillar/locale.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/locale.sls' using 'jinja' renderer: 0.0020723342895507812 [PROFILE ] Time (in seconds) to render './pillar/locale.sls' using 'yaml' renderer: 0.0006067752838134766 [DEBUG ] compile template: ./pillar/mailhub.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render import_text 'secrets/abb_password.gpg': 0.0009849071502685547 [PROFILE ] Time (in seconds) to render './pillar/mailhub.sls' using 'jinja' renderer: 0.00430750846862793 [PROFILE ] Time (in seconds) to render './pillar/mailhub.sls' using 'yaml' renderer: 0.00036835670471191406 [DEBUG ] Reading GPG keys from: ./conf/gpgkeys [PROFILE ] Time (in seconds) to render './pillar/mailhub.sls' using 'gpg' renderer: 0.0010938644409179688 [DEBUG ] compile template: ./pillar/users.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render import_text 'secrets/shtrom_htpasswd.gpg': 0.0008358955383300781 [PROFILE ] Time (in seconds) to render './pillar/users.sls' using 'jinja' renderer: 0.0049822330474853516 [PROFILE ] Time (in seconds) to render './pillar/users.sls' using 'yaml' renderer: 0.001547098159790039 [DEBUG ] Reading GPG keys from: ./conf/gpgkeys [PROFILE ] Time (in seconds) to render './pillar/users.sls' using 'gpg' renderer: 0.09530806541442871 [DEBUG ] compile template: ./pillar/munin/init.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [DEBUG ] Override __grains__: [DEBUG ] The functions from module 'grains' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded grains.get [PROFILE ] Time (in seconds) to render './pillar/munin/init.sls' using 'jinja' renderer: 0.007672786712646484 [PROFILE ] Time (in seconds) to render './pillar/munin/init.sls' using 'yaml' renderer: 0.0009596347808837891 [DEBUG ] compile template: ./pillar/munin/user.sls [DEBUG ] Jinja search path: ['./pillar', '/srv/salt/pillar'] [PROFILE ] Time (in seconds) to render './pillar/munin/user.sls' using 'jinja' renderer: 0.0016667842864990234 [PROFILE ] Time (in seconds) to render './pillar/munin/user.sls' using 'yaml' renderer: 0.0004990100860595703 [DEBUG ] Reading GPG keys from: ./conf/gpgkeys [PROFILE ] Time (in seconds) to render './pillar/munin/user.sls' using 'gpg' renderer: 0.09593772888183594 [DEBUG ] Skipping ignored and missing SLS 'os.Arch' in environment 'base' [DEBUG ] Skipping ignored and missing SLS 'hosts.net.ssji.narf.remote' in environment 'base' [DEBUG ] The functions from module 'state' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded state.apply [DEBUG ] The functions from module 'roots' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded roots.envs [DEBUG ] The functions from module 's3fs' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad roots.init: 'roots.init' is not available. [DEBUG ] Updating roots fileserver cache [DEBUG ] Gathering pillar data for state run [DEBUG ] Finished gathering pillar data for state run [DEBUG ] The functions from module 'jinja' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded jinja.render [DEBUG ] The functions from module 'yaml' are being loaded by dir() on the loaded module [DEBUG ] LazyLoaded yaml.render [DEBUG ] Returning file list from cache: age=5 cache_time=20 /srv/salt/cache/salt/master/file_lists/roots/base.p [DEBUG ] In saltenv 'base', looking at rel_path 'test2.sls' to resolve 'salt://test2.sls' [DEBUG ] In saltenv 'base', ** considering ** path '/srv/salt/running_data/var/cache/salt/minion/files/base/test2.sls' to resolve 'salt://test2.sls' [DEBUG ] compile template: /srv/salt/running_data/var/cache/salt/minion/files/base/test2.sls [DEBUG ] Jinja search path: ['/srv/salt/running_data/var/cache/salt/minion/files/base'] [DEBUG ] The functions from module 'cmd' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'config' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'cp' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'defaults' are being loaded by dir() on the loaded module [DEBUG ] Override __grains__: [DEBUG ] The functions from module 'grains' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'log' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'mine' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'pillar' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'publish' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'saltcheck' are being loaded by dir() on the loaded module [DEBUG ] The functions from module 'slsutil' are being loaded by dir() on the loaded module [DEBUG ] Could not LazyLoad state.sls_exists: 'state.sls_exists' is not available. [DEBUG ] Performing shimmed, blocking command as follows: state.sls_exists "test" [DEBUG ] Executed SHIM command. Command logged to TRACE [DEBUG ] Child Forked! PID: 1095299 STDOUT_FD: 9 STDERR_FD: 11 [DEBUG ] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE [DEBUG ] RETCODE 192.168.103.24: 1 [ERROR ] Rendering exception occurred Traceback (most recent call last): File "/opt/salt/lib/python3.10/site-packages/salt/utils/templates.py", line 469, in render_jinja_tmpl output = template.render(**decoded_context) File "/opt/salt/lib/python3.10/site-packages/jinja2/environment.py", line 1304, in render self.environment.handle_exception() File "/opt/salt/lib/python3.10/site-packages/jinja2/environment.py", line 939, in handle_exception raise rewrite_traceback_stack(source=source) File "