package_protections 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 217ae8f6eee7f084ff39ce7c4cf0bb06a08c361abff49e411d730a3a79592b71
4
- data.tar.gz: 21a4eea2876078f531a67d3705e7a0d38c11b3bf87cd265e552bda9e8661638a
3
+ metadata.gz: f7dcf5c2038b67566fabbec1bebb7f7e793552f912613449f88d5fa244959200
4
+ data.tar.gz: d067b4f72f24f52a8b37bb36da54ad0d3fb52549529b012a819ecd796231fedd
5
5
  SHA512:
6
- metadata.gz: 1633812b257db230cf9f0244c8dd397197d4feb98f37abcd9906e5660adbfb7985c82fc6758254f99771726d49d3796c2e30438a54bd14a75c9ed3b64dbca1a0
7
- data.tar.gz: e1aafb82c4a5a93425cbfc28135bae436b792681f6cbd8257f95e2d51d985a436e80d191313d2959a3e9c37423f970608109bca773a5ffe0ad01af3d6214b6da
6
+ metadata.gz: 406251f9227b31309c2d991ea9cee1119ccc581724877b3e8ff2b9b76a0dfa71147ddc900bedcbc0cf7dfc52857a180500f6bb15f6c418dd9801ebc29f9e14b7
7
+ data.tar.gz: 2cb074c305a57e514ca3f923b08bdeacb7a156e404bed441c4b169ad7b92cf86dc7f5d5ac76b0e47d97cb4ae26eba87699e06694660fabc824092ddeee08ec61
data/README.md CHANGED
@@ -10,7 +10,6 @@ This gem ships with the following checks
10
10
  2) Other packages are not using the private API of your package (via `packwerk` `enforce_privacy`)
11
11
  3) Your package has a typed public API (via the `rubocop` `PackageProtections/TypedPublicApi` cop)
12
12
  4) Your package only creates a single namespace (via the `rubocop` `PackageProtections/NamespacedUnderPackageName` cop)
13
- 4) Your package is only visible to a select number of packages (via the `packwerk` `enforce_privacy` cop)
14
13
 
15
14
  ## Initial Configuration
16
15
  Package protections first requires that your application is using [`packwerk`](https://github.com/Shopify/packwerk), [`rubocop`](https://github.com/rubocop/rubocop), and [`rubocop-sorbet`](https://github.com/Shopify/rubocop-sorbet). Follow the regular setup instructions for those tools before proceeding.
@@ -63,25 +62,6 @@ end
63
62
 
64
63
  If you've worked through all of the TODOs for this cop and are able to set the value to `fail_on_any`, you can also set `automatic_pack_namespace` which will support your pack having one global namespace without extra subdirectories. That is, instead of `packs/foo/app/services/foo/bar.rb`, you can use `packs/foo/app/services/bar.rb` and still have it define `Foo::Bar`. [See the `stimpack` README.md](https://github.com/rubyatscale/stimpack#readme) for more information.
65
64
 
66
- ### `prevent_other_packages_from_using_this_package_without_explicit_visibility`
67
- *This is only available if your package has `enforce_privacy` set to `true`!*
68
- This protection exists to help packages have control over who their clients are. When turning on this protection, only clients who are listed in your `visible_to` metadata will be allowed to consume your package. Here is an example in `packs/apples/package.yml`:
69
- ```yml
70
- enforce_privacy: true
71
- enforce_dependencies: true
72
- metadata:
73
- protections:
74
- prevent_other_packages_from_using_this_package_without_explicit_visibility: fail_on_new
75
- # ... other protections are the same
76
- visible_to:
77
- - packs/other_pack
78
- - packs/another_pack
79
- ```
80
- In this package, only `packs/other_pack` and `packs/another_pack` can use `packs/apples`. With both the `fail_on_new` and `fail_on_any` setting, only those packs can state a dependency on `packs/apples` in their `package.yml`. If any other packs state a dependency on `packs/apples`, the build will fail, even with violations. With the `fail_on_new` setting, a pack can create a dependency or privacy violation on `packs/apples` even if it's not listed. With `fail_on_any`, no violations are allowed.
81
- If `visible_to` is not set and the protection is turned on, then the package cannot be consumed by any package (a top-level package might be a good candidate for this).
82
-
83
- Note that this protection's default behavior is `fail_never`, so it can remain unset in the `package.yml`.
84
-
85
65
  ## Violation Behaviors
86
66
  #### `fail_on_any`
87
67
  If this behavior is selected, the build will fail if there is *any* issue, new or old.
@@ -34,7 +34,6 @@ module PackageProtections
34
34
  Private::IncomingPrivacyProtection.new,
35
35
  RuboCop::Cop::PackageProtections::TypedPublicApi.new,
36
36
  RuboCop::Cop::PackageProtections::NamespacedUnderPackageName.new,
37
- Private::VisibilityProtection.new,
38
37
  RuboCop::Cop::PackageProtections::OnlyClassMethods.new,
39
38
  RuboCop::Cop::PackageProtections::RequireDocumentedPublicApis.new
40
39
  ]
