squared 0.3.4 → 0.4.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 +4 -4
- data/CHANGELOG.md +67 -2
- data/README.md +13 -2
- data/README.ruby.md +168 -88
- data/lib/squared/app.rb +1 -0
- data/lib/squared/common/base.rb +6 -1
- data/lib/squared/common/class.rb +1 -1
- data/lib/squared/common/format.rb +24 -12
- data/lib/squared/common/prompt.rb +2 -2
- data/lib/squared/common/shell.rb +52 -39
- data/lib/squared/common/utils.rb +54 -1
- data/lib/squared/config.rb +1 -1
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +16 -13
- data/lib/squared/workspace/project/base.rb +429 -123
- data/lib/squared/workspace/project/docker.rb +572 -0
- data/lib/squared/workspace/project/git.rb +405 -157
- data/lib/squared/workspace/project/node.rb +51 -51
- data/lib/squared/workspace/project/python.rb +115 -24
- data/lib/squared/workspace/project/ruby.rb +33 -34
- data/lib/squared/workspace/project.rb +7 -1
- data/lib/squared/workspace/repo.rb +9 -4
- data/lib/squared/workspace/series.rb +1 -1
- metadata +2 -1
| @@ -0,0 +1,572 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Squared
         | 
| 4 | 
            +
              module Workspace
         | 
| 5 | 
            +
                module Project
         | 
| 6 | 
            +
                  class Docker < Base
         | 
| 7 | 
            +
                    include Prompt
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    COMPOSEFILE = %w[compose.yaml compose.yml docker-compose.yaml compose.yml docker-compose.yml].freeze
         | 
| 10 | 
            +
                    BAKEFILE = %w[docker-bake.json docker-bake.hcl docker-bake.override.json docker-bake.override.hcl].freeze
         | 
| 11 | 
            +
                    DIR_DOCKER = (COMPOSEFILE + BAKEFILE).freeze
         | 
| 12 | 
            +
                    OPT_DOCKER = {
         | 
| 13 | 
            +
                      common: %w[tls tlsverify config=p c|context=b D|debug H|host=q l|log-level=b tlscacert=p tlscert=p
         | 
| 14 | 
            +
                                 tlskey=p].freeze,
         | 
| 15 | 
            +
                      buildx: {
         | 
| 16 | 
            +
                        common: %w[builder=b D|debug],
         | 
| 17 | 
            +
                        build: %w[load pull push add-host=q annotation=q attest=q build-arg=qq ent=q iidfile=p label=q a-file=p
         | 
| 18 | 
            +
                                  network=b no-cache-filter=b o|output=q platform=b q|quiet secret=qq shm-size=b ssh=qq t|tag=b
         | 
| 19 | 
            +
                                  target=b ulimit=q].freeze,
         | 
| 20 | 
            +
                        bake: %w[load print pull push list=q metadata-file=p set=q].freeze,
         | 
| 21 | 
            +
                        shared: %w[check no-cache allow=q call=b? f|file=p progress=b provenance=q sbom=q].freeze
         | 
| 22 | 
            +
                      }.freeze,
         | 
| 23 | 
            +
                      compose: {
         | 
| 24 | 
            +
                        common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=b profile=b progress=b
         | 
| 25 | 
            +
                                   project-directory=p p|project-name=e].freeze,
         | 
| 26 | 
            +
                        build: %w[no-cache pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b ssh=qq].freeze,
         | 
| 27 | 
            +
                        exec: %w[dry-run privileged d|detach e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
         | 
| 28 | 
            +
                        run: %w[build dry-run no-deps quiet-pull remove-orphans rm P|service-ports use-aliases cap-add=b cap-drop=b
         | 
| 29 | 
            +
                                d|detach entrypoint=q e|env=qq i|interactive=b? l|label=q name=b T|no-TTY=b? p|publish=e pull=b
         | 
| 30 | 
            +
                                u|user=e v|volume=q w|workdir=q].freeze,
         | 
| 31 | 
            +
                        up: %w[y abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
         | 
| 32 | 
            +
                               d|detach dry-run force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start
         | 
| 33 | 
            +
                               quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch attach=b exit-code-from=b
         | 
| 34 | 
            +
                               no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze
         | 
| 35 | 
            +
                      }.freeze,
         | 
| 36 | 
            +
                      container: {
         | 
| 37 | 
            +
                        run: %w[d|detach init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet
         | 
| 38 | 
            +
                                read-only rm runtime t|tty add-host=q annotation=q a|attach=b blkio-weight-device=i cap-add=b
         | 
| 39 | 
            +
                                cap-drop=b cgroup-parent=b cgroupns=b cidfile=p detach-keys=q device=q device-cgroup-rule=q
         | 
| 40 | 
            +
                                device-read-bps=q device-read-iops=q device-write-bps=q device-write-iops=q
         | 
| 41 | 
            +
                                disable-content-trust=b? dns=e dns-option=e dns-search=e domainname=b entrypoint=q e|env=qq
         | 
| 42 | 
            +
                                env-file=p expose=e gpus=q group-add=b health-cmd=q health-interval=b health-retries=i
         | 
| 43 | 
            +
                                health-start-interval=b health-start-period=b health-timeout=b h|hostname=e io-maxbandwidth=b
         | 
| 44 | 
            +
                                io-maxiops=b ip=b ip6=e ipc=b isolation=b kernel-memory=b l|label=q label-file=p link=b
         | 
| 45 | 
            +
                                link-local-ip=b log-driver=b log-opt=q mac-address=e memory-swappiness=b mount=q name=b network=b
         | 
| 46 | 
            +
                                network-alias=b oom-score-adj=b pid=b platform=b p|publish=e pull=b restart=b runtime=b
         | 
| 47 | 
            +
                                security-opt=q shm-size=b sig-proxy=b? stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
         | 
| 48 | 
            +
                                ulimit=q user=e userns=b uts=b v|volume=q volume-driver=b volumes-from=b w|workdir=q].freeze,
         | 
| 49 | 
            +
                        exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p user=e
         | 
| 50 | 
            +
                                 w|workdir=q].freeze,
         | 
| 51 | 
            +
                        update: %w[blkio-weight=i cpu-period=i cpu-quota=i cpu-rt-period=i cpu-rt-runtime=i c|cpu-shares=i cpus=f
         | 
| 52 | 
            +
                                   cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=b pids-limit=b
         | 
| 53 | 
            +
                                   restart=q].freeze,
         | 
| 54 | 
            +
                        commit: %w[a|author=q c|change=q m|message=q pause=b?].freeze,
         | 
| 55 | 
            +
                        inspect: %w[s|size f|format=q].freeze,
         | 
| 56 | 
            +
                        start: %w[a|attach i|interactive detach-keys=q].freeze,
         | 
| 57 | 
            +
                        stop: %w[s|signal=b t|time=i].freeze,
         | 
| 58 | 
            +
                        restart: %w[s|signal=b t|time=i].freeze,
         | 
| 59 | 
            +
                        kill: %w[s|signal=b].freeze,
         | 
| 60 | 
            +
                        stats: %w[no-trunc format|q].freeze
         | 
| 61 | 
            +
                      }.freeze,
         | 
| 62 | 
            +
                      image: {
         | 
| 63 | 
            +
                        list: %w[a|all digests no-trunc f|filter=q format=q].freeze,
         | 
| 64 | 
            +
                        push: %w[disable-content-trust=b? platform=b q|quiet].freeze,
         | 
| 65 | 
            +
                        rm: %w[f|force no-prune].freeze
         | 
| 66 | 
            +
                      }.freeze
         | 
| 67 | 
            +
                    }.freeze
         | 
