I have a Puppet custom function that returns information about a user defined in OpenStack's Keystone identity service. Usage is something along the lines of:
$tenant_id = lookup_tenant_by_name($username, $password, "mytenant")
The problem is that the credentials used in this query ($username) are supposed to be created by another resource during the Puppet run (a Keystone_user resource from puppet-keystone). As far as I can tell, the call to the lookup_tenant_by_name function is being evaluated before any resource ordering happens, because no amount of dependencies in the calling code is able to force the credentials to be created prior to this function being executed.
In general, it is possible to write custom functions -- or place them appropriately in a manifest -- such that they will not be executed by Puppet until after some specified resource has been instantiated?
Short answer: You cannot make your manifest's behavior depend on resources declared inside of it.
Long answer: Parser functions are called during the compilation phase (on the master if you use one, or the agent if you use puppet apply). In neither case can it ever run before any resource is synced, because that will happen after the compiler has done all its work (including invocation of your functions).
To query information from the agent machine, you generally want to use custom facts. Still, those will be populated before even the compiler run.
Likely the best approach in this situation is to make the manifest tolerate the absence of the information, so that anything that depends on the value that your lookup_tenant_by_name function returns will only be evaluated if that value is available. This will usually be during the second Puppet run.
if $tenant_id == "" {
notify { "cannot yet find tenant $username": }
}
else {
# your code using the tenant ID
}
Related
I am tryint to reuse the code in following documentation : https://geode.apache.org/docs/guide/11/developing/region_options/dynamic_region_creation.html
The first problem that i met is that
Cache cache = CacheFactory.getAnyInstance();
Region<String,RegionAttributes<?,?>> regionAttributesMetadataRegion = createRegionAttributesMetadataRegion(cache);
should not be executed in constructor. In case it is , the code is executed in client instance , it is failed on not server error.When this fixed i receive
[fatal 2021/02/15 16:38:24.915 EET <ServerConnection on port 40527 Thread 1> tid=81] Serialization filter is rejecting class org.restcomm.cache.geode.CreateRegionFunction
java.lang.Exception:
at org.apache.geode.internal.ObjectInputStreamFilterWrapper.lambda$createSerializationFilter$0(ObjectInputStreamFilterWrapper.java:233)
The problem is that code is getting executed on dunit MemberVM and the required class is actually the part of the package under which the test is getting executed.
So i guess i should somehow register the classes ( or may be jar ) separately to dunit MemberVM. How it can be done?
Another question is: currently the code is checking if the region exists and if not it calls the method. In both cases it also tries to create the clientRegion. The question is whether this is a correct approach?
Region<?,?> cache = instance.getRegion(name);
if(cache==null) {
Execution execution = FunctionService.onServers(instance);
ArrayList argList = new ArrayList();
argList.add(name);
Function function = new CreateRegionFunction();
execution.setArguments(argList).execute(function).getResult();
}
ClientRegionFactory<Object, Object> cf=this.instance.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY).addCacheListener(new ExtendedCacheListener());
this.cache = cf.create(name);
BR
Yulian Oifa
The first problem that i met is that
Cache cache = CacheFactory.getAnyInstance();
should not be executed in constructor. In case it is , the code is executed in client instance , it is failed on not server error.When this fixed i receive
Once the Function is registered on server side, you can execute it by ID instead of sending the object across the wire (so you won't need to instantiate the function on the client), in which case you'll also avoid the Serialization filter error. As an example, FunctionService.onServers(instance).execute(CreateRegionFunction.ID).
The problem is that code is getting executed on dunit MemberVM and the required class is actually the part of the package under which the test is getting executed. So i guess i should somehow register the classes ( or may be jar ) separately to dunit MemberVM. How it can be done?
Indeed, for security reasons Geode doesn't allow serializing / deserializing arbitrary classes. Internal Geode distributed tests use the MemberVM and set a special property (serializable-object-filter) to circumvent this problem. Here's an example of how you can achieve that within your own tests.
Another question is: currently the code is checking if the region exists and if not it calls the method. In both cases it also tries to create the clientRegion. The question is whether this is a correct approach?
If the dynamically created region is used by the client application then yes, you should create it, otherwise you won't be able to use it.
As a side note, there's a lot of internal logic implemented by Geode when creating a Region so I wouldn't advice to dynamically create regions on your own. Instead, it would be advisable to use the gfsh create region command directly, or look at how it works internally (see here) and try to re-use that.
I am trying to build a custom resource which would in turn use another of my custom resource as part of its action. The pseudo-code would look something like this
customResource A
property component_id String
action: doSomething do
component_id = 1 if component_id.nil?
node.default[component_details][component_id] = ''
customResource_b "Get me component details" do
comp_id component_id
action :get_component_details
end
Chef::log.info("See the output computed by my customResourceB")
Chef::log.info(node[component_details][component_id])
end
Thing to note:
1. The role of customResource_b is to make a PS call to a REST web service and store the JSON result in node[component_details][component_id] overriding its value. I am creating this attribute node on this resource since I know it will be used later one, hence avoiding compile time issues.
Issues I am facing:
1. When testing a simple recipe that calls this resource in chef-client, the code in the resource gets executed to the last log line and after that the call to customResource_b is made. Which is something I am not expecting to happen.
Any advice would be appreciated. I am also quite new to Chef so any design improvements are also welcome
there is no need to nest chef resources, rather use chef idompotance, guards and notification.
and as usualy, you can always use a condition to decide which cookbook\recipe to run.
Most terraform providers demand a predefined flow, Create/Read/Update/Delete/Exists
I am in a weird situation developing a provider against an API where this behavior diverges a bit.
There are two kinds of resources, Host and Scope. A host can have many scopes. Scopes are updated with configurations.
This generally fits well into the terraform flow, it has a full CRUDE flow possible - except for one instance.
When a new Host is made, it automatically has a default scope attached to it. It is always there, cannot be deleted etc.
I can't figure out how to have my provider gracefully handle this, as I would want the tf to treat it like any other resource, but it doesn't have an explicit CREATE/DELETE, only READ/UPDATE/EXISTS - but every other scope attached to the host would have CREATE/DELETE.
Importing is not an option due to density, requiring an import for every host would render the entire thing pointless.
I originally was going to attempt to split Scopes and Configurations into separate resources so one could be full-filled by the Host (the host providing the Scope ID for a configuration, and then other configurations can get their scope IDs from a scope resource)
However this approach falls apart because the API for both are the same, unless I wanted to add the abstraction of creating an empty scope then applying a configuration against it, which may not be fully supported. It would essentially be two resources controlling one resource which could lead to dramatic conflicts.
A paraphrased example of an execution I thought about implementing
resource "host" "test_integrations" {
name = "test.integrations.domain.com"
account_hash = "${local.integrationAccountHash}"
services = [40]
}
resource "configuration" "test_integrations_root_configuration" {
name = "root"
parent_host = "${host.test_integrations.id}"
account_hash = "${local.integrationAccountHash}"
scope_id = "${host.test_integrations.root_scope_id}"
hostnames = ["test.integrations.domain.com"]
}
resource "scope" "test_integrations_other" {
account_hash = "${local.integrationAccountHash}"
host_hash = "${host.test_integrations.id}"
path = "/non/root/path"
name = "Some Other URI Path"
}
resource "configuration" "test_integrations_other_configuration" {
name = "other"
parent_host = "${host.test_integrations.id}"
account_hash = "${local.integrationAccountHash}"
scope_id = "${host.test_integrations_other.id}"
}
In this example flow, a configuration and scope resource unfortunately are pointing to the same resource which I am worried would cause conflicts or confusion on who is responsible for what and dramatically confuses the create/delete lifecycle
But I can't figure out how the TF lifecycle would allow for a resource that would only UPDATE/READ/EXISTS if say a flag was given (and how state would handle that)
An alternative would be to just have a Configuration resource, but then if it was the root configuration it would need to skip create/delete as it is inherently tied to the host
Ideally I'd be able to handle this situation gracefully. I am trying to avoid including the root scope/configuration in the host definition as it would create a split in how they are written and handled.
The documentation for providers implies you can use a resource AS a schema object in a resource, but does not explain how or why. If it works the way I imagine it, it may work to create a resource that is only used to inject into the host perhaps - but I don't know if that is how it works and if it is how to accomplish it.
I believe I tentatively have found a solution after asking some folks on the gopher slack.
Using AWS Provider Default VPC as a reference, I can "clone" the resource into one with a custom Create/Delete lifecycle
Loose Example:
func defaultResourceConfiguration() *schema.Resource {
drc := resourceConfiguration()
drc.Create = resourceDefaultConfigurationCreate
drc.Delete = resourceDefaultConfigurationDelete
return drc
}
func resourceDefaultConfigurationCreate(d *schema.ResourceData, m interface{}) error {
// double check it exists and update the resource instead
return resourceConfigurationUpdate(d, m)
}
func resourceDefaultConfigurationDelete(d *schema.ResourceData, m interface{}) error {
log.Printf("[WARN] Cannot destroy Default Scope Configuration. Terraform will remove this resource from the state file, however resources may remain.")
return nil
}
This should allow me to provide an identical resource that is designed to interact with the already existing one created by its parent host.
Maybe this is really simple, and I'm just not understanding something. I want to invoke a Chef resource from within an HWRP that I wrote. In my scenario, I'd like to invoke the reboot resource. How should I go about doing so?
I have tried something like the following:
def reboot_system
wu_reboot = Chef::Resource::Reboot.new('wu_reboot', :reboot_now)
wu_reboot.run_action(:reboot_now)
end
A few things. I am not sure if I should be creating an instance of Chef::Resource::Reboot or Chef::Provider::Reboot. I also don't really understand the second argument listed above..this is supposed to be the "run_context", but I don't know what that is. Finally, I do not know how to set attributes or invoke an action.
I tried using this as a format to go by, but I haven't been able to get it to work so far. Any help understanding would be much appreciated.
EDIT:
I looked at the source code and I could just execute this:
node.run_context.request_reboot(
:delay_mins => #new_resource.delay_mins,
:reason => #new_resource.reason,
:timestamp => Time.now,
:requested_by => #new_resource.name
)
However, I don't think this is the best solution. I would like to know how to accomplish invoking the resource instead of bypassing it this way.
You can find an example of using Chef-Resources inside a HWRP in an older revision of the official Jenkins cookbook (was converted to LWRP in the meantime):
https://github.com/opscode-cookbooks/jenkins/blob/v2.0.2/libraries/plugin.rb#L138-L141
Keep in mind, that the Reboot resource is rather new (Chef 12+)
You can do it the same way you would in a recipe. If you need it to run immediately, then you would do:
reboot 'now' do
action :nothing
end.run_action(:reboot_now)
Within Ruby classes, you don't have access to the Chef DSL, so you have to access the underlying implementation of the resource as a class. The name of the class will be the camelcase-conversion of the resource name. You invoke the action with the run_action method.
Your original version actually was pretty close. You only use the resource, not the provider (because the provider may not even always be the same, depending on your platform).
The run_context is an object that chef uses to pass information to the resource - for instance, you can access node attributes through run_context.node['attributename']. It is already a member variable in your provider (and I think also in the resource object); you can simply pass it in to the constructor for your new resource.
You set attributes through member variables by the same name, and you trigger the actual action with the run_action method.
r = Chef::Resource::Reboot.new("wu_reboot", run_context)
r.reason("Because we need a reboot")
r.run_action(:reboot_now)
I have two LWRPs. The first deals with creating a disk volume, formatting it, and mounting it on a virtual machine, we'll call this resource cloud_volume. The second resource (not really important what it does) needs a UUID for the newly formatted volume which is a required attribute, we'll call this resource foobar.
The resources cloud_volume and foobar are used in a recipe something like the following.
volumes.each do |mount_point, volume|
cloud_volume "#{mount_point}" do
size volume['size']
label volume['label']
action [:create, :initialize]
end
foobar "#{mount_point}" do
disk_uuid node[:volumes][mount_point][:uuid] # This is set by cloud_volume
action [:do_stuff]
end
end
So, when I do a chef run I get a Required argument disk_identifier is missing! exception.
After doing some digging I discovered that recipes are processed in two phases, a compile phase and an execute phase. It looks like the issue is at compile time as that is the point in time that node[:volumes][mount_point][:uuid] is not set.
Unfortunately I can't use the trick that OpsCode has here as notifications are being used in the cloud_volume LWRP (so it would fall into the anti-pattern shown in the documentation)
So, after all this, my question is, is there any way to get around the requirement that the value of disk_uuid be known at compile time?
A cleaner way would be to use Lazy Attribute Evaluation. This will evaluate node[:volumes][mount_point][:uuid] during execution time instead of compile
foobar "#{mount_point}" do
disk_uuid lazy { node[:volumes][mount_point][:uuid] }
action [:do_stuff]
end
Disclaimer: this is the way to go with older Chef (<11.6.0), before they added lazy attribute evaluation.
Wrap your foobar resource into ruby_block and define foobar dynamically. This way after the compile stage you will have a ruby code in resource collection and it will be evaluated in run stage.
ruby_block "mount #{mount_point} using foobar" do
block do
res = Chef::Resource::Foobar.new( mount_point, run_context )
res.disk_uuid node[:volumes][mount_point][:uuid]
res.run_action :do_stuff
end
end
This way node[:volumes][mount_point][:uuid] will not be known at compile time, but it also will not be accessed at compile time. It will only be accessed in running stage, when it should already be set.