dependabot-docker_compose 0.297.1

Sign up to get free protection for your applications and to get access to all the features.
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