# Centralized Deprecation Facility

## Overview

The centralized deprecation facility is used to deprecate (retire, withdraw, etc) features both within Chef InSpec core code, Chef InSpec core resources, plugins, and custom resources.

The most important goal of the deprecation facility was to collect decisions about what actions should be taken for a deprecation in a central place, so that it is easier to handle major version changes.

### Audience

The deprecation facility is intended to be used by core Chef InSpec developers, contributors, and resource developers. The configuration file, while intended to be easily understood and editable, is not intended to be modified by a typical Chef InSpec user. The configuration present there should be considered part of the "release configuration," not part of the "runtime configuration."

### Example

```
# In some code in Chef InSpec core
def something_crufty
  Inspec.deprecate :old_feature, 'Don't call something_crufty anymore'
end
```

If that gets called, `inspec` will consult the deprecation configuration, and then execute one of four actions: warn, fail_control, exit, or ignore.

## Concepts

### Basic Concept: Anti-Feature Flags

In many applications, `feature flags` are used to deploy new feature code, without actually revealing it to the user.  When certain conditions are met, the deployment team enables the flags, and the feature goes live.

Inverting this idea, we could use flags to withdraw features. For example, if we wish to retire the `lusers` property on the `shadow` resource, we might add:

```
# In lib/resources/shadow.rb
def lusers
  Inspec.deprecate :old_shadow_fields, 'Please use the `users` property instead.'
end
```

Then, in `etc/deprecations.json` in version 3.5, we might add:
```
  "groups": {
    "old_shadow_features": { "action": "ignore" }
  }
```

This makes the deprecation silent - it is there and ready to be triggered, but has no effect yet.  We can add more deprecation declarations, using the `old_shadow_features` group.  Then, when we decide that we'll start warning about it in v4.0 and withdraw it in v5.0, we can simply change the config file - we don't have to change every deprecation:

For v4:
```
  "groups": {
    "old_shadow_features": { "action": "warn", "suffix": "Certain properties of shadow will be withdrawn in Chef InSpec v5.0" }
  }
```

Now, a warning (fed through `Inspec::Log.warn`) will appear each time the property is accessed, but it will otherwise behave normally.

For v5, you have some choices as to how to harden the deprecation.  `fail_control` will fail any control the deprecation is used in; while `exit` will exit the inspec run entirely.

For v5:
```
  "groups": {
    "old_shadow_features": { "action": "exit", "suffix": "Certain properties of shadow were deprecated in v4 and have now been removed in v5." }
  }
```

Again, no need to update the deprecation calls; though in v6 it might make sense to remove the old code entirely.

### Groups

When you make a deprecation message, you add it to a group. Chef InSpec will read the file `$INSPEC_INSTALL_ROOT/etc/deprecation.json` to determine the known groups - you may add to that file at any time.

If you make a deprecation using a group that is unknown, Chef InSpec will by default treat it as a warning.  You can change that behavior by setting the "unknown_group_action" top-level property to any of the valid actions.

There are no restrictions on the number or the naming of groups; you can have as many or as few as needed.

### Message Assembly

For all actions except `ignore`, a message is assembled, consisting of:

 * The string 'DEPRECATION'
 * The value of the `prefix` field of the group, if any
 * The specific message provided by the deprecation declaration, if any
 * The value of the `suffix` field of the group, if any
 * If possible, the string '(used at ' followed by a `path/file.rb:lineno` to help the user locate the deprecation in *their code*.

### Actions

#### exit

Issues an ERROR via `Inspec::Log.error` with the assembled message, then immediately exits the process via Inspec::UI. The reporters are not executed.  By default, the exit code will be 3; but you can set a different code using the `exit_status` property of the group in the config file.

#### fail_control

Checks to see if the code is being called within a control block; if so, raises a FailedResource exception with the assembled message, which causes the control to fail in the reporter.

If the code is outside a control, the `warn` action is triggered instead.

#### ignore

Does nothing; this is very useful for staging deprecations - you can have the deprecations in place, then enable them later.

#### warn

Issues a WARN to the `Inspec::Log.warn` facility with the assembled message.