@@ -6,7 +6,6 @@ require 'package_protections/private/output'
6
6
  require 'package_protections/private/incoming_privacy_protection'
7
7
  require 'package_protections/private/outgoing_dependency_protection'
8
8
  require 'package_protections/private/metadata_modifiers'
9
- require 'package_protections/private/visibility_protection'
10
9
  require 'package_protections/private/configuration'
11
10
 
12
11
  module PackageProtections
@@ -66,11 +66,6 @@ module PackageProtections
66
66
  original_package.dependencies
67
67
  end
68
68
 
69
- sig { returns(T::Set[String]) }
70
- def visible_to
71
- Set.new(metadata['visible_to'] || [])
72
- end
73
-
74
69
  sig { returns(T::Array[ParsePackwerk::Violation]) }
75
70
  def violations
76
71
  deprecated_references.violations
@@ -16,23 +16,17 @@ module ApplicationFixtureHelper
16
16
  dependencies: [],
17
17
  enforce_dependencies: true,
18
18
  enforce_privacy: true,
19
- protections: {},
20
- visible_to: []
19
+ protections: {}
21
20
  )
22
21
  defaults = {
23
22
  'prevent_this_package_from_violating_its_stated_dependencies' => 'fail_on_new',
24
23
  'prevent_other_packages_from_using_this_packages_internals' => 'fail_on_new',
25
24
  'prevent_this_package_from_exposing_an_untyped_api' => 'fail_on_new',
26
25
  'prevent_this_package_from_creating_other_namespaces' => 'fail_on_new',
27
- 'prevent_other_packages_from_using_this_package_without_explicit_visibility' => 'fail_never',
28
26
  'prevent_this_package_from_exposing_instance_method_public_apis' => 'fail_never'
29
27
  }
30
28
  protections_with_defaults = defaults.merge(protections)
31
29
  metadata = { 'protections' => protections_with_defaults }
