require"retryable"unlessdefined?(Retryable)require"mixlib/archive"unlessdefined?(Mixlib::Archive)moduleBerkshelfclassCommunityRESTclass<<self# @param [String] target# file path to the tar.gz archive on disk# @param [String] destination# file path to extract the contents of the target to## @return [String]defunpack(target,destination)ifis_gzip_file(target)||is_tar_file(target)Mixlib::Archive.new(target).extract(destination)elseraiseBerkshelf::UnknownCompressionType.new(target,destination)enddestinationend# @param [String] version## @return [String]defuri_escape_version(version)version.to_s.tr(".","_")end# @param [String] uri## @return [String]defversion_from_uri(uri)File.basename(uri.to_s).tr("_",".")endprivatedefis_gzip_file(path)# You cannot write "\x1F\x8B" because the default encoding of# ruby >= 1.9.3 is UTF-8 and 8B is an invalid in UTF-8.IO.binread(path,2)==[0x1F,0x8B].pack("C*")enddefis_tar_file(path)IO.binread(path,8,257).to_s=="ustar\x0000"endendV1_API="https://supermarket.chef.io".freeze# @return [String]attr_reader:api_uri# @return [Integer]# how many retries to attempt on HTTP requestsattr_reader:retries# @return [Float]# time to wait between retriesattr_reader:retry_interval# @return [Berkshelf::RidleyCompat]attr_reader:connection# @param [String] uri (CommunityREST::V1_API)# location of community site to connect to## @option options [Integer] :retries (5)# retry requests on 5XX failures# @option options [Float] :retry_interval (0.5)# how often we should pause between retriesdefinitialize(uri=V1_API,options={})options=options.dupoptions={retries: 5,retry_interval: 0.5,ssl: Berkshelf::Config.instance.ssl}.merge(options)@api_uri=urioptions[:server_url]=uri@retries=options.delete(:retries)@retry_interval=options.delete(:retry_interval)@connection=Berkshelf::RidleyCompatJSON.new(**options)end# Download and extract target cookbook archive to the local file system,# returning its filepath.## @param [String] name# the name of the cookbook# @param [String] version# the targeted version of the cookbook## @return [String, nil]# cookbook filepath, or nil if archive does not contain a cookbookdefdownload(name,version)archive=stream(find(name,version)["file"])scratch=Dir.mktmpdirextracted=self.class.unpack(archive.path,scratch)ifFile.cookbook?(extracted)extractedelseDir.glob("#{extracted}/*").finddo|dir|File.cookbook?(dir)endendensurearchive.unlinkunlessarchive.nil?enddeffind(name,version)body=connection.get("cookbooks/#{name}/versions/#{self.class.uri_escape_version(version)}")# Artifactory responds with a 200 and blank body for unknown cookbooks.raiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")ifbody.nil?bodyrescueCookbookNotFoundraiserescueBerkshelf::APIClient::ServiceNotFoundraiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")rescueraiseCommunitySiteError.new(api_uri,"'#{name}' (#{version})")end# Returns the latest version of the cookbook and its download link.## @return [String]deflatest_version(name)body=connection.get("cookbooks/#{name}")# Artifactory responds with a 200 and blank body for unknown cookbooks.raiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")ifbody.nil?self.class.version_from_uribody["latest_version"]rescueBerkshelf::APIClient::ServiceNotFoundraiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")rescueraiseCommunitySiteError.new(api_uri,"the latest version of '#{name}'")end# @param [String] name## @return [Array]defversions(name)body=connection.get("cookbooks/#{name}")# Artifactory responds with a 200 and blank body for unknown cookbooks.raiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")ifbody.nil?body["versions"].collectdo|version_uri|self.class.version_from_uri(version_uri)endrescueBerkshelf::APIClient::ServiceNotFoundraiseCookbookNotFound.new(name,nil,"at `#{api_uri}'")rescueraiseCommunitySiteError.new(api_uri,"versions of '#{name}'")end# @param [String] name# @param [String, Semverse::Constraint] constraint## @return [String]defsatisfy(name,constraint)Semverse::Constraint.satisfy_best(constraint,versions(name)).to_srescueSemverse::NoSolutionErrornilend# Stream the response body of a remote URL to a file on the local file system## @param [String] target# a URL to stream the response body from## @return [Tempfile]defstream(target)local=Tempfile.new("community-rest-stream")local.binmodeRetryable.retryable(tries: retries,on: Berkshelf::APIClientError,sleep: retry_interval)doconnection.streaming_request(target,{},local)endensurelocal.close(false)unlesslocal.nil?endendend