| 68 | 
            +
                    private_constant :COMPOSEFILE, :BAKEFILE, :OPT_DOCKER
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    class << self
         | 
| 71 | 
            +
                      def tasks
         | 
| 72 | 
            +
                        [].freeze
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      def config?(val)
         | 
| 76 | 
            +
                        return false unless (val = as_path(val))
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        val.join('Dockerfile').exist? || DIR_DOCKER.any? { |file| val.join(file).exist? }
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    @@tasks[ref] = {
         | 
| 83 | 
            +
                      'build' => %i[tag context bake].freeze,
         | 
| 84 | 
            +
                      'compose' => %i[build run exec up].freeze,
         | 
| 85 | 
            +
                      'image' => %i[list rm].freeze,
         | 
| 86 | 
            +
                      'container' => %i[run exec update commit inspect diff start stop restart pause unpause top stats kill
         | 
| 87 | 
            +
                                        rm].freeze
         | 
| 88 | 
            +
                    }.freeze
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    attr_reader :context
         | 
| 91 | 
            +
                    attr_accessor :tag
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    def initialize(*, file: nil, context: nil, tag: nil, secrets: nil, registry: nil, **kwargs)
         | 
| 94 | 
            +
                      super
         | 
| 95 | 
            +
                      return unless dockerfile(file).exist?
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      @context = context
         | 
| 98 | 
            +
                      @tag = tag || "#{@project}:latest"
         | 
| 99 | 
            +
                      @secrets = secrets
         | 
| 100 | 
            +
                      @registry = registry
         | 
| 101 | 
            +
                      initialize_ref Docker.ref
         | 
| 102 | 
            +
                      initialize_logger(**kwargs)
         | 
| 103 | 
            +
                      initialize_env(**kwargs)
         | 
| 104 | 
            +
                      @output[4] = merge_opts(kwargs[:args], @output[4]) if kwargs[:args]
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    def ref
         | 
| 108 | 
            +
                      Docker.ref
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def populate(*, **)
         | 
| 112 | 
            +
                      super
         | 
| 113 | 
            +
                      return unless ref?(Docker.ref)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                      namespace name do
         | 