32
- if visible_to.any?
33
- metadata.merge!('visible_to' => visible_to)
34
- end
35
-
36
30
  package = ParsePackwerk::Package.new(
37
31
  name: pack_name,
38
32
  dependencies: dependencies,
@@ -10,36 +10,6 @@ module RuboCop
10
10
  extend T::Sig
11
11
  include ::PackageProtections::RubocopProtectionInterface
12
12
 
13
- # We override `cop_configs` for this protection.
14
- # The default behavior disables cops when a package has turned off a protection.
15
- # However: namespace violations can occur even when one package has TURNED OFF their namespace protection
16
- # but another package has it turned on. Therefore, all packages must always be opted in no matter what.
17
- #
18
- sig do
19
- params(packages: T::Array[::PackageProtections::ProtectedPackage])
20
- .returns(T::Array[::PackageProtections::RubocopProtectionInterface::CopConfig])
21
- end
22
- def cop_configs(packages)
23
- include_packs = T.let([], T::Array[String])
24
- packages.each do |p|
25
- enabled_for_pack = !p.violation_behavior_for(NamespacedUnderPackageName::IDENTIFIER).fail_never?
26
- if enabled_for_pack
27
- include_packs << p.name
28
- end
29
- end
30
-
31
- [
32
- ::PackageProtections::RubocopProtectionInterface::CopConfig.new(
33
- name: cop_name,
34
- enabled: include_packs.any?,
35
- metadata: {
36
- 'IncludePacks' => include_packs,
37
- 'GloballyPermittedNamespaces' => ::RuboCop::Packs.config.globally_permitted_namespaces
38
- }
39
- )
40
- ]
41
- end
42
-
43
13
  sig { override.returns(T::Array[String]) }
44
14
  def included_globs_for_pack
45
15
  [
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: package_protections
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-01 00:00:00.000000000 Z
11
+ date: 2022-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -197,7 +197,6 @@ files:
197
197
  - lib/package_protections/private/metadata_modifiers.rb
198
198
  - lib/package_protections/private/outgoing_dependency_protection.rb
199
199
  - lib/package_protections/private/output.rb
200
- - lib/package_protections/private/visibility_protection.rb
201
200
  - lib/package_protections/protected_package.rb
202
201
  - lib/package_protections/protection_interface.rb
203
202
  - lib/package_protections/rspec/application_fixture_helper.rb
@@ -1,186 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module PackageProtections
5
- module Private
6
- class VisibilityProtection
7
- extend T::Sig
8
-
9
- include ProtectionInterface
10
-
11
- IDENTIFIER = 'prevent_other_packages_from_using_this_package_without_explicit_visibility'
12
-
13
- sig { override.returns(String) }
14
- def identifier
15
- IDENTIFIER
16
- end
17
-
18
- sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
19
- def unmet_preconditions_for_behavior(behavior, package)
20
- # This protection relies on seeing privacy violations in other packages.
21
- # We also require that the other package enforces dependencies, as otherwise, if the client is using public API, it won't show up
22
- # as a privacy OR dependency violation. For now, we don't have the system structure to support that requirement.
23
- if behavior.enabled? && !package.enforces_privacy?
24
- "Package #{package.name} must have `enforce_privacy: true` to use this protection"
25
- elsif !behavior.enabled? && !package.metadata['visible_to'].nil?
26
- "Invalid configuration for package `#{package.name}`. `#{identifier}` must be turned on to use `visible_to` configuration."
27
- else
28
- nil
29
- end
30
- end
31
-
32
- #
33
- # By default, this protection does not show up when creating a new package, and its default behavior is FailNever
34
- # A package that uses this protection is not considered strictly better -- in general, we want to design packages that
35
- # are consumable by all packages. Therefore, a package that is consumable by all packages is the happy path.
36
- #
37
- # If a user wants to turn on package visibility, they must do it explicitly.
38
- #
39
- sig { returns(ViolationBehavior) }
40
- def default_behavior
41
- ViolationBehavior::FailNever
42
- end
43
-
44
- sig { override.returns(String) }
45
- def humanized_protection_name
46
- 'Visibility Violations'
47
- end
48
-
49
- sig { override.returns(String) }
50
- def humanized_protection_description
51
- <<~MESSAGE
52
- These files are using a constant from a package that restricts its usage through the `visible_to` flag in its `package.yml`
53
- To resolve these violations, work with the team who owns the package you are trying to use and to figure out the
54
- preferred public API for the behavior you want.
55
-
56
- See https://go/packwerk_cheatsheet_visibility for more info.
57
- MESSAGE
58
- end
59
-
60
- sig do
61
- override.params(
62
- new_violations: T::Array[PerFileViolation]
63
- ).returns(T::Array[Offense])
64
- end
65
- def get_offenses_for_new_violations(new_violations)
66
- new_violations.flat_map do |per_file_violation|
67
- depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
68
- violation_behavior = depended_on_package.violation_behavior_for(identifier)
69
- visible_to = depended_on_package.visible_to
70
- next [] if visible_to.include?(per_file_violation.reference_source_package.name)
71
-
72
- case violation_behavior
73
- when ViolationBehavior::FailNever
74
- next []
75
- when ViolationBehavior::FailOnNew
76
- message = message_for_fail_on_new(per_file_violation)
77
- when ViolationBehavior::FailOnAny
78
- message = message_for_fail_on_any(per_file_violation)
79
- else
80
- T.absurd(violation_behavior)
81
- end
82
-
83
- Offense.new(
84
- file: per_file_violation.filepath,
85
- message: message,
86
- violation_type: identifier,
87
- package: depended_on_package.original_package
88
- )
89
- end
90
- end
91
-
92
- sig do
93
- override.params(
94
- protected_packages: T::Array[ProtectedPackage]
95
- ).returns(T::Array[Offense])
96
- end
97
- def get_offenses_for_existing_violations(protected_packages)
98
- all_offenses = T.let([], T::Array[Offense])
99
-
100
- all_listed_violations = protected_packages.flat_map do |protected_package|
101
- protected_package.violations.flat_map do |violation|
102
- PerFileViolation.from(violation, protected_package.original_package)
103
- end
104
- end
105
-
106
- #
107
- # First we get offenses related to violations between packages, looking at all dependency and privacy
108
- # violations between packages.
109
- #
110
-
111
- # We only care about looking at each edge once. Since an edge can show up twice if its both a privacy and dependency violation,
112
- # we only look at each combination of class from package (A) that's referenced in package (B)
113
- unique_per_file_violations = all_listed_violations.uniq do |per_file_violation|
114
- [per_file_violation.reference_source_package.name, per_file_violation.constant_source_package, per_file_violation.class_name]
115
- end
116
-
117
- all_offenses += unique_per_file_violations.flat_map do |per_file_violation|
118
- depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
119
- violation_behavior = depended_on_package.violation_behavior_for(identifier)
120
- visible_to = depended_on_package.visible_to
121
- next [] if visible_to.include?(per_file_violation.reference_source_package.name)
122
-
123
- case violation_behavior
124
- when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
125
- next []
126
- when ViolationBehavior::FailOnAny
127
- message = message_for_fail_on_any(per_file_violation)
128
- else
129
- T.absurd(violation_behavior)
130
- end
131
-
132
- Offense.new(
133
- file: per_file_violation.filepath,
134
- message: message,
135
- violation_type: identifier,
136
- package: depended_on_package.original_package
137
- )
138
- end
139
-
140
- #
141
- # Then we get offenses from stated dependencies
142
- #
143
- all_offenses += protected_packages.flat_map do |protected_package|
144
- protected_package.dependencies.flat_map do |package_dependency_name|
145
- depended_on_package = Private.get_package_with_name(package_dependency_name)
146
- visible_to = depended_on_package.visible_to
147
- next [] if visible_to.include?(protected_package.name)
148
-
149
- violation_behavior = depended_on_package.violation_behavior_for(identifier)
150
-
151
- case violation_behavior
152
- when ViolationBehavior::FailNever
153
- next []
154
- when ViolationBehavior::FailOnAny, ViolationBehavior::FailOnNew
155
- # continue
156
- else
157
- T.absurd(violation_behavior)
158
- end
159
-
160
- message = "`#{protected_package.name}` cannot state a dependency on `#{depended_on_package.name}`, as it violates package visibility in `#{depended_on_package.yml}`"
161
- Offense.new(
162
- file: protected_package.yml.to_s,
163
- message: message,
164
- violation_type: identifier,
165
- package: depended_on_package.original_package
166
- )
167
- end
168
- end
169
-
170
- all_offenses
171
- end
172
-
173
- private
174
-
175
- sig { params(per_file_violation: PerFileViolation).returns(String) }
176
- def message_for_fail_on_any(per_file_violation)
177
- "#{message_for_fail_on_new(per_file_violation)} (`#{per_file_violation.constant_source_package}` set to `fail_on_any`)"
178
- end
179
-
180
- sig { params(per_file_violation: PerFileViolation).returns(String) }
181
- def message_for_fail_on_new(per_file_violation)
182
- "`#{per_file_violation.filepath}` references non-visible `#{per_file_violation.class_name}` from `#{per_file_violation.constant_source_package}`"
183
- end
184
- end
185
- end
186
- end