dependabot-docker_compose 0.297.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8800055af3f3a9fc1a7b056288ea510770fd65dcf8933eb69a66a4aed591914
4
+ data.tar.gz: 05c25842d1a91b7fc0cddff8e51b0687d5ff0f1cb200276409e25671cf245f46
5
+ SHA512:
6
+ metadata.gz: c8f586a6ee7a26cb2e0e92abca5e23a0304537236df1fac702a667dfb8117494ce16d3d3243e20b4003e181c129e998381c7e5cca72da98ffaab7f2ff5aaf9b1
7
+ data.tar.gz: 72d33eae6923a14d9ffc8f12ab9cfdf0cddab97ce91afa51a39be2bf8fa618bbb678cdc6408166f54745d3c5305800be91d5328c6c2a9eef8d421cfc7664499e
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/shared/shared_file_fetcher"
5
+
6
+ module Dependabot
7
+ module DockerCompose
8
+ class FileFetcher < Dependabot::Shared::SharedFileFetcher
9
+ FILENAME_REGEX = /(docker-)?compose(-[\w]+)?(?>\.[\w-]+)?\.ya?ml/i
10
+
11
+ sig { override.returns(T::Array[DependencyFile]) }
12
+ def fetch_files
13
+ fetched_files = []
14
+ fetched_files += correctly_encoded_docker_compose_files if allow_beta_ecosystems?
15
+
16
+ return fetched_files if fetched_files.any?
17
+
18
+ raise_appropriate_error
19
+ end
20
+
21
+ sig { override.returns(Regexp) }
22
+ def self.filename_regex
23
+ FILENAME_REGEX
24
+ end
25
+
26
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
27
+ def docker_compose_files
28
+ @docker_compose_files ||=
29
+ T.let(repo_contents(raise_errors: false)
30
+ .select { |f| f.type == "file" && f.name.match?(FILENAME_REGEX) }
31
+ .map { |f| fetch_file_from_host(f.name) }, T.nilable(T::Array[DependencyFile]))
32
+ end
33
+
34
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
35
+ def correctly_encoded_docker_compose_files
36
+ docker_compose_files.select { |f| T.must(f.content).valid_encoding? }
37
+ end
38
+
39
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
40
+ def incorrectly_encoded_docker_compose_files
41
+ docker_compose_files.reject { |f| T.must(f.content).valid_encoding? }
42
+ end
43
+
44
+ sig { override.returns(String) }
45
+ def self.required_files_message
46
+ "Repo must contain a docker-compose.yaml file."
47
+ end
48
+
49
+ private
50
+
51
+ sig { override.returns(String) }
52
+ def default_file_name
53
+ "docker-compose.yml"
54
+ end
55
+
56
+ sig { override.returns(String) }
57
+ def file_type
58
+ "Docker Compose"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Dependabot::FileFetchers.register(
65
+ "docker_compose",
66
+ Dependabot::DockerCompose::FileFetcher
67
+ )
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "yaml"
5
+ require "dependabot/shared/shared_file_parser"
6
+ require "dependabot/docker_compose/package_manager"
7
+
8
+ module Dependabot
9
+ module DockerCompose
10
+ class FileParser < Dependabot::Shared::SharedFileParser
11
+ extend T::Sig
12
+
13
+ FROM_IMAGE = %r{^(?:#{REGISTRY}/)?#{IMAGE}(?:#{TAG})?(?:#{DIGEST})?(?:#{NAME})?}
14
+
15
+ sig { returns(Ecosystem) }
16
+ def ecosystem
17
+ @ecosystem ||= T.let(
18
+ Ecosystem.new(
19
+ name: ECOSYSTEM,
20
+ package_manager: DockerPackageManager.new
21
+ ),
22
+ T.nilable(Ecosystem)
23
+ )
24
+ end
25
+
26
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
27
+ def parse
28
+ dependency_set = DependencySet.new
29
+
30
+ composefiles.each do |composefile|
31
+ yaml = YAML.safe_load(T.must(composefile.content))
32
+ yaml["services"].each do |_, service|
33
+ parsed_from_image = T.must(FROM_IMAGE.match(service["image"])).named_captures
34
+ parsed_from_image["registry"] = nil if parsed_from_image["registry"] == "docker.io"
35
+
36
+ version = version_from(parsed_from_image)
37
+ next unless version
38
+
39
+ dependency_set << build_dependency(composefile, parsed_from_image, version)
40
+ end
41
+ end
42
+
43
+ dependency_set.dependencies
44
+ end
45
+
46
+ private
47
+
48
+ sig { override.returns(String) }
49
+ def package_manager
50
+ "docker_compose"
51
+ end
52
+
53
+ sig { override.returns(String) }
54
+ def file_type
55
+ "docker-compose.yml"
56
+ end
57
+
58
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
59
+ def composefiles
60
+ dependency_files
61
+ end
62
+
63
+ sig { override.void }
64
+ def check_required_files
65
+ return if dependency_files.any?
66
+
67
+ raise "No #{file_type}!"
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ Dependabot::FileParsers.register(
74
+ "docker_compose",
75
+ Dependabot::DockerCompose::FileParser
76
+ )
@@ -0,0 +1,106 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/shared/shared_file_updater"
5
+
6
+ module Dependabot
7
+ module DockerCompose
8
+ class FileUpdater < Dependabot::Shared::SharedFileUpdater
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ YAML_REGEXP = /(docker-)?compose(?>\.[\w-]+)?\.ya?ml/i
13
+ IMAGE_REGEX = /image:\s*/
14
+
15
+ sig { override.returns(T::Array[Regexp]) }
16
+ def self.updated_files_regex
17
+ [YAML_REGEXP]
18
+ end
19
+
20
+ sig { override.returns(String) }
21
+ def file_type
22
+ "Docker compose"
23
+ end
24
+
25
+ sig { override.returns(Regexp) }
26
+ def yaml_file_pattern
27
+ YAML_REGEXP
28
+ end
29
+
30
+ sig { override.returns(Regexp) }
31
+ def container_image_regex
32
+ IMAGE_REGEX
33
+ end
34
+
35
+ sig { override.params(escaped_declaration: String).returns(Regexp) }
36
+ def build_old_declaration_regex(escaped_declaration)
37
+ %r{#{IMAGE_REGEX}\s+(docker\.io/)?#{escaped_declaration}(?=\s|$)}
38
+ end
39
+
40
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
41
+ def updated_dependency_files
42
+ updated_files = []
43
+ dependency_files.each do |file|
44
+ next unless requirement_changed?(file, T.must(dependency))
45
+
46
+ updated_files << updated_file(
47
+ file: file,
48
+ content: T.must(updated_dockerfile_content(file))
49
+ )
50
+ end
51
+
52
+ updated_files.reject! { |f| dependency_files.include?(f) }
53
+ raise "No files changed!" if updated_files.none?
54
+
55
+ updated_files
56
+ end
57
+
58
+ sig do
59
+ override.params(previous_content: String, old_source: T::Hash[Symbol, T.nilable(String)],
60
+ new_source: T::Hash[Symbol, T.nilable(String)]).returns(String)
61
+ end
62
+ def update_digest_and_tag(previous_content, old_source, new_source)
63
+ old_digest = old_source[:digest]
64
+ new_digest = new_source[:digest]
65
+
66
+ old_tag = old_source[:tag]
67
+ new_tag = new_source[:tag]
68
+
69
+ old_declaration =
70
+ if private_registry_url(old_source)
71
+ "#{private_registry_url(old_source)}/"
72
+ else
73
+ ""
74
+ end
75
+ old_declaration += T.must(dependency).name
76
+ old_declaration +=
77
+ if specified_with_tag?(old_source)
78
+ ":#{old_tag}"
79
+ else
80
+ ""
81
+ end
82
+ old_declaration +=
83
+ if old_digest&.start_with?("sha256:")
84
+ "@#{old_digest}"
85
+ else
86
+ ""
87
+ end
88
+
89
+ escaped_declaration = Regexp.escape(old_declaration)
90
+
91
+ old_declaration_regex = build_old_declaration_regex(escaped_declaration)
92
+
93
+ previous_content.gsub(old_declaration_regex) do |old_dec|
94
+ old_dec
95
+ .gsub(":#{old_tag}", ":#{new_tag}")
96
+ .gsub(old_digest.to_s, new_digest.to_s)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ Dependabot::FileUpdaters.register(
104
+ "docker_compose",
105
+ Dependabot::DockerCompose::FileUpdater
106
+ )
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/metadata_finders"
5
+ require "dependabot/metadata_finders/base"
6
+ require "dependabot/shared_helpers"
7
+ require "sorbet-runtime"
8
+
9
+ module Dependabot
10
+ module DockerCompose
11
+ class MetadataFinder < Dependabot::MetadataFinders::Base
12
+ extend T::Sig
13
+
14
+ private
15
+
16
+ sig { override.returns(T.nilable(Dependabot::Source)) }
17
+ def look_up_source
18
+ return if dependency.requirements.empty?
19
+
20
+ new_source = dependency.requirements.first&.fetch(:source)
21
+ return unless new_source && new_source[:registry] && new_source[:tag]
22
+
23
+ image_ref = "#{new_source[:registry]}/#{dependency.name}:#{new_source[:tag]}"
24
+ image_details_output = SharedHelpers.run_shell_command("regctl image inspect #{image_ref}")
25
+ image_details = JSON.parse(image_details_output)
26
+ image_source = image_details.dig("config", "Labels", "org.opencontainers.image.source")
27
+ return unless image_source
28
+
29
+ Dependabot::Source.from_url(image_source)
30
+ rescue StandardError => e
31
+ Dependabot.logger.warn("Error looking up Docker Compose source: #{e.message}")
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ Dependabot::MetadataFinders
39
+ .register("docker_compose", Dependabot::DockerCompose::MetadataFinder)
@@ -0,0 +1,53 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/docker_compose/version"
6
+ require "dependabot/ecosystem"
7
+ require "dependabot/docker_compose/requirement"
8
+
9
+ module Dependabot
10
+ module DockerCompose
11
+ ECOSYSTEM = "docker_compose"
12
+
13
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
14
+
15
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
16
+
17
+ class DockerPackageManager < Dependabot::Ecosystem::VersionManager
18
+ extend T::Sig
19
+
20
+ NAME = "docker_compose"
21
+
22
+ # As docker_compose updater is an in house custom utility, We use a placeholder
23
+ # version number for docker_compose updater
24
+ VERSION = "1.0.0"
25
+
26
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
27
+
28
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
29
+
30
+ sig do
31
+ void
32
+ end
33
+ def initialize
34
+ super(
35
+ name: NAME,
36
+ version: Version.new(VERSION),
37
+ deprecated_versions: DEPRECATED_VERSIONS,
38
+ supported_versions: SUPPORTED_VERSIONS
39
+ )
40
+ end
41
+
42
+ sig { override.returns(T::Boolean) }
43
+ def deprecated?
44
+ false
45
+ end
46
+
47
+ sig { override.returns(T::Boolean) }
48
+ def unsupported?
49
+ false
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/requirement"
7
+ require "dependabot/utils"
8
+
9
+ module Dependabot
10
+ module DockerCompose
11
+ # Lifted from the bundler package manager
12
+ class Requirement < Dependabot::Requirement
13
+ extend T::Sig
14
+
15
+ # For consistency with other languages, we define a requirements array.
16
+ # Ruby doesn't have an `OR` separator for requirements, so it always
17
+ # contains a single element.
18
+ sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
19
+ def self.requirements_array(requirement_string)
20
+ [new(T.must(requirement_string))]
21
+ end
22
+
23
+ sig { override.params(version: Version).returns(T::Boolean) }
24
+ def satisfied_by?(version)
25
+ super(version.release_part)
26
+ end
27
+
28
+ # Patches Gem::Requirement to make it accept requirement strings like
29
+ # "~> 4.2.5, >= 4.2.5.1" without first needing to split them.
30
+ sig { params(requirements: T.any(T.nilable(String), T::Array[T.nilable(String)])).void }
31
+ def initialize(*requirements)
32
+ requirements = requirements.flatten.flat_map do |req_string|
33
+ req_string.to_s.split(",").map(&:strip)
34
+ end
35
+
36
+ super(requirements)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ Dependabot::Utils
43
+ .register_requirement_class("docker_compose", Dependabot::DockerCompose::Requirement)
@@ -0,0 +1,144 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module DockerCompose
8
+ class Tag
9
+ extend T::Sig
10
+ WORDS_WITH_BUILD = /(?:(?:-[a-z]+)+-[0-9]+)+/
11
+ VERSION_REGEX = /v?(?<version>[0-9]+(?:[_.][0-9]+)*(?:\.[a-z0-9]+|#{WORDS_WITH_BUILD}|-(?:kb)?[0-9]+)*)/i
12
+ VERSION_WITH_SFX = /^#{VERSION_REGEX}(?<suffix>-[a-z][a-z0-9.\-]*)?$/i
13
+ VERSION_WITH_PFX = /^(?<prefix>[a-z][a-z0-9.\-_]*-)?#{VERSION_REGEX}$/i
14
+ VERSION_WITH_PFX_AND_SFX = /^(?<prefix>[a-z\-_]+-)?#{VERSION_REGEX}(?<suffix>-[a-z\-]+)?$/i
15
+ NAME_WITH_VERSION =
16
+ /
17
+ #{VERSION_WITH_PFX}|
18
+ #{VERSION_WITH_SFX}|
19
+ #{VERSION_WITH_PFX_AND_SFX}
20
+ /x
21
+ DIGEST = /@(?<digest>[^\s]+)/
22
+
23
+ sig { returns(String) }
24
+ attr_reader :name
25
+
26
+ sig { params(name: String).void }
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ sig { returns(String) }
32
+ def to_s
33
+ name
34
+ end
35
+
36
+ sig { returns(T::Boolean) }
37
+ def digest?
38
+ name.match?(DIGEST)
39
+ end
40
+
41
+ sig { returns(T.nilable(T::Boolean)) }
42
+ def looks_like_prerelease?
43
+ numeric_version&.match?(/[a-zA-Z]/)
44
+ end
45
+
46
+ sig { params(other: Tag).returns(T::Boolean) }
47
+ def comparable_to?(other)
48
+ return false unless comparable?
49
+
50
+ other_prefix = other.prefix
51
+ other_suffix = other.suffix
52
+ other_format = other.format
53
+
54
+ equal_prefix = prefix == other_prefix
55
+ equal_format = format == other_format
56
+ return equal_prefix && equal_format if other_format == :sha_suffixed
57
+
58
+ equal_suffix = suffix == other_suffix
59
+ equal_prefix && equal_format && equal_suffix
60
+ end
61
+
62
+ sig { returns(T::Boolean) }
63
+ def comparable?
64
+ name.match?(NAME_WITH_VERSION)
65
+ end
66
+
67
+ sig { params(other: Tag).returns(T::Boolean) }
68
+ def same_precision?(other)
69
+ other.precision == precision
70
+ end
71
+
72
+ sig { params(other: Tag).returns(T::Boolean) }
73
+ def same_but_less_precise?(other)
74
+ other.segments.zip(segments).all? do |segment, other_segment|
75
+ segment == other_segment || other_segment.nil?
76
+ end
77
+ end
78
+
79
+ sig { returns(T.nilable(T::Boolean)) }
80
+ def canonical?
81
+ return false unless numeric_version
82
+ return true if name == numeric_version
83
+
84
+ # .NET tags are suffixed with -sdk
85
+ return true if numeric_version && name == numeric_version.to_s + "-sdk"
86
+
87
+ numeric_version && name == "jdk-" + T.must(numeric_version)
88
+ end
89
+
90
+ sig { returns T.nilable(String) }
91
+ def prefix
92
+ name.match(NAME_WITH_VERSION)&.named_captures&.fetch("prefix")
93
+ end
94
+
95
+ sig { returns T.nilable(String) }
96
+ def suffix
97
+ name.match(NAME_WITH_VERSION)&.named_captures&.fetch("suffix")
98
+ end
99
+
100
+ sig { returns T.nilable(String) }
101
+ def version
102
+ name.match(NAME_WITH_VERSION)&.named_captures&.fetch("version")
103
+ end
104
+
105
+ sig { returns(Symbol) }
106
+ def format
107
+ return :sha_suffixed if name.match?(/(^|\-g?)[0-9a-f]{7,}$/)
108
+ return :year_month if version&.match?(/^[12]\d{3}(?:[.\-]|$)/)
109
+ return :year_month_day if version&.match?(/^[12](?:\d{5}|\d{7})(?:[.\-]|$)/)
110
+ return :build_num if version&.match?(/^\d+$/)
111
+
112
+ # As an example, "21-ea-32", "22-ea-7", and "22-ea-jdk-nanoserver-1809"
113
+ # are mapped to "<version>-ea-<build_num>", "<version>-ea-<build_num>",
114
+ # and "<version>-ea-jdk-nanoserver-<build_num>" respectively.
115
+ #
116
+ # That means only "22-ea-7" will be considered as a viable update
117
+ # candidate for "21-ea-32", since it's the only one that respects that
118
+ # format.
119
+ if version&.match?(WORDS_WITH_BUILD)
120
+ return :"<version>#{T.must(version).match(WORDS_WITH_BUILD).to_s.gsub(/-[0-9]+/, '-<build_num>')}"
121
+ end
122
+
123
+ :normal
124
+ end
125
+
126
+ sig { returns(T.nilable(String)) }
127
+ def numeric_version
128
+ return unless comparable?
129
+
130
+ version&.gsub(/kb/i, "")&.gsub(/-[a-z]+/, "")&.downcase
131
+ end
132
+
133
+ sig { returns(Integer) }
134
+ def precision
135
+ segments.length
136
+ end
137
+
138
+ sig { returns(T::Array[String]) }
139
+ def segments
140
+ T.must(numeric_version).split(/[.-]/)
141
+ end
142
+ end
143
+ end
144
+ end