| 116 | 
            +
                        @@tasks[Docker.ref].each do |action, flags|
         | 
| 117 | 
            +
                          next if @pass.include?(action)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                          namespace action do
         | 
| 120 | 
            +
                            flags.each do |flag|
         | 
| 121 | 
            +
                              case action
         | 
| 122 | 
            +
                              when 'build'
         | 
| 123 | 
            +
                                case flag
         | 
| 124 | 
            +
                                when :tag, :context
         | 
| 125 | 
            +
                                  format_desc(action, flag, 'opts*', before: flag == :tag ? 'name' : 'dir')
         | 
| 126 | 
            +
                                  task flag, [flag] do |_, args|
         | 
| 127 | 
            +
                                    param = param_guard(action, flag, args: args, key: flag)
         | 
| 128 | 
            +
                                    buildx(:build, args.to_a.drop(1), "#{flag}": param)
         | 
| 129 | 
            +
                                  end
         | 
| 130 | 
            +
                                when :bake
         | 
| 131 | 
            +
                                  format_desc action, flag, 'opts*,target*,context?'
         | 
| 132 | 
            +
                                  task flag do |_, args|
         | 
| 133 | 
            +
                                    args = param_guard(action, flag, args: args.to_a)
         | 
| 134 | 
            +
                                    buildx flag, args
         | 
| 135 | 
            +
                                  end
         | 
| 136 | 
            +
                                end
         | 
| 137 | 
            +
                              when 'compose'
         | 
| 138 | 
            +
                                case flag
         | 
| 139 | 
            +
                                when :build, :up
         | 
| 140 | 
            +
                                  format_desc action, flag, 'opts*,service*'
         | 
| 141 | 
            +
                                  task flag do |_, args|
         | 
| 142 | 
            +
                                    composex flag, args.to_a
         | 
| 143 | 
            +
                                  end
         | 
| 144 | 
            +
                                when :exec, :run
         | 
| 145 | 
            +
                                  format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
         | 
| 146 | 
            +
                                  task flag, [:service] do |_, args|
         | 
| 147 | 
            +
                                    service = param_guard(action, flag, args: args, key: :service)
         | 
| 148 | 
            +
                                    composex(flag, args.to_a.drop(1), service: service)
         | 
| 149 | 
            +
                                  end
         | 
| 150 | 
            +
                                end
         | 
| 151 | 
            +
                              when 'container'
         | 
| 152 | 
            +
                                case flag
         | 
| 153 | 
            +
                                when :exec, :commit
         | 
| 154 | 
            +
                                  format_desc(action, flag, 'id/name,opts*', after: flag == :exec ? 'args+' : 'tag?')
         | 
| 155 | 
            +
                                  task flag, [:id] do |_, args|
         | 
| 156 | 
            +
                                    id = param_guard(action, flag, args: args, key: :id)
         | 
| 157 | 
            +
                                    container(flag, args.to_a.drop(1), id: id)
         | 
| 158 | 
            +
                                  end
         | 
| 159 | 
            +
                                when :run
         | 
| 160 | 
            +
                                  format_desc action, flag, 'image,opts*,args*'
         | 
| 161 | 
            +
                                  task flag, [:image] do |_, args|
         | 
| 162 | 
            +
                                    image = param_guard(action, flag, args: args, key: :image)
         | 
| 163 | 
            +
                                    container(flag, args.to_a.drop(1), id: image)
         | 
| 164 | 
            +
                                  end
         | 
| 165 | 
            +
                                else
         | 
| 166 | 
            +
                                  format_desc(action, flag, 'opts*', after: "id/name#{flag == :update ? '+' : '*'}")
         | 
| 167 | 
            +
                                  task flag do |_, args|
         | 
| 168 | 
            +
                                    container flag, args.to_a
         | 
| 169 | 
            +
                                  end
         | 
| 170 | 
            +
                                end
         | 
| 171 | 
            +
                              when 'image'
         | 
| 172 | 
            +
                                format_desc(action, flag, flag == :rm ? 'id*,opts*' : 'opts*,args*')
         | 
| 173 | 
            +
                                task flag do |_, args|
         | 
| 174 | 
            +
                                  image flag, args.to_a
         | 
| 175 | 
            +
                                end
         | 
| 176 | 
            +
                              end
         | 
| 177 | 
            +
                            end
         | 
| 178 | 
            +
                          end
         | 
| 179 | 
            +
                        end
         | 
| 180 | 
            +
                      end
         | 
| 181 | 
            +
                    end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                    def clean(*, sync: invoked_sync?('clean'), **)
         | 
| 184 | 
            +
                      return super unless @clean.nil?
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                      image(:rm, sync: sync)
         | 
| 187 | 
            +
                    end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    def compose(opts, flags = nil, script: false, args: nil, from: :build, **)
         | 
| 190 | 
            +
                      return opts if script == false
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                      ret = docker_session
         | 
