pipedawg 0.3.1 → 0.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 041e80c36e4a1f30695c0d60233c8fcc3261ac734dee7d0770dde11ad1e0ac71
4
- data.tar.gz: ee48577c9c00e3ef4f76cabe22895d435b26bb10cd6dd4ff5a950dbaebaaf5bc
3
+ metadata.gz: b2e0ba1c718b0efe498f222720490aeb2566a6f5c4e6fb1f0b56e5e9874669a5
4
+ data.tar.gz: 5545deb6f783e71bb5b35bb6633aed7939a9ac0e74aefb50200020976c1b6489
5
5
  SHA512:
6
- metadata.gz: 83e2ef040fbdbec747968ca61d58079e720dd32cea77fd0dc0b39f451dab618e247cf85617f6f6f6dbaf881263e90c4b2f7b90b3df96d5c60e6a338db5b007d1
7
- data.tar.gz: 34cccd812da30055338afc7368a5f62f46c0df7ba8e7dc7c21e9590fdcd4a5d398df30be22f0e5aac6c7a9a3944f4683b1b96be79da9b75276aca89ee13e8d89
6
+ metadata.gz: 17fbfd9bb667f808c44e636d7133fa379d115b650102162d35b5e0fd7b6594466493663c7193ef8092d3e55c2480737bbf4e4bf5923260096ae7706200320d04
7
+ data.tar.gz: 338192888145922229beba1e38fa3794e2f1d7d4e197987de1b5ae7425df29f2239f47a011846b10df20723d43c93a26d26813755a33d2b193780b0812429f74
data/README.md CHANGED
@@ -42,13 +42,13 @@ gem_job = Pipedawg::Job.new(
42
42
  script: ['bundle install', 'gem build *.gemspec']
43
43
  )
44
44
 
