chef-api 0.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89bca373e08eb29df75521fe78473fb1a566e9af
4
- data.tar.gz: 4565d8fa17cfe68bbe4d204045a5f5d114ff5f92
3
+ metadata.gz: 80b62ceb2d3f3e1b7046d2115d6b741bc983499b
4
+ data.tar.gz: ade937778aea1f66776c212918e73565ed5f1bd1
5
5
  SHA512:
6
- metadata.gz: ee5206c83877648adfda095c52d688b46fd1e4a46b7745c8e9cfdf3db8e6ed4f014d353fb91e6dc38549671ff5acaa6632ee6da1edabc0b9aba9dfd77db3c0f1
7
- data.tar.gz: 46561de5a2908f9cd2dbe6781de8c67b9b625153aad5a964b532337ea775efef99408176913e9dd6d31d7440ea7c36a74bef53b725fce019f98b412ba8924078
6
+ metadata.gz: ebb178741c859052b3762e52ec0d9da19b6b9bb9df52099319e028813e03b776e1ca8401dc9506bcd65a3be8a683e631c0acc89da3d1090a98a0e549e9d16b87
7
+ data.tar.gz: bd0cb8413f6add6ddac6c2f60d38a68b87d63e905a20eb2e92b36a77b7afd61d204de15242be4287e8e3d9717c38034b63fc664eae086f7823c9444281552422
data/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
1
  ChefAPI Changelog
2
2
  =================
3
3
 
4
+ v0.4.0 (2014-07-05)
5
+ -------------------
6
+ - Support multipart POST
4
7
 
5
8
  v0.3.0 (2014-06-18)
6
9
  -------------------
data/chef-api.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_dependency 'logify', '~> 0.1'
24
24
  spec.add_dependency 'mixlib-authentication', '~> 1.3'
25
+ spec.add_dependency 'mime-types', '~> 2.3'
25
26
  end
@@ -204,7 +204,16 @@ module ChefAPI
204
204
  if data.respond_to?(:read)
205
205
  request.body_stream = data
206
206
  elsif data.is_a?(Hash)
207
- request.form_data = data
207
+ # If any of the values in the hash are File-like, assume this is a
208
+ # multi-part post
209
+ if data.values.any? { |value| value.respond_to?(:read) }
210
+ multipart = Multipart::Body.new(data)
211
+ request.content_length = multipart.content_length
212
+ request.content_type = multipart.content_type
213
+ request.body_stream = multipart.stream
214
+ else
215
+ request.form_data = data
216
+ end
208
217
  else
209
218
  request.body = data
210
219
  end
@@ -503,4 +512,165 @@ module ChefAPI
503
512
  end
504
513
  end
505
514
  end
515
+
516
+ require 'cgi'
517
+ require 'mime/types'
518
+
519
+ module Multipart
520
+ BOUNDARY = '------ChefAPIMultipartBoundary'.freeze
521
+
522
+ class Body
523
+ def initialize(params = {})
524
+ params.each do |key, value|
525
+ if value.respond_to?(:read)
526
+ parts << FilePart.new(key, value)
527
+ else
528
+ parts << ParamPart.new(key, value)
529
+ end
530
+ end
531
+
532
+ parts << EndingPart.new
533
+ end
534
+
535
+ def stream
536
+ MultiIO.new(*parts.map(&:io))
537
+ end
538
+
539
+ def content_type
540
+ "multipart/form-data; boundary=#{BOUNDARY}"
541
+ end
542
+
543
+ def content_length
544
+ parts.map(&:size).inject(:+)
545
+ end
546
+
547
+ private
548
+
549
+ def parts
550
+ @parts ||= []
551
+ end
552
+ end
553
+
554
+ class MultiIO
555
+ def initialize(*ios)
556
+ @ios = ios
557
+ @index = 0
558
+ end
559
+
560
+ # Read from IOs in order until `length` bytes have been received.
561
+ def read(length = nil, outbuf = nil)
562
+ got_result = false
563
+ outbuf = outbuf ? outbuf.replace('') : ''
564
+
565
+ while io = current_io
566
+ if result = io.read(length)
567
+ got_result ||= !result.nil?
568
+ result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
569
+ outbuf << result
570
+ length -= result.length if length
571
+ break if length == 0
572
+ end
573
+ advance_io
574
+ end
575
+
576
+ (!got_result && length) ? nil : outbuf
577
+ end
578
+
579
+ def rewind
580
+ @ios.each { |io| io.rewind }
581
+ @index = 0
582
+ end
583
+
584
+ private
585
+
586
+ def current_io
587
+ @ios[@index]
588
+ end
589
+
590
+ def advance_io
591
+ @index += 1
592
+ end
593
+ end
594
+
595
+ #
596
+ # A generic key => value part.
597
+ #
598
+ class ParamPart
599
+ def initialize(name, value)
600
+ @part = build(name, value)
601
+ end
602
+
603
+ def io
604
+ @io ||= StringIO.new(@part)
605
+ end
606
+
607
+ def size
608
+ @part.bytesize
609
+ end
610
+
611
+ private
612
+
613
+ def build(name, value)
614
+ part = %|--#{BOUNDARY}\r\n|
615
+ part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"\r\n\r\n|
616
+ part << %|#{value}\r\n|
617
+ part
618
+ end
619
+ end
620
+
621
+ #
622
+ # A File part
623
+ #
624
+ class FilePart
625
+ def initialize(name, file)
626
+ @file = file
627
+ @head = build(name, file)
628
+ @foot = "\r\n"
629
+ end
630
+
631
+ def io
632
+ @io ||= MultiIO.new(
633
+ StringIO.new(@head),
634
+ @file,
635
+ StringIO.new(@foot)
636
+ )
637
+ end
638
+
639
+ def size
640
+ @head.bytesize + @file.size + @foot.bytesize
641
+ end
642
+
643
+ private
644
+
645
+ def build(name, file)
646
+ filename = File.basename(file.path)
647
+ mime_type = MIME::Types.type_for(filename)[0] || MIME::Types['application/octet-stream'][0]
648
+
649
+ part = %|--#{BOUNDARY}\r\n|
650
+ part << %|Content-Disposition: form-data; name="#{CGI.escape(name)}"; filename="#{filename}"\r\n|
651
+ part << %|Content-Length: #{file.size}\r\n|
652
+ part << %|Content-Type: #{mime_type.simplified}|
653
+ part << %|Content-Transfer-Encoding: binary\r\n|
654
+ part << %|\r\n|
655
+ part
656
+ end
657
+ end
658
+
659
+ #
660
+ # The end of the entire request
661
+ #
662
+ class EndingPart
663
+ def initialize
664
+ @part = "--#{BOUNDARY}--\r\n\r\n"
665
+ end
666
+
667
+ def io
668
+ @io ||= StringIO.new(@part)
669
+ end
670
+
671
+ def size
672
+ @part.bytesize
673
+ end
674
+ end
675
+ end
506
676
  end
@@ -1,3 +1,3 @@
1
1
  module ChefAPI
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Vargo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-18 00:00:00.000000000 Z
11
+ date: 2014-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logify
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mime-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
41
55
  description: A tiny Chef API client with minimal dependencies
42
56
  email:
43
57
  - sethvargo@gmail.com
@@ -138,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
152
  version: '0'
139
153
  requirements: []
140
154
  rubyforge_project:
141
- rubygems_version: 2.2.2
155
+ rubygems_version: 2.3.0
142
156
  signing_key:
143
157
  specification_version: 4
144
158
  summary: A Chef API client in Ruby