| 193 | 
            +
                      if from == :build
         | 
| 194 | 
            +
                        case (n = filetype)
         | 
| 195 | 
            +
                        when 1, 2
         | 
| 196 | 
            +
                          ret << 'buildx' << 'bake'
         | 
| 197 | 
            +
                          append_file n
         | 
| 198 | 
            +
                          from = :bake
         | 
| 199 | 
            +
                        when 3, 4
         | 
| 200 | 
            +
                          ret << 'compose' << 'build'
         | 
| 201 | 
            +
                          append_file n
         | 
| 202 | 
            +
                          from = :compose
         | 
| 203 | 
            +
                        else
         | 
| 204 | 
            +
                          ret << 'build'
         | 
| 205 | 
            +
                        end
         | 
| 206 | 
            +
                      else
         | 
| 207 | 
            +
                        ret << from
         | 
| 208 | 
            +
                      end
         | 
| 209 | 
            +
                      case opts
         | 
| 210 | 
            +
                      when String
         | 
| 211 | 
            +
                        ret << opts
         | 
| 212 | 
            +
                      when Hash
         | 
| 213 | 
            +
                        ret.merge(append_hash(opts, build: true))
         | 
| 214 | 
            +
                      when Enumerable
         | 
| 215 | 
            +
                        ret.merge(opts.to_a)
         | 
| 216 | 
            +
                      end
         | 
| 217 | 
            +
                      [args, flags].each_with_index do |target, index|
         | 
| 218 | 
            +
                        next unless target
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                        target = append_any(target, target: []) unless target.is_a?(Array)
         | 
| 221 | 
            +
                        ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
         | 
| 222 | 
            +
                      end
         | 
| 223 | 
            +
                      case from
         | 
| 224 | 
            +
                      when :build
         | 
| 225 | 
            +
                        case @secrets
         | 
| 226 | 
            +
                        when String
         | 
| 227 | 
            +
                          quote_option('secret', @secrets, double: true)
         | 
| 228 | 
            +
                        when Hash
         | 
| 229 | 
            +
                          append = lambda do |type|
         | 