45
- kaniko_job = Pipedawg::KanikoJob.new(
45
+ kaniko_build_job = Pipedawg::KanikoBuildJob.new(
46
46
  'build:kaniko',
47
47
  {needs: ['build:gem'], retry: 2},
48
48
  {context:'${CI_PROJECT_DIR}/docker',external_files: {'*.gem':'gems'}}
49
49
  )
50
50
 
51
- pipeline = Pipedawg::Pipeline.new 'build:image', jobs: [gem_job, kaniko_job]
51
+ pipeline = Pipedawg::Pipeline.new 'build:image', jobs: [gem_job, kaniko_build_job]
52
52
  puts pipeline.to_yaml
53
53
  ```
54
54
 
@@ -7,7 +7,7 @@ module Pipedawg
7
7
 
8
8
  def initialize(name = 'build', opts = {}, helm_opts = {})
9
9
  @helm_opts = {
10
- chart: name,
10
+ chart: name, debug: true,
11
11
  destinations: [{ user: nil, password: nil, url: nil }],
12
12
  helm: 'helm',
13
13
  image: { entrypoint: [''], name: 'alpine/helm' },
@@ -17,13 +17,21 @@ module Pipedawg
17
17
  update
18
18
  end
19
19
 
20
- def update
20
+ def update # rubocop:disable Metrics/AbcSize
21
21
  opts[:image] = helm_opts[:image] if helm_opts[:image]
22
- opts[:script] = [] + pull + (helm_opts[:destinations].map { |d| push(d) }).flatten(1)
22
+ opts[:script] = debug + pull + (helm_opts[:destinations].map { |d| push(d) }).flatten(1)
23
23
  end
24
24
 
25
25
  private
26
26
 
27
+ def debug
28
+ if helm_opts[:debug]
29
+ Pipedawg::Util.echo_proxy_vars
30
+ else
31
+ []
32
+ end
33
+ end
34
+
27
35
  def pull
28
36
  case helm_opts[:url]
29
37
  when nil
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pipedawg
4
- # kaniko_job class
5
- class KanikoJob < Job
4
+ # kaniko_build_job class
5
+ class KanikoBuildJob < Job
6
6
  attr_accessor :kaniko_opts
7
7
 
8
8
  def initialize(name = 'build', opts = {}, kaniko_opts = {}) # rubocop:disable Metrics/MethodLength
@@ -11,13 +11,12 @@ module Pipedawg
11
11
  config: {
12
12
  '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' }
13
13
  },
14
- config_file: '/kaniko/.docker/config.json', context: '${CI_PROJECT_DIR}',
15
- destinations: [], dockerfile: 'Dockerfile', executor: '/kaniko/executor',
16
- external_files: {}, flags: [], ignore_paths: [], insecure_registries: [],
17
- image: { entrypoint: [''], name: 'gcr.io/kaniko-project/executor:debug' },
18
- options: {}, registry_certificates: {}, registry_mirrors: [],
19
- skip_tls_verify_registry: [], trusted_ca_cert_source_files: [],
20
- trusted_ca_cert_target_file: '/kaniko/ssl/certs/ca-certificates.crt'
14
+ config_file: '/kaniko/.docker/config.json', context: '${CI_PROJECT_DIR}', debug: true,
15
+ destinations: [], dockerfile: 'Dockerfile', executor: '/kaniko/executor', external_files: {},
16
+ flags: [], ignore_paths: [], insecure_registries: [],
17
+ image: { entrypoint: [''], name: 'gcr.io/kaniko-project/executor:debug' }, options: {},
18
+ registry_certificates: {}, registry_mirrors: [], skip_tls_verify_registry: [],
19
+ trusted_ca_cert_source_files: [], trusted_ca_cert_target_file: '/kaniko/ssl/certs/ca-certificates.crt'
21
20
  }.merge(kaniko_opts)
22
21
  super name, opts
23
22
  update
@@ -26,11 +25,19 @@ module Pipedawg
26
25
  def update # rubocop:disable Metrics/AbcSize
27
26
  require 'json'
28
27
  opts[:image] = kaniko_opts[:image] if kaniko_opts[:image]
29
- opts[:script] = config + cert_copies + file_copies + Array(kaniko_cmd)
28
+ opts[:script] = debug + config + cert_copies + file_copies + Array(kaniko_cmd)
30
29
  end
31
30
 
32
31
  private
33
32
 
33
+ def debug
34
+ if kaniko_opts[:debug]
35
+ Pipedawg::Util.echo_proxy_vars
36
+ else
37
+ []
38
+ end
39
+ end
40
+
34
41
  def config
35
42
  ["echo #{kaniko_opts[:config].to_json.inspect} > \"#{kaniko_opts[:config_file]}\""]
36
43
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # qualys_scan_job class
5
+ class QualysScanJob < Job # rubocop:disable Metrics/ClassLength
6
+ attr_accessor :qualys_opts
7
+
8
+ def initialize(name = 'build', opts = {}, qualys_opts = {})
9
+ @qualys_opts = {
10
+ acceptable_risk: '${QUALYS_ACCEPTABLE_IMAGE_RISK}',
11
+ artifacts: { expire_in: '1 month', paths: ['software.json', 'vulnerabilities.json'], when: 'always' },
12
+ config: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } },
13
+ debug: true, gateway: '${QUALYS_GATEWAY}', image: nil, password: '${QUALYS_PASSWORD}', rules: nil,
14
+ scan_image: '${QUALYS_IMAGE}', scan_target_prefix: 'qualys_scan_target', tags: nil, user: '${QUALYS_USERNAME}',
15
+ variables: { GIT_STRATEGY: 'clone' }
16
+ }.merge(qualys_opts)
17
+ super name, opts
18
+ update
19
+ end
20
+
21
+ def update # rubocop:disable Metrics/AbcSize
22
+ require 'json'
23
+ opts[:artifacts] = qualys_opts[:artifacts] if qualys_opts[:artifacts]
24
+ opts[:image] = qualys_opts[:image]
25
+ opts[:rules] = qualys_opts[:rules] if qualys_opts[:rules]
26
+ opts[:tags] = qualys_opts[:tags] if qualys_opts[:tags]
27
+ opts[:variables] = qualys_opts[:variables] if qualys_opts[:variables]
28
+ opts[:script] =
29
+ debug + config + image + clean_config + token + scan_start + scan_complete + artifacts + severities + outputs
30
+ end
31
+
32
+ private
33
+
34
+ def debug # rubocop:disable Metrics/MethodLength
35
+ if qualys_opts[:debug]
36
+ Pipedawg::Util.echo_proxy_vars + [
37
+ 'echo Qualys settings:', "echo Qualys gateway: \"#{qualys_opts[:gateway]}\"",
38
+ "echo Qualys username: \"#{qualys_opts[:user]}\"",
39
+ "if [ \"#{qualys_opts[:password]}\" != '' ]; then " \
40
+ 'echo Qualys password is not empty; else ' \
41
+ 'echo Qualys password is not set; exit 1; fi'
42
+ ]
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ def config
49
+ ['export CONFIG=$(mktemp -d)', "echo #{qualys_opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
50
+ end
51
+
52
+ def image
53
+ [
54
+ "image_target=\"#{qualys_opts[:scan_target_prefix]}:$(echo #{qualys_opts[:scan_image]} | sed 's/^[^/]*\\///'| sed 's/[:/]/-/g')\"", # rubocop:disable Layout/LineLength
55
+ "docker --config=\"${CONFIG}\" pull \"#{qualys_opts[:scan_image]}\"",
56
+ "docker image tag \"#{qualys_opts[:scan_image]}\" \"${image_target}\"",
57
+ "image_id=$(docker inspect --format=\"{{index .Id}}\" \"#{qualys_opts[:scan_image]}\" | cut -c8-19)",
58
+ 'echo "Image ID: ${image_id}"'
59
+ ]
60
+ end
61
+
62
+ def clean_config
63
+ [
64
+ 'rm -f "${CONFIG}/config.json"',
65
+ 'rmdir "${CONFIG}"'
66
+ ]
67
+ end
68
+
69
+ def token
70
+ ["token=$(curl -s --location --request POST \"https://#{qualys_opts[:gateway]}/auth\" --header \"Content-Type: application/x-www-form-urlencoded\" --data-urlencode \"username=#{qualys_opts[:user]}\" --data-urlencode \"password=#{qualys_opts[:password]}\" --data-urlencode \"token=true\")"] # rubocop:disable Layout/LineLength
71
+ end
72
+
73
+ def scan_start
74
+ [
75
+ 'while true; do ' \
76
+ "result=$(curl -s -o /dev/null -w ''%{http_code}'' --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\"); " + # rubocop:disable Layout/LineLength, Style/FormatStringToken
77
+ 'echo "Waiting for scan to start..."; ' \
78
+ 'echo " Result: ${result}"; ' \
79
+ 'if [ "${result}" = "200" ]; then break; fi; ' \
80
+ 'sleep 10; done'
81
+ ]
82
+ end
83
+
84
+ def scan_complete
85
+ [
86
+ 'while true; do ' \
87
+ "result=$(curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\" | jq -r '.scanStatus'); " + # rubocop:disable Layout/LineLength
88
+ 'echo "Waiting for scan to complete..."; ' \
89
+ 'echo " Result: ${result}"; ' \
90
+ 'if [ "${result}" = "SUCCESS" ]; then break; fi; ' \
91
+ 'sleep 10; done; sleep 30'
92
+ ]
93
+ end
94
+
95
+ def artifacts
96
+ [
97
+ "curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/software\" --header \"Authorization: Bearer $token\" | jq . > software.json", # rubocop:disable Layout/LineLength
98
+ "curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/vuln\" --header \"Authorization: Bearer $token\" | jq . > vulnerabilities.json" # rubocop:disable Layout/LineLength
99
+ ]
100
+ end
101
+
102
+ def severities
103
+ [
104
+ "response=$(curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/vuln/count\" --header \"Authorization: Bearer $token\")", # rubocop:disable Layout/LineLength
105
+ 'severity5=$(jq -r ".severity5Count" <<< "${response}")',
106
+ 'severity4=$(jq -r ".severity4Count" <<< "${response}")'
107
+ ]
108
+ end
109
+
110
+ def outputs # rubocop:disable Metrics/MethodLength
111
+ [
112
+ 'if [ "$severity5" = "null" ]; then ' \
113
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
114
+ 'exit 1; fi',
115
+ 'if [ "$severity4" = "null" ]; then ' \
116
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
117
+ 'exit 1; fi',
118
+ 'echo "Severity5: $severity5, Severity4: $severity4"',
119
+ 'risk=$((($severity5*3)+($severity4)))',
120
+ 'echo "Risk: $risk"',
121
+ "if (($risk > \"#{qualys_opts[:acceptable_risk]}\")); then " \
122
+ 'echo "Too many vulnerabilities. Severity5: $severity5, Severity4: $severity4" >&2; ' \
123
+ 'exit 1; fi'
124
+ ]
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # skopeo_copy_job class
5
+ class SkopeoCopyJob < Job
6
+ attr_accessor :skopeo_opts
7
+
8
+ def initialize(name = 'build', opts = {}, skopeo_opts = {})
9
+ @skopeo_opts = {
10
+ config: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } },
11
+ copy_image: nil, debug: true, destinations: [{ copy_image: nil, flags: [], options: {} }],
12
+ flags: [], logins: {}, options: {}, skopeo: 'skopeo', stage: '${CI_PROJECT_DIR}/stage',
13
+ image: { entrypoint: [''], name: 'quay.io/skopeo/stable:latest' },
14
+ trusted_ca_cert_source_files: [], trusted_ca_cert_target_file: '/etc/docker/certs.d/ca.crt'
15
+ }.merge(skopeo_opts)
16
+ super name, opts
17
+ update
18
+ end
19
+
20
+ def update # rubocop:disable Metrics/AbcSize
21
+ require 'json'
22
+ opts[:image] = skopeo_opts[:image] if skopeo_opts[:image]
23
+ opts[:rules] = skopeo_opts[:rules] if skopeo_opts[:rules]
24
+ opts[:script] = debug + config + cert_copies + login + mkstage + pull + (
25
+ skopeo_opts[:destinations].map { |d| push(d) }
26
+ ).flatten(1)
27
+ end
28
+
29
+ private
30
+
31
+ def debug
32
+ if skopeo_opts[:debug]
33
+ Pipedawg::Util.echo_proxy_vars
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ def config
40
+ ['export CONFIG=$(mktemp -d)', "echo #{skopeo_opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
41
+ end
42
+
43
+ def cert_copies
44
+ ["mkdir -p $(dirname \"#{skopeo_opts[:trusted_ca_cert_target_file]}\")"] +
45
+ Array(skopeo_opts[:trusted_ca_cert_source_files]).map do |cert|
46
+ "cat \"#{cert}\" >> \"#{skopeo_opts[:trusted_ca_cert_target_file]}\""
47
+ end
48
+ end
49
+
50
+ def login
51
+ skopeo_opts.fetch(:logins, {}).map do |k, v|
52
+ "echo \"#{v['password']}\" | #{skopeo_opts[:skopeo]} login --authfile \"${CONFIG}/config.json\" --username \"#{v['username']}\" --password-stdin \"#{k}\"" # rubocop:disable Layout/LineLength
53
+ end
54
+ end
55
+
56
+ def mkstage
57
+ ["mkdir -p \"#{skopeo_opts[:stage]}\""]
58
+ end
59
+
60
+ def pull
61
+ copy(skopeo_opts, "docker://#{skopeo_opts[:copy_image]}", "\"dir://#{skopeo_opts[:stage]}\"")
62
+ end
63
+
64
+ def push(destination_opts)
65
+ copy(destination_opts, "\"dir://#{skopeo_opts[:stage]}\"", "docker://#{destination_opts[:copy_image]}")
66
+ end
67
+
68
+ def copy(opts, source, destination)
69
+ Array(["#{skopeo_opts[:skopeo]} copy --authfile \"${CONFIG}/config.json\"", flags(opts), options(opts), source,
70
+ destination].reject(&:empty?).join(' '))
71
+ end
72
+
73
+ def flags(opts)
74
+ opts.fetch(:flags, []).uniq.map { |f| "--#{f}" }.join(' ')
75
+ end
76
+
77
+ def options(opts)
78
+ opts.fetch(:options, {}).map { |k, v| "--#{k} \"#{v}\"" }.join(' ')
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # util class
5
+ class Util
6
+ def self.expand_env_vars(item) # rubocop:disable Metrics/MethodLength
7
+ case item
8
+ when Array
9
+ item.map { |i| expand_env_vars(i) }
10
+ when Hash
11
+ item.each { |k, v| item[k] = expand_env_vars(v) }
12
+ item.transform_keys! { |k| expand_env_vars(k) }
13
+ item
14
+ when String
15
+ item.gsub(/\${([^} ]+)}/) do |e|
16
+ ENV[e.gsub('${', '').gsub('}', '')]
17
+ end
18
+ else
19
+ item
20
+ end
21
+ end
22
+
23
+ def self.puts_proxy_vars
24
+ puts 'Proxy settings:'
25
+ puts "http_proxy: #{ENV['http_proxy']}"
26
+ puts "https_proxy: #{ENV['https_proxy']}"
27
+ puts "no_proxy: #{ENV['no_proxy']}"
28
+ puts "HTTP_PROXY: #{ENV['HTTP_PROXY']}"
29
+ puts "HTTPS_PROXY: #{ENV['HTTPS_PROXY']}"
30
+ puts "NO_PROXY: #{ENV['NO_PROXY']}"
31
+ end
32
+
33
+ def self.echo_proxy_vars
34
+ script = ['echo Proxy settings:']
35
+ script << 'echo http_proxy: "${http_proxy}"'
36
+ script << 'echo https_proxy: "${https_proxy}"'
37
+ script << 'echo no_proxy: "${no_proxy}"'
38
+ script << 'echo HTTP_PROXY: "${HTTP_PROXY}"'
39
+ script << 'echo HTTPS_PROXY: "${HTTPS_PROXY}"'
40
+ script << 'echo NO_PROXY: "${NO_PROXY}"'
41
+ script
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pipedawg
4
- VERSION = '0.3.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/pipedawg.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  require 'pipedawg/job'
4
4
  require 'pipedawg/helm_copy_job'
5
- require 'pipedawg/kaniko_job'
5
+ require 'pipedawg/kaniko_build_job'
6
6
  require 'pipedawg/pipeline'
7
+ require 'pipedawg/qualys_scan_job'
8
+ require 'pipedawg/skopeo_copy_job'
9
+ require 'pipedawg/util'
7
10
  require 'pipedawg/version'
8
11
 
9
12
  module Pipedawg
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipedawg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - harbottle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-08 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Generate GitLab CI pipelines.
14
14
  email:
@@ -22,8 +22,11 @@ files:
22
22
  - lib/pipedawg.rb
23
23
  - lib/pipedawg/helm_copy_job.rb
24
24
  - lib/pipedawg/job.rb
25
- - lib/pipedawg/kaniko_job.rb
25
+ - lib/pipedawg/kaniko_build_job.rb
26
26
  - lib/pipedawg/pipeline.rb
27
+ - lib/pipedawg/qualys_scan_job.rb
28
+ - lib/pipedawg/skopeo_copy_job.rb
29
+ - lib/pipedawg/util.rb
27
30
  - lib/pipedawg/version.rb
28
31
  homepage: https://github.com/liger1978/pipedawg
29
32
  licenses: