I want to catch 2 similar route in one action block. In Rails5 I can do that easily. I first declare this:
get ':folder/:file' => 'get#index', :file => /.*/, :folder => /.*/
get ':file' => 'get#index', :file => /.*/
This allows me to catch :folder as much as folder can be like a/b/c/d... and :file at end one last filename. Second one also allow me to only catch filenames. And both routes target to same action.
However, In Grape because it is declared as blocks rather than route to method definitions, I have to write same block twice...
Is there any way to catch both /as/many/folder/and/file.ext and just /file.ext in one route parameter? I tried optional params, requirements. None of them worked.
The reason behind I use :folder/:file (twice regexp) is i can grab :folder param and :file param separately without manually splitting them.
get ':folder/:file', requirements: { file: /.*/, folder: /.*/ } do
# params[:folder] and params[:file]
end
get ':file', requirements: { file: /.*/ } do
# params[:file]. [:folder is empty.]
end
^^ I want to make them one single route. If folder exists (nested) then it will grab in folder param otherwise folder will be nil.
Ok. I've found the answer by trying and looking to refdocs.
get '(:folder/):file', requirements: { folder: /.*/, file: /.*/ } do
This works as expected.
Example:
desc 'Create transaction'
params do
requires :id, type: String
requires :from_, type: String
end
post ['/:id/addresses/:from_/transactions', '/:id/transactions'] do
end
Routes:
/api/v1/wallets/:id/addresses/:from_/transactions
/api/v1/wallets/:id/transactions
Related
This may be a simple matter of mocking a resource, but...
class myclass (
String stringParam,
Integer intParam,
File fileParam
) {
# do some things
$path = fileParam['title']
$mode = fileParam['mode']
# do some more things
}
Now I want to write an rspec-puppet test for this class. How do I either create or mock a File resource and get it into the catalog that rspec-puppet uses, so that I can reference it?
The answers to this and this got me partway there, but everything I've tried has led to myClass complaining that it's being passed a string instead of a file reference.
...
let(:params) {{
:stringParam => 'Here is my string',
:intParam => 238,
:fileParam => *??????,*
}}
There isn't really much support in rspec-puppet for this, as a class test parameters list is generated from the :params assuming only strings (or nested hashes/arrays etc. containing strings) or a couple of permitted symbol values used almost literally, :undef and :default. It doesn't have a way of passing in resource references.
A workaround exists that lets you put literal content into a manifest though, by passing an object that responds to the inspect method. For example, in your spec_helper.rb:
class ResourceReference
def initialize(ref)
#ref = ref
end
def inspect
#ref
end
end
And then in your spec:
let(:params) {{
:stringParam => 'Here is my string',
:intParam => 238,
:fileParam => ResourceReference.new("File[/etc/foo]"),
}}
rspec-puppet will call the inspect method on the ResourceReference object which returns the string you've passed in. This should be placed in the manifest unchanged.
(This was originally used as a workaround for undef, which can now be passed as :undef.)
As an aside, you can set let(:pre_condition) { "..." } to add literal content to the test manifest before the generated class { ... }, but I don't think there's a way to use that here.
I'd strongly recommend filing a feature request against rspec-puppet.
Is there a way to match urls with Sinatra using question mark?
get '/:id?inspect' do
# ...
end
get '/:id?last' do
# ...
end
get '/:id' do
# ...
end
I tried escaping the question mark \?, regex etc, but none of those worked.
I don't want to retrieve the value of inspect or last. I only want to know if they were supplied in the url.
Is that possible?
You can’t directly do what you want. When describing the route, Sinatra treats ? as defining an optional parameter, and doesn’t provide a way to escape it.
In a url a ? separates the path from the query string, and Sinatra only uses the path when matching routes. The contents of the query string are parsed and available in params, and the raw string is available as request.query_string.
Your url scheme seems rather unusual, but one possibility if you want to avoid checking the query_string in the actual route could be to create a custom condition to apply to the routes, and check in there:
set(:query) { |val| condition { request.query_string == val } }
get '/:id', :query => 'inspect' do
# ...
end
get '/:id', :query => 'last' do
# ...
end
get '/:id' do
# ...
end
A standard route is not defined by query parameters and should not be. Why don't you use a if construct on the params in the get /:id route?
I also suggest that when you want to set a query parameter in the request you set it like this: /:id?inspect=true (provide a dummy value)
I have to upload multiple files as form request. I am using the Rest Client to post my request. I am able to upload single file but I am not sure how to add multiple files in a single request.
I searched/googled for such option and I am not finding any solution that solves my problem.
Below is my code.
It has variable argument (*yamlfile) which takes one or more files. I have to upload all the files together.
The issue now is , I am getting syntax error when I add the loop to extract the file within the payload.
my assumption is now to form this outside the payload and include it inside the payload block but I am not sure how to do it.
Can someone help me with that.
( I have tried net/http/post/multipart library too and I don't find much documents around it)
def uploadRest(endpoint,archive_file_path,,yaml_file_path,*yamlfile)
$arg_len=yamlfile.length
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
# Trying to add multiple file, but I get syntax error
yamlfile.each_with_index { |yaml, index|
:job_upload_yaml_file+index => File.new("#{yaml_file_path}/#{pmml}")
}
})
response=request.execute
puts response.code
end
uploadRest(endpoint,archive_file_path,yaml_file_path,*yamlfile)
#files=Array.new
yamlfile.each{ |yaml_file|
#files.push(File.new("#{yaml_file_path}/#{yaml_file}"))
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => { :multipart => true, :job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp", :job_upload_yaml_file => #files })
response=request.execute
end
I had a similar problem and was able to get this to work by passing an array of arrays as a requests.
file1 = File.new("#{yaml_file_path}/#{yaml_file1}", 'rb')
file2 = File.new("#{yaml_file_path}/#{yaml_file}", 'rb')
request_body = [["files", file1], ["files", file2]]
RestClient.post url, request_body, request_headers
There were two issues with your question code:
1) Attempt to add a symbol to an integer
2) Attempt to insert contents of yamlfile direct into the hash (because that is what yamlfile.each_with_index returns, as opposed to how it calls your block. The return value from the block is not used)
Both of these code issues read as if you have gained experience in HAML or another templating language, and are using structures/ideas that would work in that?
There are lots of possble solutions in Ruby, but a simple approach to build up the hash in parts, as opposed to generate it in one go with clever hash-returning routines embedded. Try something like this:
payload_hash = {
:multipart => true,
:job_upload_archive => File.new(archive_file_path,'rb'),
:job_upload_path => "/tmp",
}
# This does not use the return value from each_with_index, instead it relies
# on the block to make changes to the hash by adding new key/value pairs
yamlfile.each_with_index { |yaml, index|
# This concatenates two strings, and then converts the combined
# string into the symbol that you want
file_key = ("job_upload_yaml_file"+index.to_s).to_sym
payload_hash[file_key] = File.new("#{yaml_file_path}/#{yaml}")
}
request = RestClient::Request.new(
:method => :post,
:url => endpoint,
:payload => payload_hash
)
For added code cleanliness, you could make the first two parts a separate method, and call it where it currently has payload_hash.
This should get you over current syntax hurdles. However, I have made no attempt to check whether this will allow you to upload multiple files via RESTClient.
Section1:
#params = {
"FacialImage" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\sachin.jpg'), "image/jpeg"),
"Login" => UploadIO.new(File.new('C:\temp\ATDD\Test\test\login.txt'), "application/json")
}
I have a Chef recipe for a multi-node web service, each node of which needs to get the hostname and IP of the other nodes, to put it into its own local configuration.
The code is shown below. The problem is that when the node.set[][] assignments are made in the ruby_block as shown, the values are empty when the template that relies upon them is created. If I want to create that template, I have to move all of the ruby_block code outside, and have it "loose" in the recipe. Which makes it harder to do unit-testing with Chefspec and the like.
Can any Chef guru set me straight? Is it just impossible to do node.set[] like this inside of a ruby_block? And if so, why doesn't it say so in the docs?
$cm = { :name => "web", :hostname => "" , :ip_addr => "" }
$ca = { :name => "data", :hostname => "" , :ip_addr => "" }
$cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
$component_list = [$cm, $ca, $cg]
ruby_block "get host addresses" do
block do
for cmpnt in $component_list
# do REST calls to external service to get cmpnt.hostname, ip_addr
# .......
node.set[cmpnt.name]['name'] = cmpnt.name
node.set[cmpnt.name]['host'] = cmpnt.hostname
node.set[cmpnt.name]['ip'] = cmpnt.ip_addr
end
end
end
template "/etc/app/configuration/config.xml" do
source "config.xml.erb"
variables( :dataHost => node['data']['host'],
:webHost => node['web']['host'],
:gatewayHost => node['gateway']['host'] )
action :create
end
I also added
subscribes :create, "ruby_block[get host addresses]", :immediately
to the template definition to ensure that the ruby_block ran before the template was created. This didn't make a difference.
I realize this is an old post, however for future reference, I just ran across this gist which gives a nice example of node variable assignments in the Compile vs. Converge phases. To adapt the gist to your example, you'll need to add code like the following to your ruby_block:
template_r = run_context.resource_collection.find(:template => "/etc/app/configuration/config.xml")
template_r.content node['data']['host']
template_r.content node['web']['host']
template_r.content node['gateway']['host']
For Chef 11, also see Lazy Attribute Evaluation.
The problem seems to be that attribute values inside your template resource definition get evaluated before actually invoking any resources.
I.e. the file is first executed as simple Ruby, compiling the resources, and only the the resource actions gets invoked. By that time, it is too late already.
I ran into the same problem when trying to encapsulate certain attribute manipulations into a resource. It simply does not work. Should anyone know a solution to this problem, I would appreciate it very much.
EDIT:
b = ruby_block...
...
end
b.run_action(:create)
Could possibly do the trick. It invokes the resource immediately.
The simplest answer to this is to not use chef attributes and not use ruby_block to do the work of talking to the REST API. The code can also be moved to a custom resource for better reuse:
unified_mode true
provides :my_resource
action :run do
cm = { :name => "web", :hostname => "" , :ip_addr => "" }
ca = { :name => "data", :hostname => "" , :ip_addr => "" }
cg = { :name => "gateway", :hostname => "" , :ip_addr => "" }
component_list = [cm, ca, cg]
hash = {}
for cmpnt in component_list
# do REST calls to external service to get cmpnt.hostname, ip_addr
# .......
hash[cmpnt.name] = {}
hash[cmpnt.name]['name'] = cmpnt.name
hash[cmpnt.name]['host'] = cmpnt.hostname
hash[cmpnt.name]['ip'] = cmpnt.ip_addr
end
template "/etc/app/configuration/config.xml" do
source "config.xml.erb"
variables( :dataHost => hash['data']['host'],
:webHost => hash['web']['host'],
:gatewayHost => hash['gateway']['host'] )
action :create
end
end
By using unified_mode and moving into a custom resource, it also makes it easier to use a node attribute without requiring the use of lazy {} or ruby_blocks. It also still allows chef configuration (like setting up resolv.conf or other network requirements before doing the REST calls) prior to calling this code while not having to think about compile/converge two pass issues in recipe context.
There is also no reason to use a resource like ruby_block to do pure ruby processing which does not change the system under management. In this case the ruby_block is hitting a REST service purely to collect data. That does not need to be placed into a Chef resource. It isn't clear from the question if that was being done because the questioner though it was a "best practice" (in this case it is not), or if it was being done to move execution to compile time in order to allow other chef resources that aren't part of the question to fire first (in which case using a custom resource is a much better solution than using a ruby_block).
It's been a while since this question, but in case someone is still looking for it, lazy evaluate is your friend:
template '/tmp/sql_file.sql' do
source "sql_file.sql.erb"
mode 0700
variables lazy {
# Create a new instance of MySQL library
mysql_lib = Acx::MySQL.new(
'127.0.0.1', 'root', node['mysql']['service']['pass']
)
password = node['mysql']['service']['support_admin']['ct_password']
# It returns the encrypted password after evaluate it, to
# be used in template variables
{ admin_password: mysql_lib.encrypted_password(password) }
}
end
https://docs.chef.io/resource_common.html#lazy-evaluation
Check out https://github.com/ip2k/earthquake/blob/master/lib/earthquake/commands.rb#L106.
I want to basically do alias :rt :retweet but that doesn't work
command %r|^:retweet\s+(\d+)$|, :as => :retweet do |m|
target = twitter.status(m[1])
if confirm("retweet 'RT ##{target["user"]["screen_name"]}: #{target["text"]}'")
async_e { twitter.retweet(m[1]) }
end
end
command %r|^:retweet\s+(\d+)\s+(.*)$|, :as => :retweet do |m|
target = twitter.status(m[1])
text = "#{m[2]} #{config[:quotetweet] ? "QT" : "RT"} ##{target["user"] ["screen_name"]}: #{target["text"]} (#{target["id"]})"
if confirm("unofficial retweet '#{text}'")
async_e { twitter.update(text) }
end
end
How can I make :rt do the same as :retweet without just re-defining that entire block but with :rt in place of :retweet (which I've done to get it working, but it's not a good solution and I know that there has to be a better way).
The command method is defined here:
https://github.com/ip2k/earthquake/blob/master/lib/earthquake/input.rb#L44
If you look at the body, it looks like you should just omit the block to create an alias:
command %r|^:retweet\s+(\d+)$|, :as => :rt