pipedawg-vl 1.0.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: 96aac89e278239ef899ad613b712eea25f80c2029e1769ca32420a8bd4f03976
4
+ data.tar.gz: a233bd484f256cffe03c7cb1f0bfdcb774870820e83f0091169366edb3e252c8
5
+ SHA512:
6
+ metadata.gz: 165b090587e34fb4a7ca5c9c8768ab66111b0cc9898dbf1aa51c482f237b6b3d27c81cc76471d931632961cfd568ba50626d0280a1a28937d1de3aac78cf5cc1
7
+ data.tar.gz: 7a97e66e61163d11fc9856a52504429a2ed96981b88d1f7d23753c5e86a5a11757a35ed93eb12082e60a8786b01595a329568ac7a43eba72645ee77aa81e6ba0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Richard Grainger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # pipedawg
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/pipedawg.svg)](https://rubygems.org/gems/pipedawg)
4
+
5
+ Generate GitLab CI pipelines.
6
+
7
+ ## Installation
8
+
9
+ Install `pipedawg` with:
10
+
11
+ ```
12
+ gem install pipedawg
13
+ ```
14
+
15
+ Or add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'pipedawg'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ ```sh
24
+ bundle install
25
+ ```
26
+
27
+ ## Ruby library
28
+
29
+ Example:
30
+
31
+ ```ruby
32
+ #!/usr/bin/env ruby
33
+ # frozen_string_literal: true
34
+
35
+ require 'pipedawg'
36
+
37
+ gem_job = Pipedawg::Job.new(
38
+ 'build:gem',
39
+ artifacts: ['*.gem'],
40
+ image: 'ruby',
41
+ script: ['bundle install', 'gem build *.gemspec']
42
+ )
43
+
44
+ kaniko_build_job = Pipedawg::Job::Kaniko::Build.new(
45
+ 'build:kaniko',
46
+ needs: ['build:gem'],
47
+ retry: 2,
48
+ context:'${CI_PROJECT_DIR}/docker',
49
+ external_files: {'*.gem':'gems'},
50
+ debug: false
51
+ )
52
+
53
+ pipeline = Pipedawg::Pipeline.new 'build:image', jobs: [gem_job, kaniko_build_job]
54
+ puts pipeline.to_yaml
55
+ pipeline.to_yaml_file('/tmp/pipeline.yaml')
56
+ ```
57
+
58
+ ```console
59
+ $ cat /tmp/pipeline.yaml
60
+ ---
61
+ stages:
62
+ - '1'
63
+ - '2'
64
+ build:gem:
65
+ artifacts:
66
+ - "*.gem"
67
+ cache: {}
68
+ image: ruby
69
+ needs: []
70
+ script:
71
+ - bundle install
72
+ - gem build *.gemspec
73
+ stage: '1'
74
+ tags: []
75
+ build:kaniko:
76
+ artifacts: {}
77
+ cache: {}
78
+ image:
79
+ entrypoint:
80
+ - ''
81
+ name: gcr.io/kaniko-project/executor:debug
82
+ needs:
83
+ - build:gem
84
+ retry: 2
85
+ script:
86
+ - echo "{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}"
87
+ > "/kaniko/.docker/config.json"
88
+ - cp "*.gem" "${CI_PROJECT_DIR}/docker/gems"
89
+ - '"/kaniko/executor" --context "${CI_PROJECT_DIR}/docker" --dockerfile "Dockerfile"
90
+ --destination ${CI_REGISTRY_IMAGE}:latest'
91
+ stage: '2'
92
+ tags: []
93
+ ```
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. Run `bundle exec rubocop` to run the linter. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ Note that by default, Bundler will attempt to install gems to the system, e.g. `/usr/bin`, `/usr/share`, which requires elevated access and can interfere with files that are managed by the system's package manager. This behaviour can be overridden by creating the file `.bundle/config` and adding the following line:
100
+ ```
101
+ BUNDLE_PATH: "./.bundle"
102
+ ```
103
+ When you run `bin/setup` or `bundle install`, all gems will be installed inside the .bundle directory of this project.
104
+
105
+ To make this behaviour a default for all gem projects, the above line can be added to the user's bundle config file in their home directory (`~/.bundle/config`)
106
+
107
+ ## Contributing
108
+
109
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/ValdrinLushaj/pipedawg).
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ class Helm
6
+ # Pipedawg::Job::Helm::Copy class
7
+ class Copy < Job::Helm
8
+ def initialize(name, opts = {})
9
+ opts = {
10
+ chart: name,
11
+ destinations: [{ user: nil, password: nil, url: nil }],
12
+ password: nil, url: nil, user: nil, version: nil
13
+ }.merge(opts)
14
+ super name, opts
15
+ update
16
+ end
17
+
18
+ def update
19
+ opts[:script] = debug + pull + (opts[:destinations].map { |d| push(d) }).flatten(1)
20
+ end
21
+
22
+ private
23
+
24
+ def pull
25
+ case opts[:url]
26
+ when nil
27
+ []
28
+ when %r{^oci://}
29
+ pull_oci
30
+ else
31
+ pull_classic
32
+ end
33
+ end
34
+
35
+ def push(destination)
36
+ case destination[:url]
37
+ when nil
38
+ []
39
+ when %r{^oci://}
40
+ push_oci(destination)
41
+ else
42
+ push_classic(destination)
43
+ end
44
+ end
45
+
46
+ def pull_oci # rubocop:disable Metrics/AbcSize
47
+ script = []
48
+ if opts[:url] && opts[:chart] && opts[:version]
49
+ script = ['export HELM_EXPERIMENTAL_OCI=1']
50
+ script << login_oci(opts) if opts[:user] && opts[:password]
51
+ script << "\"#{opts[:command]}\" pull \"#{opts[:url]}/#{opts[:chart]}\" --version \"#{opts[:version]}\""
52
+ end
53
+ script
54
+ end
55
+
56
+ def push_oci(destination) # rubocop:disable Metrics/AbcSize
57
+ script = []
58
+ if destination[:url] && opts[:chart] && opts[:version]
59
+ script = ['export HELM_EXPERIMENTAL_OCI=1']
60
+ script << login_oci(destination) if destination[:user] && destination[:password]
61
+ script << "\"#{opts[:command]}\" push \"#{opts[:chart]}-#{opts[:version]}.tgz\" \"#{destination[:url]}\""
62
+ end
63
+ script
64
+ end
65
+
66
+ def login_oci(login_opts)
67
+ require 'uri'
68
+ "echo \"#{login_opts[:password]}\" | \"#{opts[:command]}\" registry login --username \"#{login_opts[:user]}\" --password-stdin \"#{URI(login_opts[:url]).host}\"" # rubocop:disable Layout/LineLength
69
+ end
70
+
71
+ def pull_classic # rubocop:disable Metrics/AbcSize
72
+ script = []
73
+ if opts[:url] && opts[:chart] && opts[:version]
74
+ suffix = login_classic(opts)
75
+ script << "\"#{opts[:command]}\" repo add source \"#{opts[:url]}\"#{suffix}"
76
+ script << "\"#{opts[:command]}\" repo update"
77
+ script << "\"#{opts[:command]}\" pull \"source/#{opts[:chart]}\" --version \"#{opts[:version]}\""
78
+ end
79
+ script
80
+ end
81
+
82
+ def push_classic(destination)
83
+ script = []
84
+ if destination[:url] && opts[:chart] && opts[:version]
85
+ script << plugin_classic
86
+ suffix = login_classic(destination)
87
+ script << "\"#{opts[:command]}\" cm-push \"#{opts[:chart]}-#{opts[:version]}.tgz\" \"#{destination[:url]}\"#{suffix}" # rubocop:disable Layout/LineLength
88
+ end
89
+ script
90
+ end
91
+
92
+ def login_classic(login_opts)
93
+ if login_opts[:user] && login_opts[:password]
94
+ " --username \"#{login_opts[:user]}\" --password \"#{login_opts[:password]}\""
95
+ else
96
+ ''
97
+ end
98
+ end
99
+
100
+ def plugin_classic
101
+ "\"#{opts[:command]}\" plugin list | grep -q cm-push || \"#{opts[:command]}\" plugin install https://github.com/chartmuseum/helm-push"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ # Pipedawg::Job::Helm class
6
+ class Helm < Job
7
+ def initialize(name, opts = {})
8
+ opts = {
9
+ command: 'helm',
10
+ image: { entrypoint: [''], name: 'alpine/helm' }
11
+ }.merge(opts)
12
+ super name, opts
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ class Kaniko
6
+ # Pipedawg::Job::Kaniko::Build class
7
+ class Build < Job::Kaniko
8
+ def initialize(name, opts = {}) # rubocop:disable Metrics/MethodLength
9
+ opts = {
10
+ build_args: {},
11
+ config: { auths: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } } },
12
+ config_file: '/kaniko/.docker/config.json', context: '${CI_PROJECT_DIR}',
13
+ destinations: ['${CI_REGISTRY_IMAGE}:latest'], dockerfile: 'Dockerfile', external_files: {}, flags: [],
14
+ ignore_paths: [], insecure_registries: [], options: {}, registry_certificates: {}, registry_mirrors: [],
15
+ skip_tls_verify_registry: [], trusted_ca_cert_source_files: [],
16
+ trusted_ca_cert_target_file: '/kaniko/ssl/certs/ca-certificates.crt'
17
+ }.merge(opts)
18
+ super name, opts
19
+ update
20
+ end
21
+
22
+ def update
23
+ require 'json'
24
+ opts[:script] = debug + config + cert_copies + file_copies + Array(kaniko_cmd)
25
+ end
26
+
27
+ private
28
+
29
+ def config
30
+ ["echo #{opts[:config].to_json.inspect} > \"#{opts[:config_file]}\""]
31
+ end
32
+
33
+ def cert_copies
34
+ Array(opts[:trusted_ca_cert_source_files]).map do |cert|
35
+ "cat \"#{cert}\" >> \"#{opts[:trusted_ca_cert_target_file]}\""
36
+ end
37
+ end
38
+
39
+ def file_copies
40
+ opts[:external_files].map do |source, dest|
41
+ "cp \"#{source}\" \"#{opts[:context]}/#{dest}\""
42
+ end
43
+ end
44
+
45
+ def kaniko_cmd # rubocop:disable Metrics/AbcSize
46
+ ["\"#{opts[:command]}\" --context \"#{opts[:context]}\"",
47
+ "--dockerfile \"#{opts[:dockerfile]}\"", flags, options, build_args,
48
+ ignore_paths, insecure_registries, registry_certificates, registry_mirrors,
49
+ destinations, skip_tls_verify_registries].reject(&:empty?).join(' ')
50
+ end
51
+
52
+ def flags
53
+ flags = opts[:flags].clone
54
+ flags << 'no-push' if opts[:destinations].empty?
55
+ flags.uniq.map { |f| "--#{f}" }.join(' ')
56
+ end
57
+
58
+ def options
59
+ opts[:options].map { |k, v| "--#{k}=\"#{v}\"" }.join(' ')
60
+ end
61
+
62
+ def build_args
63
+ opts[:build_args].map { |k, v| "--build-arg #{k}=\"#{v}\"" }.join(' ')
64
+ end
65
+
66
+ def ignore_paths
67
+ Array(opts[:ignore_paths]).map { |p| "--ignore-path #{p}" }.join(' ')
68
+ end
69
+
70
+ def insecure_registries
71
+ Array(opts[:insecure_registries]).map do |r|
72
+ "--insecure-registry #{r}"
73
+ end.join(' ')
74
+ end
75
+
76
+ def registry_certificates
77
+ opts[:registry_certificates].map do |k, v|
78
+ "--registry-certificate #{k}=\"#{v}\""
79
+ end.join(' ')
80
+ end
81
+
82
+ def registry_mirrors
83
+ Array(opts[:registry_mirrors]).map { |r| "--registry-mirror #{r}" }.join(' ')
84
+ end
85
+
86
+ def destinations
87
+ opts[:destinations].map { |d| "--destination #{d}" }.join(' ')
88
+ end
89
+
90
+ def skip_tls_verify_registries
91
+ Array(opts[:skip_tls_verify_registry]).map do |r|
92
+ "--skip-tls-verify-registry #{r}"
93
+ end.join(' ')
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ # Pipedawg::Job::Kaniko class
6
+ class Kaniko < Job
7
+ def initialize(name, opts = {})
8
+ opts = {
9
+ command: '/kaniko/executor',
10
+ image: { entrypoint: [''], name: 'gcr.io/kaniko-project/executor:debug' }
11
+ }.merge(opts)
12
+ super name, opts
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ class Qualys
6
+ # Pipedawg::Job::Qualys::Scan class
7
+ class Scan < Job::Qualys # rubocop:disable Metrics/ClassLength
8
+ def initialize(name, opts = {})
9
+ opts = {
10
+ acceptable_risk: '${QUALYS_ACCEPTABLE_IMAGE_RISK}',
11
+ artifacts: { expire_in: '1 month', paths: ['software.json', 'vulnerabilities.json'], when: 'always' },
12
+ config: { auths: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } } },
13
+ gateway: '${QUALYS_GATEWAY}', image: nil, password: '${QUALYS_PASSWORD}',
14
+ scan_image: '${QUALYS_IMAGE}', scan_target_prefix: 'qualys_scan_target',
15
+ user: '${QUALYS_USERNAME}', variables: { GIT_STRATEGY: 'clone' }
16
+ }.merge(opts)
17
+ super name, opts
18
+ update
19
+ end
20
+
21
+ def update # rubocop:disable Metrics/AbcSize
22
+ require 'json'
23
+ opts[:script] =
24
+ debug + config + image + clean_config + token + scan_start +
25
+ scan_complete + artifacts + severities + outputs
26
+ end
27
+
28
+ private
29
+
30
+ def debug # rubocop:disable Metrics/MethodLength
31
+ if opts[:debug]
32
+ super + [
33
+ 'echo Qualys settings:', "echo Qualys gateway: \"#{opts[:gateway]}\"",
34
+ "echo Qualys username: \"#{opts[:user]}\"",
35
+ "if [ \"#{opts[:password]}\" != '' ]; then " \
36
+ 'echo Qualys password is not empty; else ' \
37
+ 'echo Qualys password is not set; exit 1; fi'
38
+ ]
39
+ else
40
+ []
41
+ end
42
+ end
43
+
44
+ def config
45
+ ['export CONFIG=$(mktemp -d)', "echo #{opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
46
+ end
47
+
48
+ def image
49
+ [
50
+ "image_target=\"#{opts[:scan_target_prefix]}:$(echo #{opts[:scan_image]} | sed 's/^[^/]*\\///'| sed 's/[:/]/-/g')\"", # rubocop:disable Layout/LineLength
51
+ "docker --config=\"${CONFIG}\" pull \"#{opts[:scan_image]}\"",
52
+ "docker image tag \"#{opts[:scan_image]}\" \"${image_target}\"",
53
+ "image_id=$(docker inspect --format=\"{{index .Id}}\" \"#{opts[:scan_image]}\" | cut -c8-19)",
54
+ 'echo "Image ID: ${image_id}"'
55
+ ]
56
+ end
57
+
58
+ def clean_config
59
+ [
60
+ 'rm -f "${CONFIG}/config.json"',
61
+ 'rmdir "${CONFIG}"'
62
+ ]
63
+ end
64
+
65
+ def token
66
+ ["token=$(curl -s --location --request POST \"https://#{opts[:gateway]}/auth\" --header \"Content-Type: application/x-www-form-urlencoded\" --data-urlencode \"username=#{opts[:user]}\" --data-urlencode \"password=#{opts[:password]}\" --data-urlencode \"token=true\")"] # rubocop:disable Layout/LineLength
67
+ end
68
+
69
+ def scan_start
70
+ [
71
+ 'while true; do ' \
72
+ "result=$(curl -s -o /dev/null -w ''%{http_code}'' --location --request GET \"https://#{opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\"); " + # rubocop:disable Layout/LineLength, Style/FormatStringToken
73
+ 'echo "Waiting for scan to start..."; ' \
74
+ 'echo " Result: ${result}"; ' \
75
+ 'if [ "${result}" = "200" ]; then break; fi; ' \
76
+ 'sleep 10; done'
77
+ ]
78
+ end
79
+
80
+ def scan_complete
81
+ [
82
+ 'while true; do ' \
83
+ "result=$(curl -s --location --request GET \"https://#{opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\" | jq -r '.scanStatus'); " + # rubocop:disable Layout/LineLength
84
+ 'echo "Waiting for scan to complete..."; ' \
85
+ 'echo " Result: ${result}"; ' \
86
+ 'if [ "${result}" = "SUCCESS" ]; then break; fi; ' \
87
+ 'sleep 10; done; sleep 30'
88
+ ]
89
+ end
90
+
91
+ def artifacts
92
+ [
93
+ "curl -s --location --request GET \"https://#{opts[:gateway]}/csapi/v1.2/images/$image_id/software\" --header \"Authorization: Bearer $token\" | jq . > software.json", # rubocop:disable Layout/LineLength
94
+ "curl -s --location --request GET \"https://#{opts[:gateway]}/csapi/v1.2/images/$image_id/vuln\" --header \"Authorization: Bearer $token\" | jq . > vulnerabilities.json" # rubocop:disable Layout/LineLength
95
+ ]
96
+ end
97
+
98
+ def severities
99
+ [
100
+ "response=$(curl -s --location --request GET \"https://#{opts[:gateway]}/csapi/v1.2/images/$image_id/vuln/count\" --header \"Authorization: Bearer $token\")", # rubocop:disable Layout/LineLength
101
+ 'severity5=$(jq -r ".severity5Count" <<< "${response}")',
102
+ 'severity4=$(jq -r ".severity4Count" <<< "${response}")'
103
+ ]
104
+ end
105
+
106
+ def outputs # rubocop:disable Metrics/MethodLength
107
+ [
108
+ 'if [ "$severity5" = "null" ]; then ' \
109
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
110
+ 'exit 1; fi',
111
+ 'if [ "$severity4" = "null" ]; then ' \
112
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
113
+ 'exit 1; fi',
114
+ 'echo "Severity5: $severity5, Severity4: $severity4"',
115
+ 'risk=$((($severity5*3)+($severity4)))',
116
+ 'echo "Risk: $risk"',
117
+ "if (($risk > \"#{opts[:acceptable_risk]}\")); then " \
118
+ 'echo "Too many vulnerabilities. Severity5: $severity5, Severity4: $severity4" >&2; ' \
119
+ 'exit 1; fi'
120
+ ]
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ # Pipedawg::Job::Qualys class
6
+ class Qualys < Job
7
+ def initialize(name, opts = {})
8
+ super name, opts
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ class Skopeo
6
+ # Pipedawg::Job::Skopeo::Copy class
7
+ class Copy < Job::Skopeo
8
+ def initialize(name, opts = {})
9
+ opts = {
10
+ config: {}, copy_image: name, destinations: [{ copy_image: nil, flags: [], options: {} }], flags: [],
11
+ logins: {}, options: {}, stage: '${CI_PROJECT_DIR}/stage', trusted_ca_cert_source_files: [],
12
+ trusted_ca_cert_target_file: '/etc/docker/certs.d/ca.crt'
13
+ }.merge(opts)
14
+ super name, opts
15
+ update
16
+ end
17
+
18
+ def update # rubocop:disable Metrics/AbcSize
19
+ require 'json'
20
+ opts[:script] = debug + config + cert_copies + login + mkstage + pull + (
21
+ opts[:destinations].map { |d| push(d) }
22
+ ).flatten(1)
23
+ end
24
+
25
+ private
26
+
27
+ def config
28
+ ['export CONFIG=$(mktemp -d)', "echo #{opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
29
+ end
30
+
31
+ def cert_copies
32
+ ["mkdir -p $(dirname \"#{opts[:trusted_ca_cert_target_file]}\")"] +
33
+ Array(opts[:trusted_ca_cert_source_files]).map do |cert|
34
+ "cat \"#{cert}\" >> \"#{opts[:trusted_ca_cert_target_file]}\""
35
+ end
36
+ end
37
+
38
+ def login
39
+ opts.fetch(:logins, {}).map do |k, v|
40
+ begin
41
+ command = "echo \"#{v['password']}\" | #{opts[:command]} login --authfile \"${CONFIG}/config.json\" --username \"#{v['username']}\" --password-stdin \"#{k}\"" # rubocop:disable Layout/LineLength
42
+ `#{command}`
43
+ puts "Login succeeded for #{k}"
44
+ rescue RuntimeError => e
45
+ puts "Login failed for #{k}: #{e.message}"
46
+ end
47
+ end
48
+ end
49
+
50
+ def mkstage
51
+ ["mkdir -p \"#{opts[:stage]}\""]
52
+ end
53
+
54
+ def pull
55
+ copy(opts, "docker://#{opts[:copy_image]}", "\"dir://#{opts[:stage]}\"")
56
+ end
57
+
58
+ def push(destination_opts)
59
+ copy(destination_opts, "\"dir://#{opts[:stage]}\"", "docker://#{destination_opts[:copy_image]}")
60
+ end
61
+
62
+ def copy(copy_opts, source, destination)
63
+ Array(["#{opts[:command]} copy --authfile \"${CONFIG}/config.json\"", flags(copy_opts), options(copy_opts),
64
+ source, destination].reject(&:empty?).join(' '))
65
+ end
66
+
67
+ def flags(opts)
68
+ opts.fetch(:flags, []).uniq.map { |f| "--#{f}" }.join(' ')
69
+ end
70
+
71
+ def options(opts)
72
+ opts.fetch(:options, {}).map { |k, v| "--#{k} \"#{v}\"" }.join(' ')
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ class Job
5
+ # Pipedawg::Job::Skopeo class
6
+ class Skopeo < Job
7
+ def initialize(name, opts = {})
8
+ opts = {
9
+ command: 'skopeo',
10
+ image: { entrypoint: [''], name: 'quay.io/skopeo/stable:latest' }
11
+ }.merge(opts)
12
+ super name, opts
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # job class
5
+ class Job
6
+ attr_accessor :name, :opts
7
+
8
+ def initialize(name = 'build', opts = {}) # rubocop:disable Metrics/MethodLength
9
+ @name = name
10
+ @opts = {
11
+ artifacts: {},
12
+ cache: {},
13
+ debug: true,
14
+ image: { name: 'ruby:2.5' },
15
+ needs: [],
16
+ retry: nil,
17
+ rules: nil,
18
+ script: [],
19
+ stage: 'build',
20
+ tags: [],
21
+ variables: nil
22
+ }.merge(opts)
23
+ end
24
+
25
+ def to_hash
26
+ keys = %i[artifacts cache image needs retry rules script stage tags variables]
27
+ { "#{name}": opts.slice(*keys).compact }
28
+ end
29
+
30
+ private
31
+
32
+ def debug
33
+ if opts[:debug]
34
+ Pipedawg::Util.echo_proxy_vars
35
+ else
36
+ []
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # pipeline class
5
+ class Pipeline
6
+ attr_accessor :name, :opts
7
+
8
+ def initialize(name = 'pipeline', opts = {})
9
+ @name = name
10
+ @opts = {
11
+ jobs: [Pipedawg::Job.new],
12
+ stages: ['build'],
13
+ workflow: nil
14
+ }.merge(opts)
15
+ update
16
+ end
17
+
18
+ def to_yaml
19
+ require 'json'
20
+ require 'yaml'
21
+ pipeline = opts.compact.reject { |k, _| %i[jobs].include? k }
22
+ opts[:jobs].each do |job|
23
+ pipeline.merge!(job.to_hash)
24
+ end
25
+ JSON.parse(pipeline.to_json).to_yaml
26
+ end
27
+
28
+ def to_yaml_file(file = 'pipeline.yml')
29
+ File.write(file, to_yaml)
30
+ end
31
+
32
+ def update
33
+ stages = []
34
+ opts[:jobs].each do |job|
35
+ stage = stage_from_needs(opts[:jobs], job.name)
36
+ stages << stage
37
+ job.opts[:stage] = stage.to_s
38
+ end
39
+ opts[:stages] = stages.uniq.sort.map(&:to_s)
40
+ end
41
+
42
+ private
43
+
44
+ def stage_from_needs(jobs, job_name)
45
+ if jobs.select { |job| job.name == job_name }[0].opts.fetch(:needs, []) == []
46
+ 1
47
+ else
48
+ jobs.select { |job| job.name == job_name }[0].opts[:needs].map { |need| stage_from_needs(jobs, need) }.max + 1
49
+ end
50
+ end
51
+ end
52
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ VERSION = '1.0.1'
5
+ end
data/lib/pipedawg.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pipedawg/job'
4
+ require 'pipedawg/job/helm'
5
+ require 'pipedawg/job/helm/copy'
6
+ require 'pipedawg/job/kaniko'
7
+ require 'pipedawg/job/kaniko/build'
8
+ require 'pipedawg/job/qualys'
9
+ require 'pipedawg/job/qualys/scan'
10
+ require 'pipedawg/job/skopeo'
11
+ require 'pipedawg/job/skopeo/copy'
12
+ require 'pipedawg/pipeline'
13
+ require 'pipedawg/util'
14
+ require 'pipedawg/version'
15
+
16
+ module Pipedawg
17
+ class Error < StandardError; end
18
+ # Your code goes here...
19
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pipedawg-vl
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - harbottle
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-05-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Generate GitLab CI pipelines.
14
+ email:
15
+ - harbottle@room3d3.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - README.md
22
+ - lib/pipedawg.rb
23
+ - lib/pipedawg/job.rb
24
+ - lib/pipedawg/job/helm.rb
25
+ - lib/pipedawg/job/helm/copy.rb
26
+ - lib/pipedawg/job/kaniko.rb
27
+ - lib/pipedawg/job/kaniko/build.rb
28
+ - lib/pipedawg/job/qualys.rb
29
+ - lib/pipedawg/job/qualys/scan.rb
30
+ - lib/pipedawg/job/skopeo.rb
31
+ - lib/pipedawg/job/skopeo/copy.rb
32
+ - lib/pipedawg/pipeline.rb
33
+ - lib/pipedawg/util.rb
34
+ - lib/pipedawg/version.rb
35
+ homepage: https://github.com/ValdrinLushaj/pipedawg
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ homepage_uri: https://github.com/ValdrinLushaj/pipedawg
40
+ source_code_uri: https://github.com/ValdrinLushaj/pipedawg
41
+ changelog_uri: https://github.com/ValdrinLushaj/pipedawg
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.4.10
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Generate GitLab CI pipelines.
61
+ test_files: []