| 230 | 
            +
                            as_a(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
         | 
| 231 | 
            +
                          end
         | 
| 232 | 
            +
                          append.(:file)
         | 
| 233 | 
            +
                          append.(:env)
         | 
| 234 | 
            +
                        else
         | 
| 235 | 
            +
                          as_a(@secrets).each { |arg| ret << quote_option('secret', arg) }
         | 
| 236 | 
            +
                        end
         | 
| 237 | 
            +
                        if (val = option('tag', ignore: false))
         | 
| 238 | 
            +
                          ret << quote_option('tag', val)
         | 
| 239 | 
            +
                        elsif !session_arg?('t', 'tag')
         | 
| 240 | 
            +
                          ret << quote_option('tag', tag)
         | 
| 241 | 
            +
                        end
         | 
| 242 | 
            +
                        append_context
         | 
| 243 | 
            +
                      when :bake, :compose
         | 
| 244 | 
            +
                        if (val = option(from == :bake ? 'target' : 'service', ignore: false))
         | 
| 245 | 
            +
                          ret.merge(split_escape(val).map { |s| shell_escape(s) })
         | 
| 246 | 
            +
                        end
         | 
| 247 | 
            +
                      end
         | 
| 248 | 
            +
                      ret
         | 
| 249 | 
            +
                    end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                    def buildx(flag, opts = [], tag: nil, context: nil)
         | 
| 252 | 
            +
                      cmd, opts = docker_session('buildx', opts: opts)
         | 
| 253 | 
            +
                      opts = option_sanitize(opts, OPT_DOCKER[:buildx][:common]).first
         | 
| 254 | 
            +
                      cmd << flag
         | 
| 255 | 
            +
                      out = option_sanitize(opts, OPT_DOCKER[:buildx][flag] + OPT_DOCKER[:buildx][:shared]).first
         | 
| 256 | 
            +
                      case flag
         | 
| 257 | 
            +
                      when :build
         | 
| 258 | 
            +
                        cmd.merge(as_a(tag).map { |val| quote_option('tag', val) })
         | 
| 259 | 
            +
                        append_context context
         | 
| 260 | 
            +
                      when :bake
         | 
| 261 | 
            +
                        unless out.empty?
         | 
| 262 | 
            +
                          args = out.dup
         | 
| 263 | 
            +
                          out.clear
         | 
| 264 | 
            +
                          if Dir.exist?(args.last)
         | 
| 265 | 
            +
                            if projectpath?(val = args.pop)
         | 
| 266 | 
            +
                              context = val
         | 
| 267 | 
            +
                            else
         | 
| 268 | 
            +
                              out << val
         | 
| 269 | 
            +
                            end
         | 
| 270 | 
            +
                          end
         | 
| 271 | 
            +
                          append_value(args, escape: true)
         | 
| 272 | 
            +
                          contextdir(context) if context
         | 
| 273 | 
            +
                        end
         | 
| 274 | 
            +
                      end
         | 
| 275 | 
            +
                      option_clear out
         | 
| 276 | 
            +
                      run(from: :"buildx:#{flag}")
         | 
| 277 | 
            +
                    end
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                    def composex(flag, opts = [], service: nil)
         | 
| 280 | 
            +
                      cmd, opts = docker_session('compose', opts: opts)
         | 
| 281 | 
            +
                      opts = option_sanitize(opts, OPT_DOCKER[:compose][:common]).first
         | 
| 282 | 
            +
                      append_file filetype unless session_arg?('f', 'file')
         | 
| 283 | 
            +
                      cmd << flag
         | 
| 284 | 
            +
                      out = option_sanitize(opts, OPT_DOCKER[:compose][flag] + OPT_DOCKER[:common]).first
         | 
| 285 | 
            +
                      from = :"compose:#{flag}"
         | 
| 286 | 
            +
                      case flag
         | 
| 287 | 
            +
                      when :build, :up
         | 
| 288 | 
            +
                        append_value(out, escape: true)
         | 
| 289 | 
            +
                      when :exec, :run
         | 
| 290 | 
            +
                        append_command(flag, service, out, from: from)
         | 
| 291 | 
            +
                      end
         | 
| 292 | 
            +
                      run(from: from)
         | 
| 293 | 
            +
                    end
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                    def container(flag, opts = [], id: nil)
         | 
| 296 | 
            +
                      cmd, opts = docker_session('container', flag, opts: opts)
         | 
| 297 | 
            +
                      list = OPT_DOCKER[:container].fetch(flag, [])
         | 
| 298 | 
            +
                      list += OPT_DOCKER[:container][:update] if flag == :run
         | 
| 299 | 
            +
                      out = option_sanitize(opts, list, first: flag == :exec).first
         | 
| 300 | 
            +
                      from = :"container:#{flag}"
         | 
| 301 | 
            +
                      case flag
         | 
| 302 | 
            +
                      when :exec, :run
         | 
| 303 | 
            +
                        append_command(flag, id.to_s.empty? ? tag : id, out, target: cmd, from: from)
         | 
| 304 | 
            +
                      when :update
         | 
| 305 | 
            +
                        raise_error('missing container', hint: from) if out.empty?
         | 
| 306 | 
            +
                        append_value(out, escape: true)
         | 
| 307 | 
            +
                      when :commit
         | 
| 308 | 
            +
                        latest = out.shift || tag
         | 
| 309 | 
            +
                        cmd << id << latest
         | 
| 310 | 
            +
                        raise_error("unknown args: #{out.join(', ')}", hint: from) unless out.empty?
         | 
| 311 | 
            +
                        return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
         | 
| 312 | 
            +
             | 
| 313 | 
            +
                        registry = option('registry') || @registry
         | 
| 314 | 
            +
                        run(from: from, exception: registry.nil? ? exception : true)
         | 
| 315 | 
            +
                        return unless registry
         | 
| 316 | 
            +
             | 
| 317 | 
            +
                        opts = []
         | 
| 318 | 
            +
                        append_option('platform', target: opts, equals: true)
         | 
| 319 | 
            +
                        case option('disable-content-trust', ignore: false)
         | 
| 320 | 
            +
                        when 'true', '1'
         | 
| 321 | 
            +
                          opts << 'disable-content-trust'
         | 
| 322 | 
            +
                        when 'false', '0'
         | 
| 323 | 
            +
                          opts << 'disable-content-trust=false'
         | 
| 324 | 
            +
                        end
         | 
| 325 | 
            +
                        opts << '--quiet' unless verbose
         | 
| 326 | 
            +
                        return image(:push, opts, id: latest, registry: registry)
         | 
| 327 | 
            +
                      else
         | 
| 328 | 
            +
                        if out.empty?
         | 
| 329 | 
            +
                          ps = docker_output 'ps', '-a'
         | 
| 330 | 
            +
                          status = []
         | 
| 331 | 
            +
                          no = true
         | 
| 332 | 
            +
                          case flag
         | 
| 333 | 
            +
                          when :inspect, :diff
         | 
| 334 | 
            +
                            no = false
         | 
| 335 | 
            +
                          when :start
         | 
| 336 | 
            +
                            status = %w[created exited]
         | 
| 337 | 
            +
                            no = false
         | 
| 338 | 
            +
                          when :stop, :pause
         | 
| 339 | 
            +
                            status = %w[running restarting]
         | 
| 340 | 
            +
                          when :restart
         | 
| 341 | 
            +
                            status = %w[running paused exited]
         | 
| 342 | 
            +
                          when :unpause
         | 
| 343 | 
            +
                            status << 'paused'
         | 
| 344 | 
            +
                            no = false
         | 
| 345 | 
            +
                          when :top, :stats
         | 
| 346 | 
            +
                            status << 'running'
         | 
| 347 | 
            +
                            cmd << '--no-stream' if flag == :stats
         | 
| 348 | 
            +
                            no = false
         | 
| 349 | 
            +
                          when :kill
         | 
| 350 | 
            +
                            status = %w[running restarting paused]
         | 
| 351 | 
            +
                          when :rm
         | 
| 352 | 
            +
                            status = %w[created exited dead]
         | 
| 353 | 
            +
                          end
         | 
| 354 | 
            +
                          ps.merge(status.map { |s| "--filter=\"status=#{s}\"" })
         | 
| 355 | 
            +
                          list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |id|
         | 
| 356 | 
            +
                            run(cmd.temp(id), from: from)
         | 
| 357 | 
            +
                          end
         | 
| 358 | 
            +
                          return
         | 
| 359 | 
            +
                        else
         | 
| 360 | 
            +
                          append_value(out, escape: true)
         | 
| 361 | 
            +
                        end
         | 
| 362 | 
            +
                      end
         | 
| 363 | 
            +
                      run(from: from)
         | 
| 364 | 
            +
                    end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    def image(flag, opts = [], sync: true, id: nil, registry: nil)
         | 
| 367 | 
            +
                      cmd, opts = docker_session('image', flag == :exec ? :list : flag, opts: opts)
         | 
| 368 | 
            +
                      out = option_sanitize(opts, OPT_DOCKER[:image][flag]).first
         | 
| 369 | 
            +
                      from = :"image:#{flag}"
         | 
| 370 | 
            +
                      case flag
         | 
| 371 | 
            +
                      when :list
         | 
| 372 | 
            +
                        if opts.size == out.size
         | 
| 373 | 
            +
                          index = 0
         | 
| 374 | 
            +
                          name = nil
         | 
| 375 | 
            +
                          opts.each do |opt|
         | 
| 376 | 
            +
                            if (name = opt[/^name=["']?(.+)["']?$/, 1])
         | 
| 377 | 
            +
                              opts.delete(opt)
         | 
| 378 | 
            +
                              break
         | 
| 379 | 
            +
                            end
         | 
| 380 | 
            +
                          end
         | 
| 381 | 
            +
                          flag = :run if flag == :list
         | 
| 382 | 
            +
                          list_image(flag, cmd << '-a', from: from) do |val|
         | 
| 383 | 
            +
                            container(flag, if name
         | 
| 384 | 
            +
                                              opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
         | 
| 385 | 
            +
                                            else
         | 
| 386 | 
            +
                                              opts
         | 
| 387 | 
            +
                                            end, id: val)
         | 
| 388 | 
            +
                            index += 1
         | 
| 389 | 
            +
                          end
         | 
| 390 | 
            +
                          return
         | 
| 391 | 
            +
                        else
         | 
| 392 | 
            +
                          option_clear out
         | 
| 393 | 
            +
                        end
         | 
| 394 | 
            +
                      when :rm
         | 
| 395 | 
            +
                        if id
         | 
| 396 | 
            +
                          cmd << id
         | 
| 397 | 
            +
                        elsif !out.empty?
         | 
| 398 | 
            +
                          out.each { |val| run(cmd.temp(val), sync: sync, from: from) }
         | 
| 399 | 
            +
                          return
         | 
| 400 | 
            +
                        else
         | 
| 401 | 
            +
                          list_image(flag, docker_output('image', 'ls -a'), from: from) do |val|
         | 
| 402 | 
            +
                            image(:rm, opts, sync: sync, id: val)
         | 
| 403 | 
            +
                          end
         | 
| 404 | 
            +
                          return
         | 
| 405 | 
            +
                        end
         | 
| 406 | 
            +
                      when :push
         | 
| 407 | 
            +
                        id ||= tag
         | 
| 408 | 
            +
                        raise_error(id ? "unknown args: #{out.join(', ')}" : 'no id/tag given', hint: from) unless id && out.empty?
         | 
| 409 | 
            +
                        reg = (registry || @registry).chomp('/')
         | 
| 410 | 
            +
                        uri = shell_quote("#{reg}/#{id}")
         | 
| 411 | 
            +
                        cmd << uri
         | 
| 412 | 
            +
                        img = docker_output 'image', 'tag', id, uri
         | 
| 413 | 
            +
                        return unless confirm_command(img.to_s, cmd.to_s, target: id, as: reg, title: from)
         | 
| 414 | 
            +
             | 
| 415 | 
            +
                        run(img, exception: true, sync: false, banner: false)
         | 
| 416 | 
            +
                      end
         | 
| 417 | 
            +
                      run(sync: sync, from: from)
         | 
| 418 | 
            +
                    end
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                    def build?
         | 
| 421 | 
            +
                      @output[0] != false && dockerfile.exist?
         | 
| 422 | 
            +
                    end
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                    def clean?
         | 
| 425 | 
            +
                      super || dockerfile.exist?
         | 
| 426 | 
            +
                    end
         | 
| 427 | 
            +
             | 
| 428 | 
            +
                    def dockerfile(val = nil)
         | 
| 429 | 
            +
                      if val == 'Dockerfile'
         | 
| 430 | 
            +
                        @file = false
         | 
| 431 | 
            +
                      elsif val
         | 
| 432 | 
            +
                        @file = if val.is_a?(Array)
         | 
| 433 | 
            +
                                  val = val.select { |file| basepath(file).exist? }
         | 
| 434 | 
            +
                                  val.size > 1 ? val : val.first
         | 
| 435 | 
            +
                                else
         | 
| 436 | 
            +
                                  val || DIR_DOCKER.find { |file| basepath(file).exist? }
         | 
| 437 | 
            +
                                end
         | 
| 438 | 
            +
                        @file ||= false
         | 
| 439 | 
            +
                      end
         | 
| 440 | 
            +
                      basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
         | 
| 441 | 
            +
                    end
         | 
| 442 | 
            +
             | 
| 443 | 
            +
                    private
         | 
| 444 | 
            +
             | 
| 445 | 
            +
                    def docker_session(*cmd, opts: nil)
         | 
| 446 | 
            +
                      return session('docker', *cmd) unless opts
         | 
| 447 | 
            +
             | 
| 448 | 
            +
                      ret = session 'docker'
         | 
| 449 | 
            +
                      opts = option_sanitize(opts, OPT_DOCKER[:common]).first
         | 
| 450 | 
            +
                      [ret.merge(cmd), opts]
         | 
| 451 | 
            +
                    end
         | 
| 452 | 
            +
             | 
| 453 | 
            +
                    def docker_output(*cmd, **kwargs)
         | 
| 454 | 
            +
                      session('docker', *cmd, main: false, options: false, **kwargs)
         | 
| 455 | 
            +
                    end
         | 
| 456 | 
            +
             | 
| 457 | 
            +
                    def append_command(flag, val, list, target: @session, from: nil)
         | 
| 458 | 
            +
                      raise_error('no command args', hint: from) if flag == :exec && list.empty?
         | 
| 459 | 
            +
                      target << val << list.shift
         | 
| 460 | 
            +
                      target << shell_quote(list.join(' && '), double: true, option: false) unless list.empty?
         | 
| 461 | 
            +
                    end
         | 
| 462 | 
            +
             | 
| 463 | 
            +
                    def append_file(type, target: @session)
         | 
| 464 | 
            +
                      return unless type == 2 || type == 4 || @file.is_a?(Array)
         | 
| 465 | 
            +
             | 
| 466 | 
            +
                      target.merge(as_a(@file).map { |val| quote_option('file', basepath(val)) })
         | 
| 467 | 
            +
                    end
         | 
| 468 | 
            +
             | 
| 469 | 
            +
                    def append_context(ctx = nil, target: @session)
         | 
| 470 | 
            +
                      if @file.is_a?(String) && !session_arg?('f', 'file') && !bake?(dockerfile) && !compose?(dockerfile)
         | 
| 471 | 
            +
                        target << quote_option('file', dockerfile)
         | 
| 472 | 
            +
                      end
         | 
| 473 | 
            +
                      target << contextdir(ctx || context)
         | 
| 474 | 
            +
                    end
         | 
| 475 | 
            +
             | 
| 476 | 
            +
                    def list_image(flag, cmd, hint: nil, from: nil, no: true, &blk)
         | 
| 477 | 
            +
                      pwd_set do
         | 
| 478 | 
            +
                        found = false
         | 
| 479 | 
            +
                        IO.popen(session_done(cmd << '--format=json')).each_with_index do |line, index|
         | 
| 480 | 
            +
                          data = JSON.parse(line)
         | 
| 481 | 
            +
                          rt = [data['Repository'], data['Tag']].reject { |val| val == '<none>' }.join(':')
         | 
| 482 | 
            +
                          rt = nil if tag.empty?
         | 
| 483 | 
            +
                          aa = if data['Names']
         | 
| 484 | 
            +
                                 as_a(data['Names']).join(', ')
         | 
| 485 | 
            +
                               elsif tag
         | 
| 486 | 
            +
                                 dd = true
         | 
| 487 | 
            +
                                 data['Repository']
         | 
| 488 | 
            +
                               else
         | 
| 489 | 
            +
                                 data['ID']
         | 
| 490 | 
            +
                               end
         | 
| 491 | 
            +
                          bb = index.succ.to_s
         | 
| 492 | 
            +
                          cc = bb.size + 1
         | 
| 493 | 
            +
                          a = sub_style(data['Image'] || rt || aa, styles: theme[:inline])
         | 
| 494 | 
            +
                          b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a} (#{data['ID']})"
         | 
| 495 | 
            +
                          c, d = no ? ['y/N', 'N'] : ['Y/n', 'Y']
         | 
| 496 | 
            +
                          e = time_format(time_offset(data['CreatedAt']), pass: ['ms'])
         | 
| 497 | 
            +
                          f = sub_style(ARG[:BORDER][0], styles: theme[:inline])
         | 
| 498 | 
            +
                          g = ' ' * (cc + 1)
         | 
| 499 | 
            +
                          h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
         | 
| 500 | 
            +
                          puts unless index == 0
         | 
| 501 | 
            +
                          puts "#{h + sub_style(aa, styles: theme[:subject])} (created #{e} ago)"
         | 
| 502 | 
            +
                          %w[Tag Status Ports Size].each do |key|
         | 
| 503 | 
            +
                            next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
         | 
| 504 | 
            +
             | 
| 505 | 
            +
                            puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
         | 
| 506 | 
            +
                          end
         | 
| 507 | 
            +
                          w = 9 + flag.to_s.size + 4 + aa.size
         | 
| 508 | 
            +
                          puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
         | 
| 509 | 
            +
                          found = true
         | 
| 510 | 
            +
                          next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
         | 
| 511 | 
            +
             | 
| 512 | 
            +
                          puts if @@print_order == 0
         | 
| 513 | 
            +
                          blk.call data['ID']
         | 
| 514 | 
            +
                        end
         | 
| 515 | 
            +
                        puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) unless found
         | 
| 516 | 
            +
                      end
         | 
| 517 | 
            +
                    rescue StandardError => e
         | 
| 518 | 
            +
                      log.error e
         | 
| 519 | 
            +
                      ret = on(:error, from, e)
         | 
| 520 | 
            +
                      raise if exception && ret != true
         | 
| 521 | 
            +
             | 
| 522 | 
            +
                      warn log_message(Logger::WARN, e, pass: true) if warning?
         | 
| 523 | 
            +
                    end
         | 
| 524 | 
            +
             | 
| 525 | 
            +
                    def confirm_command(*args, title: nil, target: nil, as: nil)
         | 
| 526 | 
            +
                      return false unless title && target
         | 
| 527 | 
            +
             | 
| 528 | 
            +
                      puts unless @@print_order == 0
         | 
| 529 | 
            +
                      t = title.to_s.split(':')
         | 
| 530 | 
            +
                      emphasize(args, title: message(t.first.upcase, *t.drop(1)), border: borderstyle, sub: [
         | 
| 531 | 
            +
                        { pat: /\A(\w+(?: => \w+)+)(.*)\z/, styles: theme[:header] },
         | 
| 532 | 
            +
                        { pat: /\A(.+)\z/, styles: theme[:caution] }
         | 
| 533 | 
            +
                      ])
         | 
| 534 | 
            +
                      @@print_order += 1
         | 
| 535 | 
            +
                      a = t.last.capitalize
         | 
| 536 | 
            +
                      b = sub_style(target, styles: theme[:subject])
         | 
| 537 | 
            +
                      c = as && sub_style(as, styles: theme[:inline])
         | 
| 538 | 
            +
                      confirm("#{a} #{b}#{c ? " as #{c}" : ''}? [y/N] ", 'N', timeout: 60)
         | 
| 539 | 
            +
                    end
         | 
| 540 | 
            +
             | 
| 541 | 
            +
                    def filetype(val = dockerfile)
         | 
| 542 | 
            +
                      case File.extname(val)
         | 
| 543 | 
            +
                      when '.hcl', '.json'
         | 
| 544 | 
            +
                        bake?(val) ? 1 : 2
         | 
| 545 | 
            +
                      when '.yml', '.yaml'
         | 
| 546 | 
            +
                        if compose?(val)
         | 
| 547 | 
            +
                          path.children.any? { |file| bake?(file) } ? 1 : 3
         | 
| 548 | 
            +
                        else
         | 
| 549 | 
            +
                          4
         | 
| 550 | 
            +
                        end
         | 
| 551 | 
            +
                      else
         | 
| 552 | 
            +
                        0
         | 
| 553 | 
            +
                      end
         | 
| 554 | 
            +
                    end
         | 
| 555 | 
            +
             | 
| 556 | 
            +
                    def contextdir(val = nil)
         | 
| 557 | 
            +
                      val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
         | 
| 558 | 
            +
                    end
         | 
| 559 | 
            +
             | 
| 560 | 
            +
                    def compose?(file)
         | 
| 561 | 
            +
                      COMPOSEFILE.include?(File.basename(file))
         | 
| 562 | 
            +
                    end
         | 
| 563 | 
            +
             | 
| 564 | 
            +
                    def bake?(file)
         | 
| 565 | 
            +
                      BAKEFILE.include?(File.basename(file))
         | 
| 566 | 
            +
                    end
         | 
| 567 | 
            +
                  end
         | 
| 568 | 
            +
             | 
| 569 | 
            +
                  Application.implement Docker
         | 
| 570 | 
            +
                end
         | 
| 571 | 
            +
              end
         | 
| 572 | 
            +
            end
         |