From 136d001830e6e07f98ac9a6e0268eb5106c161a4 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 20 Feb 2014 10:28:49 -0800 Subject: [PATCH 01/82] adding environments table to initial schema --- initial_schema.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/initial_schema.sql b/initial_schema.sql index eca6a71..f19a2e0 100644 --- a/initial_schema.sql +++ b/initial_schema.sql @@ -205,6 +205,18 @@ CREATE TABLE `role` ( PRIMARY KEY (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table structure for table `environments` +-- + +DROP TABLE IF EXISTS `environments`; +CREATE TABLE `environments` ( + `name` varchar(75) NOT NULL, + `note` varchar(255) DEFAULT NULL, + `environment_name` varchar(100) DEFAULT NULL, + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- -- Table structure for table `service_instance` -- From 51c3a53618879d21407bb5fc110abc21b995b6c6 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 25 Feb 2014 18:31:47 -0800 Subject: [PATCH 02/82] fixing GET to use same table as doEnvironmentsPOST --- cmdb_api.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 7d08d01..75fc68e 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1585,8 +1585,7 @@ sub doEnvironmentsServicesGET() { $environment = $requestObject->{'path'}[0]; $service = $requestObject->{'path'}[2]; - my $sql = "select name, environment_name from service_instance " . - "where type='environment'"; + my $sql = "select name, environment_name from environments"; my $rtn = &doSql($sql, undef); if ($$rtn{'err'}) { From e5288496813e06e55486291cd42fc072b88bbb56 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 11 Mar 2014 12:45:46 -0700 Subject: [PATCH 03/82] fixes for using richer environments//services for service_instance API --- cmdb_api.pm | 53 ++++++++++---------------------------------------- pp_lexicon.xml | 37 ++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 75fc68e..1620cd2 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -101,7 +101,8 @@ my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', instance_size=>'Generic', instance_location=>'Generic', column_lkup=>'Column_lkup', - environments=>'Environments' + environments=>'Environments', + environmentservice=>'Environments' } }); @@ -148,45 +149,6 @@ our $dbh; my $valid_entities = $opt->{'entities'}; -# my $valid_entities={ -# acl=>'Acl', -# vip=>'Generic', -# datacenter_subnet=>'Generic', -# role=>'Generic', -# pod_cluster=>'Generic', -# snat=>'Generic', -# pool=>'Generic', -# cluster=>'Generic', -# hardware_model=>'Generic', -# cluster_mta=>'Generic', -# system=>'System', -# device=>'System', -# blade_chassis=>'System', -# console_server=>'System', -# firewall=>'System', -# load_balancer=>'System', -# network_switch=>'System', -# power_strip=>'System', -# router=>'System', -# storage_head=>'System', -# storage_shelf=>'System', -# device_ip=>'Generic', -# newhostname=>'Provision', -# pcmsystemname=>'ProvisionPcm', -# user=>'Generic', -# currentUser=>'User', -# inv_audit=>'Generic', -# audit=>'Audit', -# inv_normalizer=>'Generic', -# fact=>'TrafficControl', -# change_queue=>'ChangeQueue', -# ip=>'Generic', -# service_instance=>'Generic', -# service_instance_data=>'Generic', -# instance_size=>'Generic', -# instance_location=>'Generic' -# }; - my $versions=[ 'v1' ]; $parser= XML::Simple->new( ); @@ -1620,8 +1582,8 @@ sub doEnvironmentsServicesGET() { %hash = (); my $list = join(', ', map { "'$_'" } @parents); - $sql = "select name, environment_name, s.svc_id, type, data_key, data_value from " . - " (select name, environment_name, svc_id, type from service_instance " . + $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . + " (select name, environment_name, note, svc_id, type from service_instance " . " where type != 'environment' "; if (defined $service) { @@ -1652,6 +1614,7 @@ sub doEnvironmentsServicesGET() { environment_name => $data->{'environment_name'}, type => $data->{'type'}, svc_id => $data->{'svc_id'}, + note => $data->{'note'}, }; } @@ -1778,9 +1741,11 @@ sub doEnvironmentsServicesPUT(){ # Determine if we need to modify any fields of the service instance record + $logger->info('found data for service_instance: ' . make_json($data)); + for my $f (&getFieldList('service_instance')) { my $value = $lkup_data->{$f}; - if (defined $value && $value ne $data->{$f}) { + if (defined $data->{$f} && $value ne $data->{$f}) { $service_updates{$f} = [ $value, $data->{$f} ]; } @@ -1788,6 +1753,8 @@ sub doEnvironmentsServicesPUT(){ } delete $data->{'svc_id'}; #ICK + $logger->info('found changes for service_instance: ' . join(',',keys(%service_updates))); + # Get all service_instance_data records that belong to this instance # We don't care about inheritance for this diff --git a/pp_lexicon.xml b/pp_lexicon.xml index 3b26634..decc316 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -18,13 +18,13 @@ - - + + config true string - + config @@ -345,7 +345,9 @@ - + + + @@ -404,22 +406,22 @@ - + string /.*/ - - + + fact string /.*/ - + fact @@ -555,7 +557,7 @@ - + @@ -753,7 +755,9 @@ config string - + + + @@ -870,8 +874,19 @@ - + + + + + + + + + + + + From 584f87e4c63ffd7d45c5ed40f1e0b836596d4a5b Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 14 Mar 2014 15:13:19 -0700 Subject: [PATCH 04/82] updating tests --- cmdb_test.pl | 4 ++-- cmdb_tests.csv | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 cmdb_tests.csv diff --git a/cmdb_test.pl b/cmdb_test.pl index 8fc483d..a615f66 100644 --- a/cmdb_test.pl +++ b/cmdb_test.pl @@ -32,7 +32,7 @@ if(length($FILE) == 0) { print "usage: inv_test.pl -f - -f: csv file with testcases (see inv_tests.csv) + -f: csv file with testcases (see cmdb_tests.csv) -p: parse testcase file only -r: run tests -d: debug @@ -134,7 +134,7 @@ () my $result=''; $test->{'host:port'}=~m|//(.*)|; my $hostport=$1; - $ua->credentials($hostport,'Authorized Personnel Only',$user,$pass); + $ua->credentials($hostport,'Operations Only',$user,$pass); if($test->{'method'} eq 'GET') { if($test->{'data'}) diff --git a/cmdb_tests.csv b/cmdb_tests.csv new file mode 100644 index 0000000..a1ffab1 --- /dev/null +++ b/cmdb_tests.csv @@ -0,0 +1,45 @@ +testname,notes,entity,entity_key,data,method,host:port,baseurl,user/pass,returncode,result type,result check +create system,,system,test.test.com,"{""fqdn"":""test.test.com"",""ipaddress"":""10.10.0.1"",""drac"":""10.10.10.1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/system/test.test.com +get system,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,ipaddress=10.10.0.1 +modify system,,system,test.test.com,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote +lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, +,"there is no delete function, this will always fail",system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +verify via audit ,,inv_audit,,"{""entity_key"":""test.test.com"",""field_name"":""notes"",""new_value"":""testnote""}",GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,new_value=testnote +create datacenter,,data_center,DC1,"{“"data_center_code"”:"”DC1"”,"”data_center_vendor"”:"”sample vendor"”}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/data_center/DC1 +,,,,,,,,,,, +create datacenter_subnet,,datacenter_subnet,,"{""data_center_code"":""DC1"",""subnet"":""10.10.0.0/24"",""notes"":""testsubnet"",""id"":1000}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location~/cmdb_api/v1/datacenter_subnet/1000 +edit datacenter_subnet,,datacenter_subnet,1000,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote +test dc autoassign precheck,verify there is no dc for system,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_center_code= +dc autoassign,"change system ip, and dc should get edited",system,test.test.com,"{""ipaddress"":""10.10.0.12""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_center_code=DC1 +delete datacenter_subnet,,datacenter_subnet,1000,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +test delete of not found,,datacenter_subnet,1000,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, +,,,,,,,,,,, +create service_instance,,service_instance,999,"{""svc_id"":999,""environment_name"":""production"",""name"":""testservice_record"",""type"":""test""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/service_instance/999 +edit service_instance,,service_instance,999,"{""note"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,note=testnote +,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, +create service_instance_data,,service_instance_data,9999,"{""svc_id"":999,""data_id"":9999,""data_key"":""test_attribute"",""data_value"":""testvalue""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/service_instance_data/9999 +edit service_instance_data,,service_instance_data,9999,"{""data_value"":""newdatavalue""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_value=newdatavalue +,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, +delete service_instance_data,,service_instance_data,9999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete service_instance,,service_instance,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, +inv_normalizer,,inv_normalizer,999,"{""id"":999,""field_name"":""product_name"",""matcher"":""testname"",""sub_value"":""SpecialTestProduct"",""entity_name"":""system""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/inv_normalizer/999 +do edit against normalizer,,system,test.test.com,"{""product_name"":""testname2""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,product_name=SpecialTestProduct +delete normalizer,,inv_normalizer,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, +lookup nonexistent role,,role,norole,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, +add role,,role,test::role,"{""role_id"":""test::role"",""role_name"":""Unit Test role"",""blessed"":1}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/role/test::role +test role,,role,test::role,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,role_name=Unit Test role +modify role,,role,test::role,"{""role_name"":""test role for unit testing""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,role_name=test role for unit testing +delete role,,role,test::role,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete nonexistenet role,,role,norole,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, +,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, +add acl,,acl,999,"{""acl_group"":""readonly"",""acl_id"":999,""entity"":""system"",""field"":""drac"",""logic"":""1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/acl/999 +verify acl,,acl,999,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,entity=system +make change against acl,,system,test.test.com,"{""drac"":""20.20.20.20""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,403,, +verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,,drac=10.10.10.1 +delete acl,,acl,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +,,,,,,,,,,, +delete datacenter,,data_center,DC1,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +,,,,,,,,,,, +"FINISHING: delete system NOTE: this will always fail, manually delete test.test.com entry",,system,test.test.com,,DELETE,,,,200,, From f799283d4e72e9bbad9bb9f39571644e0a88693c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 14 Mar 2014 15:13:40 -0700 Subject: [PATCH 05/82] updating fact mapping --- pp_lexicon.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pp_lexicon.xml b/pp_lexicon.xml index decc316..248aaa2 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -413,6 +413,7 @@ /.*/ + betterip,ipaddress @@ -655,7 +656,7 @@ fact string - kernel_release + kernelrelease From 83f6780ba06377896056914ec48406d547fe8747 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 14 Mar 2014 15:14:09 -0700 Subject: [PATCH 06/82] adding fact mapping and defining system address attribute in config for dc autoassign --- cmdb_api.pm | 84 ++++++++++++++++++++++++++++++--------------------- cmdb_test.csv | 1 - 2 files changed, 50 insertions(+), 35 deletions(-) delete mode 100644 cmdb_test.csv diff --git a/cmdb_api.pm b/cmdb_api.pm index 1620cd2..1e00a36 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -62,6 +62,8 @@ my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', 'prism_domain' => 'prism.ppops.net', 'logconfig' => '/var/www/cmdb_api/log4perl.conf', 'lexicon' => '/var/www/cmdb_api/pp_lexicon.xml', + 'ipaddress_attribute' => "ip_address", + "traffic_control_search_fields" => ["fqdn","macaddress","ipaddress"], 'entities' => { acl=>'Acl', vip=>'Generic', @@ -114,19 +116,12 @@ my $valid_entity_apis={ 'Audit' => 1 }; -##TODO unhardcode this crap -my @system_native_fields=qw(fqdn inventory_component_type system_type status ip_address - mac_address data_center_code cage_code rack_code rack_position manufacturer product_name - serial_number agent_reported); -# my @system_meta_fields=qw(asset_tag_number disk_drive_count file_systems physical_processor_count processors memory_size -# virtual operating_system operating_system_release primary_interface interfaces drac roles customers power_supply_count -# power_supply_watts is_virtual guest_fqdns host_fqdn); - my $DEBUG=$opt->{'debug'}; my $DBHOST=$opt->{'dbhost'}; my $DBUSER=$opt->{'dbuser'}; my $DBPASS=$opt->{'dbpass'}; my $DATABASE=$opt->{'database'}; +my $IPADDRESSFIELD=$opt->{'ipaddress_attribute'}; my $DRIVER=$opt->{'driver'}; my ($lexicon,$tree,$parser); my ($parms); @@ -142,7 +137,6 @@ unless($lexicon) } # database connection -#my $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS); our $dbh; #valid api types. these must exist and be parsable in the lexicon if they are 'Generic' # or have provided doGET/PUT/POST functions @@ -162,7 +156,7 @@ if($@) $logger->info("$lexicon is xml ok"); -my $tree_extended; +our $tree_extended; $tree_extended=&eat_json(&make_json($tree)); # loop through entities and add base attributes to things that subclass other stuff foreach(keys(%{$tree_extended->{entities}})) @@ -420,7 +414,7 @@ sub runACL() if(ref $groups ne 'ARRAYREF') { $logger->debug("groups ref= ".ref $groups) if ($logger->is_debug()); - $groups = [split(',',$groups)]; + $groups = [split(',',$groups)] if $groups; } my $acls = $dbh->selectall_arrayref("select * from acl where entity=?", { Slice => {} },($entity)); foreach my $field (keys(%$changes)) @@ -563,7 +557,7 @@ sub doTrafficControlPOST() my ($lkup_data,$lkup); $logger->debug("TC got POST from agent") if ($logger->is_debug()); - foreach (('fqdn','mac_address','serial_number','ip_address')) + foreach (@{$opt->{'traffic_control_search_fields'}}) { # skip serial if not dell tag next if( $_ eq 'serial_number' && length($data->{$_}) != 7 ); @@ -580,6 +574,31 @@ sub doTrafficControlPOST() } $requestObject->{'entity'}='system'; + # loop through the entity field and assign values based on 'fact' designation of attributes + # or default to just looking for an entry of the same name + my $data_assembled={ 'fqdn' => $data->{'fqdn'}}; + foreach my $attr (keys(%{$tree_extended->{'entities'}->{'system'}})) + { + if(ref($tree_extended->{'entities'}->{'system'}->{$attr}) eq 'HASH' && $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'}) + { + foreach my $fact_lookup (split(',',$tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'})) + { + $logger->debug("doing fact lookup for $attr with $fact_lookup"); + if($data->{$fact_lookup}) + { + $data_assembled->{$attr}= $data->{$fact_lookup}; + $logger->debug("found data lookup for $attr in fact $fact_lookup: $data->{$fact_lookup}"); + last; + } + } + } + else + { + $data_assembled->{$attr}= $data->{$attr} if($data->{$attr}); + } + } + $requestObject->{'body'}=make_json($data_assembled,{allow_nonref=>1}); + # if we found the entry, then setup for PUT else do POST if($lkup_data) { @@ -674,7 +693,7 @@ sub doProvisionGET() # gather data sent in with the call my $new={}; - foreach my $f (('rack_code','rack_position','asset_tag_number','manufacturer','product_name','serial_number','ip_address','mac_address','inventory_component_type')) + foreach my $f (('rack_code','rack_position','asset_tag_number','manufacturer','product_name','serial_number',$IPADDRESSFIELD,'mac_address','inventory_component_type')) { $$requestObject{'query'}{$f}=~s/^\s+//g; $$requestObject{'query'}{$f}=~s/\s+$//g; @@ -701,15 +720,15 @@ sub doProvisionGET() $newname=&setNewName($new, '.ppops.net'); # set the IP for this system since it's coming online from provisioning vlan # validate that the IP is on the provisioning vlan - if($$requestObject{'query'}{'ip_address'} && $$requestObject{'query'}{'ip_address'} =~ /10\.\d{1,3}\.25/) + if($$requestObject{'query'}{$IPADDRESSFIELD} && $$requestObject{'query'}{$IPADDRESSFIELD} =~ /10\.\d{1,3}\.25/) { $logger->info("updating IP for system entry"); - $$requestObject{'query'}{'ip_address'}=~s/^\s//g; - $$requestObject{'query'}{'ip_address'}=~s/\s$//g; - $sql='update device set ip_address=? where fqdn=?'; + $$requestObject{'query'}{$IPADDRESSFIELD}=~s/^\s//g; + $$requestObject{'query'}{$IPADDRESSFIELD}=~s/\s$//g; + $sql="update device set $IPADDRESSFIELD=? where fqdn=?"; my $sth=$dbh->prepare($sql); - $logger->debug("executing: $sql with $$requestObject{'query'}{'ip_address'},$newname") if ($logger->is_debug()); - my $rv=$sth->execute(($$requestObject{'query'}{'ip_address'},$newname)); + $logger->debug("executing: $sql with $$requestObject{'query'}{$IPADDRESSFIELD},$newname") if ($logger->is_debug()); + my $rv=$sth->execute(($$requestObject{'query'}{$IPADDRESSFIELD},$newname)); $logger->error($sth->err . " : " . $sth->errstr) if ($sth->err); } } @@ -1741,9 +1760,9 @@ sub doEnvironmentsServicesPUT(){ # Determine if we need to modify any fields of the service instance record - $logger->info('found data for service_instance: ' . make_json($data)); - for my $f (&getFieldList('service_instance')) { + my $fieldlist = &getFieldList('service_instance'); + for my $f (@{$fieldlist}) { my $value = $lkup_data->{$f}; if (defined $data->{$f} && $value ne $data->{$f}) { $service_updates{$f} = [ $value, $data->{$f} ]; @@ -1753,9 +1772,6 @@ sub doEnvironmentsServicesPUT(){ } delete $data->{'svc_id'}; #ICK - $logger->info('found changes for service_instance: ' . join(',',keys(%service_updates))); - - # Get all service_instance_data records that belong to this instance # We don't care about inheritance for this $sql = "select data_id,data_key,data_value " . @@ -2323,24 +2339,24 @@ sub doSystemPUT(){ } if( ( - $data->{'ip_address'} - && $data->{'ip_address'} ne $lkup_data->{'ip_address'} - + $data->{$IPADDRESSFIELD} + && $data->{$IPADDRESSFIELD} ne $lkup_data->{$IPADDRESSFIELD} ) || ( !exists($data->{'data_center_code'}) + && defined($lkup_data->{'data_center_code'}) && length($lkup_data->{'data_center_code'})==0 ) ) { - if($data->{'ip_address'}) + if($data->{$IPADDRESSFIELD}) { - $data->{'data_center_code'}=&lookupDC($data->{'ip_address'}); + $data->{'data_center_code'}=&lookupDC($data->{$IPADDRESSFIELD}); } else { - $data->{'data_center_code'}=&lookupDC($lkup_data->{'ip_address'}); + $data->{'data_center_code'}=&lookupDC($lkup_data->{$IPADDRESSFIELD}); } } @@ -2363,7 +2379,7 @@ sub doSystemPUT(){ # if the user is not a system user, then error out now if needed $logger->info("changes: " . &make_json($data)); $logger->info("blocked changes: " . &make_json($blocked_changes) ); - if($requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) + if(defined($requestObject->{'user'}->{'systemuser'}) && $requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) { $dbs->rollback; $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; @@ -2392,7 +2408,7 @@ sub doSystemPUT(){ if(exists $$data{$_}) { $set_sql.="," if $set_sql; - if( length($$data{$_})==0 ) + if( defined($$data{$_}) && length($$data{$_})==0 ) { $set_sql.=" d.$_=NULL"; #push(@$parms,undef); @@ -2531,10 +2547,10 @@ sub doSystemPOST(){ my $meta_fields=&getFieldList($$requestObject{'entity'},1); my ($sql,$set_sql,$parms,@errors,$rv); $data->{'inventory_component_type'} = 'system' unless $data->{'inventory_component_type'}; - if($data->{'ip_address'} && !$data->{'data_center_code'}) + if($data->{$IPADDRESSFIELD} && !$data->{'data_center_code'}) { - $data->{'data_center_code'}=&lookupDC($data->{'ip_address'}); + $data->{'data_center_code'}=&lookupDC($data->{$IPADDRESSFIELD}); } $dbs->begin_work; # construct insert sql for device table diff --git a/cmdb_test.csv b/cmdb_test.csv deleted file mode 100644 index 5957117..0000000 --- a/cmdb_test.csv +++ /dev/null @@ -1 +0,0 @@ -testname,notes,entity,entity_key,data,method,host:port,baseurl,user/pass,returncode,result type,result check create system,,system,test.test.com,"{""fqdn"":""test.test.com"",""ip_address"":""10.10.0.1"",""drac"":""10.10.10.1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/system/test.test.com get system,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,ip_address=10.10.0.1 modify system,,system,test.test.com,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,"there is no delete function, this will always fail",system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, verify via audit ,,inv_audit,,"{""entity_key"":""test.test.com"",""field_name"":""notes"",""new_value"":""testnote""}",GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,new_value=testnote create datacenter_subnet,,datacenter_subnet,,"{""data_center_code"":""SC4"",""subnet"":""10.10.0.0/24"",""notes"":""testsubnet"",""id"":1000}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location~/cmdb_api/v1/datacenter_subnet/1000 edit datacenter_subnet,,datacenter_subnet,1000,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote test dc autoassign precheck,verify there is no dc for system,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_center_code= dc autoassign,"change system ip, and dc should get edited",system,test.test.com,"{""ip_address"":""10.10.0.12""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_center_code=SC4 delete datacenter_subnet,,datacenter_subnet,1000,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, test delete of not found,,datacenter_subnet,1000,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,,,,,,,,,,, create service_instance,,service_instance,999,"{""svc_id"":999,""environment_name"":""production"",""name"":""testservice_record"",""type"":""test""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/service_instance/999 edit service_instance,,service_instance,999,"{""note"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,note=testnote ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, create service_instance_data,,service_instance_data,9999,"{""svc_id"":999,""data_id"":9999,""data_key"":""test_attribute"",""data_value"":""testvalue""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/service_instance_data/9999 edit service_instance_data,,service_instance_data,9999,"{""data_value"":""newdatavalue""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,data_value=newdatavalue ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, delete service_instance_data,,service_instance_data,9999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, delete service_instance,,service_instance,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, inv_normalizer,,inv_normalizer,999,"{""id"":999,""field_name"":""product_name"",""matcher"":""testname"",""sub_value"":""SpecialTestProduct"",""entity_name"":""system""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/inv_normalizer/999 do edit against normalizer,,system,test.test.com,"{""product_name"":""testname2""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,product_name=SpecialTestProduct delete normalizer,,inv_normalizer,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, lookup nonexistent role,,role,norole,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, add role,,role,test::role,"{""role_id"":""test::role"",""role_name"":""Unit Test role"",""blessed"":1}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/role/test::role test role,,role,test::role,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,role_name=Unit Test role modify role,,role,test::role,"{""role_name"":""test role for unit testing""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,role_name=test role for unit testing delete role,,role,test::role,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, delete nonexistenet role,,role,norole,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, add acl,,acl,999,"{""acl_group"":""readonly"",""acl_id"":999,""entity"":""system"",""field"":""drac"",""logic"":""1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/acl/999 verify acl,,acl,999,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,entity=system make change against acl,,system,test.test.com,"{""drac"":""20.20.20.20""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,403,, verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,,drac=10.10.10.1 delete acl,,acl,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,,,,,, "FINISHING: delete system NOTE: this will always fail, manually delete test.test.com entry",,system,test.test.com,,DELETE,,,,200,, \ No newline at end of file From 3c670949addecab690840de913074aec1f6a5377 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 18 Mar 2014 10:59:20 -0700 Subject: [PATCH 07/82] update to pass dc auto assign, and test for it correctly --- cmdb_api.pm | 2 +- cmdb_tests.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 1e00a36..7ca1487 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2345,7 +2345,7 @@ sub doSystemPUT(){ || ( !exists($data->{'data_center_code'}) - && defined($lkup_data->{'data_center_code'}) + #&& defined($lkup_data->{'data_center_code'}) && length($lkup_data->{'data_center_code'})==0 ) ) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index a1ffab1..21148cd 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -5,7 +5,7 @@ modify system,,system,test.test.com,"{""notes"":""testnote""}",PUT,http://localh lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,"there is no delete function, this will always fail",system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, verify via audit ,,inv_audit,,"{""entity_key"":""test.test.com"",""field_name"":""notes"",""new_value"":""testnote""}",GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,new_value=testnote -create datacenter,,data_center,DC1,"{“"data_center_code"”:"”DC1"”,"”data_center_vendor"”:"”sample vendor"”}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/data_center/DC1 +create datacenter,,data_center,DC1,"{""data_center_code"":""DC1"",""data_center_vendor"":""sample vendor""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/data_center/DC1 ,,,,,,,,,,, create datacenter_subnet,,datacenter_subnet,,"{""data_center_code"":""DC1"",""subnet"":""10.10.0.0/24"",""notes"":""testsubnet"",""id"":1000}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location~/cmdb_api/v1/datacenter_subnet/1000 edit datacenter_subnet,,datacenter_subnet,1000,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote From 945a3b8a35334c6e62ed34c2b8ae13f900ae2777 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 20 Mar 2014 11:26:07 -0700 Subject: [PATCH 08/82] adding wiki checkout --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c15b52a..24d8e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ META.yml MYMETA.yml nytprof.out pm_to_blib +wiki/ From 1a34f7680b6fee38afacf71fcd0df107cfe503d6 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 21 Mar 2014 10:44:07 -0700 Subject: [PATCH 09/82] making envservice PUT handle key change --- cmdb_api.pm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index 7ca1487..eea12e7 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1049,6 +1049,11 @@ sub doGenericPUT else { $dbs->commit; + # check to see if key value was chnaged during this put, and adjust for GET + if($$data{ $tree->{entities}->{$$requestObject{'entity'}}->{key} }) + { + $$requestObject{'path'}[0] = $$data{ $tree->{entities}->{$$requestObject{'entity'}}->{key} }; + } return &doGenericGET($requestObject); } @@ -1860,6 +1865,15 @@ sub doEnvironmentsServicesPUT(){ $dbh->commit; + #adjust key path, if key field value was changed + if($service_updates{'name'}) + { + $logger->debug(make_json($requestObject)); + $requestObject->{'path'}->[2] = $service_updates{'name'}[1]; + $logger->debug(make_json($requestObject)); + } + + $new_value = &doEnvironmentsServicesGET($requestObject); if ($did_update) { insertAuditEntry($dbh, $requestObject, 'services', From e184fdf1783d593660c58c6717e85a3784e937a2 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 1 Apr 2014 08:38:54 -0700 Subject: [PATCH 10/82] adding trafficcontrol search fields and address designation field config --- cmdb_api.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmdb_api.conf b/cmdb_api.conf index e25b4db..ca3530a 100644 --- a/cmdb_api.conf +++ b/cmdb_api.conf @@ -8,6 +8,8 @@ "prism_domain":"<%=prism_domain%>", "logconfig":"/var/www/cmdb_api/log4perl.conf", "lexicon":"/var/www/cmdb_api/pp_lexicon.xml", + "ipaddress_attribute":"ipaddress", + "traffic_control_search_fields" : ["fqdn","macaddress","ipaddress"], "entities":{ "acl":"Acl", "vip":"Generic", @@ -48,6 +50,7 @@ "instance_location":"Generic", "rack": "Generic", "column_lkup": "Column_lkup", - "environments":"Environments" + "environments":"Environments", + "environmentservice": "Environments" } } From 233464f8c18833aac347955948cb7f6e8f382b49 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 1 Apr 2014 08:39:58 -0700 Subject: [PATCH 11/82] removing unused entities --- pp_lexicon.xml | 305 ++++++------------------------------------------- 1 file changed, 38 insertions(+), 267 deletions(-) diff --git a/pp_lexicon.xml b/pp_lexicon.xml index 248aaa2..d60dce1 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -18,130 +18,6 @@ - - - - config - true - string - - - - config - string - fqdn - - - - config - string - false - - - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - - - config - true - string - - - - config - true - string - - - - config - true - string - - - - config - true - string - - inbound - outbound - - - - - config - true - string - - @@ -149,12 +25,12 @@ true string - + config false string - + config @@ -280,10 +156,9 @@ string - + - fact true string @@ -291,10 +166,9 @@ - - + - + config @@ -319,25 +193,17 @@ string false - + config text false - - - string - - - - string - - - + + @@ -391,7 +257,7 @@ string serial_number - + config string @@ -413,7 +279,7 @@ /.*/ - betterip,ipaddress + betterip,ipaddress,ip_address @@ -439,7 +305,7 @@ - fact + config string @@ -453,13 +319,13 @@ manufacturer - + string - product_name - - + productname + + @@ -486,82 +352,14 @@ - - - - - - - - - - string - - - - config - string - operatingsystem - - - - config - string - operatingsystemrelease - - - - - - - string - - - - config - string - operatingsystem - - - - config - string - operatingsystemrelease - - - - - - - - string - - - - config - string - operatingsystem - - - - config - string - operatingsystemrelease - - - - fact - float - - - - - - - + + + + + @@ -593,7 +391,7 @@ - + @@ -602,15 +400,6 @@ - - - - - - - - - config @@ -626,38 +415,35 @@ - - - - - + - - + fact + + - + - + config string operatingsystem - - + + config string operatingsystemrelease - - + + fact string kernelrelease - + false @@ -696,6 +482,7 @@ multiselect + @@ -730,18 +517,6 @@ int_timestamp - - - false - fact - string - - - - false - fact - string - @@ -757,8 +532,9 @@ string - + + @@ -847,21 +623,16 @@ - fact string - manufacturer - string - product_name - fact float @@ -877,11 +648,10 @@ - + - - + @@ -889,5 +659,6 @@ + From 0ab47a9736709971ae688b44fcb9f6c8aeb982c8 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 1 Apr 2014 08:41:48 -0700 Subject: [PATCH 12/82] removing unused entities --- cmdb_api.conf | 11 ----------- cmdb_api.pm | 12 ------------ 2 files changed, 23 deletions(-) diff --git a/cmdb_api.conf b/cmdb_api.conf index ca3530a..780bf81 100644 --- a/cmdb_api.conf +++ b/cmdb_api.conf @@ -16,7 +16,6 @@ "datacenter_subnet":"Generic", "data_center":"Generic", "role":"Generic", - "pod_cluster":"Generic", "snat":"Generic", "pool":"Generic", "cluster":"Generic", @@ -24,16 +23,6 @@ "cluster_mta":"Generic", "system":"System", "device":"System", - "blade_chassis":"System", - "console_server":"System", - "firewall":"System", - "load_balancer":"System", - "network_switch":"System", - "power_strip":"System", - "router":"System", - "storage_head":"System", - "storage_shelf":"System", - "device_ip":"Generic", "newhostname":"Provision", "pcmsystemname":"ProvisionPcm", "user":"Generic", diff --git a/cmdb_api.pm b/cmdb_api.pm index eea12e7..fa82d9f 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -70,7 +70,6 @@ my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', datacenter_subnet=>'Generic', data_center=>'Generic', role=>'Generic', - pod_cluster=>'Generic', snat=>'Generic', pool=>'Generic', cluster=>'Generic', @@ -78,16 +77,6 @@ my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', cluster_mta=>'Generic', system=>'System', device=>'System', - blade_chassis=>'System', - console_server=>'System', - firewall=>'System', - load_balancer=>'System', - network_switch=>'System', - power_strip=>'System', - router=>'System', - storage_head=>'System', - storage_shelf=>'System', - device_ip=>'Generic', newhostname=>'Provision', pcmsystemname=>'ProvisionPcm', user=>'Generic', @@ -97,7 +86,6 @@ my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', inv_normalizer=>'Generic', fact=>'TrafficControl', change_queue=>'ChangeQueue', - ip=>'Generic', service_instance=>'Generic', service_instance_data=>'Generic', instance_size=>'Generic', From ef436c3be96347a1a0507912d242cfb6e0d3a973 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 May 2014 15:48:41 -0700 Subject: [PATCH 13/82] only convert * to % for like --- cmdb_api.pm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index fa82d9f..0919ac1 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2237,7 +2237,10 @@ sub doSystemGET(){ } # my $var=$$requestObject{'query'}{$_}; # $var=~s/\*/%/g; - $getparams{$_}{val} =~ s/\*/%/g; + if($op !~ /RLIKE/) + { + $getparams{$_}{val} =~ s/\*/%/g; + } #push(@$parms,$var); push(@$parms,$getparams{$_}{val}); } @@ -2271,7 +2274,10 @@ sub doSystemGET(){ # my $var=$$requestObject{'query'}{$_}; # $var=~s/\*/%/g; # push(@$parms,$var); - $getparams{$_}{val} =~ s/\*/%/g; + if($op !~ /RLIKE/) + { + $getparams{$_}{val} =~ s/\*/%/g; + } push(@$parms,$getparams{$_}{val}); } } From 4a2ab14f9ba94f0e374a6f074878d782748377eb Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 8 May 2014 15:33:33 -0700 Subject: [PATCH 14/82] allow for POSTing to path with resource key or just to the services endpoint --- cmdb_api.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 0919ac1..8150a22 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1917,7 +1917,14 @@ sub doEnvironmentsServicesPOST(){ # FIXME: should this be how we handle this? No point in specifying either # of these attributes in the request. delete $data->{svc_id}; - $data->{name} = $service; + if($service) + { + $data->{name} = $service; + } + else + { + $service = $data->{name}; + } $data->{'environment_name'} = $environment; if (not defined $service) { From 046eb4552698dd3240cb1457b48e988d10a07f0b Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 12 May 2014 13:06:44 -0700 Subject: [PATCH 15/82] allow for diff api install lcoatoins --- init.pl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/init.pl b/init.pl index bd602b8..59b1d76 100644 --- a/init.pl +++ b/init.pl @@ -16,7 +16,6 @@ # limitations under the License. ############ -use lib qw(/var/www/cmdb_api /opt/pptools); -use ppenv; +use lib qw(/var/www/cmdb_api /opt/cmdb /opt/cmdb-api /opt/cmdb_api); use cmdb_api; 1; From b213f611574b03812061b61a51ea5321c900b448 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 7 Aug 2014 11:31:44 -0700 Subject: [PATCH 16/82] making log output useful --- log4perl.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/log4perl.conf b/log4perl.conf index 1eed5ef..5eb975a 100644 --- a/log4perl.conf +++ b/log4perl.conf @@ -20,5 +20,6 @@ log4perl.logger.inventory.cmdb_api = DEBUG, FileAppndr1 log4perl.appender.FileAppndr1 = Log::Log4perl::Appender::File log4perl.appender.FileAppndr1.filename = /var/log/httpd/cmdb_api.log -log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::SimpleLayout +log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::PatternLayout +log4perl.appender.FileAppndr1.layout.ConversionPattern=%d %p:%P> %F{1}:%L %M - %m%n ###################################################### From d6bb98aed2b6e15cfe4ffabcd96bc94083057528 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 7 Aug 2014 11:34:31 -0700 Subject: [PATCH 17/82] fix to make environment services api use query params to be able to query for service records with specific attribute/values. this could not be done in the sql, so the query results are filtered by the query params using noms::jcel --- NOMS/JCEL.pm | 445 +++++++++++++++++++++++++++++++++++++++++++++++++++ cmdb_api.pm | 67 +++++++- 2 files changed, 506 insertions(+), 6 deletions(-) create mode 100644 NOMS/JCEL.pm diff --git a/NOMS/JCEL.pm b/NOMS/JCEL.pm new file mode 100644 index 0000000..f3081b2 --- /dev/null +++ b/NOMS/JCEL.pm @@ -0,0 +1,445 @@ +#!perl + +package NOMS::JCEL; +use strict; +use Data::Dumper; +use Carp; + +# TODO +# Need a way to express (field1 = 'v1' OR field2 = 'v2'). The current +# assumption is that this will be handled by the "statement" oriented +# language surrounding this expression language; for example pattern/action +# statements where two patterns lead to the same action. The problem +# with incorporating operators at the higher level is that a construct +# like { "or": [condition_list] } and { "and": [condition_list] } could +# refer to object attributes called "or" and "and" + +=head1 NAME + +NOMS::JCEL - JSON (or Jeremy's) Condition Expression Language + +=head1 SYNOPSIS + + my $obj = from_json('{ "service": "http", "status": "up" }'); + my $jcel = NOMS::JCEL->new(from_json('{ "service": ["http", "ftp"] }')); + + print "Can use for file transfer" if $jcel->test($obj); + +=head1 DESCRIPTION + +This module implements an expression language you can use to implement +conditional logic expressed as a "mapping" (like that resulting from +deserializing JSON). + +=head2 Methods + +=over 4 + +=item new + + NOMS::JSON->new($expression[, $options]); + +The expression is a hash reference to the JCEL expression. Options is a +hash reference containing options and values. The only supported option is +C<'debug'>. + +=item test + + $jcel->test($object); + +Evaluate the test(s) in the expression against the object. Returns 1 (true) +or 0 (false). + +=item as_sql + + $jcel->as_sql($options); + +Print a SQL condition predicate corresponding with the conditional expression. +In a scalar context, produces just the SQL text. In a list context, produces a +list, the first member of which is the SQL text and the remaining members of +which are literal values corresponding with SQL placeholders (see +B). The $options hash reference contains the following options and +values: + +=back + +=over 8 + +=item literals + +If true, prints the values as quoted literals. *Note:* it is possible to cause +errors; this module's attempt to quote literals is not perfect. It's safer to +leave this value alone (false). + +=back + +=head2 Language + +=head3 Conditions + + condition ::= { attribute: rvalue } + condition :: = [ condition, ... ] + +Each condition consists of a key, the object attribute to examine for a match, +and the rvalue, or comparison, to make against the value in the object +corresponding to the key. + +And empty condition list is always true in tests, and returns an empty SQL +predicate (which if plugged into an SQL statement without examination, may +cause a syntax error). + +=head3 Rvalues + + rvalue ::= literal + rvalue ::= { operator: literal } + rvalue ::= [ rvalue, ... ] + +=head3 Operator + +=over 4 + +=item = + +General-purpose string comparison. The rvalue may contain shell-like glob +patterns (C<*>, C and C<[]>). Numbers are stringified and compared as +strings. + +=item != + +Negation of B<=>. + +=item eq + +String equality comparsion. No wildcards are allowed. + +=item ne + +String inequality comparison. + +=item ~ + +Regular expression matching. The rvalue is a Perl regular expression which is +matched against the lvalue. + +=item !~ + +Negation of regular expression matching. + +=item == + +=item E + +=item E + +=item EE + +=item E= + +=item E= + +Numerical comparisons. + +=back + +=head2 Null Values + +In testing, JCEL considers two null values (as well as the case where the +object attribute does not exist) equivalent. Thus: + + my $jcel = NOMS::JCEL->new(from_json('{ "state": null }')); + +This condition will evaluate to true when testing either a hash where the +B key is C or where the B key does not exist. + +=head1 KNOWN ISSUES + +The B method is new and experimental. There are some cases where it +will not produce syntactically correct SQL, such as for an empty condition. + +=head1 AUTHOR + +Jeremy Brinkley, Ejbrinkley@evernote.comE + +=cut + +use vars qw($me $VERSION); + +BEGIN { + $me = 'NOMS::JCEL'; + $VERSION = '__VERSION__'; +} + +sub eql { + my ($lvalue, $rvalue) = @_; + + my $p = glob2pat($rvalue); + + return 1 if $lvalue =~ /$p/; + + return 1 if !defined($lvalue) and !defined($rvalue); + + return 0; +} + + +# http://www.perlmonks.org/?node_id=708493 +sub glob2pat { + my $globstr = shift; + my %patmap = ( + '*' => '.*', + '?' => '.', + '[' => '[', + ']' => ']', + '-' => '-', + ); + + $globstr =~ s{(?:^|(?<=[^\\]))(.)} { $patmap{$1} || "\Q$1" }ge; + + my $pattern = '^' . $globstr . '$'; + + return $pattern; +} + +sub isglob { + my ($g) = @_; + my $is = 0; + + $is = 1 if $g =~ /^[\?\*]/; + $is = 1 if $g =~ /[^\\][\?\*]/; + + return $is; +} + +sub glob2sql { + my ($g) = @_; + my %map = ( + '_' => '\_', + '%' => '\%', + '*' => '%', + '?' => '_' + ); + $g =~ s{(?:^|(?<=[^\\]))(.)} { $map{$1} || $1 }ge; + + return $g; +} + + +sub sqlquote { + my ($s) = @_; + + $s =~ s/\'/\\'/g; + + return "'" . $s . "'"; +} + +sub new { + my ($class, $condition, $opt) = @_; + + my $self = bless({}, $class); + + $self->{'condition'} = $condition; + $self->dbg("init condition: " . ddump($condition)); + $self->{'options'} = $opt; + $self->{'op'} = { + '=' => sub { eql($_[0], $_[1]) }, + '!=' => sub { not eql($_[0], $_[1]) }, + 'eq' => sub { $_[0] eq $_[1] }, + 'ne' => sub { $_[0] ne $_[1] }, + '==' => sub { $_[0] == $_[1] }, + '<>' => sub { $_[0] != $_[1] }, + '~' => sub { $_[0] =~ /$_[1]/ }, + '!~' => sub { $_[0] !~ /$_[1]/ }, + '>' => sub { $_[0] > $_[1] }, + '<' => sub { $_[0] < $_[1] }, + '>=' => sub { $_[0] >= $_[1] }, + '<=' => sub { $_[0] <= $_[1] } + }; + + $self->{'sqlop'} = { + '=' => sub { isglob($_[1]) ? ($_[0] . ' LIKE ?', glob2sql($_[1])) : + ($_[0] . ' = ?', $_[1]) }, + '!=' => sub { isglob($_[1]) ? ($_[0] . ' NOT LIKE ?', glob2sql($_[1])) : + ($_[0] . ' != ?', $_[1]) }, + 'eq' => sub { ($_[0] . ' = ?', $_[1]) }, + 'ne' => sub { ($_[0] . ' != ?', $_[1]) }, + '==' => sub { ($_[0] . ' = ?', $_[1]) }, + '<>' => sub { ($_[0] . ' != ?', $_[1]) }, + '>' => sub { ($_[0] . ' > ?', $_[1]) }, + '<' => sub { ($_[0] . ' < ?', $_[1]) }, + '>=' => sub { ($_[0] . ' >= ?', $_[1]) }, + '<=' => sub { ($_[0] . ' <= ?', $_[1]) }, + '~' => sub { ($_[0] . ' REGEXP ?', $_[1]) }, + '!~' => sub { ($_[0] . ' NOT REGEXP ?', $_[1]) } + }; + + $self->{'sqlop-literal'} = { + '=' => sub { isglob($_[1]) ? + $_[0] . ' LIKE ' . sqlquote(glob2sql($_[1])) : + $_[0] . ' = ' . sqlquote($_[1]) }, + '!=' => sub { isglob($_[1]) ? + $_[0] . ' NOT LIKE ' . sqlquote(glob2sql($_[1])) : + $_[0] . ' = ' . sqlquote($_[1]) }, + 'ne' => sub { $_[0] . ' != ' . sqlquote($_[1]) }, + '==' => sub { $_[0] . ' = ' . $_[1] }, + '<>' => sub { $_[0] . ' != ' . $_[1] }, + '>' => sub { $_[0] . ' > ' . $_[1] }, + '<' => sub { $_[0] . ' < ' . $_[1] }, + '>=' => sub { $_[0] . ' >= ' . $_[1] }, + '<=' => sub { $_[0] . ' <= ' . $_[1] }, + '~' => sub { $_[0] . ' REGEXP ' . sqlquote($_[1]) }, + '!~' => sub { $_[0] . ' NOT REGEXP ' . sqlquote($_[1]) } + }; + + + return $self; +} + +sub test { + my ($self, $object) = @_; + + my $condition = $self->{'condition'}; + + my $rv = 0; + eval { + $rv = $self->match_condition($condition, $object); + }; + if ($@) { + my $err = $@; + chomp($err); + $err =~ s/ at \S*JCEL.pm.*//; + carp $err; + } + + return $rv; +} + +sub as_sql { + my ($self, $sqlopt) = @_; + my $literals = 0; + + $literals = 1 if (defined($sqlopt) and $sqlopt->{'literals'}); + + my ($st, @literals) = $self->sql($self->{'condition'}, + { 'literals' => $literals }); + + return (wantarray ? ($st, @literals) : $st); +} + +sub sql { + my ($self, $condition, $options, @literals) = @_; + my $sqltext = ''; + + if (ref($condition) eq 'ARRAY') { + my @subsqltexts = (); + for my $subcondition (@{$condition}) { + my ($subsqltext, @sublits) = $self->sql($subcondition, $options); + push(@subsqltexts, $subsqltext); + push(@literals, @sublits); + } + $sqltext = join(' AND ', @subsqltexts); + } else { + my ($lvalue) = keys(%$condition); + my $rvalue = $condition->{$lvalue}; + my $op = '='; + my $optype = ($options->{'literals'} ? 'sqlop-literal' : 'sqlop'); + if (ref($rvalue)) { + if (ref($rvalue) eq 'ARRAY') { + $op = 'IN'; + my $rvaluetext = $options->{'literals'} ? + '(' . join(', ', map { sqlquote($_) } @$rvalue) . ')' : + '(' . join(', ', map { '?' } @$rvalue) . ')'; + $sqltext = join(' ', $lvalue, $op, $rvaluetext); + push(@literals, @$rvalue); + } elsif (ref($rvalue) eq 'HASH') { + ($op) = keys(%$rvalue); + $rvalue = $rvalue->{$op}; + ($sqltext, $rvalue) = $self->{$optype}->{$op}->($lvalue, $rvalue); + push(@literals, $rvalue); + } + } else { + ($sqltext, $rvalue) = $self->{$optype}->{$op}->($lvalue, $rvalue); + push(@literals, $rvalue); + } + } + + return ($options->{'literals'} ? ($sqltext) : ($sqltext, @literals)); +} + +sub match_condition { + my ($self, $condition, $object) = @_; + + $self->dbg("match_condition(" . ddump($condition) . ", " . ddump($object) + . ')'); + + # and + if (ref($condition) eq 'ARRAY') { + for my $subcondition (@$condition) { + return 0 unless $self->match_condition($subcondition, $object); + } + return 1; + } + + if (! ref($condition) or ref($condition) ne 'HASH') { + die "Condition must be HASH or ARRAY reference"; + } + + return 1 unless keys %$condition; # empty condition is true + + for my $attr (keys %$condition) { + my $rvalue = $condition->{$attr}; + return 0 unless $self->match_rvalue($rvalue, $object->{$attr}); + } + + return 1; +} + +sub match_rvalue { + my ($self, $rvalue, $lvalue) = @_; + + $self->dbg("match_rvalue(" . ddump($rvalue) . ", " . ddump($lvalue) . ')'); + + if (ref($rvalue)) { + if (ref($rvalue) eq 'HASH') { + my ($op) = keys %$rvalue; + $self->dbg(" op is: " . ddump($op)); + my $simple_rvalue = $rvalue->{$op}; + if (ref($simple_rvalue)) { + die "rvalue of explicit operator must be simple"; + } + return tv($self->{'op'}->{$op}->($lvalue, $simple_rvalue)); + } elsif (ref($rvalue) eq 'ARRAY') { + $self->dbg(" rvalue is list"); + for my $simple_rvalue (@$rvalue) { + if (ref($simple_rvalue)) { + die "each rvalue in rvalue list must be simple"; + } + $self->dbg(" checking simple rvalue: " + . ddump($simple_rvalue)); + return 1 if $self->{'op'}->{'='}->($lvalue, $simple_rvalue); + } + return 0; + } + } else { + $self->dbg(" rvalue is simple: $rvalue"); + return tv($self->{'op'}->{'='}->($lvalue, $rvalue)); + } +} + +sub ddump { + my $var = 'var0'; + Data::Dumper->new([@_],[map {$var++} @_])->Terse(1)->Indent(0)->Dump; +} + +sub dbg { + my ($self, @msg) = @_; + print STDERR "DBG($me): ", join("\nDBG($me): ", @msg), "\n" + if $self->{'options'}->{'debug'}; +} + +sub tv { + my ($val) = @_; + return 1 if $val; + return 0; +} + +1; diff --git a/cmdb_api.pm b/cmdb_api.pm index 8150a22..6f94854 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -41,6 +41,7 @@ use Apache::DBI; use Date::Manip; use Optconfig; use DBI; +use NOMS::JCEL; sub eat_json { my ($json_text, $opthash) = @_; @@ -1067,8 +1068,13 @@ sub doGenericPOST $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; return 'ACL blocked change: ' . &make_json($blocked_changes); } + foreach my $f (@{&getFieldList($$requestObject{'entity'})}) { + if((!exists $$data{$f} || $$data{$f} == '') && $tree_extended->{entities}->{$entity}->{$f}->{default_value}) + { + $$data{$_} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; + } if(exists $$data{$f}) { $$data{$f}=&doFieldNormalization($entity,$f,$$data{$f}); @@ -1532,7 +1538,7 @@ sub parseQueryParams my $op = $2; my $val = $3; next if $key =~ /^_/; - next unless (grep(/^$key$/,@$valid_fields)); + next if (!grep(/^$key$/,@$valid_fields) && scalar(@$valid_fields) > 0); $val =~ s/'//g; $op = 'LIKE' if $op eq '='; $op = 'NOT LIKE' if $op eq '!='; @@ -1612,8 +1618,8 @@ sub doEnvironmentsServicesGET() { $sql .= "and environment_name in ($list)) as s " . "left join service_instance_data as d on s.svc_id = d.svc_id " . "order by field(environment_name, $list)"; - $rtn = &doSql($sql, undef); + if ($$rtn{'err'}) { $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; return $$rtn{'err'} . " : " . $$rtn{'errstr'}; @@ -1644,10 +1650,54 @@ sub doEnvironmentsServicesGET() { } } - if (scalar(keys %hash) == 1) { - return (values %hash)[0]; - } elsif (keys %hash) { + if ($requestObject->{'getparams'}) { + parseQueryParams($requestObject->{'getparams'}, \%getparams,[]); + } + # remove params that would have gotten parsed into the sql query + foreach my $query_parm (['name','type']) + { + delete $getparams{$query_parm}; + } + + # if other attribute querys are coming in, then evaluate them assemble new results of matches + if(scalar(keys(%getparams)) > 0) + { + my @ranges=split(/[&;]/, $requestObject->{'getparams'}); + my $jcel_params={}; + foreach my $range (@ranges) { + next unless $range =~ /(\w+)([!~>=<]+)(.*)/; + my $key = $1; + my $op = $2; + my $val = $3; + # we are parsing the query string (yet again). skip the attrs that get searched by the sql + next if (grep(/^$key$/,['name','type']) ); + $jcel_params->{$key}->{$op}=$val; + } + my @filtered_results; + # assemble jcel config from passed in parameters to use for testing the service records + $logger->debug("jcel condition: " . make_json($jcel_params)); + my $jcel = NOMS::JCEL->new($jcel_params); + foreach my $service_record_key (keys %hash) + { + if ($jcel->test($hash{$service_record_key})) + { + push(@filtered_results,\%{$hash{$service_record_key}}); + } + } + return \@filtered_results if scalar(@filtered_results); + # there were no results left after filtering. so + $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; + return; + + } + + # return results as array when query is used + if (!defined $service && keys(%hash)) { return [values %hash]; + } + # otherwise service was accessed via full resource path, so return as record + elsif (keys %hash) { + return (values %hash)[0]; } else { $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; return; @@ -2564,10 +2614,15 @@ sub doSystemPOST(){ $data->{'inventory_component_type'} = 'system' unless $data->{'inventory_component_type'}; if($data->{$IPADDRESSFIELD} && !$data->{'data_center_code'}) { - $data->{'data_center_code'}=&lookupDC($data->{$IPADDRESSFIELD}); } $dbs->begin_work; + + if((!exists $$data{$_} || $$data{$_} == '') && $tree_extended->{entities}->{'system'}->{$_}->{default_value}) + { + $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; + } + # construct insert sql for device table foreach(@$device_fields) { From 5fb1e62b47b08195b34fd93b26feb6c1a54062e8 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 11 Aug 2014 13:27:07 -0700 Subject: [PATCH 18/82] perltidy the file and make sure meta field !~/!= query returns true for null --- cmdb_api.pm | 5082 ++++++++++++++++++++++++++------------------------- 1 file changed, 2636 insertions(+), 2446 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 6f94854..c28d99c 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -28,12 +28,12 @@ use Apache2::RequestIO (); use Apache2::Connection; use Apache2::Access; use APR::Brigade (); -use APR::Bucket (); +use APR::Bucket (); use Log::Log4perl qw(:easy); use Apache2::Filter (); use Apache2::Const -compile => qw(OK HTTP_NOT_FOUND HTTP_OK HTTP_FAILED_DEPENDENCY HTTP_NOT_ACCEPTABLE HTTP_NO_CONTENT HTTP_INTERNAL_SERVER_ERROR DECLINED HTTP_ACCEPTED HTTP_CREATED HTTP_UNAUTHORIZED SERVER_ERROR MODE_READBYTES HTTP_CONFLICT HTTP_FORBIDDEN HTTP_METHOD_NOT_ALLOWED); -use APR::Const -compile => qw(SUCCESS BLOCK_READ); -use constant IOBUFSIZE => 8192; +use APR::Const -compile => qw(SUCCESS BLOCK_READ); +use constant IOBUFSIZE => 8192; use JSON; use XML::Parser; use XML::Simple; @@ -43,944 +43,993 @@ use Optconfig; use DBI; use NOMS::JCEL; -sub eat_json { - my ($json_text, $opthash) = @_; - return ($JSON::VERSION > 2.0 ? from_json($json_text, $opthash) : JSON->new()->jsonToObj($json_text, $opthash)); +sub eat_json +{ + my ( $json_text, $opthash ) = @_; + return ( $JSON::VERSION > 2.0 ? from_json( $json_text, $opthash ) : JSON->new()->jsonToObj( $json_text, $opthash ) ); } -sub make_json { - my ($obj, $opthash) = @_; - return ($JSON::VERSION > 2.0 ? to_json($obj, $opthash) : JSON->new()->objToJson($obj, $opthash)); +sub make_json +{ + my ( $obj, $opthash ) = @_; + return ( $JSON::VERSION > 2.0 ? to_json( $obj, $opthash ) : JSON->new()->objToJson( $obj, $opthash ) ); } - -my $opt = Optconfig->new('cmdb_api', { 'driver=s' => 'mysql', - 'dbuser=s' => 'dbuser', - 'dbpass=s' => 'dbpass', - 'dbhost' => 'localhost', - 'database' => 'inventory', - 'debug' => 1, - 'prism_domain' => 'prism.ppops.net', - 'logconfig' => '/var/www/cmdb_api/log4perl.conf', - 'lexicon' => '/var/www/cmdb_api/pp_lexicon.xml', - 'ipaddress_attribute' => "ip_address", - "traffic_control_search_fields" => ["fqdn","macaddress","ipaddress"], - 'entities' => { - acl=>'Acl', - vip=>'Generic', - datacenter_subnet=>'Generic', - data_center=>'Generic', - role=>'Generic', - snat=>'Generic', - pool=>'Generic', - cluster=>'Generic', - hardware_model=>'Generic', - cluster_mta=>'Generic', - system=>'System', - device=>'System', - newhostname=>'Provision', - pcmsystemname=>'ProvisionPcm', - user=>'Generic', - currentUser=>'User', - inv_audit=>'Generic', - audit=>'Audit', - inv_normalizer=>'Generic', - fact=>'TrafficControl', - change_queue=>'ChangeQueue', - service_instance=>'Generic', - service_instance_data=>'Generic', - instance_size=>'Generic', - instance_location=>'Generic', - column_lkup=>'Column_lkup', - environments=>'Environments', - environmentservice=>'Environments' - } - }); - -my $valid_entity_apis={ - 'Environments' => 1, - 'ChangeQueue' => 1, - 'System' => 1, - 'Generic' => 1, - 'Audit' => 1 +my $opt = Optconfig->new( + 'cmdb_api', + { + 'driver=s' => 'mysql', + 'dbuser=s' => 'dbuser', + 'dbpass=s' => 'dbpass', + 'dbhost' => 'localhost', + 'database' => 'inventory', + 'debug' => 1, + 'prism_domain' => 'prism.ppops.net', + 'logconfig' => '/var/www/cmdb_api/log4perl.conf', + 'lexicon' => '/var/www/cmdb_api/pp_lexicon.xml', + 'ipaddress_attribute' => "ip_address", + "traffic_control_search_fields" => [ "fqdn", "macaddress", "ipaddress" ], + 'entities' => { + acl => 'Acl', + vip => 'Generic', + datacenter_subnet => 'Generic', + data_center => 'Generic', + role => 'Generic', + snat => 'Generic', + pool => 'Generic', + cluster => 'Generic', + hardware_model => 'Generic', + cluster_mta => 'Generic', + system => 'System', + device => 'System', + newhostname => 'Provision', + pcmsystemname => 'ProvisionPcm', + user => 'Generic', + currentUser => 'User', + inv_audit => 'Generic', + audit => 'Audit', + inv_normalizer => 'Generic', + fact => 'TrafficControl', + change_queue => 'ChangeQueue', + service_instance => 'Generic', + service_instance_data => 'Generic', + instance_size => 'Generic', + instance_location => 'Generic', + column_lkup => 'Column_lkup', + environments => 'Environments', + environmentservice => 'Environments' + } + } +); + +my $valid_entity_apis = { + 'Environments' => 1, + 'ChangeQueue' => 1, + 'System' => 1, + 'Generic' => 1, + 'Audit' => 1 }; -my $DEBUG=$opt->{'debug'}; -my $DBHOST=$opt->{'dbhost'}; -my $DBUSER=$opt->{'dbuser'}; -my $DBPASS=$opt->{'dbpass'}; -my $DATABASE=$opt->{'database'}; -my $IPADDRESSFIELD=$opt->{'ipaddress_attribute'}; -my $DRIVER=$opt->{'driver'}; -my ($lexicon,$tree,$parser); +my $DEBUG = $opt->{'debug'}; +my $DBHOST = $opt->{'dbhost'}; +my $DBUSER = $opt->{'dbuser'}; +my $DBPASS = $opt->{'dbpass'}; +my $DATABASE = $opt->{'database'}; +my $IPADDRESSFIELD = $opt->{'ipaddress_attribute'}; +my $DRIVER = $opt->{'driver'}; +my ( $lexicon, $tree, $parser ); my ($parms); -my $log_config_file=$opt->{'logconfig'}; +my $log_config_file = $opt->{'logconfig'}; Log::Log4perl::init($log_config_file); my $logger = Log::Log4perl->get_logger('inventory.cmdb_api'); -unless($lexicon) +unless ($lexicon) { -#TODO this hardcoded path is bad fix it - $lexicon=$opt->{'lexicon'}; + #TODO this hardcoded path is bad fix it + $lexicon = $opt->{'lexicon'}; } # database connection our $dbh; -#valid api types. these must exist and be parsable in the lexicon if they are 'Generic' + +#valid api types. these must exist and be parsable in the lexicon if they are 'Generic' # or have provided doGET/PUT/POST functions my $valid_entities = $opt->{'entities'}; -my $versions=[ 'v1' ]; +my $versions = ['v1']; + +$parser = XML::Simple->new(); +eval { $tree = $parser->XMLin($lexicon); }; -$parser= XML::Simple->new( ); -eval { $tree=$parser->XMLin($lexicon); }; # show error and die if xml parsing of the lexicon failed -if($@) +if ($@) { - $logger->fatal("error parsing $lexicon\n$@"); - exit; + $logger->fatal("error parsing $lexicon\n$@"); + exit; } -$logger->info("$lexicon is xml ok"); +$logger->info("$lexicon is xml ok"); our $tree_extended; -$tree_extended=&eat_json(&make_json($tree)); +$tree_extended = &eat_json( &make_json($tree) ); + # loop through entities and add base attributes to things that subclass other stuff -foreach(keys(%{$tree_extended->{entities}})) +foreach ( keys( %{ $tree_extended->{entities} } ) ) { - if($tree_extended->{entities}->{$_}->{extends}) - { - my $extends=&lkupXMLPath($tree->{entities}->{$_}->{extends}); - foreach my $attr (keys(%$extends)) - { - $tree_extended->{entities}->{$_}->{$attr}=$extends->{$attr}; - } - } + if ( $tree_extended->{entities}->{$_}->{extends} ) + { + my $extends = &lkupXMLPath( $tree->{entities}->{$_}->{extends} ); + foreach my $attr ( keys(%$extends) ) + { + $tree_extended->{entities}->{$_}->{$attr} = $extends->{$attr}; + } + } } -$logger->debug("lexicon: " . &make_json($tree,{pretty=>1,allow_nonref=>1}) ) if ($logger->is_debug()); -$logger->debug("lexicon extended: " . &make_json($tree_extended,{pretty=>1,allow_nonref=>1}) ) if ($logger->is_debug()); +$logger->debug( "lexicon: " . &make_json( $tree, { pretty => 1, allow_nonref => 1 } ) ) if ( $logger->is_debug() ); +$logger->debug( "lexicon extended: " . &make_json( $tree_extended, { pretty => 1, allow_nonref => 1 } ) ) if ( $logger->is_debug() ); sub lkupXMLPath() { - my $str=shift; - my @seg=split('/',$str); - shift(@seg); - shift(@seg); - my $rtn='$tree->{' . shift(@seg) . '}'; - foreach(@seg) - { - $rtn.='->{' . $_ . '}'; - } - return eval($rtn); + my $str = shift; + my @seg = split( '/', $str ); + shift(@seg); + shift(@seg); + my $rtn = '$tree->{' . shift(@seg) . '}'; + foreach (@seg) + { + $rtn .= '->{' . $_ . '}'; + } + return eval($rtn); } # mod perl2 handler -sub handler() { - $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS); - my $r = shift; - my $up_uri = $r->unparsed_uri(); - $up_uri =~ s/.+\?//; - my $uri = uri_unescape($up_uri); - my $req=Apache2::Request->new($r); - my ($requestObject,$data,$formatted_data); - %{$$requestObject{'query'}}=%{$req->param} if $req->param; - $$requestObject{'getparams'}=$uri; - $$requestObject{'stat'}=Apache2::Const::HTTP_OK; - $$requestObject{'_format'}=$$requestObject{'query'}{'_format'} || 'json'; - $$requestObject{'method'}=$req->method(); - @{$$requestObject{'path'}}=split('/',$req->uri()); - $$requestObject{'pathstr'}=$req->uri(); - $$requestObject{'user'}=&doGenericGET({entity=>'user',path=>[$req->user]}) if $req->user; - $$requestObject{'http_auth_user'}=$req->user if $req->user; - $$requestObject{'ip_address'}=$r->connection->remote_ip(); - if($$requestObject{'method'} ne 'GET') - { - $$requestObject{'body'}= read_post($r); - } - else - { - $$requestObject{'body'}=''; - } - shift(@{$$requestObject{'path'}}); - if(shift(@{$$requestObject{'path'}}) eq 'cmdb_api') - { - $$requestObject{'requested_api'}=shift(@{$$requestObject{'path'}}); - $$requestObject{'entity'}=shift(@{$$requestObject{'path'}}); - $logger->debug(&make_json($requestObject,{pretty=>1,allow_nonref=>1,allow_blessed=>1})) if ($logger->is_debug()); - # do help if it was asked - if( exists $requestObject->{'query'}->{'help'} || exists $requestObject->{'query'}->{'lexicon'}) - { - $requestObject->{'help'}=1; - if(!$requestObject->{'requested_api'}) - { - $r->print(&make_json($versions)); - return Apache2::Const::OK; - - } - elsif( !$requestObject->{'entity'}) - { - my $ents=[]; - my $lex={}; - if($requestObject->{'query'}->{'lexicon'}) - { - foreach(keys(%$valid_entities)) - { - $lex->{$_}= $tree->{entities}->{$_}; - no strict 'refs'; - # check each attribute and populate enumerations if needed - foreach my $attr (keys(%{$lex->{$_}})) - { - if($lex->{$_}->{$attr} && ref($lex->{$_}->{$attr}) eq 'HASH' && - defined $lex->{$_}->{$attr}->{'enumeration'} && - defined $lex->{$_}->{$attr}->{'enumeration'}->{'entity'} && - defined $lex->{$_}->{$attr}->{'enumeration'}->{'attribute'}) - { - $lex->{$_}->{$attr}->{'enumeration'}->{'enumerator'}=&doColumn_lkupGET($requestObject,$lex->{$_}{$attr}{'enumeration'}{'entity'},$lex->{$_}{$attr}{'enumeration'}->{'attribute'}); - } - } - } - $r->print(&make_json($lex)); - } - else - { - foreach(keys(%$valid_entities)) - { - push(@$ents,$_) if ( $valid_entity_apis->{ $valid_entities->{$_} } == 1 ); - } - $r->print(&make_json($ents)); - } - return Apache2::Const::OK; - } - else - { - #$r->print(&make_json(&getFieldList($requestObject->{'entity'}))); - $r->print(&make_json( $tree->{entities}->{$requestObject->{'entity'}}, {pretty => 1,allow_nonref=>1})); - return Apache2::Const::OK; - } - - } - - - # check for valid entity - unless($$requestObject{'entity'} && $$valid_entities{$$requestObject{'entity'}}) - { - $logger->debug( "valid entities:") if ($logger->is_debug()); - $logger->debug( "entity lkup: $$valid_entities{$$requestObject{'entity'}}") if ($logger->is_debug()); - $r->print('valid entity required'); - return Apache2::Const::HTTP_NOT_ACCEPTABLE; - } - - #deal with the connection and produce data - $data=&ProcessRequest($requestObject); - $r->status($$requestObject{'stat'}); - if($$requestObject{'stat'} eq '500') - { - $data={ - success => 'false', - message => $data - }; - } - $logger->debug( "final return of status: $$requestObject{'stat'}") if ($logger->is_debug()); - -#TODO reconcile the '"string" data that comes back from above and how we output it (errors, etc...) - if($$requestObject{'headers_out'}) - { - $r->headers_out->add($$requestObject{'headers_out'}[0]=>$$requestObject{'headers_out'}[1]); - } -#TODO make output format based on accept content header - if(!defined $data && keys(%{$$requestObject{'query'}}) > 0 ) - { - $data = []; - } - # set output format - if(defined $data) - { - if($$requestObject{'_format'} eq 'json') - { - $r->content_type('application/json'); - $formatted_data=&make_json($data,{pretty=>1,allow_nonref=>1,allow_blessed=>1}); - } - elsif($$requestObject{'_format'} eq 'xml') - { - $r->content_type('text/xml'); - $formatted_data=XMLout($data); - } - elsif($$requestObject{'_format'} eq 'text') - { - $logger->debug( "output data as text") if ($logger->is_debug()); - $formatted_data=$data; - } - $r->print($formatted_data); - } - } - else - { - $logger->error("error parsing api str"); - } +sub handler() +{ + $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); + my $r = shift; + my $up_uri = $r->unparsed_uri(); + $up_uri =~ s/.+\?//; + my $uri = uri_unescape($up_uri); + my $req = Apache2::Request->new($r); + my ( $requestObject, $data, $formatted_data ); + %{ $$requestObject{'query'} } = %{ $req->param } if $req->param; + $$requestObject{'getparams'} = $uri; + $$requestObject{'stat'} = Apache2::Const::HTTP_OK; + $$requestObject{'_format'} = $$requestObject{'query'}{'_format'} || 'json'; + $$requestObject{'method'} = $req->method(); + @{ $$requestObject{'path'} } = split( '/', $req->uri() ); + $$requestObject{'pathstr'} = $req->uri(); + $$requestObject{'user'} = &doGenericGET( { entity => 'user', path => [ $req->user ] } ) if $req->user; + $$requestObject{'http_auth_user'} = $req->user if $req->user; + $$requestObject{'ip_address'} = $r->connection->remote_ip(); + + if ( $$requestObject{'method'} ne 'GET' ) + { + $$requestObject{'body'} = read_post($r); + } + else + { + $$requestObject{'body'} = ''; + } + shift( @{ $$requestObject{'path'} } ); + if ( shift( @{ $$requestObject{'path'} } ) eq 'cmdb_api' ) + { + $$requestObject{'requested_api'} = shift( @{ $$requestObject{'path'} } ); + $$requestObject{'entity'} = shift( @{ $$requestObject{'path'} } ); + $logger->debug( &make_json( $requestObject, { pretty => 1, allow_nonref => 1, allow_blessed => 1 } ) ) if ( $logger->is_debug() ); + + # do help if it was asked + if ( exists $requestObject->{'query'}->{'help'} || exists $requestObject->{'query'}->{'lexicon'} ) + { + $requestObject->{'help'} = 1; + if ( !$requestObject->{'requested_api'} ) + { + $r->print( &make_json($versions) ); + return Apache2::Const::OK; + + } + elsif ( !$requestObject->{'entity'} ) + { + my $ents = []; + my $lex = {}; + if ( $requestObject->{'query'}->{'lexicon'} ) + { + foreach ( keys(%$valid_entities) ) + { + $lex->{$_} = $tree->{entities}->{$_}; + no strict 'refs'; + + # check each attribute and populate enumerations if needed + foreach my $attr ( keys( %{ $lex->{$_} } ) ) + { + if ( $lex->{$_}->{$attr} + && ref( $lex->{$_}->{$attr} ) eq 'HASH' + && defined $lex->{$_}->{$attr}->{'enumeration'} + && defined $lex->{$_}->{$attr}->{'enumeration'}->{'entity'} + && defined $lex->{$_}->{$attr}->{'enumeration'}->{'attribute'} ) + { + $lex->{$_}->{$attr}->{'enumeration'}->{'enumerator'} = &doColumn_lkupGET( $requestObject, $lex->{$_}{$attr}{'enumeration'}{'entity'}, $lex->{$_}{$attr}{'enumeration'}->{'attribute'} ); + } + } + } + $r->print( &make_json($lex) ); + } + else + { + foreach ( keys(%$valid_entities) ) + { + push( @$ents, $_ ) if ( $valid_entity_apis->{ $valid_entities->{$_} } == 1 ); + } + $r->print( &make_json($ents) ); + } + return Apache2::Const::OK; + } + else + { + #$r->print(&make_json(&getFieldList($requestObject->{'entity'}))); + $r->print( &make_json( $tree->{entities}->{ $requestObject->{'entity'} }, { pretty => 1, allow_nonref => 1 } ) ); + return Apache2::Const::OK; + } + + } + + # check for valid entity + unless ( $$requestObject{'entity'} && $$valid_entities{ $$requestObject{'entity'} } ) + { + $logger->debug("valid entities:") if ( $logger->is_debug() ); + $logger->debug("entity lkup: $$valid_entities{$$requestObject{'entity'}}") if ( $logger->is_debug() ); + $r->print('valid entity required'); + return Apache2::Const::HTTP_NOT_ACCEPTABLE; + } + + #deal with the connection and produce data + $data = &ProcessRequest($requestObject); + $r->status( $$requestObject{'stat'} ); + if ( $$requestObject{'stat'} eq '500' ) + { + $data = { + success => 'false', + message => $data + }; + } + $logger->debug("final return of status: $$requestObject{'stat'}") if ( $logger->is_debug() ); + + #TODO reconcile the '"string" data that comes back from above and how we output it (errors, etc...) + if ( $$requestObject{'headers_out'} ) + { + $r->headers_out->add( $$requestObject{'headers_out'}[0] => $$requestObject{'headers_out'}[1] ); + } + + #TODO make output format based on accept content header + if ( !defined $data && keys( %{ $$requestObject{'query'} } ) > 0 ) + { + $data = []; + } + + # set output format + if ( defined $data ) + { + if ( $$requestObject{'_format'} eq 'json' ) + { + $r->content_type('application/json'); + $formatted_data = &make_json( $data, { pretty => 1, allow_nonref => 1, allow_blessed => 1 } ); + } + elsif ( $$requestObject{'_format'} eq 'xml' ) + { + $r->content_type('text/xml'); + $formatted_data = XMLout($data); + } + elsif ( $$requestObject{'_format'} eq 'text' ) + { + $logger->debug("output data as text") if ( $logger->is_debug() ); + $formatted_data = $data; + } + $r->print($formatted_data); + } + } + else + { + $logger->error("error parsing api str"); + } return Apache2::Const::OK; } sub doFieldNormalization() { - my($entity,$field,$value)=@_; - my $newvalue; - $value=~s/^\ //g if defined $value; - $value=~s/\ $//g if defined $value; - my $matchers=$dbh->selectall_arrayref('select matcher,sub_value from inv_normalizer where entity_name=? and field_name=?', - {},($entity,$field)); - foreach(@$matchers) - { - - if($value=~m/$$_[0]/i) - { - $logger->debug( "matched with $$_[0] and subbing $$_[1]") if ($logger->is_debug()); - return $$_[1]; - } - } - if(ref $value eq 'ARRAY') - { - $value=join(',',@$value); - } - return $value; + my ( $entity, $field, $value ) = @_; + my $newvalue; + $value =~ s/^\ //g if defined $value; + $value =~ s/\ $//g if defined $value; + my $matchers = $dbh->selectall_arrayref( 'select matcher,sub_value from inv_normalizer where entity_name=? and field_name=?', {}, ( $entity, $field ) ); + foreach (@$matchers) + { + + if ( $value =~ m/$$_[0]/i ) + { + $logger->debug("matched with $$_[0] and subbing $$_[1]") if ( $logger->is_debug() ); + return $$_[1]; + } + } + if ( ref $value eq 'ARRAY' ) + { + $value = join( ',', @$value ); + } + return $value; } - -# lifted from mod_perl2 docs, does body content read for post/put -sub read_post { - my $r = shift; - my $bb = APR::Brigade->new($r->pool,$r->connection->bucket_alloc); - my $data = ''; - my $seen_eos = 0; - do { - $r->input_filters->get_brigade($bb, Apache2::Const::MODE_READBYTES,APR::Const::BLOCK_READ, IOBUFSIZE); - for (my $b = $bb->first; $b; $b = $bb->next($b)) { - if ($b->is_eos) { - $seen_eos++; - last; - } - if ($b->read(my $buf)) { - $data .= $buf; - } - $b->remove; # optimization to reuse memory - } - } while (!$seen_eos); - $bb->destroy; - return $data; - } - - - +# lifted from mod_perl2 docs, does body content read for post/put +sub read_post +{ + my $r = shift; + my $bb = APR::Brigade->new( $r->pool, $r->connection->bucket_alloc ); + my $data = ''; + my $seen_eos = 0; + do + { + $r->input_filters->get_brigade( $bb, Apache2::Const::MODE_READBYTES, APR::Const::BLOCK_READ, IOBUFSIZE ); + for ( my $b = $bb->first ; $b ; $b = $bb->next($b) ) + { + if ( $b->is_eos ) + { + $seen_eos++; + last; + } + if ( $b->read( my $buf ) ) + { + $data .= $buf; + } + $b->remove; # optimization to reuse memory + } + } while ( !$seen_eos ); + $bb->destroy; + return $data; +} #processes lexicon to get fields for an entity sub getFieldList() { - my $entity=shift; - my $bare=shift || 0; - my @arr; - foreach(keys(%{$tree->{entities}->{$entity}})) - { - next if ($_ eq 'key' || $_ eq 'extends' || $_ eq 'table'); - push(@arr,$_); - } - if($valid_entities->{$entity} eq 'system' && !$bare) - { - foreach(keys(%{$tree->{entities}->{device}})) - { - next if ($_ eq 'key' || $_ eq 'extends' || $_ eq 'table'); - push(@arr,$_); - } - } - $logger->info("processed fields for $entity : " . join(',',@arr) ); - return \@arr; + my $entity = shift; + my $bare = shift || 0; + my @arr; + foreach ( keys( %{ $tree->{entities}->{$entity} } ) ) + { + next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' ); + push( @arr, $_ ); + } + if ( $valid_entities->{$entity} eq 'system' && !$bare ) + { + foreach ( keys( %{ $tree->{entities}->{device} } ) ) + { + next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' ); + push( @arr, $_ ); + } + } + $logger->info( "processed fields for $entity : " . join( ',', @arr ) ); + return \@arr; } sub runACL() { - my($req,$r,$entity,$changes,$blocked_changes)=@_; - my($groups) = $req->{'user'}->{'groups'}; - if(ref $groups ne 'ARRAYREF') - { - $logger->debug("groups ref= ".ref $groups) if ($logger->is_debug()); - $groups = [split(',',$groups)] if $groups; - } - my $acls = $dbh->selectall_arrayref("select * from acl where entity=?", { Slice => {} },($entity)); - foreach my $field (keys(%$changes)) - { - foreach my $acl (@$acls) - { - #skip if acl group not in users grouplist - next unless(grep(/^$acl->{'acl_group'}$/,@$groups)); - # skip of the field the acl applies to isn't being changed - next unless($field eq $acl->{'field'} || $acl->{'field'} eq '*'); - $logger->info("found acl to process: " . &make_json($acl) ); - my $eval=$acl->{'logic'}; - my $out=&doEval($req,$r,$field,$changes,$acl->{'logic'}); - if($@) - { - die 'error compiling ACL'; - } - if( $out ) - { - $logger->info("acl ran and blocked"); - $blocked_changes->{ $field }=$changes->{ $field }; - delete $changes->{ $field }; - } - } - } - return ($changes,$blocked_changes); + my ( $req, $r, $entity, $changes, $blocked_changes ) = @_; + my ($groups) = $req->{'user'}->{'groups'}; + if ( ref $groups ne 'ARRAYREF' ) + { + $logger->debug( "groups ref= " . ref $groups ) if ( $logger->is_debug() ); + $groups = [ split( ',', $groups ) ] if $groups; + } + my $acls = $dbh->selectall_arrayref( "select * from acl where entity=?", { Slice => {} }, ($entity) ); + foreach my $field ( keys(%$changes) ) + { + foreach my $acl (@$acls) + { + #skip if acl group not in users grouplist + next unless ( grep( /^$acl->{'acl_group'}$/, @$groups ) ); + + # skip of the field the acl applies to isn't being changed + next unless ( $field eq $acl->{'field'} || $acl->{'field'} eq '*' ); + $logger->info( "found acl to process: " . &make_json($acl) ); + my $eval = $acl->{'logic'}; + my $out = &doEval( $req, $r, $field, $changes, $acl->{'logic'} ); + if ($@) + { + die 'error compiling ACL'; + } + if ($out) + { + $logger->info("acl ran and blocked"); + $blocked_changes->{$field} = $changes->{$field}; + delete $changes->{$field}; + } + } + } + return ( $changes, $blocked_changes ); } sub doEval() { - my($req,$r,$f,$changes,$logic)=@_; - #$logger->debug("ACL EVAL: $logic ") - return eval($logic); -} + my ( $req, $r, $f, $changes, $logic ) = @_; + #$logger->debug("ACL EVAL: $logic ") + return eval($logic); +} # looks for function to process the request, based on entity specification in $valid_entities and http method sub ProcessRequest() { - $logger->info("detemining request process function"); - - my $requestObject=shift; - my $func='do' . $$valid_entities{$$requestObject{'entity'}} . $$requestObject{'method'}; - if($$requestObject{'method'} eq 'PUT') - { - unless ($$requestObject{'path'}[0]) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return 'no key specified'; - } - #$$requestObject{'stat'}=Apache2::Const::HTTP_ACCEPTED; - } - if($$requestObject{'method'} eq 'POST') - { - $$requestObject{'stat'}=Apache2::Const::HTTP_CREATED; - } - no strict 'refs'; - if(exists &$func) - { - $logger->info("found function $func"); - return &$func($requestObject); - } - else - { - $logger->error("no function found $func"); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return 'no entity function' - } + $logger->info("detemining request process function"); + + my $requestObject = shift; + my $func = 'do' . $$valid_entities{ $$requestObject{'entity'} } . $$requestObject{'method'}; + if ( $$requestObject{'method'} eq 'PUT' ) + { + unless ( $$requestObject{'path'}[0] ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return 'no key specified'; + } + + #$$requestObject{'stat'}=Apache2::Const::HTTP_ACCEPTED; + } + if ( $$requestObject{'method'} eq 'POST' ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_CREATED; + } + no strict 'refs'; + if ( exists &$func ) + { + $logger->info("found function $func"); + return &$func($requestObject); + } + else + { + $logger->error("no function found $func"); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return 'no entity function'; + } } sub doColumn_lkupGET() { - my $requestObject=shift; - my $entity=shift || $$requestObject{'path'}[0]; - my $col=shift || $$requestObject{'path'}[1]; - - my %lkup; - if($entity eq 'system') - { - $entity = 'device'; - } - my $entity_fields=&getFieldList($entity); -$logger->info("$entity field list: " . join (',',@$entity_fields)); - foreach (@$entity_fields) { $lkup{$_}++;} -$logger->info('doing col lkup for ' . $entity . '-> ' . $col); - - my $sql; - if($lkup{$col}) - { - $sql="select distinct $col,? from $entity order by 1 limit 2000"; - } - else - { - $sql='select distinct metadata_value from device_metadata where metadata_name=? order by 1 limit 2000'; - } - my $res=$dbh->selectcol_arrayref($sql,{},($col)); - my @new; - - foreach(@$res){$_=~s/\"//g;push(@new,$_) if $_;} - - return \@new; + my $requestObject = shift; + my $entity = shift || $$requestObject{'path'}[0]; + my $col = shift || $$requestObject{'path'}[1]; + + my %lkup; + if ( $entity eq 'system' ) + { + $entity = 'device'; + } + my $entity_fields = &getFieldList($entity); + $logger->info( "$entity field list: " . join( ',', @$entity_fields ) ); + foreach (@$entity_fields) { $lkup{$_}++; } + $logger->info( 'doing col lkup for ' . $entity . '-> ' . $col ); + + my $sql; + if ( $lkup{$col} ) + { + $sql = "select distinct $col,? from $entity order by 1 limit 2000"; + } + else + { + $sql = 'select distinct metadata_value from device_metadata where metadata_name=? order by 1 limit 2000'; + } + my $res = $dbh->selectcol_arrayref( $sql, {}, ($col) ); + my @new; + + foreach (@$res) { $_ =~ s/\"//g; push( @new, $_ ) if $_; } + + return \@new; } #audit info retreival sub doAuditGET() { - my $requestObject=shift; - my $entity=$$requestObject{'path'}[0]; - if($$valid_entities{$entity} eq 'System') - { - $entity='device'; - } - my $lkup=$$requestObject{'path'}[1]; - my $sql='select * from inv_audit where entity_name=? and entity_key=? order by change_time'; - return &recordFetch($requestObject,$sql,[$entity,$lkup]); + my $requestObject = shift; + my $entity = $$requestObject{'path'}[0]; + if ( $$valid_entities{$entity} eq 'System' ) + { + $entity = 'device'; + } + my $lkup = $$requestObject{'path'}[1]; + my $sql = 'select * from inv_audit where entity_name=? and entity_key=? order by change_time'; + return &recordFetch( $requestObject, $sql, [ $entity, $lkup ] ); } - #special api to check current user for write access sub doUserGET() { - my $requestObject=shift; - if($requestObject->{'user'}) - { - return $requestObject->{'user'}; - } - else - { - return { username=> $requestObject->{'http_auth_user'}}; - } + my $requestObject = shift; + if ( $requestObject->{'user'} ) + { + return $requestObject->{'user'}; + } + else + { + return { username => $requestObject->{'http_auth_user'} }; + } } # new traffic control api sub doTrafficControlPOST() { - my $requestObject=shift; - $requestObject->{'user'}=&doGenericGET({entity=>'user',path=>['trafficcontrol']}); - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - my ($lkup_data,$lkup); - $logger->debug("TC got POST from agent") if ($logger->is_debug()); - - foreach (@{$opt->{'traffic_control_search_fields'}}) - { - # skip serial if not dell tag - next if( $_ eq 'serial_number' && length($data->{$_}) != 7 ); - $lkup= $dbh->selectall_arrayref("select * from device where $_=?", { Slice => {} },($data->{$_})); - if(scalar(@$lkup) == 1) - { - $lkup_data=$$lkup[0]; - last; - } - } - if(ref $lkup_data eq 'ARRAY' && scalar(@$lkup_data) == 0) - { - $lkup_data=''; - } - $requestObject->{'entity'}='system'; - - # loop through the entity field and assign values based on 'fact' designation of attributes - # or default to just looking for an entry of the same name - my $data_assembled={ 'fqdn' => $data->{'fqdn'}}; - foreach my $attr (keys(%{$tree_extended->{'entities'}->{'system'}})) - { - if(ref($tree_extended->{'entities'}->{'system'}->{$attr}) eq 'HASH' && $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'}) - { - foreach my $fact_lookup (split(',',$tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'})) - { - $logger->debug("doing fact lookup for $attr with $fact_lookup"); - if($data->{$fact_lookup}) - { - $data_assembled->{$attr}= $data->{$fact_lookup}; - $logger->debug("found data lookup for $attr in fact $fact_lookup: $data->{$fact_lookup}"); - last; - } - } - } - else - { - $data_assembled->{$attr}= $data->{$attr} if($data->{$attr}); - } - } - $requestObject->{'body'}=make_json($data_assembled,{allow_nonref=>1}); - - # if we found the entry, then setup for PUT else do POST - if($lkup_data) - { - $logger->info("TC found system " . $lkup_data->{'fqdn'} . ", doing PUT"); - $requestObject->{'path'}=[$lkup_data->{'fqdn'}]; - &doSystemPUT($requestObject); - } - else - { - $logger->info("TC doing POST for new system"); - &doSystemPOST($requestObject); - } - -} + my $requestObject = shift; + $requestObject->{'user'} = &doGenericGET( { entity => 'user', path => ['trafficcontrol'] } ); + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + my ( $lkup_data, $lkup ); + $logger->debug("TC got POST from agent") if ( $logger->is_debug() ); + + foreach ( @{ $opt->{'traffic_control_search_fields'} } ) + { + # skip serial if not dell tag + next if ( $_ eq 'serial_number' && length( $data->{$_} ) != 7 ); + $lkup = $dbh->selectall_arrayref( "select * from device where $_=?", { Slice => {} }, ( $data->{$_} ) ); + if ( scalar(@$lkup) == 1 ) + { + $lkup_data = $$lkup[0]; + last; + } + } + if ( ref $lkup_data eq 'ARRAY' && scalar(@$lkup_data) == 0 ) + { + $lkup_data = ''; + } + $requestObject->{'entity'} = 'system'; + + # loop through the entity field and assign values based on 'fact' designation of attributes + # or default to just looking for an entry of the same name + my $data_assembled = { 'fqdn' => $data->{'fqdn'} }; + foreach my $attr ( keys( %{ $tree_extended->{'entities'}->{'system'} } ) ) + { + if ( ref( $tree_extended->{'entities'}->{'system'}->{$attr} ) eq 'HASH' && $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'} ) + { + foreach my $fact_lookup ( split( ',', $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'} ) ) + { + $logger->debug("doing fact lookup for $attr with $fact_lookup"); + if ( $data->{$fact_lookup} ) + { + $data_assembled->{$attr} = $data->{$fact_lookup}; + $logger->debug("found data lookup for $attr in fact $fact_lookup: $data->{$fact_lookup}"); + last; + } + } + } + else + { + $data_assembled->{$attr} = $data->{$attr} if ( $data->{$attr} ); + } + } + $requestObject->{'body'} = make_json( $data_assembled, { allow_nonref => 1 } ); + + # if we found the entry, then setup for PUT else do POST + if ($lkup_data) + { + $logger->info( "TC found system " . $lkup_data->{'fqdn'} . ", doing PUT" ); + $requestObject->{'path'} = [ $lkup_data->{'fqdn'} ]; + &doSystemPUT($requestObject); + } + else + { + $logger->info("TC doing POST for new system"); + &doSystemPOST($requestObject); + } +} sub doProvisionPcmGET() { - my $requestObject=shift; - my $id = $$requestObject{'path'}[0]; + my $requestObject = shift; + my $id = $$requestObject{'path'}[0]; - if(!$id) - { - $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_ACCEPTABLE; - return {'error'=> 'missing data (id)'}; - } + if ( !$id ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_ACCEPTABLE; + return { 'error' => 'missing data (id)' }; + } - my ($sql,$sth,$existing_data); - $sql = 'select fqdn from device where serial_number=?'; - $sth=$dbh->prepare($sql); - $sth->execute($id); - $existing_data=$sth->fetchall_arrayref({},undef); + my ( $sql, $sth, $existing_data ); + $sql = 'select fqdn from device where serial_number=?'; + $sth = $dbh->prepare($sql); + $sth->execute($id); + $existing_data = $sth->fetchall_arrayref( {}, undef ); - if (scalar(@$existing_data)>1) - { - $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return {'error'=>'Multiple systems with the same ID'}; - } - elsif(scalar(@$existing_data)) - { - $$requestObject{'stat'} = Apache2::Const::HTTP_OK; - return {'fqdn'=>$$existing_data[0]{'fqdn'}} - } + if ( scalar(@$existing_data) > 1 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return { 'error' => 'Multiple systems with the same ID' }; + } + elsif ( scalar(@$existing_data) ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_OK; + return { 'fqdn' => $$existing_data[0]{'fqdn'} }; + } - my $record={}; - $$record{'inventory_component_type'} = 'system'; - $$record{'status'} = 'idle'; - $$record{'serial_number'} = $id; + my $record = {}; + $$record{'inventory_component_type'} = 'system'; + $$record{'status'} = 'idle'; + $$record{'serial_number'} = $id; - my $name = &setNewName($record, '.' . $opt->{'prism_domain'}); + my $name = &setNewName( $record, '.' . $opt->{'prism_domain'} ); - $$requestObject{'stat'} = Apache2::Const::HTTP_OK; - return {'fqdn'=>$name}; + $$requestObject{'stat'} = Apache2::Const::HTTP_OK; + return { 'fqdn' => $name }; } # special api to fetch new hostname for provisioning sub doProvisionGET() { - my $requestObject=shift; - my ($sql,$sth,$rv); - # this code seems pointless, why check for length 7 if we are going to remove chars? - if($$requestObject{'query'}{'serial_number'} && length($$requestObject{'query'}{'serial_number'}) == 7 ) - { - $$requestObject{'query'}{'serial_number'}=~s/^\s+//g; - $$requestObject{'query'}{'serial_number'}=~s/\s+$//g; - } - if($$requestObject{'query'}{'serial_number'} && length($$requestObject{'query'}{'serial_number'}) == 7 ) - { - $sql ='select fqdn from device where serial_number=?'; - $sth=$dbh->prepare($sql); - $rv=$sth->execute(($$requestObject{'query'}{'serial_number'})); - } - elsif(!$$requestObject{'query'}{'mac_address'}) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; - return 'missing data (mac_address)' - } - else - { - $sql ='select fqdn from device where mac_address=?'; - $sth=$dbh->prepare($sql); - $rv=$sth->execute(($$requestObject{'query'}{'mac_address'})); - } - # unless($$requestObject{'query'}{'serial_number'}) - # { - # $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; - # return 'missing data (serial_number)'; - # } - # lkup system - my $data=$sth->fetchall_arrayref({},undef); - my ($hostname,$newname); - - # gather data sent in with the call - my $new={}; - foreach my $f (('rack_code','rack_position','asset_tag_number','manufacturer','product_name','serial_number',$IPADDRESSFIELD,'mac_address','inventory_component_type')) - { - $$requestObject{'query'}{$f}=~s/^\s+//g; - $$requestObject{'query'}{$f}=~s/\s+$//g; - if($$requestObject{'query'}{$f}) - { - $$new{$f}=&doFieldNormalization('system',$f,$$requestObject{'query'}{$f}); - #$$new{$f}=$$requestObject{'query'}{$f}; - } - } - - # there should only be one system - if(scalar(@$data)>1) - { - $logger->warn("found too many systems in inventory: " . scalar(@$data) ); - return 'too many entries'; -#TODO do something about finding more than one system - } - elsif(scalar(@$data)) - { - $logger->info("found system in inventory: " . $$data[0]{'fqdn'}); - # call setNewName which will rename if it doesn't match - $$new{'fqdn'}=$$data[0]{'fqdn'}; - $$new{'status'}=$$data[0]{'status'}; - $newname=&setNewName($new, '.ppops.net'); - # set the IP for this system since it's coming online from provisioning vlan - # validate that the IP is on the provisioning vlan - if($$requestObject{'query'}{$IPADDRESSFIELD} && $$requestObject{'query'}{$IPADDRESSFIELD} =~ /10\.\d{1,3}\.25/) - { - $logger->info("updating IP for system entry"); - $$requestObject{'query'}{$IPADDRESSFIELD}=~s/^\s//g; - $$requestObject{'query'}{$IPADDRESSFIELD}=~s/\s$//g; - $sql="update device set $IPADDRESSFIELD=? where fqdn=?"; - my $sth=$dbh->prepare($sql); - $logger->debug("executing: $sql with $$requestObject{'query'}{$IPADDRESSFIELD},$newname") if ($logger->is_debug()); - my $rv=$sth->execute(($$requestObject{'query'}{$IPADDRESSFIELD},$newname)); - $logger->error($sth->err . " : " . $sth->errstr) if ($sth->err); - } - } - # no entry found, insert system into inventory with new name - else - { - unless($$requestObject{'query'}{'inventory_component_type'}) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; - return 'missing data (inventory_component_type)'; - } - $$new{'status'}='idle'; - $newname=&setNewName($new, '.ppops.net'); - if($newname=~/ERROR/) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; - } - } - if($$requestObject{'_format'} eq 'text') - { - $hostname=$newname; - } - else - { - $hostname={fqdn=>$newname}; - - } - $logger->info("new name assigned : $newname"); - return $hostname; - + my $requestObject = shift; + my ( $sql, $sth, $rv ); + + # this code seems pointless, why check for length 7 if we are going to remove chars? + if ( $$requestObject{'query'}{'serial_number'} && length( $$requestObject{'query'}{'serial_number'} ) == 7 ) + { + $$requestObject{'query'}{'serial_number'} =~ s/^\s+//g; + $$requestObject{'query'}{'serial_number'} =~ s/\s+$//g; + } + if ( $$requestObject{'query'}{'serial_number'} && length( $$requestObject{'query'}{'serial_number'} ) == 7 ) + { + $sql = 'select fqdn from device where serial_number=?'; + $sth = $dbh->prepare($sql); + $rv = $sth->execute( ( $$requestObject{'query'}{'serial_number'} ) ); + } + elsif ( !$$requestObject{'query'}{'mac_address'} ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FAILED_DEPENDENCY; + return 'missing data (mac_address)'; + } + else + { + $sql = 'select fqdn from device where mac_address=?'; + $sth = $dbh->prepare($sql); + $rv = $sth->execute( ( $$requestObject{'query'}{'mac_address'} ) ); + } + + # unless($$requestObject{'query'}{'serial_number'}) + # { + # $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; + # return 'missing data (serial_number)'; + # } + # lkup system + my $data = $sth->fetchall_arrayref( {}, undef ); + my ( $hostname, $newname ); + + # gather data sent in with the call + my $new = {}; + foreach my $f ( ( 'rack_code', 'rack_position', 'asset_tag_number', 'manufacturer', 'product_name', 'serial_number', $IPADDRESSFIELD, 'mac_address', 'inventory_component_type' ) ) + { + $$requestObject{'query'}{$f} =~ s/^\s+//g; + $$requestObject{'query'}{$f} =~ s/\s+$//g; + if ( $$requestObject{'query'}{$f} ) + { + $$new{$f} = &doFieldNormalization( 'system', $f, $$requestObject{'query'}{$f} ); + + #$$new{$f}=$$requestObject{'query'}{$f}; + } + } + + # there should only be one system + if ( scalar(@$data) > 1 ) + { + $logger->warn( "found too many systems in inventory: " . scalar(@$data) ); + return 'too many entries'; + + #TODO do something about finding more than one system + } + elsif ( scalar(@$data) ) + { + $logger->info( "found system in inventory: " . $$data[0]{'fqdn'} ); + + # call setNewName which will rename if it doesn't match + $$new{'fqdn'} = $$data[0]{'fqdn'}; + $$new{'status'} = $$data[0]{'status'}; + $newname = &setNewName( $new, '.ppops.net' ); + + # set the IP for this system since it's coming online from provisioning vlan + # validate that the IP is on the provisioning vlan + if ( $$requestObject{'query'}{$IPADDRESSFIELD} && $$requestObject{'query'}{$IPADDRESSFIELD} =~ /10\.\d{1,3}\.25/ ) + { + $logger->info("updating IP for system entry"); + $$requestObject{'query'}{$IPADDRESSFIELD} =~ s/^\s//g; + $$requestObject{'query'}{$IPADDRESSFIELD} =~ s/\s$//g; + $sql = "update device set $IPADDRESSFIELD=? where fqdn=?"; + my $sth = $dbh->prepare($sql); + $logger->debug("executing: $sql with $$requestObject{'query'}{$IPADDRESSFIELD},$newname") if ( $logger->is_debug() ); + my $rv = $sth->execute( ( $$requestObject{'query'}{$IPADDRESSFIELD}, $newname ) ); + $logger->error( $sth->err . " : " . $sth->errstr ) if ( $sth->err ); + } + } + + # no entry found, insert system into inventory with new name + else + { + unless ( $$requestObject{'query'}{'inventory_component_type'} ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FAILED_DEPENDENCY; + return 'missing data (inventory_component_type)'; + } + $$new{'status'} = 'idle'; + $newname = &setNewName( $new, '.ppops.net' ); + if ( $newname =~ /ERROR/ ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FAILED_DEPENDENCY; + } + } + if ( $$requestObject{'_format'} eq 'text' ) + { + $hostname = $newname; + } + else + { + $hostname = { fqdn => $newname }; + + } + $logger->info("new name assigned : $newname"); + return $hostname; + } sub setNewName() { - my $r=shift; - my $suffix=shift || ""; - my ($sql,$parms,$where); - my $newname; - # check to see if the name is correct or if box is set to production/deployment - if ($$r{'fqdn'}!~/m\d{7}\.ppops\.net/ && $$r{'status'} ne 'production' && $$r{'status'} ne 'deployment' ) { - if (defined $$r{'mac_address'}) { - $newname = 'temp-' . $$r{'mac_address'}; - $newname =~ s/://g; - } elsif (defined $$r{'serial_number'}) { - $newname = 'temp-' . $$r{'serial_number'}; - } else { - return "ERROR: must provide serial number or MAC address"; - } - } else { - $newname=$$r{'fqdn'}; - } - - # fqdn passed in, so updating existing - if ($$r{'fqdn'}) { - $sql="update device set fqdn=?"; - push(@$parms,$newname); - } - # otherwise trying insert - else { - $sql='insert into device set fqdn=?'; - push(@$parms,$newname); - } - foreach my $f (keys(%$r)) { - if ($$r{$f} && $f ne 'fqdn') { - $sql.=", $f=? "; - push(@$parms,$$r{$f}); - } - } - # doing device update so adding where clause - if ($sql =~ /update\ device/) { - $where= " where fqdn=?"; - push(@$parms,$$r{'fqdn'}); - } - - my $dbh=DBI->connect("DBI:mysql:database=inventory;host=$DBHOST", - $DBUSER,$DBPASS,{AutoCommit=>0,RaiseError=>1}); - my $sth; - - eval { - $sth = $dbh->prepare("$sql$where"); - executeDbStatement($sth, $sql, @$parms); - - my $device_id = $sth->{mysql_insertid}; - - # We need to generate a name based on the auto-incremented - # id column if this is a new device - if ($newname =~ /^temp-/) { - $newname = sprintf "m%07d", $device_id; - $newname .= $suffix; - - $sql = "update device set fqdn=? where id=?"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, ($newname, $device_id)); - } - - $dbh->commit; - }; - if ($@) { - my $errstr; - - if (defined $sth && $sth->err) { - $errstr = $sth->err . " : " . $sth->errstr; - $logger->error($errstr); - } else { - $errstr = $@; - } - - $newname = "ERROR: $errstr"; - } - - return $newname; -} + my $r = shift; + my $suffix = shift || ""; + my ( $sql, $parms, $where ); + my $newname; + + # check to see if the name is correct or if box is set to production/deployment + if ( $$r{'fqdn'} !~ /m\d{7}\.ppops\.net/ && $$r{'status'} ne 'production' && $$r{'status'} ne 'deployment' ) + { + if ( defined $$r{'mac_address'} ) + { + $newname = 'temp-' . $$r{'mac_address'}; + $newname =~ s/://g; + } + elsif ( defined $$r{'serial_number'} ) + { + $newname = 'temp-' . $$r{'serial_number'}; + } + else + { + return "ERROR: must provide serial number or MAC address"; + } + } + else + { + $newname = $$r{'fqdn'}; + } -sub doSql(){ - my $sql=shift; - my $parms=shift; - my $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS); - - my $sth=$dbh->prepare($sql); - my $sql_out; - $logger->debug("executing: $sql with " . &make_json($parms,{allow_nonref=>1}) ) if ($logger->is_debug()); - my $rv=$sth->execute(@$parms); - $logger->error($sth->err . " : " . $sth->errstr ) if ($sth->err); - if($sth->err) - { - return {err=>$sth->err , errstr=> $sth->errstr}; - } - if($sql=~/select/) - { - $sql_out=$sth->fetchall_arrayref({},undef); - } - return { data=>$sql_out }; + # fqdn passed in, so updating existing + if ( $$r{'fqdn'} ) + { + $sql = "update device set fqdn=?"; + push( @$parms, $newname ); + } + + # otherwise trying insert + else + { + $sql = 'insert into device set fqdn=?'; + push( @$parms, $newname ); + } + foreach my $f ( keys(%$r) ) + { + if ( $$r{$f} && $f ne 'fqdn' ) + { + $sql .= ", $f=? "; + push( @$parms, $$r{$f} ); + } + } + + # doing device update so adding where clause + if ( $sql =~ /update\ device/ ) + { + $where = " where fqdn=?"; + push( @$parms, $$r{'fqdn'} ); + } + + my $dbh = DBI->connect( "DBI:mysql:database=inventory;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); + my $sth; + + eval { + $sth = $dbh->prepare("$sql$where"); + executeDbStatement( $sth, $sql, @$parms ); + + my $device_id = $sth->{mysql_insertid}; + + # We need to generate a name based on the auto-incremented + # id column if this is a new device + if ( $newname =~ /^temp-/ ) + { + $newname = sprintf "m%07d", $device_id; + $newname .= $suffix; + + $sql = "update device set fqdn=? where id=?"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, ( $newname, $device_id ) ); + } + + $dbh->commit; + }; + if ($@) + { + my $errstr; + + if ( defined $sth && $sth->err ) + { + $errstr = $sth->err . " : " . $sth->errstr; + $logger->error($errstr); + } + else + { + $errstr = $@; + } + + $newname = "ERROR: $errstr"; + } + + return $newname; } -sub recordFetch(){ - my $requestObject=shift; - my $sql=shift; - my $parms=shift; - my $return; - - my $rtn=&doSql($sql,$parms); - if($$rtn{'err'}) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $$rtn{'err'} . " : " . $$rtn{'errstr'}; - } - my $data=$$rtn{'data'}; - - # format output inside extjs compatible object if requested - if($$requestObject{'query'}{'_extjs'}) - { - $return={ - records=>$data, - total=>scalar(@$data), - metaData=>{ - root=>'records', - totalProperty=>'total', - id=>$tree->{entities}->{$$requestObject{'entity'}}->{key}, - fields=>&getFieldList($$requestObject{'entity'}) - } - }; - if( !scalar(@{$$return{metaData}{fields}}) && scalar(@{$$return{records}}) ) - { - @{$$return{metaData}{fields}}=keys(%{$$return{records}[0]}) - } - } - elsif($$requestObject{'path'}[0]) - { - $return=$$data[0]; - } - else - { - $return=$data - } - return $return; - + +sub doSql() +{ + my $sql = shift; + my $parms = shift; + my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); + + my $sth = $dbh->prepare($sql); + my $sql_out; + $logger->debug( "executing: $sql with " . &make_json( $parms, { allow_nonref => 1 } ) ) if ( $logger->is_debug() ); + my $rv = $sth->execute(@$parms); + $logger->error( $sth->err . " : " . $sth->errstr ) if ( $sth->err ); + if ( $sth->err ) + { + return { err => $sth->err, errstr => $sth->errstr }; + } + if ( $sql =~ /select/ ) + { + $sql_out = $sth->fetchall_arrayref( {}, undef ); + } + return { data => $sql_out }; } +sub recordFetch() +{ + my $requestObject = shift; + my $sql = shift; + my $parms = shift; + my $return; + + my $rtn = &doSql( $sql, $parms ); + if ( $$rtn{'err'} ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $$rtn{'err'} . " : " . $$rtn{'errstr'}; + } + my $data = $$rtn{'data'}; + + # format output inside extjs compatible object if requested + if ( $$requestObject{'query'}{'_extjs'} ) + { + $return = { + records => $data, + total => scalar(@$data), + metaData => { + root => 'records', + totalProperty => 'total', + id => $tree->{entities}->{ $$requestObject{'entity'} }->{key}, + fields => &getFieldList( $$requestObject{'entity'} ) + } + }; + if ( !scalar( @{ $$return{metaData}{fields} } ) && scalar( @{ $$return{records} } ) ) + { + @{ $$return{metaData}{fields} } = keys( %{ $$return{records}[0] } ); + } + } + elsif ( $$requestObject{'path'}[0] ) + { + $return = $$data[0]; + } + else + { + $return = $data; + } + return $return; + +} # handle generic updates sub doGenericPUT { - my ($sql); - my $requestObject=shift; - my $entity=$$requestObject{'entity'}; - $logger->info("processing PUT"); - my $dbs=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS,{AutoCommit=>1}); - $dbs->begin_work; - my (@sql,$parms,@errors); - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - #audit fetch record to comparison during audit - my $now=$dbh->selectcol_arrayref('select now()'); - my @entity_fields=@{&getFieldList($$requestObject{'entity'})}; - my $lkup_data=&doGenericGET($requestObject); - if(scalar(keys(%$lkup_data)) == 0) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return 'There is no resource at this location'; - - } - if($$lkup_data{'metaData'}) - { - $lkup_data=$$lkup_data{'records'}[0] - } - elsif(ref $lkup_data eq 'ARRAYREF') - { - $lkup_data=$$lkup_data[0]; - } - - # strip out unchanged data and trigger mtime if needed - my $mtime; - foreach(@entity_fields) - { - $$data{$_}=&doFieldNormalization($entity,$_,$$data{$_}) if exists $$data{$_}; - $mtime= $$now[0] if(exists $$data{$_} && !$tree_extended->{entities}->{'system'}->{$_}->{meta} ); - delete $$data{$_} if(defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_}); - } - my $blocked_changes={}; - &runACL($requestObject,$lkup_data,$entity,$data,$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("changes: " . &make_json($data)); - $logger->info("blocked changes: " . &make_json($blocked_changes) ); - if($requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - return 'ACL blocked change: ' . &make_json($blocked_changes); - } - if(scalar(keys(%$blocked_changes))) - { - my $change_item={ - change_ip=>$$requestObject{'ip_address'}, - change_user=>$requestObject->{'user'}->{'username'}, - change_time=>$$now[0], - entity=>$$requestObject{'entity'}, - entity_key=>$$lkup_data{$tree->{'entities'}->{$$requestObject{'entity'}}->{'key'}}, - change_content=>&make_json($blocked_changes) - }; - &doGenericPOST({ - entity=>'change_queue', - body=>&make_json($change_item), - }); - $logger->info("queued change"); - return "Change queued for approval"; - } - - foreach my $f (@entity_fields) - { - if(exists $$data{$f}) - { - if($$data{$f} eq '') - { - push(@$parms,undef); - } - else - { - push(@$parms,$$data{$f}); - } - - push(@sql,"$f=?"); - #audit check each field and record change if done - if(!defined $$lkup_data{$f} || $$data{$f} ne $$lkup_data{$f}) - { - $dbs->do('insert into inv_audit set + my ($sql); + my $requestObject = shift; + my $entity = $$requestObject{'entity'}; + $logger->info("processing PUT"); + my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); + $dbs->begin_work; + my ( @sql, $parms, @errors ); + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + + #audit fetch record to comparison during audit + my $now = $dbh->selectcol_arrayref('select now()'); + my @entity_fields = @{ &getFieldList( $$requestObject{'entity'} ) }; + my $lkup_data = &doGenericGET($requestObject); + if ( scalar( keys(%$lkup_data) ) == 0 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return 'There is no resource at this location'; + + } + if ( $$lkup_data{'metaData'} ) + { + $lkup_data = $$lkup_data{'records'}[0]; + } + elsif ( ref $lkup_data eq 'ARRAYREF' ) + { + $lkup_data = $$lkup_data[0]; + } + + # strip out unchanged data and trigger mtime if needed + my $mtime; + foreach (@entity_fields) + { + $$data{$_} = &doFieldNormalization( $entity, $_, $$data{$_} ) if exists $$data{$_}; + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{entities}->{'system'}->{$_}->{meta} ); + delete $$data{$_} if ( defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); + } + my $blocked_changes = {}; + &runACL( $requestObject, $lkup_data, $entity, $data, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "changes: " . &make_json($data) ); + $logger->info( "blocked changes: " . &make_json($blocked_changes) ); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + return 'ACL blocked change: ' . &make_json($blocked_changes); + } + if ( scalar( keys(%$blocked_changes) ) ) + { + my $change_item = { + change_ip => $$requestObject{'ip_address'}, + change_user => $requestObject->{'user'}->{'username'}, + change_time => $$now[0], + entity => $$requestObject{'entity'}, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + change_content => &make_json($blocked_changes) + }; + &doGenericPOST( + { + entity => 'change_queue', + body => &make_json($change_item), + } + ); + $logger->info("queued change"); + return "Change queued for approval"; + } + + foreach my $f (@entity_fields) + { + if ( exists $$data{$f} ) + { + if ( $$data{$f} eq '' ) + { + push( @$parms, undef ); + } + else + { + push( @$parms, $$data{$f} ); + } + + push( @sql, "$f=?" ); + + #audit check each field and record change if done + if ( !defined $$lkup_data{$f} || $$data{$f} ne $$lkup_data{$f} ) + { + $dbs->do( + 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -989,108 +1038,115 @@ sub doGenericPUT change_time=?, change_user=?, change_ip=?', - {}, - ($entity, - $$lkup_data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}, - $f, #field - $$lkup_data{$f}, #old val - $$data{$f}, # new val - $$now[0], - $requestObject->{'user'}->{'username'}, # user - $$requestObject{'ip_address'} # ip - ) - ); - } - } - } - if(scalar(@sql) == 0) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_NO_CONTENT; - $dbs->commit; - return; - } - my $sql_set=join(',',@sql); - # assemple final sql - $sql="update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; - push(@$parms,$$requestObject{'path'}[0]); - - ## do sql and record any errors - my $sth=$dbs->prepare($sql); - if ($sth->err) - { - push(@errors,$dbs->err . ": " . $dbs->errstr); - $logger->error($sth->err . " : " . $sth->errstr); - } - $logger->debug("executing: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - my $rv=$sth->execute(@$parms); - if ($sth->err) - { - push(@errors,$dbs->err . ": " . $dbs->errstr); - $logger->error($sth->err . " : " . $sth->errstr ); - } - - if(scalar(@errors)) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return \@errors; - } - else - { - $dbs->commit; - # check to see if key value was chnaged during this put, and adjust for GET - if($$data{ $tree->{entities}->{$$requestObject{'entity'}}->{key} }) - { - $$requestObject{'path'}[0] = $$data{ $tree->{entities}->{$$requestObject{'entity'}}->{key} }; - } - return &doGenericGET($requestObject); - } - + {}, + ( + $entity, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $f, #field + $$lkup_data{$f}, #old val + $$data{$f}, # new val + $$now[0], + $requestObject->{'user'}->{'username'}, # user + $$requestObject{'ip_address'} # ip + ) + ); + } + } + } + if ( scalar(@sql) == 0 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NO_CONTENT; + $dbs->commit; + return; + } + my $sql_set = join( ',', @sql ); + + # assemple final sql + $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; + push( @$parms, $$requestObject{'path'}[0] ); + + ## do sql and record any errors + my $sth = $dbs->prepare($sql); + if ( $sth->err ) + { + push( @errors, $dbs->err . ": " . $dbs->errstr ); + $logger->error( $sth->err . " : " . $sth->errstr ); + } + $logger->debug( "executing: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + my $rv = $sth->execute(@$parms); + if ( $sth->err ) + { + push( @errors, $dbs->err . ": " . $dbs->errstr ); + $logger->error( $sth->err . " : " . $sth->errstr ); + } + + if ( scalar(@errors) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return \@errors; + } + else + { + $dbs->commit; + + # check to see if key value was chnaged during this put, and adjust for GET + if ( $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} } ) + { + $$requestObject{'path'}[0] = $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }; + } + return &doGenericGET($requestObject); + } + } # handle generic creations sub doGenericPOST { - my ($sql); - my $requestObject=shift; - my $entity=$$requestObject{'entity'}; - $logger->info("processing POST"); - my (@sql,$parms); - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - my $blocked_changes={}; - &runACL($requestObject,{},$entity,$data,$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("blocked PUT fields: " . &make_json($blocked_changes) ); - my $now=$dbh->selectcol_arrayref('select now()'); - if($requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) - { - $dbh->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - return 'ACL blocked change: ' . &make_json($blocked_changes); - } - - foreach my $f (@{&getFieldList($$requestObject{'entity'})}) - { - if((!exists $$data{$f} || $$data{$f} == '') && $tree_extended->{entities}->{$entity}->{$f}->{default_value}) - { - $$data{$_} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; - } - if(exists $$data{$f}) - { - $$data{$f}=&doFieldNormalization($entity,$f,$$data{$f}); - push(@$parms,$$data{$f}); - push(@sql,"$f=?"); - } - } - my $sql_set=join(',',@sql); - $sql="insert into $entity set $sql_set"; - my $sth=$dbh->prepare($sql); - # set for error and return if db prepare had errors - $logger->error($sth->err . " : " . $sth->errstr) if ($sth->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR if $sth->err; - return $sth->err . " : " . $sth->errstr if ($sth->err); - #audit entry for create - $dbh->do('insert into inv_audit set + my ($sql); + my $requestObject = shift; + my $entity = $$requestObject{'entity'}; + $logger->info("processing POST"); + my ( @sql, $parms ); + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + my $blocked_changes = {}; + &runACL( $requestObject, {}, $entity, $data, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "blocked PUT fields: " . &make_json($blocked_changes) ); + my $now = $dbh->selectcol_arrayref('select now()'); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) + { + $dbh->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + return 'ACL blocked change: ' . &make_json($blocked_changes); + } + + foreach my $f ( @{ &getFieldList( $$requestObject{'entity'} ) } ) + { + if ( ( !exists $$data{$f} || $$data{$f} == '' ) && $tree_extended->{entities}->{$entity}->{$f}->{default_value} ) + { + $$data{$_} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; + } + if ( exists $$data{$f} ) + { + $$data{$f} = &doFieldNormalization( $entity, $f, $$data{$f} ); + push( @$parms, $$data{$f} ); + push( @sql, "$f=?" ); + } + } + my $sql_set = join( ',', @sql ); + $sql = "insert into $entity set $sql_set"; + my $sth = $dbh->prepare($sql); + + # set for error and return if db prepare had errors + $logger->error( $sth->err . " : " . $sth->errstr ) if ( $sth->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR if $sth->err; + return $sth->err . " : " . $sth->errstr if ( $sth->err ); + + #audit entry for create + $dbh->do( + 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -1099,189 +1155,199 @@ sub doGenericPOST change_time=?, change_user=?, change_ip=?', - {}, - ($entity, - $$data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}, - 'record', #field - '', #old val - 'CREATED', # new val - $$now[0], - $requestObject->{'user'}->{'username'}, # user - $$requestObject{'ip_address'} # ip - ) - ); - - # run sql - $logger->debug("executing: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - my $rv=$sth->execute(@$parms); - #return error if db insert had errors - if($sth->err) - { - $logger->error($sth->err . " : " . $sth->errstr ); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $sth->err . " : " . $sth->errstr; - } - $$requestObject{'headers_out'}=['Location',"/cmdb_api/v1/" . $entity . "/" . $$data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}]; - return; + {}, + ( + $entity, + $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + 'record', #field + '', #old val + 'CREATED', # new val + $$now[0], + $requestObject->{'user'}->{'username'}, # user + $$requestObject{'ip_address'} # ip + ) + ); + + # run sql + $logger->debug( "executing: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + my $rv = $sth->execute(@$parms); + + #return error if db insert had errors + if ( $sth->err ) + { + $logger->error( $sth->err . " : " . $sth->errstr ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $sth->err . " : " . $sth->errstr; + } + $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/" . $entity . "/" . $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} } ]; + return; } -sub doAclDELETE { - doGenericDELETE(@_); +sub doAclDELETE +{ + doGenericDELETE(@_); } -sub doAclGET { - doGenericGET(@_); +sub doAclGET +{ + doGenericGET(@_); } -sub doAclPOST { - my ($sql); - my $requestObject=shift; - my $entity=$$requestObject{'entity'}; - $logger->info("processing POST"); - my (@sql,$parms); - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - - if($$data{'logic'}) - { - my $r = { }; - my $f = ''; - my $req = $requestObject; - my $changes = {}; - eval($$data{'logic'}); - if($@) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return "Syntax error in ACL logic: $@"; - } - } - - foreach my $f (@{&getFieldList($$requestObject{'entity'})}) - { - if(exists $$data{$f}) - { - $$data{$f}=&doFieldNormalization($entity,$f,$$data{$f}); - push(@$parms,$$data{$f}); - push(@sql,"$f=?") - } - } - my $sql_set=join(',',@sql); - $sql="insert into $entity set $sql_set"; - my $sth=$dbh->prepare($sql); - if($sth->err) - { - $logger->error($sth->err . " : " . $sth->errstr); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $sth->err . " : " . $sth->errstr; - } - $logger->debug("executing: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - my $rv=$sth->execute(@$parms); - if($sth->err) - { - $logger->error($sth->err . " : " . $sth->errstr ); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $sth->err . " : " . $sth->errstr; - } - $$requestObject{'headers_out'}=['Location',"/cmdb_api/v1/acl/" . $$data{'acl_id'}]; - return; +sub doAclPOST +{ + my ($sql); + my $requestObject = shift; + my $entity = $$requestObject{'entity'}; + $logger->info("processing POST"); + my ( @sql, $parms ); + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + + if ( $$data{'logic'} ) + { + my $r = {}; + my $f = ''; + my $req = $requestObject; + my $changes = {}; + eval( $$data{'logic'} ); + if ($@) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return "Syntax error in ACL logic: $@"; + } + } + foreach my $f ( @{ &getFieldList( $$requestObject{'entity'} ) } ) + { + if ( exists $$data{$f} ) + { + $$data{$f} = &doFieldNormalization( $entity, $f, $$data{$f} ); + push( @$parms, $$data{$f} ); + push( @sql, "$f=?" ); + } + } + my $sql_set = join( ',', @sql ); + $sql = "insert into $entity set $sql_set"; + my $sth = $dbh->prepare($sql); + if ( $sth->err ) + { + $logger->error( $sth->err . " : " . $sth->errstr ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $sth->err . " : " . $sth->errstr; + } + $logger->debug( "executing: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + my $rv = $sth->execute(@$parms); + if ( $sth->err ) + { + $logger->error( $sth->err . " : " . $sth->errstr ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $sth->err . " : " . $sth->errstr; + } + $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/acl/" . $$data{'acl_id'} ]; + return; } -sub doAclPUT { - my ($sql); - my $requestObject=shift; - my $entity=$$requestObject{'entity'}; - $logger->info("processing PUT"); - my $dbs=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS,{AutoCommit=>1}); - $dbs->begin_work; - my (@sql,$parms,@errors); - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - #audit fetch record to comparison during audit - my $now=$dbh->selectcol_arrayref('select now()'); - my @entity_fields=@{&getFieldList($$requestObject{'entity'})}; - my $lkup_data=&doGenericGET($requestObject); - if($$lkup_data{'metaData'}) - { - $lkup_data=$$lkup_data{'records'}[0] - } - elsif(ref $lkup_data eq 'ARRAYREF') - { - $lkup_data=$$lkup_data[0]; - } - - # strip out unchanged data and trigger mtime if needed - my $mtime; - foreach(@entity_fields) - { - $$data{$_}=&doFieldNormalization($entity,$_,$$data{$_}) if exists $$data{$_}; - $mtime= $$now[0] if(exists $$data{$_} && !$tree_extended->{entities}->{'system'}->{$_}->{meta} ); - delete $$data{$_} if(exists $$data{$_} && exists $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_}); - } - my $blocked_changes={}; - &runACL($requestObject,$lkup_data,$entity,$data,$blocked_changes); - - # If the change is otherwise acceptable, and we are changing the logic, - # verify syntax - if($$data{'logic'}) - { - my $r = { }; - my $f = ''; - my $req = $requestObject; - my $changes = {}; - eval($$data{'logic'}); - if($@) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return "Syntax error in ACL logic: $@" - } - } - - # if the user is not a system user, then error out now if needed - $logger->info("changes: " . &make_json($data) ); - $logger->info("blocked changes: " . &make_json($blocked_changes) ); - if($requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return 'ACL blocked change: ' . &make_json($blocked_changes); - } - if(scalar(keys(%$blocked_changes))) - { - my $change_item={ - change_ip=>$$requestObject{'ip_address'}, - change_user=>$requestObject->{'user'}->{'username'}, - change_time=>$$now[0], - entity=>$$requestObject{'entity'}, - entity_key=>$$lkup_data{$tree->{'entities'}->{$$requestObject{'entity'}}->{'key'}}, - change_content=>&make_json($blocked_changes) - }; - &doGenericPOST({ - entity=>'change_queue', - body=>&make_json($change_item), - }); - $logger->info("queued change"); - return "Change queued for approval"; - } - - foreach my $f (@entity_fields) - { - if(exists $$data{$f}) - { - if($$data{$f} eq '') - { - push(@$parms,undef); - } - else - { - push(@$parms,$$data{$f}); - } - - push(@sql,"$f=?"); - #audit check each field and record change if done - if($$data{$f} ne $$lkup_data{$f}) - { - $dbs->do('insert into inv_audit set +sub doAclPUT +{ + my ($sql); + my $requestObject = shift; + my $entity = $$requestObject{'entity'}; + $logger->info("processing PUT"); + my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); + $dbs->begin_work; + my ( @sql, $parms, @errors ); + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + + #audit fetch record to comparison during audit + my $now = $dbh->selectcol_arrayref('select now()'); + my @entity_fields = @{ &getFieldList( $$requestObject{'entity'} ) }; + my $lkup_data = &doGenericGET($requestObject); + if ( $$lkup_data{'metaData'} ) + { + $lkup_data = $$lkup_data{'records'}[0]; + } + elsif ( ref $lkup_data eq 'ARRAYREF' ) + { + $lkup_data = $$lkup_data[0]; + } + + # strip out unchanged data and trigger mtime if needed + my $mtime; + foreach (@entity_fields) + { + $$data{$_} = &doFieldNormalization( $entity, $_, $$data{$_} ) if exists $$data{$_}; + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{entities}->{'system'}->{$_}->{meta} ); + delete $$data{$_} if ( exists $$data{$_} && exists $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); + } + my $blocked_changes = {}; + &runACL( $requestObject, $lkup_data, $entity, $data, $blocked_changes ); + + # If the change is otherwise acceptable, and we are changing the logic, + # verify syntax + if ( $$data{'logic'} ) + { + my $r = {}; + my $f = ''; + my $req = $requestObject; + my $changes = {}; + eval( $$data{'logic'} ); + if ($@) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return "Syntax error in ACL logic: $@"; + } + } + + # if the user is not a system user, then error out now if needed + $logger->info( "changes: " . &make_json($data) ); + $logger->info( "blocked changes: " . &make_json($blocked_changes) ); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return 'ACL blocked change: ' . &make_json($blocked_changes); + } + if ( scalar( keys(%$blocked_changes) ) ) + { + my $change_item = { + change_ip => $$requestObject{'ip_address'}, + change_user => $requestObject->{'user'}->{'username'}, + change_time => $$now[0], + entity => $$requestObject{'entity'}, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + change_content => &make_json($blocked_changes) + }; + &doGenericPOST( + { + entity => 'change_queue', + body => &make_json($change_item), + } + ); + $logger->info("queued change"); + return "Change queued for approval"; + } + + foreach my $f (@entity_fields) + { + if ( exists $$data{$f} ) + { + if ( $$data{$f} eq '' ) + { + push( @$parms, undef ); + } + else + { + push( @$parms, $$data{$f} ); + } + + push( @sql, "$f=?" ); + + #audit check each field and record change if done + if ( $$data{$f} ne $$lkup_data{$f} ) + { + $dbs->do( + 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -1290,425 +1356,459 @@ sub doAclPUT { change_time=?, change_user=?, change_ip=?', - {}, - ($entity, - $$lkup_data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}, - $f, #field - $$lkup_data{$f}, #old val - $$data{$f}, # new val - $$now[0], - $requestObject->{'user'}->{'username'}, # user - $$requestObject{'ip_address'} # ip - ) - ); - } - } - } - my $sql_set=join(',',@sql); - # assemple final sql - $sql="update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; - push(@$parms,$$requestObject{'path'}[0]); - - ## do sql and record any errors - my $sth=$dbs->prepare($sql); - if ($sth->err) - { - push(@errors,$dbs->err . ": " . $dbs->errstr); - $logger->error($sth->err . " : " . $sth->errstr); - } - $logger->debug("executing: $sql with " . join(',',@$parms)) if ($logger->is_debug()); - my $rv=$sth->execute(@$parms); - if ($sth->err) - { - push(@errors,$dbs->err . ": " . $dbs->errstr); - $logger->error($sth->err . " : " . $sth->errstr); - } - - if(scalar(@errors)) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return \@errors; - } - else - { - $dbs->commit; - } - return; + {}, + ( + $entity, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $f, #field + $$lkup_data{$f}, #old val + $$data{$f}, # new val + $$now[0], + $requestObject->{'user'}->{'username'}, # user + $$requestObject{'ip_address'} # ip + ) + ); + } + } + } + my $sql_set = join( ',', @sql ); + + # assemple final sql + $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; + push( @$parms, $$requestObject{'path'}[0] ); + + ## do sql and record any errors + my $sth = $dbs->prepare($sql); + if ( $sth->err ) + { + push( @errors, $dbs->err . ": " . $dbs->errstr ); + $logger->error( $sth->err . " : " . $sth->errstr ); + } + $logger->debug( "executing: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + my $rv = $sth->execute(@$parms); + if ( $sth->err ) + { + push( @errors, $dbs->err . ": " . $dbs->errstr ); + $logger->error( $sth->err . " : " . $sth->errstr ); + } + + if ( scalar(@errors) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return \@errors; + } + else + { + $dbs->commit; + } + return; } sub doChangeQueuePOST() { - doGenericPOST(@_); + doGenericPOST(@_); } + sub doChangeQueuePUT() { - doGenericPUT(@_); + doGenericPUT(@_); } + sub doChangeQueueDELETE { - doGenericDELETE(@_); + doGenericDELETE(@_); } + #handles changequeue fetches sub doChangeQueueGET() { - # for internal requests: - # ro = { entity, path[0] query } - - my ($keyval,$sql,$parms,$return); - $logger->info("processing GET"); - my $requestObject=shift; - # check to see if this entity requires special processing, otherwise handle with generic - # assemble sql based on input parameters - $sql="select * from change_queue ch left join device d on ch.entity_key=d.fqdn where "; - - # check for path key value and add if specified - if($$requestObject{'path'}[0]) - { - $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ($logger->is_debug()); - $sql.=" $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; - push(@$parms,$$requestObject{'path'}[0]); - } - $logger->debug("getparms: $$requestObject{getparams}") if ($logger->is_debug()); - my $device_fields=&getFieldList('device',0); - my $change_fields=&getFieldList('change_queue',0); - if($$requestObject{getparams}) { - my @ranges=split(/[&;]/, $$requestObject{getparams}); - foreach my $range (@ranges) { - next unless $range =~ /(\w+)([!~>=<]+)(.+)/; - my $key = $1; - my $op = $2; - my $val = $3; + # for internal requests: + # ro = { entity, path[0] query } + + my ( $keyval, $sql, $parms, $return ); + $logger->info("processing GET"); + my $requestObject = shift; + + # check to see if this entity requires special processing, otherwise handle with generic + # assemble sql based on input parameters + $sql = "select * from change_queue ch left join device d on ch.entity_key=d.fqdn where "; + + # check for path key value and add if specified + if ( $$requestObject{'path'}[0] ) + { + $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); + $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; + push( @$parms, $$requestObject{'path'}[0] ); + } + $logger->debug("getparms: $$requestObject{getparams}") if ( $logger->is_debug() ); + my $device_fields = &getFieldList( 'device', 0 ); + my $change_fields = &getFieldList( 'change_queue', 0 ); + if ( $$requestObject{getparams} ) + { + my @ranges = split( /[&;]/, $$requestObject{getparams} ); + foreach my $range (@ranges) + { + next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + my $key = $1; + my $op = $2; + my $val = $3; next if $key =~ /^_/; - next unless (grep(/^$key$/,@$device_fields) || grep(/^$key$/,@$change_fields)); - $val =~ s/'/%/g; - $op = 'LIKE' if $op eq '='; - $op = 'NOT LIKE' if $op eq '!='; - $op = 'RLIKE' if $op eq '~'; - $op = 'NOT RLIKE' if $op eq '!~'; - $logger->debug("Found param: $key $op $val") if ($logger->is_debug()); - $val =~ s/\*/%/g; - $sql.=" and " if($sql!~/where\ $/); - $sql.= grep(/^$key$/,@$device_fields) ? " d." : " ch."; - $sql.="$key $op '$val'"; - - } - } - $sql=~s/where\ // if($sql=~/where\ $/); - my $rtn= &recordFetch($requestObject,$sql,$parms); - - if(ref $rtn eq 'HASH' && defined $rtn->{metaData} && defined $rtn->{metaData}->{fields}) - { - push(@{$rtn->{metaData}->{fields}},@$device_fields); - } - return $rtn; + next unless ( grep( /^$key$/, @$device_fields ) || grep( /^$key$/, @$change_fields ) ); + $val =~ s/'/%/g; + $op = 'LIKE' if $op eq '='; + $op = 'NOT LIKE' if $op eq '!='; + $op = 'RLIKE' if $op eq '~'; + $op = 'NOT RLIKE' if $op eq '!~'; + $logger->debug("Found param: $key $op $val") if ( $logger->is_debug() ); + $val =~ s/\*/%/g; + $sql .= " and " if ( $sql !~ /where\ $/ ); + $sql .= grep( /^$key$/, @$device_fields ) ? " d." : " ch."; + $sql .= "$key $op '$val'"; + + } + } + $sql =~ s/where\ // if ( $sql =~ /where\ $/ ); + my $rtn = &recordFetch( $requestObject, $sql, $parms ); + + if ( ref $rtn eq 'HASH' && defined $rtn->{metaData} && defined $rtn->{metaData}->{fields} ) + { + push( @{ $rtn->{metaData}->{fields} }, @$device_fields ); + } + return $rtn; } #handles generic fetches sub doGenericGET() { - # for internal requests: - # ro = { entity, path[0] query } - - my ($keyval,$sql,$parms,$return); - $logger->info("processing GET"); - my $requestObject=shift; - # check to see if this entity requires special processing, otherwise handle with generic - # assemble sql based on input parameters - $sql="select * from $$requestObject{'entity'} where "; - - # check for path key value and add if specified - if($$requestObject{'path'}[0]) - { - $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ($logger->is_debug()); - $sql.=" $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; - push(@$parms,$$requestObject{'path'}[0]); - } - elsif($$requestObject{getparams}) { - my @ranges=split(/[&;]/, $$requestObject{getparams}); - foreach my $range (@ranges) { - next unless $range =~ /(\w+)([!~>=<]+)(.+)/; - my $key = $1; - my $op = $2; - my $val = $3; + # for internal requests: + # ro = { entity, path[0] query } + + my ( $keyval, $sql, $parms, $return ); + $logger->info("processing GET"); + my $requestObject = shift; + + # check to see if this entity requires special processing, otherwise handle with generic + # assemble sql based on input parameters + $sql = "select * from $$requestObject{'entity'} where "; + + # check for path key value and add if specified + if ( $$requestObject{'path'}[0] ) + { + $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); + $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; + push( @$parms, $$requestObject{'path'}[0] ); + } + elsif ( $$requestObject{getparams} ) + { + my @ranges = split( /[&;]/, $$requestObject{getparams} ); + foreach my $range (@ranges) + { + next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + my $key = $1; + my $op = $2; + my $val = $3; next if $key =~ /^_/; - $val =~ s/'//g; - $op = 'LIKE' if $op eq '='; - $op = 'NOT LIKE' if $op eq '!='; - $op = 'RLIKE' if $op eq '~'; - $op = 'NOT RLIKE' if $op eq '!~'; - $logger->debug("Found param: $key $op $val") if ($logger->is_debug()); - $val =~ s/\*/%/g; - $sql.=" and " if($sql!~/where\ $/); - $sql.=" $key $op '$val'"; - - } - } - $sql=~s/where\ // if($sql=~/where\ $/); - my $rec=&recordFetch($requestObject,$sql,$parms); - if(!$rec) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return; - } - else - { - return $rec; - } + $val =~ s/'//g; + $op = 'LIKE' if $op eq '='; + $op = 'NOT LIKE' if $op eq '!='; + $op = 'RLIKE' if $op eq '~'; + $op = 'NOT RLIKE' if $op eq '!~'; + $logger->debug("Found param: $key $op $val") if ( $logger->is_debug() ); + $val =~ s/\*/%/g; + $sql .= " and " if ( $sql !~ /where\ $/ ); + $sql .= " $key $op '$val'"; + + } + } + $sql =~ s/where\ // if ( $sql =~ /where\ $/ ); + my $rec = &recordFetch( $requestObject, $sql, $parms ); + if ( !$rec ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return; + } + else + { + return $rec; + } } sub doGenericDELETE { - my $requestObject=shift; - my $entity=$$requestObject{'entity'}; - my ($sql,$parms); - # continue if entity key segment of the path is there - if($requestObject->{'path'}[0]) - { - my $lkup_data=&doGenericGET($requestObject); - if(!defined $lkup_data) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return; - } - if($$lkup_data{'metaData'}) - { - $lkup_data=$$lkup_data{'records'}[0] - } - elsif(ref $lkup_data eq 'ARRAYREF') - { - $lkup_data=$$lkup_data[0]; - } - my $blocked_changes={}; - &runACL($requestObject,$lkup_data,$entity,{},$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("blocked DELETE operation: " . &make_json($blocked_changes) ); - if($requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) - { - $dbh->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - return 'ACL blocked Delete: ' . &make_json($blocked_changes); - } - - $sql="delete from $requestObject->{'entity'} where $tree_extended->{entities}->{ $requestObject->{'entity'} }->{'key'} = ?"; - $parms=[ $requestObject->{'path'}[0] ]; - $dbh->do($sql,{},@$parms); - if($dbh->err) - { - $logger->error("sql: $sql with " . join(',',@$parms) ); - $logger->error($dbh->err . " : " . $dbh->errstr ) if ($dbh->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - } - else - { - $dbh->do('insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=now(), - change_user=?, - change_ip=?', - {}, - ($requestObject->{'entity'}, - $requestObject->{'path'}[0], - 'record', #field - substr(make_json($lkup_data),0,100). '...', #old val - 'DELETED', # new val - $requestObject->{'user'}->{'username'}, # user - $requestObject->{'ip_address'} # ip - ) - ); - } - - } - + my $requestObject = shift; + my $entity = $$requestObject{'entity'}; + my ( $sql, $parms ); + + # continue if entity key segment of the path is there + if ( $requestObject->{'path'}[0] ) + { + my $lkup_data = &doGenericGET($requestObject); + if ( !defined $lkup_data ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return; + } + if ( $$lkup_data{'metaData'} ) + { + $lkup_data = $$lkup_data{'records'}[0]; + } + elsif ( ref $lkup_data eq 'ARRAYREF' ) + { + $lkup_data = $$lkup_data[0]; + } + my $blocked_changes = {}; + &runACL( $requestObject, $lkup_data, $entity, {}, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "blocked DELETE operation: " . &make_json($blocked_changes) ); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) + { + $dbh->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + return 'ACL blocked Delete: ' . &make_json($blocked_changes); + } + + $sql = "delete from $requestObject->{'entity'} where $tree_extended->{entities}->{ $requestObject->{'entity'} }->{'key'} = ?"; + $parms = [ $requestObject->{'path'}[0] ]; + $dbh->do( $sql, {}, @$parms ); + if ( $dbh->err ) + { + $logger->error( "sql: $sql with " . join( ',', @$parms ) ); + $logger->error( $dbh->err . " : " . $dbh->errstr ) if ( $dbh->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + } + else + { + $dbh->do( + 'insert into inv_audit set + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=now(), + change_user=?, + change_ip=?', + {}, + ( + $requestObject->{'entity'}, + $requestObject->{'path'}[0], + 'record', #field + substr( make_json($lkup_data), 0, 100 ) . '...', #old val + 'DELETED', # new val + $requestObject->{'user'}->{'username'}, # user + $requestObject->{'ip_address'} # ip + ) + ); + } + + } + } sub parseQueryParams { - my ($data, $getparams, $valid_fields) = @_; + my ( $data, $getparams, $valid_fields ) = @_; + + my @ranges = split( /[&;]/, $data ); + foreach my $range (@ranges) + { + # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + next unless $range =~ /(\w+)([!~>=<]+)(.*)/; + my $key = $1; + my $op = $2; + my $val = $3; + next if $key =~ /^_/; + next if ( !grep( /^$key$/, @$valid_fields ) && scalar(@$valid_fields) > 0 ); + $val =~ s/'//g; + $op = 'LIKE' if $op eq '='; + $op = 'NOT LIKE' if $op eq '!='; + $op = 'RLIKE' if $op eq '~'; + $op = 'NOT RLIKE' if $op eq '!~'; + $logger->debug("Found param: $key $op $val") if ( $logger->is_debug() ); + $$getparams{$key}{op} = $op; + $$getparams{$key}{val} = $val; + } - my @ranges=split(/[&;]/, $data); - foreach my $range (@ranges) { -# next unless $range =~ /(\w+)([!~>=<]+)(.+)/; - next unless $range =~ /(\w+)([!~>=<]+)(.*)/; - my $key = $1; - my $op = $2; - my $val = $3; - next if $key =~ /^_/; - next if (!grep(/^$key$/,@$valid_fields) && scalar(@$valid_fields) > 0); - $val =~ s/'//g; - $op = 'LIKE' if $op eq '='; - $op = 'NOT LIKE' if $op eq '!='; - $op = 'RLIKE' if $op eq '~'; - $op = 'NOT RLIKE' if $op eq '!~'; - $logger->debug("Found param: $key $op $val") if ($logger->is_debug()); - $$getparams{$key}{op}=$op; - $$getparams{$key}{val}=$val; - } - - return $getparams; + return $getparams; } -sub doEnvironmentsServicesGET() { - my $requestObject=shift; - my $environment; - my $service; - my @parents; - my @params; - my %getparams; - my %hash; - my $environment_tag; - - $environment = $requestObject->{'path'}[0]; - $service = $requestObject->{'path'}[2]; - - my $sql = "select name, environment_name from environments"; - - my $rtn = &doSql($sql, undef); - if ($$rtn{'err'}) { - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $$rtn{'err'} . " : " . $$rtn{'errstr'}; - } - - if ($requestObject->{'getparams'}) { - @params = split(/[&;]/, $requestObject->{'getparams'}); - parseQueryParams($requestObject->{'getparams'}, \%getparams, - [ 'type', 'name' ]); - } - - $environment_tag = 1 if defined $requestObject->{'query'}->{'_tag_environment'}; - - for my $env (@{$rtn->{'data'}}) { - my $parent = $env->{'environment_name'}; - my $name = $env->{'name'}; - - if ($parent eq $name) { - $parent = undef; - } - - $hash{$name} = $parent; - } - - $parents[0] = $environment; - while (defined $parents[-1]) { - push @parents, $hash{$parents[-1]}; - } - pop @parents; - - %hash = (); - my $list = join(', ', map { "'$_'" } @parents); - $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . - " (select name, environment_name, note, svc_id, type from service_instance " . - " where type != 'environment' "; - - if (defined $service) { - $sql .= "and name like '$service' "; - } - - for my $key (keys %getparams) { - $sql .= sprintf "and %s %s '%s' ", - $key, - $getparams{$key}{op}, - $getparams{$key}{val}; - } - - $sql .= "and environment_name in ($list)) as s " . - "left join service_instance_data as d on s.svc_id = d.svc_id " . - "order by field(environment_name, $list)"; - $rtn = &doSql($sql, undef); - - if ($$rtn{'err'}) { - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return $$rtn{'err'} . " : " . $$rtn{'errstr'}; - } - - for my $data (@{$rtn->{'data'}}) { - if (not exists $hash{$data->{'name'}}) { - $hash{$data->{'name'}} = { - name => $data->{'name'}, - environment_name => $data->{'environment_name'}, - type => $data->{'type'}, - svc_id => $data->{'svc_id'}, - note => $data->{'note'}, - }; - } - - my $svc = $hash{$data->{'name'}}; - my $key = $data->{'data_key'}; - my $value = $data->{'data_value'}; - next if ((not defined $key) || exists $svc->{$key}); - - if ($environment_tag) { - $svc->{$key} = { value => $value, - environment_name => - $data->{'environment_name'} }; - } else { - $svc->{$key} = $value; - } - } - - if ($requestObject->{'getparams'}) { - parseQueryParams($requestObject->{'getparams'}, \%getparams,[]); - } - # remove params that would have gotten parsed into the sql query - foreach my $query_parm (['name','type']) - { - delete $getparams{$query_parm}; - } - - # if other attribute querys are coming in, then evaluate them assemble new results of matches - if(scalar(keys(%getparams)) > 0) - { - my @ranges=split(/[&;]/, $requestObject->{'getparams'}); - my $jcel_params={}; - foreach my $range (@ranges) { - next unless $range =~ /(\w+)([!~>=<]+)(.*)/; +sub doEnvironmentsServicesGET() +{ + my $requestObject = shift; + my $environment; + my $service; + my @parents; + my @params; + my %getparams; + my %hash; + my $environment_tag; + + $environment = $requestObject->{'path'}[0]; + $service = $requestObject->{'path'}[2]; + + my $sql = "select name, environment_name from environments"; + + my $rtn = &doSql( $sql, undef ); + if ( $$rtn{'err'} ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $$rtn{'err'} . " : " . $$rtn{'errstr'}; + } + + if ( $requestObject->{'getparams'} ) + { + @params = split( /[&;]/, $requestObject->{'getparams'} ); + parseQueryParams( $requestObject->{'getparams'}, \%getparams, [ 'type', 'name' ] ); + } + + $environment_tag = 1 if defined $requestObject->{'query'}->{'_tag_environment'}; + + for my $env ( @{ $rtn->{'data'} } ) + { + my $parent = $env->{'environment_name'}; + my $name = $env->{'name'}; + + if ( $parent eq $name ) + { + $parent = undef; + } + + $hash{$name} = $parent; + } + + $parents[0] = $environment; + while ( defined $parents[-1] ) + { + push @parents, $hash{ $parents[-1] }; + } + pop @parents; + + %hash = (); + my $list = join( ', ', map { "'$_'" } @parents ); + $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . " (select name, environment_name, note, svc_id, type from service_instance " . " where type != 'environment' "; + + if ( defined $service ) + { + $sql .= "and name like '$service' "; + } + + for my $key ( keys %getparams ) + { + $sql .= sprintf "and %s %s '%s' ", $key, $getparams{$key}{op}, $getparams{$key}{val}; + } + + $sql .= "and environment_name in ($list)) as s " . "left join service_instance_data as d on s.svc_id = d.svc_id " . "order by field(environment_name, $list)"; + $rtn = &doSql( $sql, undef ); + + if ( $$rtn{'err'} ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return $$rtn{'err'} . " : " . $$rtn{'errstr'}; + } + + for my $data ( @{ $rtn->{'data'} } ) + { + if ( not exists $hash{ $data->{'name'} } ) + { + $hash{ $data->{'name'} } = { + name => $data->{'name'}, + environment_name => $data->{'environment_name'}, + type => $data->{'type'}, + svc_id => $data->{'svc_id'}, + note => $data->{'note'}, + }; + } + + my $svc = $hash{ $data->{'name'} }; + my $key = $data->{'data_key'}; + my $value = $data->{'data_value'}; + next if ( ( not defined $key ) || exists $svc->{$key} ); + + if ($environment_tag) + { + $svc->{$key} = { + value => $value, + environment_name => $data->{'environment_name'} + }; + } + else + { + $svc->{$key} = $value; + } + } + + if ( $requestObject->{'getparams'} ) + { + parseQueryParams( $requestObject->{'getparams'}, \%getparams, [] ); + } + + # remove params that would have gotten parsed into the sql query + foreach my $query_parm ( [ 'name', 'type' ] ) + { + delete $getparams{$query_parm}; + } + + # if other attribute querys are coming in, then evaluate them assemble new results of matches + if ( scalar( keys(%getparams) ) > 0 ) + { + my @ranges = split( /[&;]/, $requestObject->{'getparams'} ); + my $jcel_params = {}; + foreach my $range (@ranges) + { + next unless $range =~ /(\w+)([!~>=<]+)(.*)/; my $key = $1; - my $op = $2; + my $op = $2; my $val = $3; + # we are parsing the query string (yet again). skip the attrs that get searched by the sql - next if (grep(/^$key$/,['name','type']) ); - $jcel_params->{$key}->{$op}=$val; - } - my @filtered_results; - # assemble jcel config from passed in parameters to use for testing the service records - $logger->debug("jcel condition: " . make_json($jcel_params)); - my $jcel = NOMS::JCEL->new($jcel_params); - foreach my $service_record_key (keys %hash) - { - if ($jcel->test($hash{$service_record_key})) - { - push(@filtered_results,\%{$hash{$service_record_key}}); - } - } - return \@filtered_results if scalar(@filtered_results); - # there were no results left after filtering. so - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return; - - } - - # return results as array when query is used - if (!defined $service && keys(%hash)) { - return [values %hash]; - } - # otherwise service was accessed via full resource path, so return as record - elsif (keys %hash) { - return (values %hash)[0]; - } else { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return; - } + next if ( grep( /^$key$/, [ 'name', 'type' ] ) ); + $jcel_params->{$key}->{$op} = $val; + } + my @filtered_results; + + # assemble jcel config from passed in parameters to use for testing the service records + $logger->debug( "jcel condition: " . make_json($jcel_params) ); + my $jcel = NOMS::JCEL->new($jcel_params); + foreach my $service_record_key ( keys %hash ) + { + if ( $jcel->test( $hash{$service_record_key} ) ) + { + push( @filtered_results, \%{ $hash{$service_record_key} } ); + } + } + return \@filtered_results if scalar(@filtered_results); + + # there were no results left after filtering. so + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return; + + } + + # return results as array when query is used + if ( !defined $service && keys(%hash) ) + { + return [ values %hash ]; + } + + # otherwise service was accessed via full resource path, so return as record + elsif ( keys %hash ) + { + return ( values %hash )[0]; + } + else + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return; + } } -sub insertAuditEntry { - my ($dbh, $requestObject, $entity, $key, - $name, $old, $new, $time) = @_; +sub insertAuditEntry +{ + my ( $dbh, $requestObject, $entity, $key, $name, $old, $new, $time ) = @_; - my $sql = 'insert into inv_audit set + my $sql = 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -1718,775 +1818,848 @@ sub insertAuditEntry { change_user=?, change_ip=?'; - $dbh->do($sql, {}, ($entity, $key, $name, - $old, $new, $time, - $requestObject->{user}->{username}, - $$requestObject{ip_address})); + $dbh->do( $sql, {}, ( $entity, $key, $name, $old, $new, $time, $requestObject->{user}->{username}, $$requestObject{ip_address} ) ); } -sub executeDbStatement { - my ($sth, $sql, @parms) = @_; +sub executeDbStatement +{ + my ( $sth, $sql, @parms ) = @_; - if ($logger->is_debug()) { - my $msg; + if ( $logger->is_debug() ) + { + my $msg; - if (@parms) { - $msg = "executing: $sql with " . join(',', @parms); - } else { - $msg = "executing: $sql" - } + if (@parms) + { + $msg = "executing: $sql with " . join( ',', @parms ); + } + else + { + $msg = "executing: $sql"; + } - $logger->debug($msg); - } + $logger->debug($msg); + } - return $sth->execute(@parms); + return $sth->execute(@parms); } -sub doEnvironmentsServicesPUT(){ - my $requestObject=shift; - $logger->info("processing PUT"); - my $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST", - $DBUSER,$DBPASS,{AutoCommit=>0,RaiseError=>1}); - my $environment = $requestObject->{'path'}[0]; - my $service = $requestObject->{'path'}[2]; - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - my $blocked_changes={}; - my $svc_id; - my $sth; - my $lkup_data; - my @inserts; - my @updates; - my @deletes; - my %service_updates; - my %service_attributes; - my $sql; - my $error; - my $old_value; - my $new_value; - my $did_update; - - eval { - # Get service_instance record for the requested service - $sql = "select svc_id,type,name,environment_name,note from service_instance where environment_name=? and name=? and type!='environment'"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, $environment, $service); - $lkup_data = $sth->fetchall_arrayref({}, undef); - - if ($sth->rows == 0) { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - $dbh->rollback; - $error = 'There is no resource at this location'; - return; - } elsif ($sth->rows > 1) { - $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - $dbh->rollback; - $error = 'Multiple services with the same ID'; - return; - } - - $old_value = &doEnvironmentsServicesGET($requestObject); - - &runACL($requestObject,{},'services',$data,$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("blocked PUT fields: " . &make_json($blocked_changes) ); - my $now=$dbh->selectcol_arrayref('select now()'); - if ($requestObject->{'user'}->{'systemuser'} ne '1' && - scalar(keys(%$blocked_changes))) { - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - $dbh->rollback; - $error = 'ACL blocked change: ' . &make_json($blocked_changes); - return; - } - - $lkup_data = $$lkup_data[0]; - $svc_id = $lkup_data->{'svc_id'}; - - # Determine if we need to modify any fields of the service instance record - - - my $fieldlist = &getFieldList('service_instance'); - for my $f (@{$fieldlist}) { - my $value = $lkup_data->{$f}; - if (defined $data->{$f} && $value ne $data->{$f}) { - $service_updates{$f} = [ $value, $data->{$f} ]; - } - - delete $data->{$f}; #ICK - } - delete $data->{'svc_id'}; #ICK - - # Get all service_instance_data records that belong to this instance - # We don't care about inheritance for this - $sql = "select data_id,data_key,data_value " . - "from service_instance_data where svc_id=?"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, $svc_id); - $lkup_data = $sth->fetchall_arrayref({}, undef); - - #populate lookup data into a structure of the service_instance for reference - for my $row (@$lkup_data) { - $service_attributes{$row->{'data_key'}} = - [ $row->{'data_value'}, $row->{'data_id'} ]; - } - - my $field_list=&getFieldList('service_instance'); - for my $key (keys %$data) { - #skip if a native service_instance field - if(grep(/^$key$/,@$field_list)){ - next; - } - if (exists $service_attributes{$key}) { - if (not defined $data->{$key}) { - push @deletes, $key; - } elsif ($service_attributes{$key}[0] ne - $data->{$key}) { - push @updates, $key; - } - } else { - push @inserts, $key; - } - } - - - if (keys %service_updates) { - my $sql_set; - my @parms; - - $sql_set = join(', ', map { "$_=?" } keys %service_updates); - for my $key (keys %service_updates) { - push @parms, $service_updates{$key}[1]; - } - push @parms, $svc_id; - - $sql="update service_instance set $sql_set"; - $sql .= " where svc_id=?"; - - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, @parms); - $did_update = 1; - } - - if (@inserts) { - # Create any new service_instance_data records - $sql = "insert into service_instance_data " . - "(data_key, data_value, svc_id) values "; - $sql .= join(',', map {sprintf("('%s','%s','%s')", - $_, $data->{$_}, $svc_id)} @inserts); - - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql); - $did_update = 1; - } - - if (@updates) { - # Modify existing service_instance_data records - $sql = "update service_instance_data set " . - "data_value=? where data_key=? and svc_id=?"; - $sth = $dbh->prepare($sql); - - - for my $key (@updates) { - executeDbStatement($sth, $sql, $data->{$key}, $key, $svc_id); - } - $did_update = 1; - } - - if (@deletes) { - $sql = "delete from service_instance_data where " . - "svc_id=? and data_key in "; - $sql .= '(' . join(',', map { "'$_'" } @deletes) . ')'; - $sth = $dbh->prepare($sql); - - executeDbStatement($sth, $sql, $svc_id); - $did_update = 1; - } - - $dbh->commit; - - #adjust key path, if key field value was changed - if($service_updates{'name'}) - { - $logger->debug(make_json($requestObject)); - $requestObject->{'path'}->[2] = $service_updates{'name'}[1]; - $logger->debug(make_json($requestObject)); - } - - - $new_value = &doEnvironmentsServicesGET($requestObject); - if ($did_update) { - insertAuditEntry($dbh, $requestObject, 'services', - "$environment/$service", 'record', - make_json($old_value), - make_json($new_value), - $$now[0]); - $dbh->commit; - } - }; - if ($@) { - my $errstr; - - if (defined $sth && $sth->err) { - $errstr = $sth->err . " : " . $sth->errstr; - $logger->error($errstr); - } else { - $errstr = $@; - } - - $$requestObject{'stat'} = - Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - - eval { $dbh->rollback; }; - - $error = $errstr; - } - - if (defined $error) { - return $error; - } else { - return $new_value; - } -} +sub doEnvironmentsServicesPUT() +{ + my $requestObject = shift; + $logger->info("processing PUT"); + my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); + my $environment = $requestObject->{'path'}[0]; + my $service = $requestObject->{'path'}[2]; + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + my $blocked_changes = {}; + my $svc_id; + my $sth; + my $lkup_data; + my @inserts; + my @updates; + my @deletes; + my %service_updates; + my %service_attributes; + my $sql; + my $error; + my $old_value; + my $new_value; + my $did_update; + + eval { + # Get service_instance record for the requested service + $sql = "select svc_id,type,name,environment_name,note from service_instance where environment_name=? and name=? and type!='environment'"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, $environment, $service ); + $lkup_data = $sth->fetchall_arrayref( {}, undef ); + + if ( $sth->rows == 0 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + $dbh->rollback; + $error = 'There is no resource at this location'; + return; + } + elsif ( $sth->rows > 1 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + $dbh->rollback; + $error = 'Multiple services with the same ID'; + return; + } + + $old_value = &doEnvironmentsServicesGET($requestObject); + + &runACL( $requestObject, {}, 'services', $data, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "blocked PUT fields: " . &make_json($blocked_changes) ); + my $now = $dbh->selectcol_arrayref('select now()'); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' + && scalar( keys(%$blocked_changes) ) ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + $dbh->rollback; + $error = 'ACL blocked change: ' . &make_json($blocked_changes); + return; + } + + $lkup_data = $$lkup_data[0]; + $svc_id = $lkup_data->{'svc_id'}; + + # Determine if we need to modify any fields of the service instance record -sub doEnvironmentsServicesPOST(){ - my $requestObject=shift; - $logger->info("processing POST"); - my $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST", - $DBUSER,$DBPASS,{AutoCommit=>0,RaiseError=>1}); - my $environment = $requestObject->{'path'}[0]; - my $service = $requestObject->{'path'}[2]; - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - my $blocked_changes={}; - my $svc_id; - my $sth; - my $lkup_data; - my %service_updates; - my %service_attributes; - my $sql; - my $error; - - # FIXME: should this be how we handle this? No point in specifying either - # of these attributes in the request. - delete $data->{svc_id}; - if($service) - { - $data->{name} = $service; - } - else - { - $service = $data->{name}; - } - $data->{'environment_name'} = $environment; - - if (not defined $service) { - # FIXME: what's the best way to handle this? Is this the correct - # status code - $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_ACCEPTABLE; - return 'Service name required'; - } - - eval { - # Get service_instance record for the requested service - $sql = "select svc_id from service_instance where environment_name=? and name=?"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, $environment, $service); - $lkup_data = $sth->fetchall_arrayref({}, undef); - - if ($sth->rows) { - # FIXME: what's the best way to handle this? should we be able to - # turn a POST into a PUT? Is this the correct status code to use? - $dbh->rollback; - $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - $error = 'Service already exists'; - return; - } - - &runACL($requestObject,{},'services',$data,$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("blocked PUT fields: " . &make_json($blocked_changes) ); - my $now=$dbh->selectcol_arrayref('select now()'); - if ($requestObject->{'user'}->{'systemuser'} ne '1' && - scalar(keys(%$blocked_changes))) { - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - $dbh->rollback; - return 'ACL blocked change: ' . &make_json($blocked_changes); - } - - my @columns; - my @values; - - for my $field (qw/name environment_name type notes/) { - if (defined $data->{$field}) { - push @columns, $field; - push @values, $data->{$field}; - delete $data->{$field}; - } - } - - # Create service_instance record - $sql = sprintf("insert into service_instance (%s) values (%s)", - join(', ', @columns), join(', ', map { "'$_'" } @values)); - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql); - $svc_id = $sth->{mysql_insertid}; - - # Create service_instance_data records - $sql = "insert into service_instance_data " . - "(svc_id, data_key, data_value) values "; - - - $sql .= join(',', map {sprintf("('%s','%s','%s')", - $svc_id, $_, $data->{$_})} keys %$data); - - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql); - - insertAuditEntry($dbh, $requestObject, 'services', "$environment/$service", - 'record', '', 'CREATED', $$now[0]); - $dbh->commit; - }; - if ($@) { - my $errstr; - - if ($sth->err) { - $errstr = $sth->err . " : " . $sth->errstr; - $logger->error($errstr); - } else { - $errstr = $@; - } - - $$requestObject{'stat'} = - Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - - eval { $dbh->rollback; }; - - return $errstr; - } - - - return $error if (defined $error); - - $$requestObject{'headers_out'}=['Location',"/cmndb_api/v1/environments/" . - $environment . "/services/" . - $service]; - return; + my $fieldlist = &getFieldList('service_instance'); + for my $f ( @{$fieldlist} ) + { + my $value = $lkup_data->{$f}; + if ( defined $data->{$f} && $value ne $data->{$f} ) + { + $service_updates{$f} = [ $value, $data->{$f} ]; + } + + delete $data->{$f}; #ICK + } + delete $data->{'svc_id'}; #ICK + + # Get all service_instance_data records that belong to this instance + # We don't care about inheritance for this + $sql = "select data_id,data_key,data_value " . "from service_instance_data where svc_id=?"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, $svc_id ); + $lkup_data = $sth->fetchall_arrayref( {}, undef ); + + #populate lookup data into a structure of the service_instance for reference + for my $row (@$lkup_data) + { + $service_attributes{ $row->{'data_key'} } = + [ $row->{'data_value'}, $row->{'data_id'} ]; + } + + my $field_list = &getFieldList('service_instance'); + for my $key ( keys %$data ) + { + #skip if a native service_instance field + if ( grep( /^$key$/, @$field_list ) ) + { + next; + } + if ( exists $service_attributes{$key} ) + { + if ( not defined $data->{$key} ) + { + push @deletes, $key; + } + elsif ( $service_attributes{$key}[0] ne $data->{$key} ) + { + push @updates, $key; + } + } + else + { + push @inserts, $key; + } + } + + if ( keys %service_updates ) + { + my $sql_set; + my @parms; + + $sql_set = join( ', ', map { "$_=?" } keys %service_updates ); + for my $key ( keys %service_updates ) + { + push @parms, $service_updates{$key}[1]; + } + push @parms, $svc_id; + + $sql = "update service_instance set $sql_set"; + $sql .= " where svc_id=?"; + + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, @parms ); + $did_update = 1; + } + + if (@inserts) + { + # Create any new service_instance_data records + $sql = "insert into service_instance_data " . "(data_key, data_value, svc_id) values "; + $sql .= join( ',', map { sprintf( "('%s','%s','%s')", $_, $data->{$_}, $svc_id ) } @inserts ); + + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql ); + $did_update = 1; + } + + if (@updates) + { + # Modify existing service_instance_data records + $sql = "update service_instance_data set " . "data_value=? where data_key=? and svc_id=?"; + $sth = $dbh->prepare($sql); + + for my $key (@updates) + { + executeDbStatement( $sth, $sql, $data->{$key}, $key, $svc_id ); + } + $did_update = 1; + } + + if (@deletes) + { + $sql = "delete from service_instance_data where " . "svc_id=? and data_key in "; + $sql .= '(' . join( ',', map { "'$_'" } @deletes ) . ')'; + $sth = $dbh->prepare($sql); + + executeDbStatement( $sth, $sql, $svc_id ); + $did_update = 1; + } + + $dbh->commit; + + #adjust key path, if key field value was changed + if ( $service_updates{'name'} ) + { + $logger->debug( make_json($requestObject) ); + $requestObject->{'path'}->[2] = $service_updates{'name'}[1]; + $logger->debug( make_json($requestObject) ); + } + + $new_value = &doEnvironmentsServicesGET($requestObject); + if ($did_update) + { + insertAuditEntry( $dbh, $requestObject, 'services', "$environment/$service", 'record', make_json($old_value), make_json($new_value), $$now[0] ); + $dbh->commit; + } + }; + if ($@) + { + my $errstr; + + if ( defined $sth && $sth->err ) + { + $errstr = $sth->err . " : " . $sth->errstr; + $logger->error($errstr); + } + else + { + $errstr = $@; + } + + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + + eval { $dbh->rollback; }; + + $error = $errstr; + } + + if ( defined $error ) + { + return $error; + } + else + { + return $new_value; + } } -sub doEnvironmentsServicesDELETE(){ - my $requestObject=shift; - $logger->info("processing DELETE"); - my $dbh=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST", - $DBUSER,$DBPASS,{AutoCommit=>0,RaiseError=>1}); - my $environment = $requestObject->{'path'}[0]; - my $service = $requestObject->{'path'}[2]; - my $blocked_changes={}; - my $svc_id; - my $sth; - my $lkup_data; - my %service_updates; - my %service_attributes; - my $sql; - my $error; - my $old_value; - - eval { - # Get service_instance record for the requested service - $sql = "select svc_id from service_instance where environment_name=? and name=?"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, $environment, $service); - $lkup_data = $sth->fetchall_arrayref({}, undef); - - - if ($sth->rows == 0) { - # FIXME: what's the best way to handle this? should we be able to - # turn a POST into a PUT? Is this the correct status code to use? - $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; - $dbh->rollback; - $error = 'There is no resource at this location'; - return; - } - - $svc_id = $$lkup_data[0]->{'svc_id'}; - - &runACL($requestObject,$lkup_data,'services',{},$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("blocked DELETE operation: " . &make_json($blocked_changes) ); - if ($requestObject->{'user'}->{'systemuser'} ne '1' && - scalar(keys(%$blocked_changes))) { - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - $dbh->rollback; - $error = 'ACL blocked Delete: ' . &make_json($blocked_changes); - return; - } - my $now=$dbh->selectcol_arrayref('select now()'); - - $old_value = &doEnvironmentsServicesGET($requestObject); - - $sql = "delete from service_instance where svc_id=?"; - $sth = $dbh->prepare($sql); - executeDbStatement($sth, $sql, $svc_id); - - insertAuditEntry($dbh, $requestObject, 'services', - "$environment/$service", - 'record', - make_json($old_value), - 'DELETED', $$now[0]); - $dbh->commit; - }; - if ($@) { - my $errstr; - - if ($sth->err) { - $errstr = $sth->err . " : " . $sth->errstr; - $logger->error($errstr); - } else { - $errstr = $@; - } - - $$requestObject{'stat'} = - Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - - eval { $dbh->rollback; }; - - $error = $errstr; - } - - if (defined $error) { - return $error; - } else { - return; - } +sub doEnvironmentsServicesPOST() +{ + my $requestObject = shift; + $logger->info("processing POST"); + my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); + my $environment = $requestObject->{'path'}[0]; + my $service = $requestObject->{'path'}[2]; + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + my $blocked_changes = {}; + my $svc_id; + my $sth; + my $lkup_data; + my %service_updates; + my %service_attributes; + my $sql; + my $error; + + # FIXME: should this be how we handle this? No point in specifying either + # of these attributes in the request. + delete $data->{svc_id}; + if ($service) + { + $data->{name} = $service; + } + else + { + $service = $data->{name}; + } + $data->{'environment_name'} = $environment; + + if ( not defined $service ) + { + # FIXME: what's the best way to handle this? Is this the correct + # status code + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_ACCEPTABLE; + return 'Service name required'; + } + + eval { + # Get service_instance record for the requested service + $sql = "select svc_id from service_instance where environment_name=? and name=?"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, $environment, $service ); + $lkup_data = $sth->fetchall_arrayref( {}, undef ); + + if ( $sth->rows ) + { + # FIXME: what's the best way to handle this? should we be able to + # turn a POST into a PUT? Is this the correct status code to use? + $dbh->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + $error = 'Service already exists'; + return; + } + + &runACL( $requestObject, {}, 'services', $data, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "blocked PUT fields: " . &make_json($blocked_changes) ); + my $now = $dbh->selectcol_arrayref('select now()'); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' + && scalar( keys(%$blocked_changes) ) ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + $dbh->rollback; + return 'ACL blocked change: ' . &make_json($blocked_changes); + } + + my @columns; + my @values; + + for my $field (qw/name environment_name type notes/) + { + if ( defined $data->{$field} ) + { + push @columns, $field; + push @values, $data->{$field}; + delete $data->{$field}; + } + } + + # Create service_instance record + $sql = sprintf( "insert into service_instance (%s) values (%s)", join( ', ', @columns ), join( ', ', map { "'$_'" } @values ) ); + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql ); + $svc_id = $sth->{mysql_insertid}; + + # Create service_instance_data records + $sql = "insert into service_instance_data " . "(svc_id, data_key, data_value) values "; + + $sql .= join( ',', map { sprintf( "('%s','%s','%s')", $svc_id, $_, $data->{$_} ) } keys %$data ); + + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql ); + + insertAuditEntry( $dbh, $requestObject, 'services', "$environment/$service", 'record', '', 'CREATED', $$now[0] ); + $dbh->commit; + }; + if ($@) + { + my $errstr; + + if ( $sth->err ) + { + $errstr = $sth->err . " : " . $sth->errstr; + $logger->error($errstr); + } + else + { + $errstr = $@; + } + + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + + eval { $dbh->rollback; }; + + return $errstr; + } + + return $error if ( defined $error ); + + $$requestObject{'headers_out'} = [ 'Location', "/cmndb_api/v1/environments/" . $environment . "/services/" . $service ]; + return; } -sub doEnvironmentsGET(){ - my $requestObject=shift; - my @path = @{$requestObject->{'path'}}; - my @parms; - my $environment; - my $service; - my $get_services = 0; - - if ($path[1] eq 'services') { - return &doEnvironmentsServicesGET($requestObject); - } elsif ($path[1]) { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - } else { - return &doGenericGET($requestObject); - } +sub doEnvironmentsServicesDELETE() +{ + my $requestObject = shift; + $logger->info("processing DELETE"); + my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); + my $environment = $requestObject->{'path'}[0]; + my $service = $requestObject->{'path'}[2]; + my $blocked_changes = {}; + my $svc_id; + my $sth; + my $lkup_data; + my %service_updates; + my %service_attributes; + my $sql; + my $error; + my $old_value; + + eval { + # Get service_instance record for the requested service + $sql = "select svc_id from service_instance where environment_name=? and name=?"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, $environment, $service ); + $lkup_data = $sth->fetchall_arrayref( {}, undef ); + + if ( $sth->rows == 0 ) + { + # FIXME: what's the best way to handle this? should we be able to + # turn a POST into a PUT? Is this the correct status code to use? + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + $dbh->rollback; + $error = 'There is no resource at this location'; + return; + } + + $svc_id = $$lkup_data[0]->{'svc_id'}; + + &runACL( $requestObject, $lkup_data, 'services', {}, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "blocked DELETE operation: " . &make_json($blocked_changes) ); + if ( $requestObject->{'user'}->{'systemuser'} ne '1' + && scalar( keys(%$blocked_changes) ) ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + $dbh->rollback; + $error = 'ACL blocked Delete: ' . &make_json($blocked_changes); + return; + } + my $now = $dbh->selectcol_arrayref('select now()'); + + $old_value = &doEnvironmentsServicesGET($requestObject); + + $sql = "delete from service_instance where svc_id=?"; + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql, $svc_id ); + + insertAuditEntry( $dbh, $requestObject, 'services', "$environment/$service", 'record', make_json($old_value), 'DELETED', $$now[0] ); + $dbh->commit; + }; + if ($@) + { + my $errstr; + + if ( $sth->err ) + { + $errstr = $sth->err . " : " . $sth->errstr; + $logger->error($errstr); + } + else + { + $errstr = $@; + } + + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + + eval { $dbh->rollback; }; + + $error = $errstr; + } + + if ( defined $error ) + { + return $error; + } + else + { + return; + } } -sub doEnvironmentsPUT(){ - my $requestObject=shift; - my @path = @{$requestObject->{'path'}}; - my @parms; - my $environment; - my $service; - my $get_services = 0; - - $environment = $path[0]; - $service = $path[2]; - - if ($path[1] eq 'services') { - if (defined $service) { - return &doEnvironmentsServicesPUT($requestObject); - } else { - $$requestObject{'stat'}=Apache2::Const::HTTP_METHOD_NOT_ALLOWED; - } - } elsif ($path[1]) { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - } else { - return &doGenericPUT($requestObject); - } +sub doEnvironmentsGET() +{ + my $requestObject = shift; + my @path = @{ $requestObject->{'path'} }; + my @parms; + my $environment; + my $service; + my $get_services = 0; + + if ( $path[1] eq 'services' ) + { + return &doEnvironmentsServicesGET($requestObject); + } + elsif ( $path[1] ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + } + else + { + return &doGenericGET($requestObject); + } } -sub doEnvironmentsPOST(){ - my $requestObject=shift; - my @path = @{$requestObject->{'path'}}; - my @parms; - my $get_services = 0; +sub doEnvironmentsPUT() +{ + my $requestObject = shift; + my @path = @{ $requestObject->{'path'} }; + my @parms; + my $environment; + my $service; + my $get_services = 0; + + $environment = $path[0]; + $service = $path[2]; + + if ( $path[1] eq 'services' ) + { + if ( defined $service ) + { + return &doEnvironmentsServicesPUT($requestObject); + } + else + { + $$requestObject{'stat'} = Apache2::Const::HTTP_METHOD_NOT_ALLOWED; + } + } + elsif ( $path[1] ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + } + else + { + return &doGenericPUT($requestObject); + } +} - my $environment = $path[0]; - my $service = $path[2]; +sub doEnvironmentsPOST() +{ + my $requestObject = shift; + my @path = @{ $requestObject->{'path'} }; + my @parms; + my $get_services = 0; + my $environment = $path[0]; + my $service = $path[2]; - if ($path[1] eq 'services') { - return &doEnvironmentsServicesPOST($requestObject); - } elsif ($path[1]) { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - } else { - return &doGenericPOST($requestObject); - } + if ( $path[1] eq 'services' ) + { + return &doEnvironmentsServicesPOST($requestObject); + } + elsif ( $path[1] ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + } + else + { + return &doGenericPOST($requestObject); + } } -sub doEnvironmentsDELETE(){ - my $requestObject=shift; - my @path = @{$requestObject->{'path'}}; - my @parms; - my $get_services = 0; - - my $environment = $path[0]; - my $service = $path[2]; - - if ($path[1] eq 'services') { - if (defined $service) { - return &doEnvironmentsServicesDELETE($requestObject); - } else { - $$requestObject{'stat'}=Apache2::Const::HTTP_METHOD_NOT_ALLOWED; - } - } elsif ($path[1]) { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - } else { - return &doGenericDELETE($requestObject); - } +sub doEnvironmentsDELETE() +{ + my $requestObject = shift; + my @path = @{ $requestObject->{'path'} }; + my @parms; + my $get_services = 0; + + my $environment = $path[0]; + my $service = $path[2]; + + if ( $path[1] eq 'services' ) + { + if ( defined $service ) + { + return &doEnvironmentsServicesDELETE($requestObject); + } + else + { + $$requestObject{'stat'} = Apache2::Const::HTTP_METHOD_NOT_ALLOWED; + } + } + elsif ( $path[1] ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + } + else + { + return &doGenericDELETE($requestObject); + } } # special functions to handle device and systems -sub doSystemGET(){ - my $requestObject=shift; - my $x=0; - my $device_fields=&getFieldList('device',1); - my $meta_fields=&getFieldList($$requestObject{'entity'},1); - my ($field_sql,$join_sql,$sql,$where_sql,$parms); - if($$requestObject{'path'}[0]) - { - $where_sql=' d.fqdn=?'; - push(@$parms,$$requestObject{'path'}[0]); - } - my %getparams; - # parse custom URL query options ( for allowing ` ! etc...) - if($$requestObject{getparams}) { - my @ranges=split(/[&;]/, $$requestObject{getparams}); - foreach my $range (@ranges) { -# next unless $range =~ /(\w+)([!~>=<]+)(.+)/; - next unless $range =~ /(\w+)([!~>=<]+)(.*)/; +sub doSystemGET() +{ + my $requestObject = shift; + my $x = 0; + my $device_fields = &getFieldList( 'device', 1 ); + my $meta_fields = &getFieldList( $$requestObject{'entity'}, 1 ); + my ( $field_sql, $join_sql, $sql, $where_sql, $parms ); + if ( $$requestObject{'path'}[0] ) + { + $where_sql = ' d.fqdn=?'; + push( @$parms, $$requestObject{'path'}[0] ); + } + my %getparams; + + # parse custom URL query options ( for allowing ` ! etc...) + if ( $$requestObject{getparams} ) + { + my @ranges = split( /[&;]/, $$requestObject{getparams} ); + foreach my $range (@ranges) + { + # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + next unless $range =~ /(\w+)([!~>=<]+)(.*)/; my $key = $1; - my $op = $2; + my $op = $2; my $val = $3; next if $key =~ /^_/; - next unless (grep(/^$key$/,@$device_fields) || grep(/^$key$/,@$meta_fields)); + next unless ( grep( /^$key$/, @$device_fields ) || grep( /^$key$/, @$meta_fields ) ); $val =~ s/'//g; - $op = 'LIKE' if $op eq '='; - $op = 'NOT LIKE' if $op eq '!='; - $op = 'RLIKE' if $op eq '~'; - $op = 'NOT RLIKE' if $op eq '!~'; - $logger->debug("Found param: $key $op $val") if ($logger->is_debug()); - $getparams{$key}{op}=$op; - $getparams{$key}{val}=$val; - } - } - foreach(@$device_fields) - { - $field_sql.="," if $field_sql; - $field_sql.="d.$_"; - #if($$requestObject{'query'}{$_}) - if(defined $getparams{$_}) - { - my $op = $getparams{$_}{op} ? $getparams{$_}{op} : 'LIKE'; - $where_sql.=" and " if $where_sql; - if($getparams{$_}{val} eq '') - { - $where_sql.=" (d.$_ $op ?"; - $where_sql.=" OR d.$_"; - $where_sql.=$getparams{$_}{op} eq 'LIKE' ? " is null)" : " is not null)"; - } - else - { - $where_sql.=" d.$_ $op ?"; - } - # my $var=$$requestObject{'query'}{$_}; - # $var=~s/\*/%/g; - if($op !~ /RLIKE/) - { - $getparams{$_}{val} =~ s/\*/%/g; - } - #push(@$parms,$var); - push(@$parms,$getparams{$_}{val}); - } - } - foreach(@$meta_fields) - { - $field_sql.="," if $field_sql; - if($_ eq 'guest_fqdns') { - my $host = $getparams{fqdn} ? $getparams{fqdn}{val} : $$requestObject{'path'}[0]; - $field_sql.=" (select group_concat(fqdn) as guest_fqdns from device_metadata where metadata_value=d.fqdn and metadata_name=\"host_fqdn\" group by \"all\") as $_"; - } - else { - $field_sql.="m$x.metadata_value as $_"; - $join_sql.=" left join device_metadata m$x on d.fqdn=m$x.fqdn and m$x.metadata_name='$_'"; - # if($$requestObject{'query'}{$_}) - if(defined $getparams{$_}) - { - my $op = $getparams{$_}{op} ? $getparams{$_}{op} : 'LIKE'; - $where_sql.=" and " if $where_sql; - if($getparams{$_}{val} eq '') - { - $where_sql.=" ( m$x.metadata_value $op ?"; - $where_sql.=" OR m$x.metadata_value"; - $where_sql.=$getparams{$_}{op} eq 'LIKE' ? " is null)" : " is not null)"; - } - else - { - $where_sql.=" m$x.metadata_value $op ?"; - } - # $where_sql.=" m$x.metadata_value like ?"; - # my $var=$$requestObject{'query'}{$_}; - # $var=~s/\*/%/g; - # push(@$parms,$var); - if($op !~ /RLIKE/) - { - $getparams{$_}{val} =~ s/\*/%/g; - } - push(@$parms,$getparams{$_}{val}); - } - } - $x++; - } - $field_sql.="," if $field_sql; - $field_sql.= " count(ch.id) as changes "; - $join_sql.=" left join change_queue ch on d.fqdn=ch.entity_key"; - - #$sql="select $field_sql from device d $join_sql where $where_sql group by d.fqdn"; - $sql="select $field_sql from device d $join_sql "; - $sql.="where $where_sql" if $where_sql; - $sql.=" group by d.fqdn"; - - - my $rec=&recordFetch($requestObject,$sql,$parms); - if(!$rec) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_NOT_FOUND; - return; - } - else - { - return $rec; - } - + $op = 'LIKE' if $op eq '='; + $op = 'NOT LIKE' if $op eq '!='; + $op = 'RLIKE' if $op eq '~'; + $op = 'NOT RLIKE' if $op eq '!~'; + $logger->debug("Found param: $key $op $val") if ( $logger->is_debug() ); + $getparams{$key}{op} = $op; + $getparams{$key}{val} = $val; + } + } + foreach (@$device_fields) + { + $field_sql .= "," if $field_sql; + $field_sql .= "d.$_"; + + #if($$requestObject{'query'}{$_}) + if ( defined $getparams{$_} ) + { + my $op = $getparams{$_}{op} ? $getparams{$_}{op} : 'LIKE'; + $where_sql .= " and " if $where_sql; + if ( $getparams{$_}{val} eq '' ) + { + $where_sql .= " (d.$_ $op ?"; + $where_sql .= " OR d.$_"; + $where_sql .= $getparams{$_}{op} eq 'LIKE' ? " is null)" : " is not null)"; + } + else + { + $where_sql .= " d.$_ $op ?"; + } + + # my $var=$$requestObject{'query'}{$_}; + # $var=~s/\*/%/g; + if ( $op !~ /RLIKE/ ) + { + $getparams{$_}{val} =~ s/\*/%/g; + } + + #push(@$parms,$var); + push( @$parms, $getparams{$_}{val} ); + } + } + foreach (@$meta_fields) + { + $field_sql .= "," if $field_sql; + if ( $_ eq 'guest_fqdns' ) + { + my $host = $getparams{fqdn} ? $getparams{fqdn}{val} : $$requestObject{'path'}[0]; + $field_sql .= " (select group_concat(fqdn) as guest_fqdns from device_metadata where metadata_value=d.fqdn and metadata_name=\"host_fqdn\" group by \"all\") as $_"; + } + else + { + $field_sql .= "m$x.metadata_value as $_"; + $join_sql .= " left join device_metadata m$x on d.fqdn=m$x.fqdn and m$x.metadata_name='$_'"; + + # if($$requestObject{'query'}{$_}) + if ( defined $getparams{$_} ) + { + my $op = $getparams{$_}{op} ? $getparams{$_}{op} : 'LIKE'; + $where_sql .= " and " if $where_sql; + if ( $getparams{$_}{val} eq '' ) + { + $where_sql .= " ( m$x.metadata_value $op ?"; + $where_sql .= " OR m$x.metadata_value"; + $where_sql .= $getparams{$_}{op} eq 'LIKE' ? " is null)" : " is not null)"; + } + + # add a or is null as NOT matches will be true for null outer joins + elsif ( $getparams{$_}{op} =~ m/NOT/ ) + { + $where_sql .= " ( m$x.metadata_value $op ?"; + $where_sql .= " OR m$x.metadata_value is null)"; + } + else + { + $where_sql .= " m$x.metadata_value $op ?"; + } + + # $where_sql.=" m$x.metadata_value like ?"; + # my $var=$$requestObject{'query'}{$_}; + # $var=~s/\*/%/g; + # push(@$parms,$var); + if ( $op !~ /RLIKE/ ) + { + $getparams{$_}{val} =~ s/\*/%/g; + } + push( @$parms, $getparams{$_}{val} ); + } + } + $x++; + } + $field_sql .= "," if $field_sql; + $field_sql .= " count(ch.id) as changes "; + $join_sql .= " left join change_queue ch on d.fqdn=ch.entity_key"; + + #$sql="select $field_sql from device d $join_sql where $where_sql group by d.fqdn"; + $sql = "select $field_sql from device d $join_sql "; + $sql .= "where $where_sql" if $where_sql; + $sql .= " group by d.fqdn"; + + my $rec = &recordFetch( $requestObject, $sql, $parms ); + if ( !$rec ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_NOT_FOUND; + return; + } + else + { + return $rec; + } + } -sub doSystemPUT(){ - my $requestObject=shift; - my $dbs=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS,{AutoCommit=>1}); - my $x=0; - my $fqdn=$$requestObject{'path'}[0]; - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - $logger->info("processing PUT data $$requestObject{'body'}"); - my $device_fields=&getFieldList('device',1); - my $meta_fields=&getFieldList($$requestObject{'entity'},1); - my ($sql,$set_sql,$parms,@errors,$rv); - my $now=$dbh->selectcol_arrayref('select now()'); - my $lkup_data=&doSystemGET($requestObject); - # Check to make sure the date modified/versiom of the record being submitted matches the - # the stored record. if the stored record is newer, return error - if( defined $$data{'date_modified'} ) - { - my $date_modified_submitted=ParseDate($$data{'date_modified'}); - my $date_modified_stored=ParseDate($$lkup_data{'date_modified'}); - if( Date_Cmp($date_modified_submitted,$date_modified_stored) != 0) - { - $$requestObject{'stat'}=Apache2::Const::HTTP_CONFLICT; - $logger->debug("Modification date of submitted record ($$data{'date_modified'}) is old: $$lkup_data{'date_modified'}" ) if ($logger->is_debug()); - return "stored record has already been modified"; - } - } - if($$lkup_data{'metaData'}) - { - $lkup_data=$$lkup_data{'records'}[0] - } - elsif(ref $lkup_data eq 'ARRAYREF') - { - $lkup_data=$$lkup_data[0]; - } - $dbs->begin_work; - - if(exists $$data{'agent_type'}) - { - $$data{'agent_reported'}=$$now[0]; - } - if( - ( - $data->{$IPADDRESSFIELD} - && $data->{$IPADDRESSFIELD} ne $lkup_data->{$IPADDRESSFIELD} - ) - || - ( - !exists($data->{'data_center_code'}) - #&& defined($lkup_data->{'data_center_code'}) - && length($lkup_data->{'data_center_code'})==0 - ) - ) - { - if($data->{$IPADDRESSFIELD}) - { - $data->{'data_center_code'}=&lookupDC($data->{$IPADDRESSFIELD}); - } - else - { - $data->{'data_center_code'}=&lookupDC($lkup_data->{$IPADDRESSFIELD}); - } - } - - # strip out unchanged data and trigger mtime if needed - my $mtime; - foreach(@$device_fields) - { - $$data{$_}=&doFieldNormalization('system',$_,$$data{$_}) if exists $$data{$_}; - $mtime= $$now[0] if(exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); - delete $$data{$_} if(defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_}); - } - foreach(@$meta_fields) - { - $$data{$_}=&doFieldNormalization('system',$_,$$data{$_}) if exists $$data{$_}; - $mtime= $$now[0] if(exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); - delete $$data{$_} if(defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_}); - } - my $blocked_changes={}; - &runACL($requestObject,$lkup_data,'system',$data,$blocked_changes); - # if the user is not a system user, then error out now if needed - $logger->info("changes: " . &make_json($data)); - $logger->info("blocked changes: " . &make_json($blocked_changes) ); - if(defined($requestObject->{'user'}->{'systemuser'}) && $requestObject->{'user'}->{'systemuser'} ne '1' && scalar(keys(%$blocked_changes))) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_FORBIDDEN; - return 'ACL blocked change: ' . &make_json($blocked_changes); - } - if(scalar(keys(%$blocked_changes))) - { - my $change_item={ - change_ip=>$$requestObject{'ip_address'}, - change_user=>$requestObject->{'user'}->{'username'}, - change_time=>$$now[0], - entity=>$$requestObject{'entity'}, - entity_key=>$$lkup_data{$tree->{'entities'}->{$$requestObject{'entity'}}->{'key'}}, - change_content=>&make_json($blocked_changes) - }; - &doGenericPOST({ - entity=>'change_queue', - body=>&make_json($change_item), - }); - $logger->warn("queued change for " . $$lkup_data{$tree->{'entities'}->{$$requestObject{'entity'}}->{'key'}} ); - } - - # construct update sql for device table - foreach(@$device_fields) - { - if(exists $$data{$_}) - { - $set_sql.="," if $set_sql; - if( defined($$data{$_}) && length($$data{$_})==0 ) - { - $set_sql.=" d.$_=NULL"; - #push(@$parms,undef); - } - else - { - $set_sql.=" d.$_=?"; - push(@$parms,$$data{$_}); - } - #audit - if( !$tree_extended->{entities}->{'system'}->{$_}->{meta}) - { - $dbs->do('insert into inv_audit set +sub doSystemPUT() +{ + my $requestObject = shift; + my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); + my $x = 0; + my $fqdn = $$requestObject{'path'}[0]; + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + $logger->info("processing PUT data $$requestObject{'body'}"); + my $device_fields = &getFieldList( 'device', 1 ); + my $meta_fields = &getFieldList( $$requestObject{'entity'}, 1 ); + my ( $sql, $set_sql, $parms, @errors, $rv ); + my $now = $dbh->selectcol_arrayref('select now()'); + my $lkup_data = &doSystemGET($requestObject); + + # Check to make sure the date modified/versiom of the record being submitted matches the + # the stored record. if the stored record is newer, return error + if ( defined $$data{'date_modified'} ) + { + my $date_modified_submitted = ParseDate( $$data{'date_modified'} ); + my $date_modified_stored = ParseDate( $$lkup_data{'date_modified'} ); + if ( Date_Cmp( $date_modified_submitted, $date_modified_stored ) != 0 ) + { + $$requestObject{'stat'} = Apache2::Const::HTTP_CONFLICT; + $logger->debug("Modification date of submitted record ($$data{'date_modified'}) is old: $$lkup_data{'date_modified'}") if ( $logger->is_debug() ); + return "stored record has already been modified"; + } + } + if ( $$lkup_data{'metaData'} ) + { + $lkup_data = $$lkup_data{'records'}[0]; + } + elsif ( ref $lkup_data eq 'ARRAYREF' ) + { + $lkup_data = $$lkup_data[0]; + } + $dbs->begin_work; + + if ( exists $$data{'agent_type'} ) + { + $$data{'agent_reported'} = $$now[0]; + } + if ( + ( $data->{$IPADDRESSFIELD} && $data->{$IPADDRESSFIELD} ne $lkup_data->{$IPADDRESSFIELD} ) + || ( + !exists( $data->{'data_center_code'} ) + + #&& defined($lkup_data->{'data_center_code'}) + && length( $lkup_data->{'data_center_code'} ) == 0 + ) + ) + { + if ( $data->{$IPADDRESSFIELD} ) + { + $data->{'data_center_code'} = &lookupDC( $data->{$IPADDRESSFIELD} ); + } + else + { + $data->{'data_center_code'} = &lookupDC( $lkup_data->{$IPADDRESSFIELD} ); + } + } + + # strip out unchanged data and trigger mtime if needed + my $mtime; + foreach (@$device_fields) + { + $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); + delete $$data{$_} if ( defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); + } + foreach (@$meta_fields) + { + $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); + delete $$data{$_} if ( defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); + } + my $blocked_changes = {}; + &runACL( $requestObject, $lkup_data, 'system', $data, $blocked_changes ); + + # if the user is not a system user, then error out now if needed + $logger->info( "changes: " . &make_json($data) ); + $logger->info( "blocked changes: " . &make_json($blocked_changes) ); + if ( defined( $requestObject->{'user'}->{'systemuser'} ) && $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; + return 'ACL blocked change: ' . &make_json($blocked_changes); + } + if ( scalar( keys(%$blocked_changes) ) ) + { + my $change_item = { + change_ip => $$requestObject{'ip_address'}, + change_user => $requestObject->{'user'}->{'username'}, + change_time => $$now[0], + entity => $$requestObject{'entity'}, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + change_content => &make_json($blocked_changes) + }; + &doGenericPOST( + { + entity => 'change_queue', + body => &make_json($change_item), + } + ); + $logger->warn( "queued change for " . $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} } ); + } + + # construct update sql for device table + foreach (@$device_fields) + { + if ( exists $$data{$_} ) + { + $set_sql .= "," if $set_sql; + if ( defined( $$data{$_} ) && length( $$data{$_} ) == 0 ) + { + $set_sql .= " d.$_=NULL"; + + #push(@$parms,undef); + } + else + { + $set_sql .= " d.$_=?"; + push( @$parms, $$data{$_} ); + } + + #audit + if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} ) + { + $dbs->do( + 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -2495,76 +2668,81 @@ sub doSystemPUT(){ change_time=?, change_user=?, change_ip=?', - {}, - ('device', - $$lkup_data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}, - $_, #field - $$lkup_data{$_}, #old val - $$data{$_}, # new val - $$now[0], - $requestObject->{'user'}->{'username'}, # user - $$requestObject{'ip_address'} # ip - ) - ); - } - } - } - if($mtime) - { - $set_sql.="," if $set_sql; - $set_sql.=" d.date_modified=?"; - push(@$parms,$mtime); - } - $sql="update device d set $set_sql where fqdn=?"; - push(@$parms,$fqdn); - $logger->debug("doing sql: $sql with " . &make_json($parms) ) if ($logger->is_debug()); - $dbs->do($sql,{},@$parms) or push(@errors,$dbs->err . ": " . $dbs->errstr); - if($dbs->err) - { - $logger->error($dbs->err . " : " . $dbs->errstr ) if ($dbs->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - $dbs->rollback; - return \@errors; - } - - ## check for fqdn change and adjust internal fqdn var to reflect the new name - if(exists $$data{'fqdn'} && $$lkup_data{'fqdn'} ne $$data{'fqdn'}) - { - $fqdn=$$data{'fqdn'}; - } - #update or insert into ip table - #doUpdateIps($fqdn,$data); - - - # do update or insert into device_metadata - foreach(@$meta_fields) - { - if(exists $$data{$_}) - { - $$data{$_}=&doFieldNormalization('system',$_,$$data{$_}); - my $lkup=$dbs->selectrow_hashref("select fqdn from device_metadata where metadata_name=? and fqdn=?",{},($_,$fqdn)); - # if exists do update - if($$lkup{'fqdn'}) - { - $sql="update device_metadata set metadata_value=? where metadata_name=? and fqdn=?"; - } - # otherwise insert into device_metadata - else - { - $sql="insert into device_metadata set metadata_value=?,metadata_name=?,fqdn=?,date_created=now()"; - } - @$parms=($$data{$_},$_,$fqdn); - $logger->debug("doing sql: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - $rv=$dbs->do($sql,{},@$parms) or push(@errors,$dbs->err . ": " . $dbs->errstr); - if($dbs->err) - { - $logger->error($dbs->err . " : " . $dbs->errstr ) if ($dbs->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - } - #audit - if( !$tree_extended->{entities}->{'system'}->{$_}->{meta}) - { - $dbs->do('insert into inv_audit set + {}, + ( + 'device', + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $_, #field + $$lkup_data{$_}, #old val + $$data{$_}, # new val + $$now[0], + $requestObject->{'user'}->{'username'}, # user + $$requestObject{'ip_address'} # ip + ) + ); + } + } + } + if ($mtime) + { + $set_sql .= "," if $set_sql; + $set_sql .= " d.date_modified=?"; + push( @$parms, $mtime ); + } + $sql = "update device d set $set_sql where fqdn=?"; + push( @$parms, $fqdn ); + $logger->debug( "doing sql: $sql with " . &make_json($parms) ) if ( $logger->is_debug() ); + $dbs->do( $sql, {}, @$parms ) or push( @errors, $dbs->err . ": " . $dbs->errstr ); + if ( $dbs->err ) + { + $logger->error( $dbs->err . " : " . $dbs->errstr ) if ( $dbs->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + $dbs->rollback; + return \@errors; + } + + ## check for fqdn change and adjust internal fqdn var to reflect the new name + if ( exists $$data{'fqdn'} && $$lkup_data{'fqdn'} ne $$data{'fqdn'} ) + { + $fqdn = $$data{'fqdn'}; + } + + #update or insert into ip table + #doUpdateIps($fqdn,$data); + + # do update or insert into device_metadata + foreach (@$meta_fields) + { + if ( exists $$data{$_} ) + { + $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ); + my $lkup = $dbs->selectrow_hashref( "select fqdn from device_metadata where metadata_name=? and fqdn=?", {}, ( $_, $fqdn ) ); + + # if exists do update + if ( $$lkup{'fqdn'} ) + { + $sql = "update device_metadata set metadata_value=? where metadata_name=? and fqdn=?"; + } + + # otherwise insert into device_metadata + else + { + $sql = "insert into device_metadata set metadata_value=?,metadata_name=?,fqdn=?,date_created=now()"; + } + @$parms = ( $$data{$_}, $_, $fqdn ); + $logger->debug( "doing sql: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + $rv = $dbs->do( $sql, {}, @$parms ) or push( @errors, $dbs->err . ": " . $dbs->errstr ); + if ( $dbs->err ) + { + $logger->error( $dbs->err . " : " . $dbs->errstr ) if ( $dbs->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + } + + #audit + if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} ) + { + $dbs->do( + 'insert into inv_audit set entity_name=?, entity_key=?, field_name=?, @@ -2573,125 +2751,132 @@ sub doSystemPUT(){ change_time=?, change_user=?, change_ip=?', - {}, - ('device', - $$lkup_data{$tree->{entities}->{$$requestObject{'entity'}}->{key}}, - $_, #field - $$lkup_data{$_}, #old val - $$data{$_}, # new val - $$now[0], - $requestObject->{'user'}->{'username'}, # user - $$requestObject{'ip_address'} # ip - ) - ); - } - } - } - if(scalar(@errors)) - { - $dbs->rollback; - $logger->error("error encountered " . scalar(@errors) . ": " . &make_json(\@errors) ); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return \@errors; - } - else - { - # $$requestObject{'stat'}=Apache2::Const::HTTP_NO_CONTENT; - $dbs->commit; - return &doSystemGET($requestObject); - } + {}, + ( + 'device', + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $_, #field + $$lkup_data{$_}, #old val + $$data{$_}, # new val + $$now[0], + $requestObject->{'user'}->{'username'}, # user + $$requestObject{'ip_address'} # ip + ) + ); + } + } + } + if ( scalar(@errors) ) + { + $dbs->rollback; + $logger->error( "error encountered " . scalar(@errors) . ": " . &make_json( \@errors ) ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return \@errors; + } + else + { + # $$requestObject{'stat'}=Apache2::Const::HTTP_NO_CONTENT; + $dbs->commit; + return &doSystemGET($requestObject); + } } -sub doSystemPOST(){ - my $requestObject=shift; - my $dbs=DBI->connect("DBI:$DRIVER:database=$DATABASE;host=$DBHOST",$DBUSER,$DBPASS,{AutoCommit=>1}); - my $x=0; - my $data=&eat_json($$requestObject{'body'},{allow_nonref=>1}); - my $fqdn=$$data{'fqdn'}; - $logger->info("processing POST data $$requestObject{'body'}"); - my $device_fields=&getFieldList('device',1); - my $meta_fields=&getFieldList($$requestObject{'entity'},1); - my ($sql,$set_sql,$parms,@errors,$rv); - $data->{'inventory_component_type'} = 'system' unless $data->{'inventory_component_type'}; - if($data->{$IPADDRESSFIELD} && !$data->{'data_center_code'}) - { - $data->{'data_center_code'}=&lookupDC($data->{$IPADDRESSFIELD}); - } - $dbs->begin_work; - - if((!exists $$data{$_} || $$data{$_} == '') && $tree_extended->{entities}->{'system'}->{$_}->{default_value}) - { - $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; - } - - # construct insert sql for device table - foreach(@$device_fields) - { - next if $_ eq 'created_by'; - if(exists $$data{$_}) - { - $$data{$_}=&doFieldNormalization('system',$_,$$data{$_}); - $set_sql.="," if $set_sql; - if( length($$data{$_})==0 ) - { - $set_sql.=" $_=NULL"; - #push(@$parms,undef); - } - else - { - $set_sql.=" $_=?"; - push(@$parms,$$data{$_}); - } - } - } - $sql="insert into device set created_by='', $set_sql"; - $logger->debug("doing sql: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - $dbs->do($sql,{},@$parms) or push(@errors,$dbs->err . ": " . $dbs->errstr); - if($dbs->err) - { - $logger->error($dbs->err . " : " . $dbs->errstr) if ($dbs->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - $dbs->rollback; - return \@errors; - } - #doUpdateIps($fqdn,$data); - - # do update or insert into device_metadata - foreach(@$meta_fields) - { - if(exists $$data{$_}) - { - $$data{$_}=&doFieldNormalization('system',$_,$$data{$_}) if exists $$data{$_}; - $sql="insert into device_metadata set metadata_value=?,metadata_name=?,fqdn=?,date_created=now()"; - @$parms=($$data{$_},$_,$fqdn); - $logger->debug("doing sql: $sql with " . join(',',@$parms) ) if ($logger->is_debug()); - $rv=$dbs->do($sql,{},@$parms) or push(@errors,$dbs->err . ": " . $dbs->errstr); - if($dbs->err) - { - $logger->error($dbs->err . " : " . $dbs->errstr ) if ($dbs->err); - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - } - - } - } - if(scalar(@errors)) - { - $dbs->rollback; - $$requestObject{'stat'}=Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; - return \@errors; - } - else - { - $dbs->commit; - $$requestObject{'headers_out'}=['Location',"/cmdb_api/v1/system/$fqdn"]; - } - return; - + +sub doSystemPOST() +{ + my $requestObject = shift; + my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); + my $x = 0; + my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); + my $fqdn = $$data{'fqdn'}; + $logger->info("processing POST data $$requestObject{'body'}"); + my $device_fields = &getFieldList( 'device', 1 ); + my $meta_fields = &getFieldList( $$requestObject{'entity'}, 1 ); + my ( $sql, $set_sql, $parms, @errors, $rv ); + $data->{'inventory_component_type'} = 'system' unless $data->{'inventory_component_type'}; + + if ( $data->{$IPADDRESSFIELD} && !$data->{'data_center_code'} ) + { + $data->{'data_center_code'} = &lookupDC( $data->{$IPADDRESSFIELD} ); + } + $dbs->begin_work; + + if ( ( !exists $$data{$_} || $$data{$_} == '' ) && $tree_extended->{entities}->{'system'}->{$_}->{default_value} ) + { + $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; + } + + # construct insert sql for device table + foreach (@$device_fields) + { + next if $_ eq 'created_by'; + if ( exists $$data{$_} ) + { + $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ); + $set_sql .= "," if $set_sql; + if ( length( $$data{$_} ) == 0 ) + { + $set_sql .= " $_=NULL"; + + #push(@$parms,undef); + } + else + { + $set_sql .= " $_=?"; + push( @$parms, $$data{$_} ); + } + } + } + $sql = "insert into device set created_by='', $set_sql"; + $logger->debug( "doing sql: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + $dbs->do( $sql, {}, @$parms ) or push( @errors, $dbs->err . ": " . $dbs->errstr ); + if ( $dbs->err ) + { + $logger->error( $dbs->err . " : " . $dbs->errstr ) if ( $dbs->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + $dbs->rollback; + return \@errors; + } + + #doUpdateIps($fqdn,$data); + + # do update or insert into device_metadata + foreach (@$meta_fields) + { + if ( exists $$data{$_} ) + { + $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; + $sql = "insert into device_metadata set metadata_value=?,metadata_name=?,fqdn=?,date_created=now()"; + @$parms = ( $$data{$_}, $_, $fqdn ); + $logger->debug( "doing sql: $sql with " . join( ',', @$parms ) ) if ( $logger->is_debug() ); + $rv = $dbs->do( $sql, {}, @$parms ) or push( @errors, $dbs->err . ": " . $dbs->errstr ); + if ( $dbs->err ) + { + $logger->error( $dbs->err . " : " . $dbs->errstr ) if ( $dbs->err ); + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + } + + } + } + if ( scalar(@errors) ) + { + $dbs->rollback; + $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + return \@errors; + } + else + { + $dbs->commit; + $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/system/$fqdn" ]; + } + return; + } sub lookupDC() { - my $ip=shift; - my $dc=$dbh->selectcol_arrayref('select data_center_code from + my $ip = shift; + my $dc = $dbh->selectcol_arrayref( + 'select data_center_code from datacenter_subnet s where INET_ATON(?) BETWEEN INET_ATON( @@ -2702,52 +2887,57 @@ sub lookupDC() INSTR(subnet, "/")-1))+ POW(2, 32-SUBSTRING(subnet, INSTR(subnet , "/")+1 - ))-1',{},($ip)); - return $$dc[0]; + ))-1', {}, ($ip) + ); + return $$dc[0]; } -sub doUpdateIps { - my $fqdn = shift; - my $data = shift; - my $ips = getExistingIps($fqdn); - my @interfaces; - foreach my $slot (grep(/mac[_]{0,1}address_.*/, keys %$data)) { - $slot =~ /mac[_]{0,1}address_(.*)/; - push(@interfaces, $1); - } - #check posted/put'd interfaces against those in DB - foreach my $interface (@interfaces) { #foreach posted interface - $logger->info("interface: $interface"); - my $mac_post= $data->{(grep(/mac[_]{0,1}address_$interface$/, keys %$data))[0]}; - my $add_post= $data->{(grep(/ip[_]{0,1}address_$interface$/, keys %$data))[0]}; - - $logger->info("\tmac_post: $mac_post"); - $logger->info("\tmac_db: $ips->{$interface}->{mac_address}"); - $logger->info("\tadd_post: $add_post"); - $logger->info("\tadd_db: $ips->{$interface}->{address}"); - doGenericPUT({entity=>'ip', body=>to_json({ fqdn=>$fqdn, address=>$add_post, interface=>$interface,mac_address=>$mac_post}),getparams=>"interface=$interface"}); - next; - unless($mac_post eq $ips->{$interface}->{mac_address}) { - #update changed mac - my $sql = "update ip set mac_address='$mac_post' where fqdn='$fqdn';"; - $logger->debug("$sql") if ($logger->is_debug()); - } - unless($add_post eq $ips->{$interface}->{address}) { - #update changed address - my $sql = "update ip set address='$add_post' where fqdn='$fqdn' and interface='$interface';"; - $logger->debug("$sql") if ($logger->is_debug()); - my $sth=$dbh->prepare($sql); - my $rv=$sth->execute(); - $logger->error($sth->err . " : " . $sth->errstr ) if ($sth->err); - } +sub doUpdateIps +{ + my $fqdn = shift; + my $data = shift; + my $ips = getExistingIps($fqdn); + my @interfaces; + foreach my $slot ( grep( /mac[_]{0,1}address_.*/, keys %$data ) ) + { + $slot =~ /mac[_]{0,1}address_(.*)/; + push( @interfaces, $1 ); + } + #check posted/put'd interfaces against those in DB + foreach my $interface (@interfaces) + { #foreach posted interface + $logger->info("interface: $interface"); + my $mac_post = $data->{ ( grep( /mac[_]{0,1}address_$interface$/, keys %$data ) )[0] }; + my $add_post = $data->{ ( grep( /ip[_]{0,1}address_$interface$/, keys %$data ) )[0] }; + + $logger->info("\tmac_post: $mac_post"); + $logger->info("\tmac_db: $ips->{$interface}->{mac_address}"); + $logger->info("\tadd_post: $add_post"); + $logger->info("\tadd_db: $ips->{$interface}->{address}"); + doGenericPUT( { entity => 'ip', body => to_json( { fqdn => $fqdn, address => $add_post, interface => $interface, mac_address => $mac_post } ), getparams => "interface=$interface" } ); + next; + unless ( $mac_post eq $ips->{$interface}->{mac_address} ) + { + #update changed mac + my $sql = "update ip set mac_address='$mac_post' where fqdn='$fqdn';"; + $logger->debug("$sql") if ( $logger->is_debug() ); } -} - + unless ( $add_post eq $ips->{$interface}->{address} ) + { + #update changed address + my $sql = "update ip set address='$add_post' where fqdn='$fqdn' and interface='$interface';"; + $logger->debug("$sql") if ( $logger->is_debug() ); + my $sth = $dbh->prepare($sql); + my $rv = $sth->execute(); + $logger->error( $sth->err . " : " . $sth->errstr ) if ( $sth->err ); -1; + } + } +} +1; ## HTTP codes for reference #define RESPONSE_CODES 57 From ffa0468b2343863bed9216a45cf2b2ca5f52517e Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 11 Aug 2014 13:59:33 -0700 Subject: [PATCH 19/82] adding default_value support for entity attributes --- cmdb_api.pm | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index c28d99c..d51071e 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -395,14 +395,14 @@ sub getFieldList() my @arr; foreach ( keys( %{ $tree->{entities}->{$entity} } ) ) { - next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' ); + next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order'); push( @arr, $_ ); } if ( $valid_entities->{$entity} eq 'system' && !$bare ) { foreach ( keys( %{ $tree->{entities}->{device} } ) ) { - next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' ); + next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order' ); push( @arr, $_ ); } } @@ -2792,7 +2792,6 @@ sub doSystemPOST() my $device_fields = &getFieldList( 'device', 1 ); my $meta_fields = &getFieldList( $$requestObject{'entity'}, 1 ); my ( $sql, $set_sql, $parms, @errors, $rv ); - $data->{'inventory_component_type'} = 'system' unless $data->{'inventory_component_type'}; if ( $data->{$IPADDRESSFIELD} && !$data->{'data_center_code'} ) { @@ -2800,14 +2799,14 @@ sub doSystemPOST() } $dbs->begin_work; - if ( ( !exists $$data{$_} || $$data{$_} == '' ) && $tree_extended->{entities}->{'system'}->{$_}->{default_value} ) - { - $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; - } - # construct insert sql for device table foreach (@$device_fields) { + if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{'system'}->{$_}->{default_value} ) + { + $logger->debug("using default value ($tree_extended->{entities}->{'system'}->{$_}->{default_value}) for $_"); + $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; + } next if $_ eq 'created_by'; if ( exists $$data{$_} ) { @@ -2842,6 +2841,12 @@ sub doSystemPOST() # do update or insert into device_metadata foreach (@$meta_fields) { + if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{'system'}->{"$_"}->{default_value} ) + { + $logger->debug("using default value ($tree_extended->{entities}->{'system'}->{$_}->{default_value}) for $_"); + $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; + } + if ( exists $$data{$_} ) { $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; From 48f6eeacfae8a95cab6b635c7cb7539d2f4cf860 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 11 Aug 2014 14:00:21 -0700 Subject: [PATCH 20/82] adding default_value for a few fields --- pp_lexicon.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pp_lexicon.xml b/pp_lexicon.xml index d60dce1..88db2f9 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -228,7 +228,7 @@ string fqdn - + config string @@ -531,7 +531,7 @@ config string - + From c72c0a4a9af862a6a967d5312d4343eee0040e5b Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 11 Aug 2014 14:08:47 -0700 Subject: [PATCH 21/82] fix for default_value in doGenericPOST --- cmdb_api.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index d51071e..e4da3bc 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1124,9 +1124,10 @@ sub doGenericPOST foreach my $f ( @{ &getFieldList( $$requestObject{'entity'} ) } ) { - if ( ( !exists $$data{$f} || $$data{$f} == '' ) && $tree_extended->{entities}->{$entity}->{$f}->{default_value} ) + if ( ( !exists $$data{$f} || $$data{$f} eq '' ) && $tree_extended->{entities}->{$entity}->{$f}->{default_value} ) { - $$data{$_} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; + $logger->debug("using default value ($tree_extended->{entities}->{$entity}->{$f}->{default_value}) for $f"); + $$data{$f} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; } if ( exists $$data{$f} ) { From ee39d444857041ef00300ee682a9c8ff1d7e1398 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 11 Aug 2014 14:33:07 -0700 Subject: [PATCH 22/82] change select * to select in doGenericGET --- cmdb_api.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index e4da3bc..4d942ac 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1489,9 +1489,8 @@ sub doGenericGET() $logger->info("processing GET"); my $requestObject = shift; - # check to see if this entity requires special processing, otherwise handle with generic # assemble sql based on input parameters - $sql = "select * from $$requestObject{'entity'} where "; + $sql = "select " . join(',',@{ &getFieldList( $$requestObject{'entity'} ) }). " from $$requestObject{'entity'} where "; # check for path key value and add if specified if ( $$requestObject{'path'}[0] ) From 2dc9f57ea1b1ce5889503f61658f285abe486f99 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Sun, 19 Oct 2014 22:06:08 -0700 Subject: [PATCH 23/82] adjusting forceselect on enumeration --- pp_lexicon.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pp_lexicon.xml b/pp_lexicon.xml index 88db2f9..61cfd83 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -317,14 +317,14 @@ string manufacturer - + string productname - + @@ -430,7 +430,7 @@ config string operatingsystem - + @@ -480,7 +480,7 @@ multiselect - + From 1dbe03a4484fbd5799c98710d25d0c02158a37e1 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Sun, 19 Oct 2014 22:10:01 -0700 Subject: [PATCH 24/82] removing dep on ppenv --- cmdb_api.pm | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 4d942ac..fa3bf76 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -19,8 +19,6 @@ package cmdb_api; use strict; use warnings; -use lib '/opt/pptools'; -use ppenv; use URI::Escape; use Apache2::RequestRec (); use Apache2::Request; From dc8df3ad6b44209a8c96d5ff6648c14d5406700c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Sun, 19 Oct 2014 23:56:19 -0700 Subject: [PATCH 25/82] fixing wrong hardcoded db --- cmdb_api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index fa3bf76..37f8580 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -827,7 +827,7 @@ sub setNewName() push( @$parms, $$r{'fqdn'} ); } - my $dbh = DBI->connect( "DBI:mysql:database=inventory;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); + my $dbh = DBI->connect( "DBI:mysql:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0, RaiseError => 1 } ); my $sth; eval { From 9c7116186bc122360d778cc85b990cec58f71660 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 24 Oct 2014 11:03:31 -0700 Subject: [PATCH 26/82] adding more info to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6c27a8d..7002f85 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,8 @@ cmdb-api ======== CMDB API Server + +The inventory CMDB started as way to track assets and some parts of configuration of these assets, and evolved (for better or for worse) to be able to manage and track non-physical assets (VMs, VIPs, etc...) and also to manage non-system configuration (service, cluster instance, etc...) + +It is now a part of a larger set of loosely coupled Operations tools that we collectively refer to as NOMS: +https://github.com/evernote/noms-client/wiki From 2bcf606c7a455df02d5803b2383f6a66114c3ec1 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 10 Nov 2014 16:29:50 -0800 Subject: [PATCH 27/82] adding verbage for noms --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7002f85..ae225e7 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,8 @@ The inventory CMDB started as way to track assets and some parts of configuratio It is now a part of a larger set of loosely coupled Operations tools that we collectively refer to as NOMS: https://github.com/evernote/noms-client/wiki + +The API works very well with the UI project +CMDB-UI (https://github.com/isaacfinnegan/cmdb-ui) - Web-based GUI for the NOMS CMDB + + From 45964a7f62724d3b0b3bbc08eda5fb6d74c7b99e Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 10 Nov 2014 16:30:15 -0800 Subject: [PATCH 28/82] handling apache 2.4 not supporting connection->remoteip --- cmdb_api.pm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 37f8580..c57bef8 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -191,6 +191,7 @@ sub handler() $up_uri =~ s/.+\?//; my $uri = uri_unescape($up_uri); my $req = Apache2::Request->new($r); + my $connection = $r->connection; my ( $requestObject, $data, $formatted_data ); %{ $$requestObject{'query'} } = %{ $req->param } if $req->param; $$requestObject{'getparams'} = $uri; @@ -201,8 +202,15 @@ sub handler() $$requestObject{'pathstr'} = $req->uri(); $$requestObject{'user'} = &doGenericGET( { entity => 'user', path => [ $req->user ] } ) if $req->user; $$requestObject{'http_auth_user'} = $req->user if $req->user; - $$requestObject{'ip_address'} = $r->connection->remote_ip(); - + # apache 2.4 doesn't have this anymore + if( $connection->can('remote_ip') ) + { + $$requestObject{'ip_address'} = $connection->remote_ip(); + } + else + { + $$requestObject{'ip_address'} = $r->useragent_ip; + } if ( $$requestObject{'method'} ne 'GET' ) { $$requestObject{'body'} = read_post($r); From a61e3e5dd0fe13188aaa6198254d118d3718a638 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 11 Nov 2014 17:56:29 -0800 Subject: [PATCH 29/82] adjust naming api to nccsystemname. pcmsystemname still supported --- cmdb_api.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index c57bef8..f8f8f9d 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -81,7 +81,8 @@ my $opt = Optconfig->new( system => 'System', device => 'System', newhostname => 'Provision', - pcmsystemname => 'ProvisionPcm', + pcmsystemname => 'ProvisionNcc', + nccsystemname => 'ProvisionNcc', user => 'Generic', currentUser => 'User', inv_audit => 'Generic', @@ -624,7 +625,7 @@ sub doTrafficControlPOST() } -sub doProvisionPcmGET() +sub doProvisionNccGET() { my $requestObject = shift; my $id = $$requestObject{'path'}[0]; From 1b109097dc02568db50f1bbc6285d421ab69caf5 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 13 Nov 2014 13:24:42 -0800 Subject: [PATCH 30/82] updating setnewname to implement lexicon defaults when its creating a new system record --- cmdb_api.pm | 55 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index f8f8f9d..a39a7f8 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -665,11 +665,14 @@ sub doProvisionNccGET() } # special api to fetch new hostname for provisioning +# this API is deprecated sub doProvisionGET() { my $requestObject = shift; my ( $sql, $sth, $rv ); + $logger->warn("doProvisionGET API is deprecated and should not longer be used"); + # this code seems pointless, why check for length 7 if we are going to remove chars? if ( $$requestObject{'query'}{'serial_number'} && length( $$requestObject{'query'}{'serial_number'} ) == 7 ) { @@ -786,6 +789,7 @@ sub setNewName() my $newname; # check to see if the name is correct or if box is set to production/deployment + #TODO: this should be kicking in based on violating an ACL or all just needs to go away if ( $$r{'fqdn'} !~ /m\d{7}\.ppops\.net/ && $$r{'status'} ne 'production' && $$r{'status'} ne 'deployment' ) { if ( defined $$r{'mac_address'} ) @@ -855,6 +859,19 @@ sub setNewName() $sql = "update device set fqdn=? where id=?"; $sth = $dbh->prepare($sql); executeDbStatement( $sth, $sql, ( $newname, $device_id ) ); + # we also need to setup any default field values that the lexicon has + my $data=&applyDefaults({},'system'); + # assemble whats needed to call the systemPUT api to make the update + doSystemPUT( + { + 'path' => ['$newname'], + 'body' => make_json($data), + 'entity' => 'system', + 'user' => { 'systemuser' => 1, 'username' => 'lexicon_defaults' }, + 'ip_address' => 'nccsystemname' + } + ); + } $dbh->commit; @@ -1128,14 +1145,9 @@ sub doGenericPOST $$requestObject{'stat'} = Apache2::Const::HTTP_FORBIDDEN; return 'ACL blocked change: ' . &make_json($blocked_changes); } - + $data = &applyDefaults($data,$entity); foreach my $f ( @{ &getFieldList( $$requestObject{'entity'} ) } ) { - if ( ( !exists $$data{$f} || $$data{$f} eq '' ) && $tree_extended->{entities}->{$entity}->{$f}->{default_value} ) - { - $logger->debug("using default value ($tree_extended->{entities}->{$entity}->{$f}->{default_value}) for $f"); - $$data{$f} = $tree_extended->{entities}->{$entity}->{$f}->{default_value}; - } if ( exists $$data{$f} ) { $$data{$f} = &doFieldNormalization( $entity, $f, $$data{$f} ); @@ -2788,6 +2800,24 @@ sub doSystemPUT() } } +sub applyDefaults() +{ + my $data=shift; + my $entity=shift; + + my $fields = &getFieldList( $entity ); + + foreach (@$fields) + { + if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{default_value} ) + { + $logger->debug("using default value ($tree_extended->{entities}->{ $entity }->{$_}->{default_value}) for $_"); + $$data{$_} = $tree_extended->{entities}->{ $entity }->{$_}->{default_value}; + } + } + return $data; +} + sub doSystemPOST() { my $requestObject = shift; @@ -2806,14 +2836,11 @@ sub doSystemPOST() } $dbs->begin_work; + #setup default values if there are any needed + $data=&applyDefaults($data,'system'); # construct insert sql for device table foreach (@$device_fields) { - if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{'system'}->{$_}->{default_value} ) - { - $logger->debug("using default value ($tree_extended->{entities}->{'system'}->{$_}->{default_value}) for $_"); - $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; - } next if $_ eq 'created_by'; if ( exists $$data{$_} ) { @@ -2848,12 +2875,6 @@ sub doSystemPOST() # do update or insert into device_metadata foreach (@$meta_fields) { - if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{'system'}->{"$_"}->{default_value} ) - { - $logger->debug("using default value ($tree_extended->{entities}->{'system'}->{$_}->{default_value}) for $_"); - $$data{$_} = $tree_extended->{entities}->{'system'}->{$_}->{default_value}; - } - if ( exists $$data{$_} ) { $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; From 941cc415dcb50bc0c6b4069a0db67e202062eb53 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 13 Nov 2014 14:42:25 -0800 Subject: [PATCH 31/82] trying to fix default field value application and minor logging fixes --- cmdb_api.pm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index a39a7f8..c61a047 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -405,7 +405,7 @@ sub getFieldList() next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order'); push( @arr, $_ ); } - if ( $valid_entities->{$entity} eq 'system' && !$bare ) + if ( $entity eq 'system' && !$bare ) { foreach ( keys( %{ $tree->{entities}->{device} } ) ) { @@ -860,11 +860,14 @@ sub setNewName() $sth = $dbh->prepare($sql); executeDbStatement( $sth, $sql, ( $newname, $device_id ) ); # we also need to setup any default field values that the lexicon has + + #commit so the next edit works + $dbh->commit; my $data=&applyDefaults({},'system'); # assemble whats needed to call the systemPUT api to make the update doSystemPUT( { - 'path' => ['$newname'], + 'path' => ["$newname"], 'body' => make_json($data), 'entity' => 'system', 'user' => { 'systemuser' => 1, 'username' => 'lexicon_defaults' }, @@ -1135,10 +1138,10 @@ sub doGenericPOST my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); my $blocked_changes = {}; &runACL( $requestObject, {}, $entity, $data, $blocked_changes ); + $logger->info( "blocked POST fields: " . &make_json($blocked_changes) ); - # if the user is not a system user, then error out now if needed - $logger->info( "blocked PUT fields: " . &make_json($blocked_changes) ); my $now = $dbh->selectcol_arrayref('select now()'); + # if the user is not a system user, then error out now if needed if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) { $dbh->rollback; @@ -2141,7 +2144,7 @@ sub doEnvironmentsServicesPOST() &runACL( $requestObject, {}, 'services', $data, $blocked_changes ); # if the user is not a system user, then error out now if needed - $logger->info( "blocked PUT fields: " . &make_json($blocked_changes) ); + $logger->info( "blocked POST fields: " . &make_json($blocked_changes) ); my $now = $dbh->selectcol_arrayref('select now()'); if ( $requestObject->{'user'}->{'systemuser'} ne '1' && scalar( keys(%$blocked_changes) ) ) @@ -2806,12 +2809,11 @@ sub applyDefaults() my $entity=shift; my $fields = &getFieldList( $entity ); - foreach (@$fields) { if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{default_value} ) { - $logger->debug("using default value ($tree_extended->{entities}->{ $entity }->{$_}->{default_value}) for $_"); + $logger->debug("using ($entity) default value '$tree_extended->{entities}->{ $entity }->{$_}->{default_value}' for $_"); $$data{$_} = $tree_extended->{entities}->{ $entity }->{$_}->{default_value}; } } From 58b6fd7cbf5abe5fee2734424f6a85032ace59d7 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 13 Nov 2014 15:14:36 -0800 Subject: [PATCH 32/82] entities dont ahave labels --- pp_lexicon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pp_lexicon.xml b/pp_lexicon.xml index 61cfd83..7d8342b 100644 --- a/pp_lexicon.xml +++ b/pp_lexicon.xml @@ -168,7 +168,7 @@ - + config From 8923b0b14f6fa52ead3aee7951ac8b9222216bec Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 13 Nov 2014 15:15:03 -0800 Subject: [PATCH 33/82] logger does the right there here, dont need the condition --- cmdb_api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index c61a047..0e497fe 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -301,7 +301,7 @@ sub handler() message => $data }; } - $logger->debug("final return of status: $$requestObject{'stat'}") if ( $logger->is_debug() ); + $logger->debug("final return of status: $$requestObject{'stat'}"); #TODO reconcile the '"string" data that comes back from above and how we output it (errors, etc...) if ( $$requestObject{'headers_out'} ) From f5dc0396c5de605b35786601946d58d8ed034a47 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 13 Nov 2014 15:15:17 -0800 Subject: [PATCH 34/82] updating as productname was changed from product_name --- cmdb_tests.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index 21148cd..0d4651a 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -23,8 +23,8 @@ edit service_instance_data,,service_instance_data,9999,"{""data_value"":""newdat delete service_instance_data,,service_instance_data,9999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, delete service_instance,,service_instance,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, -inv_normalizer,,inv_normalizer,999,"{""id"":999,""field_name"":""product_name"",""matcher"":""testname"",""sub_value"":""SpecialTestProduct"",""entity_name"":""system""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/inv_normalizer/999 -do edit against normalizer,,system,test.test.com,"{""product_name"":""testname2""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,product_name=SpecialTestProduct +inv_normalizer,,inv_normalizer,999,"{""id"":999,""field_name"":""productname"",""matcher"":""testname"",""sub_value"":""SpecialTestProduct"",""entity_name"":""system""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/inv_normalizer/999 +do edit against normalizer,,system,test.test.com,"{""productname"":""testname2""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,productname=SpecialTestProduct delete normalizer,,inv_normalizer,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, lookup nonexistent role,,role,norole,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, From 34d4ea086db73e033f0f3e9dd22518fa47039958 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Sun, 23 Nov 2014 21:43:53 -0800 Subject: [PATCH 35/82] changing the way service data is assembled to show inherited values when viewing with ?_meta, which is the new way to do ?_tag_environment --- cmdb_api.pm | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 0e497fe..1b0e32d 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1685,7 +1685,7 @@ sub doEnvironmentsServicesGET() parseQueryParams( $requestObject->{'getparams'}, \%getparams, [ 'type', 'name' ] ); } - $environment_tag = 1 if defined $requestObject->{'query'}->{'_tag_environment'}; + $environment_tag = 1 if (defined $requestObject->{'query'}->{'_tag_environment'} || defined $requestObject->{'query'}->{'_meta'}); for my $env ( @{ $rtn->{'data'} } ) { @@ -1706,9 +1706,8 @@ sub doEnvironmentsServicesGET() push @parents, $hash{ $parents[-1] }; } pop @parents; - %hash = (); - my $list = join( ', ', map { "'$_'" } @parents ); + my $list = join( ', ', map { "'$_'" } reverse(@parents) ); $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . " (select name, environment_name, note, svc_id, type from service_instance " . " where type != 'environment' "; if ( defined $service ) @@ -1742,18 +1741,41 @@ sub doEnvironmentsServicesGET() note => $data->{'note'}, }; } + else + { + $hash{ $data->{'name'} }->{'name'} = $data->{'name'}; + $hash{ $data->{'name'} }->{'environment_name'} = $data->{'environment_name'}; + $hash{ $data->{'name'} }->{'type'} = $data->{'type'}; + $hash{ $data->{'name'} }->{'svc_id'} = $data->{'svc_id'}; + $hash{ $data->{'name'} }->{'note'} = $data->{'note'}; + } my $svc = $hash{ $data->{'name'} }; my $key = $data->{'data_key'}; my $value = $data->{'data_value'}; - next if ( ( not defined $key ) || exists $svc->{$key} ); + next if ( not defined $key || grep(/^$key$/,['name','note','type','svc_id','environment_name'] ) ); + # next if ( ( not defined $key ) || exists $svc->{$key} ); if ($environment_tag) { + my $inherited=0; + $logger->debug("TEST $key " . $svc->{$key}); + if(exists $svc->{$key}) + { + $inherited = $svc->{$key}; + $logger->debug("inherited $key"); + } + $svc->{$key} = { value => $value, environment_name => $data->{'environment_name'} }; + + if($inherited != 0) + { + $svc->{$key}->{'inherited'} = $inherited; + } + } else { From 303af775e997f6a8dab147184d1dbd5bb1e2e31e Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 8 Dec 2014 12:43:08 -0800 Subject: [PATCH 36/82] converting to json lexicon and prefixing meta related attributes with _ --- cmdb_api.pm | 65 ++-- lexicon.json | 989 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1024 insertions(+), 30 deletions(-) create mode 100644 lexicon.json diff --git a/cmdb_api.pm b/cmdb_api.pm index 1b0e32d..80b0dfd 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -33,9 +33,8 @@ use Apache2::Const -compile => qw(OK HTTP_NOT_FOUND HTTP_OK HTTP_FAILED_DEPENDEN use APR::Const -compile => qw(SUCCESS BLOCK_READ); use constant IOBUFSIZE => 8192; use JSON; -use XML::Parser; -use XML::Simple; use Apache::DBI; +use XML::Simple; use Date::Manip; use Optconfig; use DBI; @@ -64,7 +63,7 @@ my $opt = Optconfig->new( 'debug' => 1, 'prism_domain' => 'prism.ppops.net', 'logconfig' => '/var/www/cmdb_api/log4perl.conf', - 'lexicon' => '/var/www/cmdb_api/pp_lexicon.xml', + 'lexicon' => '/var/www/cmdb_api/lexicon.json', 'ipaddress_attribute' => "ip_address", "traffic_control_search_fields" => [ "fqdn", "macaddress", "ipaddress" ], 'entities' => { @@ -139,8 +138,14 @@ my $valid_entities = $opt->{'entities'}; my $versions = ['v1']; -$parser = XML::Simple->new(); -eval { $tree = $parser->XMLin($lexicon); }; +eval { + if (open (my $json_str, $lexicon)) + { + local $/ = undef; + $tree = eat_json(<$json_str>); + close($json_str); + } +}; # show error and die if xml parsing of the lexicon failed if ($@) @@ -159,7 +164,7 @@ foreach ( keys( %{ $tree_extended->{entities} } ) ) { if ( $tree_extended->{entities}->{$_}->{extends} ) { - my $extends = &lkupXMLPath( $tree->{entities}->{$_}->{extends} ); + my $extends = &lkupLexiconPath( $tree->{entities}->{$_}->{extends} ); foreach my $attr ( keys(%$extends) ) { $tree_extended->{entities}->{$_}->{$attr} = $extends->{$attr}; @@ -169,7 +174,7 @@ foreach ( keys( %{ $tree_extended->{entities} } ) ) $logger->debug( "lexicon: " . &make_json( $tree, { pretty => 1, allow_nonref => 1 } ) ) if ( $logger->is_debug() ); $logger->debug( "lexicon extended: " . &make_json( $tree_extended, { pretty => 1, allow_nonref => 1 } ) ) if ( $logger->is_debug() ); -sub lkupXMLPath() +sub lkupLexiconPath() { my $str = shift; my @seg = split( '/', $str ); @@ -253,11 +258,11 @@ sub handler() { if ( $lex->{$_}->{$attr} && ref( $lex->{$_}->{$attr} ) eq 'HASH' - && defined $lex->{$_}->{$attr}->{'enumeration'} - && defined $lex->{$_}->{$attr}->{'enumeration'}->{'entity'} - && defined $lex->{$_}->{$attr}->{'enumeration'}->{'attribute'} ) + && defined $lex->{$_}->{$attr}->{'_enumeration'} + && defined $lex->{$_}->{$attr}->{'_enumeration'}->{'entity'} + && defined $lex->{$_}->{$attr}->{'_enumeration'}->{'attribute'} ) { - $lex->{$_}->{$attr}->{'enumeration'}->{'enumerator'} = &doColumn_lkupGET( $requestObject, $lex->{$_}{$attr}{'enumeration'}{'entity'}, $lex->{$_}{$attr}{'enumeration'}->{'attribute'} ); + $lex->{$_}->{$attr}->{'_enumeration'}->{'enumerator'} = &doColumn_lkupGET( $requestObject, $lex->{$_}{$attr}{'_enumeration'}{'entity'}, $lex->{$_}{$attr}{'_enumeration'}->{'attribute'} ); } } } @@ -402,14 +407,14 @@ sub getFieldList() my @arr; foreach ( keys( %{ $tree->{entities}->{$entity} } ) ) { - next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order'); + next if ( $_ =~ /^_/ || $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order'); push( @arr, $_ ); } if ( $entity eq 'system' && !$bare ) { foreach ( keys( %{ $tree->{entities}->{device} } ) ) { - next if ( $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order' ); + next if ( $_ =~ /^_/ || $_ eq 'key' || $_ eq 'extends' || $_ eq 'table' || $_ eq 'field_order' ); push( @arr, $_ ); } } @@ -590,9 +595,9 @@ sub doTrafficControlPOST() my $data_assembled = { 'fqdn' => $data->{'fqdn'} }; foreach my $attr ( keys( %{ $tree_extended->{'entities'}->{'system'} } ) ) { - if ( ref( $tree_extended->{'entities'}->{'system'}->{$attr} ) eq 'HASH' && $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'} ) + if ( ref( $tree_extended->{'entities'}->{'system'}->{$attr} ) eq 'HASH' && $tree_extended->{'entities'}->{'system'}->{$attr}->{'_fact'} ) { - foreach my $fact_lookup ( split( ',', $tree_extended->{'entities'}->{'system'}->{$attr}->{'fact'} ) ) + foreach my $fact_lookup ( split( ',', $tree_extended->{'entities'}->{'system'}->{$attr}->{'_fact'} ) ) { $logger->debug("doing fact lookup for $attr with $fact_lookup"); if ( $data->{$fact_lookup} ) @@ -945,7 +950,7 @@ sub recordFetch() metaData => { root => 'records', totalProperty => 'total', - id => $tree->{entities}->{ $$requestObject{'entity'} }->{key}, + id => $tree->{entities}->{ $$requestObject{'entity'} }->{_key}, fields => &getFieldList( $$requestObject{'entity'} ) } }; @@ -1068,7 +1073,7 @@ sub doGenericPUT {}, ( $entity, - $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }, $f, #field $$lkup_data{$f}, #old val $$data{$f}, # new val @@ -1089,7 +1094,7 @@ sub doGenericPUT my $sql_set = join( ',', @sql ); # assemple final sql - $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; + $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{_key} . "=?"; push( @$parms, $$requestObject{'path'}[0] ); ## do sql and record any errors @@ -1118,9 +1123,9 @@ sub doGenericPUT $dbs->commit; # check to see if key value was chnaged during this put, and adjust for GET - if ( $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} } ) + if ( $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} } ) { - $$requestObject{'path'}[0] = $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }; + $$requestObject{'path'}[0] = $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }; } return &doGenericGET($requestObject); } @@ -1181,7 +1186,7 @@ sub doGenericPOST {}, ( $entity, - $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }, 'record', #field '', #old val 'CREATED', # new val @@ -1202,7 +1207,7 @@ sub doGenericPOST $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; return $sth->err . " : " . $sth->errstr; } - $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/" . $entity . "/" . $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} } ]; + $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/" . $entity . "/" . $$data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} } ]; return; } @@ -1382,7 +1387,7 @@ sub doAclPUT {}, ( $entity, - $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }, $f, #field $$lkup_data{$f}, #old val $$data{$f}, # new val @@ -1397,7 +1402,7 @@ sub doAclPUT my $sql_set = join( ',', @sql ); # assemple final sql - $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{key} . "=?"; + $sql = "update $entity set $sql_set where " . $tree->{entities}->{$entity}->{_key} . "=?"; push( @$parms, $$requestObject{'path'}[0] ); ## do sql and record any errors @@ -1460,8 +1465,8 @@ sub doChangeQueueGET() # check for path key value and add if specified if ( $$requestObject{'path'}[0] ) { - $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); - $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; + $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{_key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); + $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{_key} like ?"; push( @$parms, $$requestObject{'path'}[0] ); } $logger->debug("getparms: $$requestObject{getparams}") if ( $logger->is_debug() ); @@ -1517,8 +1522,8 @@ sub doGenericGET() # check for path key value and add if specified if ( $$requestObject{'path'}[0] ) { - $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); - $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{key} like ?"; + $logger->debug("found $tree->{entities}->{$$requestObject{'entity'}}->{_key} : $$requestObject{'path'}[0] in url") if ( $logger->is_debug() ); + $sql .= " $tree->{entities}->{$$requestObject{'entity'}}->{_key} like ?"; push( @$parms, $$requestObject{'path'}[0] ); } elsif ( $$requestObject{getparams} ) @@ -2715,7 +2720,7 @@ sub doSystemPUT() {}, ( 'device', - $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }, $_, #field $$lkup_data{$_}, #old val $$data{$_}, # new val @@ -2798,7 +2803,7 @@ sub doSystemPUT() {}, ( 'device', - $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{key} }, + $$lkup_data{ $tree->{entities}->{ $$requestObject{'entity'} }->{_key} }, $_, #field $$lkup_data{$_}, #old val $$data{$_}, # new val diff --git a/lexicon.json b/lexicon.json new file mode 100644 index 0000000..3b0c42e --- /dev/null +++ b/lexicon.json @@ -0,0 +1,989 @@ +{ + "acl": { + "acl_group": { + "_format": "string", + "_label": "Group" + }, + "acl_id": { + "_format": "int", + "_label": "ID" + }, + "entity": { + "_format": "string", + "_label": "Entity" + }, + "field": { + "_format": "string", + "_label": "Field" + }, + "_field_order": "acl_id,acl_group,entity,field,logic", + "_key": "acl_id", + "logic": { + "_format": "string", + "_label": "Block Logic" + } + }, + "change_queue": { + "change_content": { + "_format": "string", + "_label": "Change" + }, + "change_ip": { + "_format": "string", + "_label": "IP Address" + }, + "change_time": { + "_format": "string", + "_label": "Time" + }, + "change_user": { + "_format": "string", + "_label": "User" + }, + "entity": { + "_format": "string", + "_label": "Entity" + }, + "entity_key": { + "_format": "string", + "_label": "Key" + }, + "id": { + "_format": "string", + "_label": "ID" + }, + "_key": "id" + }, + "data_center": { + "data_center_city": { + "_label": "City" + }, + "data_center_code": { + "_format": "string", + "_label": "Datacenter", + "_required": "true" + }, + "data_center_country": { + "_label": "Country" + }, + "data_center_phone": { + "_label": "Phone" + }, + "data_center_vendor": { + "_label": "Vendor" + }, + "_field_order": "data_center_code,data_center_vendor,data_center_city,data_center_country,data_center_telephone", + "_key": "data_center_code" + }, + "datacenter_subnet": { + "data_center_code": { + "_flavor": "config", + "_maps_to": "/lexicon/entities/data_center/data_center_code", + "_required": "false" + }, + "_field_order": "id,subnet,ipv6_subnet,data_center_code,nodes,az_id", + "gateway": { + "_flavor": "config", + "_format": "string", + "_label": "Gateway", + "_required": "false" + }, + "id": { + "_flavor": "config", + "_format": "string", + "_label": "ID", + "_required": "true" + }, + "_key": "id", + "notes": { + "_flavor": "config", + "_format": "text", + "_label": "Notes", + "_required": "false" + }, + "subnet": { + "_flavor": "config", + "_format": "string", + "_label": "Subnet", + "_required": "true" + }, + "vlan": { + "_flavor": "config", + "_format": "string", + "_label": "VLAN", + "_required": "false" + } + }, + "device": { + "agent_reported": { + "_flavor": "_fact", + "_label": "Agent Reported", + "_meta": "true", + "_required": "false", + "_type": "date" + }, + "cage_code": { + "_field_category": "HW Info", + "_label": "Cage", + "_maps_to": "/lexicon/entities/cage/cage_code", + "_required": "false" + }, + "created_by": { + "_label": "Created by", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "data_center_code": { + "_enumeration": { + "attribute": "data_center_code", + "entity": "data_center", + "enumerator": [ + "BA1", + "DC1", + "ETN", + "TEST01" + ] + }, + "_flavor": "_fact", + "_format": "string", + "_label": "Datacenter", + "_required": "true" + }, + "date_created": { + "_label": "Created at", + "_required": "false", + "_type": "date" + }, + "date_modified": { + "_label": "Modified at", + "_required": "false", + "_type": "date" + }, + "fqdn": { + "_fact": "fqdn", + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Name", + "_required": "true" + }, + "inventory_component_type": { + "default_value": "system", + "_enumeration": { + "enumerator": [ + "blade_chassis", + "console_server", + "firewall", + "load_balancer", + "network_switch", + "other", + "power_strip", + "router", + "rsa_appliance", + "storage_head", + "storage_shelf", + "system", + "vpn" + ] + }, + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Type", + "_required": "true", + "_validation": { + "is_string": {} + } + }, + "ipaddress": { + "_fact": "betterip,ipaddress,ip_address", + "_field_category": "main", + "_format": "string", + "_label": "IP Address", + "_required": "true", + "_validation": { + "regex": "/.*/" + } + }, + "_key": "fqdn", + "macaddress": { + "_flavor": "_fact", + "_format": "string", + "_label": "MAC Address", + "_required": "true", + "_validation": { + "regex": "/.*/" + } + }, + "manufacturer": { + "_enumeration": { + "attribute": "manufacturer", + "entity": "system", + "enumerator": [ + "Dell", + "HP", + "iXsystems", + "Supermicro" + ], + "forceselect": "false" + }, + "_fact": "manufacturer", + "_field_category": "HW Info", + "_format": "string", + "_label": "Manufacturer" + }, + "productname": { + "_enumeration": { + "attribute": "productname", + "entity": "system", + "enumerator": [ + "SpecialTestProduct" + ], + "forceselect": "false" + }, + "_fact": "productname", + "_field_category": "HW Info", + "_format": "string", + "_label": "Product" + }, + "rack_code": { + "_field_category": "HW Info", + "_label": "Rack", + "_maps_to": "/lexicon/entities/rack/rack_code", + "_required": "false" + }, + "rack_position": { + "_field_category": "HW Info", + "_flavor": "config", + "_format": "string", + "_label": "Rack Position", + "_validation": { + "is_int": {}, + "less_than_or_equal_to": "42" + } + }, + "serial_number": { + "_fact": "serial_number", + "_field_category": "HW Info", + "_format": "string", + "_label": "Serial" + }, + "status": { + "_enumeration": { + "enumerator": [ + "production", + "deployment", + "allocated", + "idle", + "in transit", + "degraded", + "decommissioned", + "disposed" + ] + }, + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Status", + "_required": "false", + "_validation": { + "is_string": {} + } + }, + "system_type": { + "_enumeration": { + "enumerator": [ + "POD", + "Archiving", + "Core", + "EDN", + "Eng" + ] + }, + "_flavor": "config", + "_format": "string", + "_label": "Business", + "_required": "false", + "_validation": { + "is_string": {} + } + } + }, + "environments": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Parent Environment", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "_key": "name", + "name": { + "_label": "Name", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "note": { + "_label": "Note", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + } + }, + "environmentservice": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "_field_order": "name,type,note,environment_name", + "_key": "name", + "name": { + "_label": "SVC Name", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "note": { + "_label": "Note", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "_type": { + "_label": "Type", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + } + }, + "hardware_model": { + "hardware_class": { + "_label": "HW Class", + "_maps_to": "/lexicon/data_elements/product_variant", + "_required": "false" + }, + "_key": "hardware_class", + "manufacturer": { + "_format": "string", + "_label": "Manufacturer" + }, + "power_supply_count": { + "_label": "Pwr Supply Count", + "_maps_to": "/lexicon/data_elements/power_supply_count", + "_required": "false" + }, + "power_supply_watts": { + "_format": "float", + "_label": "Power Supply(Watts)", + "_validation": { + "is_float": {} + } + }, + "product_name": { + "_format": "string", + "_label": "Product" + }, + "size": { + "_format": "int", + "_label": "Size (RU)", + "_required": "false" + } + }, + "inv_audit": { + "change_ip": { + "_format": "string", + "_label": "IP Address" + }, + "change_time": { + "_format": "string", + "_label": "Time" + }, + "change_user": { + "_format": "string", + "_label": "User" + }, + "entity_key": { + "_format": "string", + "_label": "Key" + }, + "entity_name": { + "_format": "string", + "_label": "Entity" + }, + "field_name": { + "_format": "string", + "_label": "Field" + }, + "_key": "entity_key", + "new_value": { + "_format": "string", + "_label": "New Value" + }, + "old_value": { + "_format": "string", + "_label": "Old Value" + } + }, + "inv_normalizer": { + "entity_name": { + "_format": "string", + "_label": "Entity", + "_required": "true" + }, + "field_name": { + "_format": "string", + "_label": "Field", + "_required": "true" + }, + "_field_order": "id,entity_name,field_name,matcher,sub_value", + "id": { + "_format": "int", + "_label": "ID", + "_required": "true" + }, + "_key": "id", + "matcher": { + "_format": "string", + "_label": "Regex Matcher", + "_required": "true" + }, + "sub_value": { + "_format": "string", + "_label": "Substitute Value", + "_required": "true" + } + }, + "rack": { + "cage_code": { + "_maps_to": "/lexicon/entities/cage/cage_code", + "_required": "true" + }, + "data_center_code": { + "_maps_to": "/lexicon/entities/data_center/data_center_code", + "_required": "true" + }, + "_key": "rack_code", + "rack_code": { + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + } + }, + "role": { + "_field_order": "role_id,role_name", + "_key": "role_id", + "role_id": { + "_format": "string", + "_label": "Role", + "_required": "true" + }, + "role_name": { + "_label": "Description", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + } + }, + "service_instance": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "_field_order": "svc_id,name,type,note,environment_name", + "_key": "svc_id", + "name": { + "_label": "SVC Name", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "note": { + "_label": "Note", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "svc_id": { + "_label": "SVC ID", + "_maps_to": "/lexicon/data_elements/int", + "_required": "true" + }, + "_type": { + "_label": "Type", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + } + }, + "service_instance_data": { + "data_id": { + "_label": "Data ID", + "_maps_to": "/lexicon/data_elements/int", + "_required": "true" + }, + "data_key": { + "_label": "Key", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "data_value": { + "_label": "Value", + "_maps_to": "/lexicon/data_elements/string", + "_required": "true" + }, + "_key": "data_id", + "svc_id": { + "_label": "SVC ID", + "_maps_to": "/lexicon/data_elements/int", + "_required": "true" + } + }, + "system": { + "agent_type": { + "_flavor": "_fact", + "_label": "Agent Type", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "asset_tag_number": { + "_field_category": "HW Info", + "_flavor": "config", + "_format": "string", + "_label": "Asset Tag" + }, + "audit_info": { + "_flavor": "config", + "_format": "string", + "_label": "Phys Audit", + "_required": "false" + }, + "bios_vendor": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Bios Vendor", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "bios_version": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Bios Version", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "cloud": { + "_label": "Cloud", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "config_agent_output": { + "_field_category": "Puppet", + "_format": "text", + "_label": "Puppet Output", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "config_agent_status": { + "_field_category": "Puppet", + "_label": "Puppet Status", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "config_agent_summary": { + "_field_category": "Puppet", + "_label": "Puppet Summary", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "config_agent_timestamp": { + "_field_category": "Puppet", + "_label": "Puppet Timestamp", + "_meta": "true", + "_required": "false", + "_type": "date" + }, + "customers": { + "customer": { + "_maps_to": "/lexicon/entities/customer" + }, + "_label": "Customers", + "_type": "array" + }, + "disk_drive_count": { + "_field_category": "HW Info", + "_label": "Drive Count", + "_maps_to": "/lexicon/data_elements/int", + "_required": "false" + }, + "drac": { + "_field_category": "HW Info", + "_label": "DRAC", + "_maps_to": "/lexicon/entities/drac", + "_required": "false" + }, + "drac_macaddress": { + "_field_category": "HW Info", + "_label": "Drac MacAddress", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "drac_version": { + "_field_category": "HW Info", + "_label": "Drac Version", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "environment_name": { + "default_value": "production", + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "_extends": "/lexicon/entities/device", + "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", + "file_systems": { + "file_system": { + "_maps_to": "/lexicon/entities/file_system" + }, + "_label": "File Systems", + "_type": "array" + }, + "guest_fqdns": { + "fqdn": { + "_maps_to": "/lexicon/data_elements/fqdn", + "_required": "false" + }, + "_label": "Guest Hosts", + "_type": "array" + }, + "hardware_class": { + "_enumeration": { + "attribute": "hardware_class", + "entity": "hardware_model", + "enumerator": [ + "1USystem", + "2USystem", + "3USystem", + "4USystem" + ] + }, + "_field_category": "HW Info", + "_label": "Class", + "_required": "false" + }, + "host_fqdn": { + "_label": "Parent Host", + "_maps_to": "/lexicon/data_elements/fqdn", + "_required": "false" + }, + "image": { + "_label": "System Image", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "interfaces": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "NICs", + "_required": "false" + }, + "ipmi_ip": { + "_field_category": "HW Info", + "_label": "IPMI Address" + }, + "ipv6address": { + "_label": "IPv6 Address", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "is_virtual": { + "_label": "Virtual", + "_maps_to": "/lexicon/data_elements/bool", + "_required": "false" + }, + "kernelrelease": { + "_fact": "kernelrelease", + "_flavor": "_fact", + "_format": "string", + "_label": "Kernel Release" + }, + "_key": "fqdn", + "memorysize": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Memory", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "netdriver": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver" + }, + "netdriver_duplex": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Duplex" + }, + "netdriver_firmware": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Firmware" + }, + "netdriver_speed": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Speed" + }, + "netdriver_version": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Version" + }, + "notes": { + "_field_category": "main", + "_format": "text", + "_label": "Notes", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "operatingsystem": { + "_enumeration": { + "attribute": "operating_system", + "entity": "system", + "enumerator": [ + "Debian", + "RedHat" + ], + "forceselect": "false" + }, + "_fact": "operatingsystem", + "_flavor": "config", + "_format": "string", + "_label": "OS" + }, + "operatingsystemrelease": { + "_fact": "operatingsystemrelease", + "_flavor": "config", + "_format": "string", + "_label": "OS Release" + }, + "physical_processor_count": { + "_field_category": "HW Info", + "_label": "Processor Count", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "power_consumption_avg": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int", + "_label": "Average Power Consumption(Watts)", + "_validation": { + "is_int": {} + } + }, + "power_consumption_peak": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int", + "_label": "Peak Power Consumption(Watts)", + "_validation": { + "is_int": {} + } + }, + "power_consumption_peak_time": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int_timestamp", + "_label": "Time of Peak Power (Unix Timestamp, UTC)" + }, + "power_supply_watts": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "float", + "_label": "Power Supply(Watts)", + "_validation": { + "is_float": {} + } + }, + "raidbaddrives": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Bad Drives", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "raidcontroller": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Controller", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "raiddrives": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Drives", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "raiddrivestatus": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Drive Status", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "raidtype": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Type", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "raidvolumes": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Volumes", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "role_version": { + "_fact": "notestore_version", + "field_catetory": "main", + "_label": "Role Version" + }, + "roles": { + "_enumeration": { + "attribute": "role_id", + "entity": "role", + "enumerator": [ + "hadoop_datanode", + "nagios", + "repo::apt" + ], + "forceselect": "true" + }, + "_field_category": "main", + "_format": "multiselect", + "_label": "Roles" + }, + "size": { + "_label": "Instance Size", + "_maps_to": "/lexicon/data_elements/string", + "_meta": "true", + "_required": "false" + }, + "svc_id": { + "_enumeration": { + "attribute": "name", + "entity": "service_instance", + "enumerator": [ + "cmdb", + "ldap_ba1", + "testsvc" + ] + }, + "_field_category": "main", + "_label": "Service Record", + "_maps_to": "/lexicon/data_elements/int", + "_required": "false" + }, + "tags": { + "_label": "JSON Tags", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "virtual": { + "_flavor": "_fact", + "_label": "VM Type", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + }, + "warranty_info": { + "_field_category": "HW Info", + "_format": "text", + "_label": "Warranty Info", + "_maps_to": "/lexicon/data_elements/string", + "_required": "false" + } + }, + "user": { + "_field_order": "username,systemuser,groups,writeaccess,sshkey", + "groups": { + "_enumeration": { + "enumerator": [ + "OPS", + "IT", + "QA" + ] + }, + "_format": "array", + "_label": "Groups" + }, + "_key": "username", + "sshkey": { + "_format": "text", + "_label": "SSH Key" + }, + "systemuser": { + "_format": "bool", + "_label": "System User" + }, + "username": { + "_format": "string", + "_label": "Username" + }, + "writeaccess": { + "_format": "bool", + "_label": "Write Access" + } + } +} \ No newline at end of file From 6957b2d7d7af449fdc43c5ef483554941b81ff17 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 8 Dec 2014 15:10:47 -0800 Subject: [PATCH 37/82] entities key needed --- lexicon.json | 1845 ++++++++++++++++++++++++-------------------------- 1 file changed, 888 insertions(+), 957 deletions(-) diff --git a/lexicon.json b/lexicon.json index 3b0c42e..c8a9d12 100644 --- a/lexicon.json +++ b/lexicon.json @@ -1,989 +1,920 @@ { - "acl": { - "acl_group": { - "_format": "string", - "_label": "Group" - }, - "acl_id": { - "_format": "int", - "_label": "ID" - }, - "entity": { - "_format": "string", - "_label": "Entity" - }, - "field": { - "_format": "string", - "_label": "Field" - }, - "_field_order": "acl_id,acl_group,entity,field,logic", - "_key": "acl_id", - "logic": { - "_format": "string", - "_label": "Block Logic" - } - }, - "change_queue": { - "change_content": { - "_format": "string", - "_label": "Change" - }, - "change_ip": { - "_format": "string", - "_label": "IP Address" - }, - "change_time": { - "_format": "string", - "_label": "Time" - }, - "change_user": { - "_format": "string", - "_label": "User" - }, - "entity": { - "_format": "string", - "_label": "Entity" - }, - "entity_key": { - "_format": "string", - "_label": "Key" - }, - "id": { - "_format": "string", - "_label": "ID" - }, - "_key": "id" - }, - "data_center": { - "data_center_city": { - "_label": "City" - }, - "data_center_code": { - "_format": "string", - "_label": "Datacenter", - "_required": "true" - }, - "data_center_country": { - "_label": "Country" - }, - "data_center_phone": { - "_label": "Phone" - }, - "data_center_vendor": { - "_label": "Vendor" - }, - "_field_order": "data_center_code,data_center_vendor,data_center_city,data_center_country,data_center_telephone", - "_key": "data_center_code" - }, - "datacenter_subnet": { - "data_center_code": { - "_flavor": "config", - "_maps_to": "/lexicon/entities/data_center/data_center_code", - "_required": "false" - }, - "_field_order": "id,subnet,ipv6_subnet,data_center_code,nodes,az_id", - "gateway": { - "_flavor": "config", - "_format": "string", - "_label": "Gateway", - "_required": "false" - }, - "id": { - "_flavor": "config", - "_format": "string", - "_label": "ID", - "_required": "true" - }, - "_key": "id", - "notes": { - "_flavor": "config", - "_format": "text", - "_label": "Notes", - "_required": "false" - }, - "subnet": { - "_flavor": "config", - "_format": "string", - "_label": "Subnet", - "_required": "true" - }, - "vlan": { - "_flavor": "config", - "_format": "string", - "_label": "VLAN", - "_required": "false" - } - }, - "device": { - "agent_reported": { - "_flavor": "_fact", - "_label": "Agent Reported", - "_meta": "true", - "_required": "false", - "_type": "date" - }, - "cage_code": { - "_field_category": "HW Info", - "_label": "Cage", - "_maps_to": "/lexicon/entities/cage/cage_code", - "_required": "false" - }, - "created_by": { - "_label": "Created by", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "data_center_code": { - "_enumeration": { - "attribute": "data_center_code", - "entity": "data_center", - "enumerator": [ - "BA1", - "DC1", - "ETN", - "TEST01" - ] - }, - "_flavor": "_fact", - "_format": "string", - "_label": "Datacenter", - "_required": "true" - }, - "date_created": { - "_label": "Created at", - "_required": "false", - "_type": "date" + "entities": { + "acl": { + "acl_group": { + "_format": "string", + "_label": "Group" + }, + "acl_id": { + "_format": "int", + "_label": "ID" + }, + "entity": { + "_format": "string", + "_label": "Entity" + }, + "field": { + "_format": "string", + "_label": "Field" + }, + "_field_order": "acl_id,acl_group,entity,field,logic", + "_key": "acl_id", + "logic": { + "_format": "string", + "_label": "Block Logic" + } }, - "date_modified": { - "_label": "Modified at", - "_required": "false", - "_type": "date" + "change_queue": { + "change_content": { + "_format": "string", + "_label": "Change" + }, + "change_ip": { + "_format": "string", + "_label": "IP Address" + }, + "change_time": { + "_format": "string", + "_label": "Time" + }, + "change_user": { + "_format": "string", + "_label": "User" + }, + "entity": { + "_format": "string", + "_label": "Entity" + }, + "entity_key": { + "_format": "string", + "_label": "Key" + }, + "id": { + "_format": "string", + "_label": "ID" + }, + "_key": "id" }, - "fqdn": { - "_fact": "fqdn", - "_field_category": "main", - "_flavor": "config", - "_format": "string", - "_label": "Name", - "_required": "true" + "data_center": { + "data_center_city": { + "_label": "City" + }, + "data_center_code": { + "_format": "string", + "_label": "Datacenter", + "_required": "true" + }, + "data_center_country": { + "_label": "Country" + }, + "data_center_phone": { + "_label": "Phone" + }, + "data_center_vendor": { + "_label": "Vendor" + }, + "_field_order": "data_center_code,data_center_vendor,data_center_city,data_center_country,data_center_telephone", + "_key": "data_center_code" }, - "inventory_component_type": { - "default_value": "system", - "_enumeration": { - "enumerator": [ - "blade_chassis", - "console_server", - "firewall", - "load_balancer", - "network_switch", - "other", - "power_strip", - "router", - "rsa_appliance", - "storage_head", - "storage_shelf", - "system", - "vpn" - ] - }, - "_field_category": "main", - "_flavor": "config", - "_format": "string", - "_label": "Type", - "_required": "true", - "_validation": { - "is_string": {} + "datacenter_subnet": { + "data_center_code": { + "_flavor": "config", + "_required": "false" + }, + "_field_order": "id,subnet,ipv6_subnet,data_center_code,nodes,az_id", + "gateway": { + "_flavor": "config", + "_format": "string", + "_label": "Gateway", + "_required": "false" + }, + "id": { + "_flavor": "config", + "_format": "string", + "_label": "ID", + "_required": "true" + }, + "_key": "id", + "notes": { + "_flavor": "config", + "_format": "text", + "_label": "Notes", + "_required": "false" + }, + "subnet": { + "_flavor": "config", + "_format": "string", + "_label": "Subnet", + "_required": "true" + }, + "vlan": { + "_flavor": "config", + "_format": "string", + "_label": "VLAN", + "_required": "false" } }, - "ipaddress": { - "_fact": "betterip,ipaddress,ip_address", - "_field_category": "main", - "_format": "string", - "_label": "IP Address", - "_required": "true", - "_validation": { - "regex": "/.*/" + "device": { + "agent_reported": { + "_flavor": "_fact", + "_label": "Agent Reported", + "_meta": "true", + "_required": "false", + "_type": "date" + }, + "cage_code": { + "_field_category": "HW Info", + "_label": "Cage", + "_required": "false" + }, + "created_by": { + "_label": "Created by", + "_required": "false" + }, + "data_center_code": { + "_enumeration": { + "attribute": "data_center_code", + "entity": "data_center", + "enumerator": [ + "BA1", + "DC1", + "ETN", + "TEST01" + ] + }, + "_flavor": "_fact", + "_format": "string", + "_label": "Datacenter", + "_required": "true" + }, + "date_created": { + "_label": "Created at", + "_required": "false", + "_type": "date" + }, + "date_modified": { + "_label": "Modified at", + "_required": "false", + "_type": "date" + }, + "fqdn": { + "_fact": "fqdn", + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Name", + "_required": "true" + }, + "inventory_component_type": { + "default_value": "system", + "_enumeration": { + "enumerator": [ + "blade_chassis", + "console_server", + "firewall", + "load_balancer", + "network_switch", + "other", + "power_strip", + "router", + "rsa_appliance", + "storage_head", + "storage_shelf", + "system", + "vpn" + ] + }, + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Type", + "_required": "true", + "_validation": { + "is_string": {} + } + }, + "ipaddress": { + "_fact": "betterip,ipaddress,ip_address", + "_field_category": "main", + "_format": "string", + "_label": "IP Address", + "_required": "true", + "_validation": { + "regex": "/.*/" + } + }, + "_key": "fqdn", + "macaddress": { + "_flavor": "_fact", + "_format": "string", + "_label": "MAC Address", + "_required": "true", + "_validation": { + "regex": "/.*/" + } + }, + "manufacturer": { + "_enumeration": { + "attribute": "manufacturer", + "entity": "system", + "enumerator": [ + "Dell", + "HP", + "iXsystems", + "Supermicro" + ], + "forceselect": "false" + }, + "_fact": "manufacturer", + "_field_category": "HW Info", + "_format": "string", + "_label": "Manufacturer" + }, + "productname": { + "_enumeration": { + "attribute": "productname", + "entity": "system", + "enumerator": [ + "SpecialTestProduct" + ], + "forceselect": "false" + }, + "_fact": "productname", + "_field_category": "HW Info", + "_format": "string", + "_label": "Product" + }, + "rack_code": { + "_field_category": "HW Info", + "_label": "Rack", + "_required": "false" + }, + "rack_position": { + "_field_category": "HW Info", + "_flavor": "config", + "_format": "string", + "_label": "Rack Position", + "_validation": { + "is_int": {}, + "less_than_or_equal_to": "42" + } + }, + "serial_number": { + "_fact": "serial_number", + "_field_category": "HW Info", + "_format": "string", + "_label": "Serial" + }, + "status": { + "_enumeration": { + "enumerator": [ + "production", + "deployment", + "allocated", + "idle", + "in transit", + "degraded", + "decommissioned", + "disposed" + ] + }, + "_field_category": "main", + "_flavor": "config", + "_format": "string", + "_label": "Status", + "_required": "false", + "_validation": { + "is_string": {} + } + }, + "system_type": { + "_enumeration": { + "enumerator": [ + "POD", + "Archiving", + "Core", + "EDN", + "Eng" + ] + }, + "_flavor": "config", + "_format": "string", + "_label": "Business", + "_required": "false", + "_validation": { + "is_string": {} + } } }, - "_key": "fqdn", - "macaddress": { - "_flavor": "_fact", - "_format": "string", - "_label": "MAC Address", - "_required": "true", - "_validation": { - "regex": "/.*/" + "environments": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Parent Environment", + "_required": "true" + }, + "_key": "name", + "name": { + "_label": "Name", + "_required": "true" + }, + "note": { + "_label": "Note", + "_required": "false" } }, - "manufacturer": { - "_enumeration": { - "attribute": "manufacturer", - "entity": "system", - "enumerator": [ - "Dell", - "HP", - "iXsystems", - "Supermicro" - ], - "forceselect": "false" - }, - "_fact": "manufacturer", - "_field_category": "HW Info", - "_format": "string", - "_label": "Manufacturer" - }, - "productname": { - "_enumeration": { - "attribute": "productname", - "entity": "system", - "enumerator": [ - "SpecialTestProduct" - ], - "forceselect": "false" - }, - "_fact": "productname", - "_field_category": "HW Info", - "_format": "string", - "_label": "Product" - }, - "rack_code": { - "_field_category": "HW Info", - "_label": "Rack", - "_maps_to": "/lexicon/entities/rack/rack_code", - "_required": "false" - }, - "rack_position": { - "_field_category": "HW Info", - "_flavor": "config", - "_format": "string", - "_label": "Rack Position", - "_validation": { - "is_int": {}, - "less_than_or_equal_to": "42" + "environmentservice": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", + "_required": "false" + }, + "_field_order": "name,type,note,environment_name", + "_key": "name", + "name": { + "_label": "SVC Name", + "_required": "true" + }, + "note": { + "_label": "Note", + "_required": "false" + }, + "_type": { + "_label": "Type", + "_required": "false" } }, - "serial_number": { - "_fact": "serial_number", - "_field_category": "HW Info", - "_format": "string", - "_label": "Serial" - }, - "status": { - "_enumeration": { - "enumerator": [ - "production", - "deployment", - "allocated", - "idle", - "in transit", - "degraded", - "decommissioned", - "disposed" - ] - }, - "_field_category": "main", - "_flavor": "config", - "_format": "string", - "_label": "Status", - "_required": "false", - "_validation": { - "is_string": {} + "hardware_model": { + "hardware_class": { + "_label": "HW Class", + "_required": "false" + }, + "_key": "hardware_class", + "manufacturer": { + "_format": "string", + "_label": "Manufacturer" + }, + "power_supply_count": { + "_label": "Pwr Supply Count", + "_required": "false" + }, + "power_supply_watts": { + "_format": "float", + "_label": "Power Supply(Watts)", + "_validation": { + "is_float": {} + } + }, + "product_name": { + "_format": "string", + "_label": "Product" + }, + "size": { + "_format": "int", + "_label": "Size (RU)", + "_required": "false" } }, - "system_type": { - "_enumeration": { - "enumerator": [ - "POD", - "Archiving", - "Core", - "EDN", - "Eng" - ] - }, - "_flavor": "config", - "_format": "string", - "_label": "Business", - "_required": "false", - "_validation": { - "is_string": {} + "inv_audit": { + "change_ip": { + "_format": "string", + "_label": "IP Address" + }, + "change_time": { + "_format": "string", + "_label": "Time" + }, + "change_user": { + "_format": "string", + "_label": "User" + }, + "entity_key": { + "_format": "string", + "_label": "Key" + }, + "entity_name": { + "_format": "string", + "_label": "Entity" + }, + "field_name": { + "_format": "string", + "_label": "Field" + }, + "_key": "entity_key", + "new_value": { + "_format": "string", + "_label": "New Value" + }, + "old_value": { + "_format": "string", + "_label": "Old Value" } - } - }, - "environments": { - "environment_name": { - "_enumeration": { - "attribute": "name", - "entity": "environments", - "enumerator": [ - "lab", - "production", - "secondlevel", - "testing" - ] - }, - "_field_category": "main", - "_label": "Parent Environment", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - }, - "_key": "name", - "name": { - "_label": "Name", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" }, - "note": { - "_label": "Note", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - } - }, - "environmentservice": { - "environment_name": { - "_enumeration": { - "attribute": "name", - "entity": "environments", - "enumerator": [ - "lab", - "production", - "secondlevel", - "testing" - ] - }, - "_field_category": "main", - "_label": "Environment", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "_field_order": "name,type,note,environment_name", - "_key": "name", - "name": { - "_label": "SVC Name", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - }, - "note": { - "_label": "Note", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "_type": { - "_label": "Type", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - } - }, - "hardware_model": { - "hardware_class": { - "_label": "HW Class", - "_maps_to": "/lexicon/data_elements/product_variant", - "_required": "false" - }, - "_key": "hardware_class", - "manufacturer": { - "_format": "string", - "_label": "Manufacturer" - }, - "power_supply_count": { - "_label": "Pwr Supply Count", - "_maps_to": "/lexicon/data_elements/power_supply_count", - "_required": "false" - }, - "power_supply_watts": { - "_format": "float", - "_label": "Power Supply(Watts)", - "_validation": { - "is_float": {} + "inv_normalizer": { + "entity_name": { + "_format": "string", + "_label": "Entity", + "_required": "true" + }, + "field_name": { + "_format": "string", + "_label": "Field", + "_required": "true" + }, + "_field_order": "id,entity_name,field_name,matcher,sub_value", + "id": { + "_format": "int", + "_label": "ID", + "_required": "true" + }, + "_key": "id", + "matcher": { + "_format": "string", + "_label": "Regex Matcher", + "_required": "true" + }, + "sub_value": { + "_format": "string", + "_label": "Substitute Value", + "_required": "true" } }, - "product_name": { - "_format": "string", - "_label": "Product" - }, - "size": { - "_format": "int", - "_label": "Size (RU)", - "_required": "false" - } - }, - "inv_audit": { - "change_ip": { - "_format": "string", - "_label": "IP Address" - }, - "change_time": { - "_format": "string", - "_label": "Time" - }, - "change_user": { - "_format": "string", - "_label": "User" - }, - "entity_key": { - "_format": "string", - "_label": "Key" - }, - "entity_name": { - "_format": "string", - "_label": "Entity" - }, - "field_name": { - "_format": "string", - "_label": "Field" - }, - "_key": "entity_key", - "new_value": { - "_format": "string", - "_label": "New Value" - }, - "old_value": { - "_format": "string", - "_label": "Old Value" - } - }, - "inv_normalizer": { - "entity_name": { - "_format": "string", - "_label": "Entity", - "_required": "true" - }, - "field_name": { - "_format": "string", - "_label": "Field", - "_required": "true" - }, - "_field_order": "id,entity_name,field_name,matcher,sub_value", - "id": { - "_format": "int", - "_label": "ID", - "_required": "true" - }, - "_key": "id", - "matcher": { - "_format": "string", - "_label": "Regex Matcher", - "_required": "true" - }, - "sub_value": { - "_format": "string", - "_label": "Substitute Value", - "_required": "true" - } - }, - "rack": { - "cage_code": { - "_maps_to": "/lexicon/entities/cage/cage_code", - "_required": "true" - }, - "data_center_code": { - "_maps_to": "/lexicon/entities/data_center/data_center_code", - "_required": "true" - }, - "_key": "rack_code", - "rack_code": { - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - } - }, - "role": { - "_field_order": "role_id,role_name", - "_key": "role_id", - "role_id": { - "_format": "string", - "_label": "Role", - "_required": "true" - }, - "role_name": { - "_label": "Description", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - } - }, - "service_instance": { - "environment_name": { - "_enumeration": { - "attribute": "name", - "entity": "environments", - "enumerator": [ - "lab", - "production", - "secondlevel", - "testing" - ] - }, - "_field_category": "main", - "_label": "Environment", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "_field_order": "svc_id,name,type,note,environment_name", - "_key": "svc_id", - "name": { - "_label": "SVC Name", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - }, - "note": { - "_label": "Note", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "svc_id": { - "_label": "SVC ID", - "_maps_to": "/lexicon/data_elements/int", - "_required": "true" - }, - "_type": { - "_label": "Type", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - } - }, - "service_instance_data": { - "data_id": { - "_label": "Data ID", - "_maps_to": "/lexicon/data_elements/int", - "_required": "true" - }, - "data_key": { - "_label": "Key", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - }, - "data_value": { - "_label": "Value", - "_maps_to": "/lexicon/data_elements/string", - "_required": "true" - }, - "_key": "data_id", - "svc_id": { - "_label": "SVC ID", - "_maps_to": "/lexicon/data_elements/int", - "_required": "true" - } - }, - "system": { - "agent_type": { - "_flavor": "_fact", - "_label": "Agent Type", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "asset_tag_number": { - "_field_category": "HW Info", - "_flavor": "config", - "_format": "string", - "_label": "Asset Tag" - }, - "audit_info": { - "_flavor": "config", - "_format": "string", - "_label": "Phys Audit", - "_required": "false" - }, - "bios_vendor": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Bios Vendor", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "bios_version": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Bios Version", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "cloud": { - "_label": "Cloud", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "config_agent_output": { - "_field_category": "Puppet", - "_format": "text", - "_label": "Puppet Output", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "config_agent_status": { - "_field_category": "Puppet", - "_label": "Puppet Status", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "config_agent_summary": { - "_field_category": "Puppet", - "_label": "Puppet Summary", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "config_agent_timestamp": { - "_field_category": "Puppet", - "_label": "Puppet Timestamp", - "_meta": "true", - "_required": "false", - "_type": "date" - }, - "customers": { - "customer": { - "_maps_to": "/lexicon/entities/customer" + "rack": { + "cage_code": { + "_required": "true" }, - "_label": "Customers", - "_type": "array" - }, - "disk_drive_count": { - "_field_category": "HW Info", - "_label": "Drive Count", - "_maps_to": "/lexicon/data_elements/int", - "_required": "false" - }, - "drac": { - "_field_category": "HW Info", - "_label": "DRAC", - "_maps_to": "/lexicon/entities/drac", - "_required": "false" - }, - "drac_macaddress": { - "_field_category": "HW Info", - "_label": "Drac MacAddress", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "drac_version": { - "_field_category": "HW Info", - "_label": "Drac Version", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "environment_name": { - "default_value": "production", - "_enumeration": { - "attribute": "name", - "entity": "environments", - "enumerator": [ - "lab", - "production", - "secondlevel", - "testing" - ] - }, - "_field_category": "main", - "_label": "Environment", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" + "data_center_code": { + "_required": "true" + }, + "_key": "rack_code", + "rack_code": { + "_required": "false" + } }, - "_extends": "/lexicon/entities/device", - "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", - "file_systems": { - "file_system": { - "_maps_to": "/lexicon/entities/file_system" - }, - "_label": "File Systems", - "_type": "array" + "role": { + "_field_order": "role_id,role_name", + "_key": "role_id", + "role_id": { + "_format": "string", + "_label": "Role", + "_required": "true" + }, + "role_name": { + "_label": "Description", + "_required": "true" + } }, - "guest_fqdns": { - "fqdn": { - "_maps_to": "/lexicon/data_elements/fqdn", + "service_instance": { + "environment_name": { + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", "_required": "false" }, - "_label": "Guest Hosts", - "_type": "array" - }, - "hardware_class": { - "_enumeration": { - "attribute": "hardware_class", - "entity": "hardware_model", - "enumerator": [ - "1USystem", - "2USystem", - "3USystem", - "4USystem" - ] - }, - "_field_category": "HW Info", - "_label": "Class", - "_required": "false" - }, - "host_fqdn": { - "_label": "Parent Host", - "_maps_to": "/lexicon/data_elements/fqdn", - "_required": "false" - }, - "image": { - "_label": "System Image", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "interfaces": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "NICs", - "_required": "false" - }, - "ipmi_ip": { - "_field_category": "HW Info", - "_label": "IPMI Address" - }, - "ipv6address": { - "_label": "IPv6 Address", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "is_virtual": { - "_label": "Virtual", - "_maps_to": "/lexicon/data_elements/bool", - "_required": "false" - }, - "kernelrelease": { - "_fact": "kernelrelease", - "_flavor": "_fact", - "_format": "string", - "_label": "Kernel Release" - }, - "_key": "fqdn", - "memorysize": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Memory", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "netdriver": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "Netdriver" - }, - "netdriver_duplex": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "Netdriver Duplex" - }, - "netdriver_firmware": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "Netdriver Firmware" - }, - "netdriver_speed": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "Netdriver Speed" - }, - "netdriver_version": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "string", - "_label": "Netdriver Version" - }, - "notes": { - "_field_category": "main", - "_format": "text", - "_label": "Notes", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "operatingsystem": { - "_enumeration": { - "attribute": "operating_system", - "entity": "system", - "enumerator": [ - "Debian", - "RedHat" - ], - "forceselect": "false" - }, - "_fact": "operatingsystem", - "_flavor": "config", - "_format": "string", - "_label": "OS" - }, - "operatingsystemrelease": { - "_fact": "operatingsystemrelease", - "_flavor": "config", - "_format": "string", - "_label": "OS Release" - }, - "physical_processor_count": { - "_field_category": "HW Info", - "_label": "Processor Count", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "power_consumption_avg": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "int", - "_label": "Average Power Consumption(Watts)", - "_validation": { - "is_int": {} + "_field_order": "svc_id,name,type,note,environment_name", + "_key": "svc_id", + "name": { + "_label": "SVC Name", + "_required": "true" + }, + "note": { + "_label": "Note", + "_required": "false" + }, + "svc_id": { + "_label": "SVC ID", + "_required": "true" + }, + "_type": { + "_label": "Type", + "_required": "false" } }, - "power_consumption_peak": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "int", - "_label": "Peak Power Consumption(Watts)", - "_validation": { - "is_int": {} + "service_instance_data": { + "data_id": { + "_label": "Data ID", + "_required": "true" + }, + "data_key": { + "_label": "Key", + "_required": "true" + }, + "data_value": { + "_label": "Value", + "_required": "true" + }, + "_key": "data_id", + "svc_id": { + "_label": "SVC ID", + "_required": "true" } }, - "power_consumption_peak_time": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "int_timestamp", - "_label": "Time of Peak Power (Unix Timestamp, UTC)" - }, - "power_supply_watts": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_format": "float", - "_label": "Power Supply(Watts)", - "_validation": { - "is_float": {} + "system": { + "agent_type": { + "_flavor": "_fact", + "_label": "Agent Type", + "_required": "false" + }, + "asset_tag_number": { + "_field_category": "HW Info", + "_flavor": "config", + "_format": "string", + "_label": "Asset Tag" + }, + "audit_info": { + "_flavor": "config", + "_format": "string", + "_label": "Phys Audit", + "_required": "false" + }, + "bios_vendor": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Bios Vendor", + "_required": "false" + }, + "bios_version": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Bios Version", + "_required": "false" + }, + "cloud": { + "_label": "Cloud", + "_meta": "true", + "_required": "false" + }, + "config_agent_output": { + "_field_category": "Puppet", + "_format": "text", + "_label": "Puppet Output", + "_meta": "true", + "_required": "false" + }, + "config_agent_status": { + "_field_category": "Puppet", + "_label": "Puppet Status", + "_meta": "true", + "_required": "false" + }, + "config_agent_summary": { + "_field_category": "Puppet", + "_label": "Puppet Summary", + "_meta": "true", + "_required": "false" + }, + "config_agent_timestamp": { + "_field_category": "Puppet", + "_label": "Puppet Timestamp", + "_meta": "true", + "_required": "false", + "_type": "date" + }, + "disk_drive_count": { + "_field_category": "HW Info", + "_label": "Drive Count", + "_required": "false" + }, + "drac": { + "_field_category": "HW Info", + "_label": "DRAC", + "_required": "false" + }, + "drac_macaddress": { + "_field_category": "HW Info", + "_label": "Drac MacAddress", + "_required": "false" + }, + "drac_version": { + "_field_category": "HW Info", + "_label": "Drac Version", + "_required": "false" + }, + "environment_name": { + "default_value": "production", + "_enumeration": { + "attribute": "name", + "entity": "environments", + "enumerator": [ + "lab", + "production", + "secondlevel", + "testing" + ] + }, + "_field_category": "main", + "_label": "Environment", + "_required": "false" + }, + "_extends": "/lexicon/entities/device", + "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", + "guest_fqdns": { + "fqdn": { + "_required": "false" + }, + "_label": "Guest Hosts", + "_type": "array" + }, + "hardware_class": { + "_enumeration": { + "attribute": "hardware_class", + "entity": "hardware_model", + "enumerator": [ + "1USystem", + "2USystem", + "3USystem", + "4USystem" + ] + }, + "_field_category": "HW Info", + "_label": "Class", + "_required": "false" + }, + "host_fqdn": { + "_label": "Parent Host", + "_required": "false" + }, + "image": { + "_label": "System Image", + "_meta": "true", + "_required": "false" + }, + "interfaces": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "NICs", + "_required": "false" + }, + "ipmi_ip": { + "_field_category": "HW Info", + "_label": "IPMI Address" + }, + "ipv6address": { + "_label": "IPv6 Address", + "_required": "false" + }, + "is_virtual": { + "_label": "Virtual", + "_required": "false" + }, + "kernelrelease": { + "_fact": "kernelrelease", + "_flavor": "_fact", + "_format": "string", + "_label": "Kernel Release" + }, + "_key": "fqdn", + "memorysize": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Memory", + "_required": "false" + }, + "netdriver": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver" + }, + "netdriver_duplex": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Duplex" + }, + "netdriver_firmware": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Firmware" + }, + "netdriver_speed": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Speed" + }, + "netdriver_version": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "string", + "_label": "Netdriver Version" + }, + "notes": { + "_field_category": "main", + "_format": "text", + "_label": "Notes", + "_required": "false" + }, + "operatingsystem": { + "_enumeration": { + "attribute": "operating_system", + "entity": "system", + "enumerator": [ + "Debian", + "RedHat" + ], + "forceselect": "false" + }, + "_fact": "operatingsystem", + "_flavor": "config", + "_format": "string", + "_label": "OS" + }, + "operatingsystemrelease": { + "_fact": "operatingsystemrelease", + "_flavor": "config", + "_format": "string", + "_label": "OS Release" + }, + "physical_processor_count": { + "_field_category": "HW Info", + "_label": "Processor Count", + "_required": "false" + }, + "power_consumption_avg": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int", + "_label": "Average Power Consumption(Watts)", + "_validation": { + "is_int": {} + } + }, + "power_consumption_peak": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int", + "_label": "Peak Power Consumption(Watts)", + "_validation": { + "is_int": {} + } + }, + "power_consumption_peak_time": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "int_timestamp", + "_label": "Time of Peak Power (Unix Timestamp, UTC)" + }, + "power_supply_watts": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_format": "float", + "_label": "Power Supply(Watts)", + "_validation": { + "is_float": {} + } + }, + "raidbaddrives": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Bad Drives", + "_required": "false" + }, + "raidcontroller": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Controller", + "_required": "false" + }, + "raiddrives": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Drives", + "_required": "false" + }, + "raiddrivestatus": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Drive Status", + "_required": "false" + }, + "raidtype": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Type", + "_required": "false" + }, + "raidvolumes": { + "_field_category": "HW Info", + "_flavor": "_fact", + "_label": "Raid Volumes", + "_required": "false" + }, + "role_version": { + "_fact": "notestore_version", + "field_catetory": "main", + "_label": "Role Version" + }, + "roles": { + "_enumeration": { + "attribute": "role_id", + "entity": "role", + "enumerator": [ + "hadoop_datanode", + "nagios", + "repo::apt" + ], + "forceselect": "true" + }, + "_field_category": "main", + "_format": "multiselect", + "_label": "Roles" + }, + "size": { + "_label": "Instance Size", + "_meta": "true", + "_required": "false" + }, + "svc_id": { + "_enumeration": { + "attribute": "name", + "entity": "service_instance", + "enumerator": [ + "cmdb", + "ldap_ba1", + "testsvc" + ] + }, + "_field_category": "main", + "_label": "Service Record", + "_required": "false" + }, + "tags": { + "_label": "JSON Tags", + "_required": "false" + }, + "virtual": { + "_flavor": "_fact", + "_label": "VM Type", + "_required": "false" + }, + "warranty_info": { + "_field_category": "HW Info", + "_format": "text", + "_label": "Warranty Info", + "_required": "false" } }, - "raidbaddrives": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Bad Drives", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "raidcontroller": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Controller", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "raiddrives": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Drives", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "raiddrivestatus": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Drive Status", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "raidtype": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Type", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "raidvolumes": { - "_field_category": "HW Info", - "_flavor": "_fact", - "_label": "Raid Volumes", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "role_version": { - "_fact": "notestore_version", - "field_catetory": "main", - "_label": "Role Version" - }, - "roles": { - "_enumeration": { - "attribute": "role_id", - "entity": "role", - "enumerator": [ - "hadoop_datanode", - "nagios", - "repo::apt" - ], - "forceselect": "true" - }, - "_field_category": "main", - "_format": "multiselect", - "_label": "Roles" - }, - "size": { - "_label": "Instance Size", - "_maps_to": "/lexicon/data_elements/string", - "_meta": "true", - "_required": "false" - }, - "svc_id": { - "_enumeration": { - "attribute": "name", - "entity": "service_instance", - "enumerator": [ - "cmdb", - "ldap_ba1", - "testsvc" - ] - }, - "_field_category": "main", - "_label": "Service Record", - "_maps_to": "/lexicon/data_elements/int", - "_required": "false" - }, - "tags": { - "_label": "JSON Tags", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "virtual": { - "_flavor": "_fact", - "_label": "VM Type", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - }, - "warranty_info": { - "_field_category": "HW Info", - "_format": "text", - "_label": "Warranty Info", - "_maps_to": "/lexicon/data_elements/string", - "_required": "false" - } - }, - "user": { - "_field_order": "username,systemuser,groups,writeaccess,sshkey", - "groups": { - "_enumeration": { - "enumerator": [ - "OPS", - "IT", - "QA" - ] - }, - "_format": "array", - "_label": "Groups" - }, - "_key": "username", - "sshkey": { - "_format": "text", - "_label": "SSH Key" - }, - "systemuser": { - "_format": "bool", - "_label": "System User" - }, - "username": { - "_format": "string", - "_label": "Username" - }, - "writeaccess": { - "_format": "bool", - "_label": "Write Access" + "user": { + "_field_order": "username,systemuser,groups,writeaccess,sshkey", + "groups": { + "_enumeration": { + "enumerator": [ + "OPS", + "IT", + "QA" + ] + }, + "_format": "array", + "_label": "Groups" + }, + "_key": "username", + "sshkey": { + "_format": "text", + "_label": "SSH Key" + }, + "systemuser": { + "_format": "bool", + "_label": "System User" + }, + "username": { + "_format": "string", + "_label": "Username" + }, + "writeaccess": { + "_format": "bool", + "_label": "Write Access" + } } } } \ No newline at end of file From d382d6094b1db2758af542cf0fb875ba19f7894c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 8 Dec 2014 15:16:31 -0800 Subject: [PATCH 38/82] put back missing extends for system, changing it to _extends --- cmdb_api.pm | 4 ++-- lexicon.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 80b0dfd..133f343 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -162,9 +162,9 @@ $tree_extended = &eat_json( &make_json($tree) ); # loop through entities and add base attributes to things that subclass other stuff foreach ( keys( %{ $tree_extended->{entities} } ) ) { - if ( $tree_extended->{entities}->{$_}->{extends} ) + if ( $tree_extended->{entities}->{$_}->{_extends} ) { - my $extends = &lkupLexiconPath( $tree->{entities}->{$_}->{extends} ); + my $extends = &lkupLexiconPath( $tree->{entities}->{$_}->{_extends} ); foreach my $attr ( keys(%$extends) ) { $tree_extended->{entities}->{$_}->{$attr} = $extends->{$attr}; diff --git a/lexicon.json b/lexicon.json index c8a9d12..86d8fd7 100644 --- a/lexicon.json +++ b/lexicon.json @@ -632,7 +632,8 @@ }, "_field_category": "main", "_label": "Environment", - "_required": "false" + "_required": "false", + "_extends": "device" }, "_extends": "/lexicon/entities/device", "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", From 0eee8b1a8b8a5e8c2aec55c33bff554f15147351 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 8 Dec 2014 15:20:57 -0800 Subject: [PATCH 39/82] fix extraneous extends --- lexicon.json | 1 - 1 file changed, 1 deletion(-) diff --git a/lexicon.json b/lexicon.json index 86d8fd7..e3afafd 100644 --- a/lexicon.json +++ b/lexicon.json @@ -633,7 +633,6 @@ "_field_category": "main", "_label": "Environment", "_required": "false", - "_extends": "device" }, "_extends": "/lexicon/entities/device", "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", From 412df49b42125e0d675822a6fc93ff9ca4852324 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 9 Dec 2014 10:28:53 -0800 Subject: [PATCH 40/82] remove deprecated lexicon --- pp_lexicon.xml | 664 ------------------------------------------------- 1 file changed, 664 deletions(-) delete mode 100644 pp_lexicon.xml diff --git a/pp_lexicon.xml b/pp_lexicon.xml deleted file mode 100644 index 7d8342b..0000000 --- a/pp_lexicon.xml +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - - - config - true - string - - - - config - false - string - - - - config - true - string - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - true - string - - - - config - false - string - - - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - true - string - - - - config - false - string - - - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - false - string - - - - config - true - string - - - - config - false - string - - - - - - true - string - - - - - - - - - - - config - string - true - - - - config - string - true - - - - config - string - false - - - - config - string - false - - - - - config - text - false - - - - - - - - - - - - - - - - - - - - - - - - - - config - string - fqdn - - - - config - string - - blade_chassis - console_server - firewall - load_balancer - network_switch - other - power_strip - router - rsa_appliance - storage_head - storage_shelf - system - vpn - - - - - - - - - string - serial_number - - - - config - string - - POD - Archiving - Core - EDN - Eng - - - - - - - - - string - - /.*/ - - betterip,ipaddress,ip_address - - - - fact - string - - /.*/ - - - - - fact - true - string - - - - - - - - - - - - config - string - - - 42 - - - - - - string - manufacturer - - - - - - string - productname - - - - - - - - - - - false - config - string - - production - deployment - allocated - idle - in transit - degraded - decommissioned - disposed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - config - string - - - - - - - - - - - - - - fact - - - - - - - - - - config - string - operatingsystem - - - - - config - string - operatingsystemrelease - - - - fact - string - kernelrelease - - - - false - string - - - - fact - string - - - - fact - string - - - - fact - string - - - - fact - string - - - - fact - string - - - - - - - multiselect - - - - - - - - - - fact - float - - - - - - - fact - int - - - - - - - fact - int - - - - - - - fact - int_timestamp - - - - - - - - - - - - - false - config - string - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - int - - - - true - string - - - - true - string - - - - true - string - - - - true - string - - - - - - - - - - - - - - - - - OPS - IT - QA - - - - - - - - - - - - - - - - - - - - - - - - - - string - - - - string - - - - - - float - - - - - - - int - false - - - - - - - - - - - - - - - - - - - - From df88638499ba8a8902b0b0deef00383b2618aa3e Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 11 Dec 2014 13:50:04 -0800 Subject: [PATCH 41/82] fix for note being an object in ui, as it gets stored incorrectly as a service_instance_data resource instead of the field in service_instance --- cmdb_api.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 133f343..f9d3130 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1713,7 +1713,7 @@ sub doEnvironmentsServicesGET() pop @parents; %hash = (); my $list = join( ', ', map { "'$_'" } reverse(@parents) ); - $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . " (select name, environment_name, note, svc_id, type from service_instance " . " where type != 'environment' "; + $sql = "select name, environment_name, note, s.svc_id, type, data_key, data_value from " . " (select name, environment_name, note, svc_id, type from service_instance " . " where name like '%' "; if ( defined $service ) { @@ -2184,7 +2184,7 @@ sub doEnvironmentsServicesPOST() my @columns; my @values; - for my $field (qw/name environment_name type notes/) + for my $field (qw/name environment_name type note/) { if ( defined $data->{$field} ) { From e73284c7df4e073ff20d0212a1ba03f4ffdbd01b Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 11 Dec 2014 15:58:47 -0800 Subject: [PATCH 42/82] fix for note being an object in ui, as it gets stored incorrectly as a service_instance_data resource instead of the field in service_instance --- cmdb_api.pm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index f9d3130..778dfd8 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2200,14 +2200,16 @@ sub doEnvironmentsServicesPOST() executeDbStatement( $sth, $sql ); $svc_id = $sth->{mysql_insertid}; - # Create service_instance_data records - $sql = "insert into service_instance_data " . "(svc_id, data_key, data_value) values "; - - $sql .= join( ',', map { sprintf( "('%s','%s','%s')", $svc_id, $_, $data->{$_} ) } keys %$data ); + if(scalar(keys(%$data)) > 0) + { + # Create service_instance_data records + $sql = "insert into service_instance_data " . "(svc_id, data_key, data_value) values "; - $sth = $dbh->prepare($sql); - executeDbStatement( $sth, $sql ); + $sql .= join( ',', map { sprintf( "('%s','%s','%s')", $svc_id, $_, $data->{$_} ) } keys %$data ); + $sth = $dbh->prepare($sql); + executeDbStatement( $sth, $sql ); + } insertAuditEntry( $dbh, $requestObject, 'services', "$environment/$service", 'record', '', 'CREATED', $$now[0] ); $dbh->commit; }; From 42942e55eb42b012801a3c6209aea3b0bab60aa0 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 12 Dec 2014 11:13:31 -0800 Subject: [PATCH 43/82] fixing lexicon incorrect typo or key --- lexicon.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lexicon.json b/lexicon.json index e3afafd..efe9212 100644 --- a/lexicon.json +++ b/lexicon.json @@ -358,7 +358,7 @@ "_label": "Note", "_required": "false" }, - "_type": { + "type": { "_label": "Type", "_required": "false" } @@ -632,7 +632,7 @@ }, "_field_category": "main", "_label": "Environment", - "_required": "false", + "_required": "false" }, "_extends": "/lexicon/entities/device", "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", From d9a0e10e1e4f58930e69fb186649e85f8d566c68 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Fri, 2 Jan 2015 18:49:45 -0800 Subject: [PATCH 44/82] more updates for lexicon chnages --- cmdb_api.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 778dfd8..b987c49 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1029,7 +1029,7 @@ sub doGenericPUT change_user => $requestObject->{'user'}->{'username'}, change_time => $$now[0], entity => $$requestObject{'entity'}, - entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'_key'} }, change_content => &make_json($blocked_changes) }; &doGenericPOST( @@ -1343,7 +1343,7 @@ sub doAclPUT change_user => $requestObject->{'user'}->{'username'}, change_time => $$now[0], entity => $$requestObject{'entity'}, - entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'_key'} }, change_content => &make_json($blocked_changes) }; &doGenericPOST( @@ -1596,7 +1596,7 @@ sub doGenericDELETE return 'ACL blocked Delete: ' . &make_json($blocked_changes); } - $sql = "delete from $requestObject->{'entity'} where $tree_extended->{entities}->{ $requestObject->{'entity'} }->{'key'} = ?"; + $sql = "delete from $requestObject->{'entity'} where $tree_extended->{entities}->{ $requestObject->{'entity'} }->{'_key'} = ?"; $parms = [ $requestObject->{'path'}[0] ]; $dbh->do( $sql, {}, @$parms ); if ( $dbh->err ) @@ -2676,7 +2676,7 @@ sub doSystemPUT() change_user => $requestObject->{'user'}->{'username'}, change_time => $$now[0], entity => $$requestObject{'entity'}, - entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} }, + entity_key => $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'_key'} }, change_content => &make_json($blocked_changes) }; &doGenericPOST( @@ -2685,7 +2685,7 @@ sub doSystemPUT() body => &make_json($change_item), } ); - $logger->warn( "queued change for " . $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'key'} } ); + $logger->warn( "queued change for " . $$lkup_data{ $tree->{'entities'}->{ $$requestObject{'entity'} }->{'_key'} } ); } # construct update sql for device table From 6eeb506cc0a26cff37883e6c3ca3a890fb3cf6b7 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 09:56:41 -0800 Subject: [PATCH 45/82] removing enumerator items from roles field --- lexicon.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lexicon.json b/lexicon.json index efe9212..e4dd2ca 100644 --- a/lexicon.json +++ b/lexicon.json @@ -839,11 +839,6 @@ "_enumeration": { "attribute": "role_id", "entity": "role", - "enumerator": [ - "hadoop_datanode", - "nagios", - "repo::apt" - ], "forceselect": "true" }, "_field_category": "main", From a6f23f0020cf7293b42d27b1a0b7be2e9087c9a6 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 09:57:17 -0800 Subject: [PATCH 46/82] adjust path for request to new fqdn when its changed so the PUT -> doGETSystem works --- cmdb_api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index b987c49..40c461f 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -252,7 +252,6 @@ sub handler() { $lex->{$_} = $tree->{entities}->{$_}; no strict 'refs'; - # check each attribute and populate enumerations if needed foreach my $attr ( keys( %{ $lex->{$_} } ) ) { @@ -2756,6 +2755,7 @@ sub doSystemPUT() if ( exists $$data{'fqdn'} && $$lkup_data{'fqdn'} ne $$data{'fqdn'} ) { $fqdn = $$data{'fqdn'}; + $$requestObject{'path'}[0] = $fqdn; } #update or insert into ip table From 84148831235d73e93edcc15325258d84f56a7ed7 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 11:06:54 -0800 Subject: [PATCH 47/82] adding delete for system resource --- cmdb_api.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index 40c461f..eea366a 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2440,6 +2440,12 @@ sub doEnvironmentsDELETE() } } +sub doSystemDELETE() +{ + my $requestObject = shift; + return &doGenericDELETE($requestObject); +} + # special functions to handle device and systems sub doSystemGET() { From fc09b3fe88c75077cdda76b4863dc1055162bb70 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 11:29:26 -0800 Subject: [PATCH 48/82] need to adjust entity before calling doGenericDELETE --- cmdb_api.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index eea366a..225a3ad 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2443,6 +2443,7 @@ sub doEnvironmentsDELETE() sub doSystemDELETE() { my $requestObject = shift; + $$requestObject{'entity'} = 'device'; return &doGenericDELETE($requestObject); } From f0b677fe85fe65b0091672aedaf6563f629d129c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 11:29:58 -0800 Subject: [PATCH 49/82] fixing test for deleteing system --- cmdb_tests.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index 0d4651a..fd75e71 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -40,6 +40,6 @@ make change against acl,,system,test.test.com,"{""drac"":""20.20.20.20""}",PUT,h verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,,drac=10.10.10.1 delete acl,,acl,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,,,,,, -delete datacenter,,data_center,DC1,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,,,,,, -"FINISHING: delete system NOTE: this will always fail, manually delete test.test.com entry",,system,test.test.com,,DELETE,,,,200,, +delete system,,system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete datacenter,,data_center,DC1,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, From 7938c8f23e2984a2a59c94a6fef934f18f476686 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 5 Jan 2015 13:19:54 -0800 Subject: [PATCH 50/82] adding note to test --- cmdb_tests.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index fd75e71..35d511b 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -5,6 +5,8 @@ modify system,,system,test.test.com,"{""notes"":""testnote""}",PUT,http://localh lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,"there is no delete function, this will always fail",system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, verify via audit ,,inv_audit,,"{""entity_key"":""test.test.com"",""field_name"":""notes"",""new_value"":""testnote""}",GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,new_value=testnote +rename system,,system,test.test.com,"{""fqdn"":""test2.test.com"",""note"":""rename""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,fqdn=test2.test.com +change system name back,,system,test2.test.com,"{""fqdn"":""test.test.com"",""note"":""rename""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,fqdn=test.test.com create datacenter,,data_center,DC1,"{""data_center_code"":""DC1"",""data_center_vendor"":""sample vendor""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/data_center/DC1 ,,,,,,,,,,, create datacenter_subnet,,datacenter_subnet,,"{""data_center_code"":""DC1"",""subnet"":""10.10.0.0/24"",""notes"":""testsubnet"",""id"":1000}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location~/cmdb_api/v1/datacenter_subnet/1000 From 0f07ea1856d356c7d53fc0a42c10b30f225067b2 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 6 Jan 2015 11:17:24 -0800 Subject: [PATCH 51/82] changing 2nd rename test to be via trafficcontrol api --- cmdb_tests.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index 35d511b..2487f93 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -6,7 +6,7 @@ lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_a ,"there is no delete function, this will always fail",system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, verify via audit ,,inv_audit,,"{""entity_key"":""test.test.com"",""field_name"":""notes"",""new_value"":""testnote""}",GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,new_value=testnote rename system,,system,test.test.com,"{""fqdn"":""test2.test.com"",""note"":""rename""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,fqdn=test2.test.com -change system name back,,system,test2.test.com,"{""fqdn"":""test.test.com"",""note"":""rename""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,fqdn=test.test.com +change system name via trafficcontrol,,fact,,"{""fqdn"":""test.test.com"",""ipaddress"":""10.10.0.1"",""note"":""rename""}",POST,http://localhost:80,/cmdb_api/v1/,,201,data,fqdn=test.test.com create datacenter,,data_center,DC1,"{""data_center_code"":""DC1"",""data_center_vendor"":""sample vendor""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/data_center/DC1 ,,,,,,,,,,, create datacenter_subnet,,datacenter_subnet,,"{""data_center_code"":""DC1"",""subnet"":""10.10.0.0/24"",""notes"":""testsubnet"",""id"":1000}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location~/cmdb_api/v1/datacenter_subnet/1000 From 4a4e0625b49b91ee0b6b294478cf0668db9f88b2 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 6 Jan 2015 11:17:32 -0800 Subject: [PATCH 52/82] adjusting TC logging --- cmdb_api.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 225a3ad..cae08aa 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -570,16 +570,17 @@ sub doTrafficControlPOST() $requestObject->{'user'} = &doGenericGET( { entity => 'user', path => ['trafficcontrol'] } ); my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); my ( $lkup_data, $lkup ); - $logger->debug("TC got POST from agent") if ( $logger->is_debug() ); + $logger->debug("TC got POST from agent, finding system with fields: " . join(',',@{ $opt->{'traffic_control_search_fields'} }) ) if ( $logger->is_debug() ); foreach ( @{ $opt->{'traffic_control_search_fields'} } ) { # skip serial if not dell tag - next if ( $_ eq 'serial_number' && length( $data->{$_} ) != 7 ); + next if ( $_ eq 'serial_number' && length( $data->{$_} ) < 4 ); $lkup = $dbh->selectall_arrayref( "select * from device where $_=?", { Slice => {} }, ( $data->{$_} ) ); - if ( scalar(@$lkup) == 1 ) + if ( scalar(@$lkup) == 1 ) { $lkup_data = $$lkup[0]; + $logger->debug("TC found system with $_ : $data->{$_}" ); last; } } From e915119e4abc97f1f2009618fd87e98adddc1307 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 8 Jan 2015 17:11:38 -0800 Subject: [PATCH 53/82] fixing optconfig definition, and typo bug and sql statement that includes no-longer needed condition --- cmdb_api.pm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index cae08aa..d35b2d2 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -58,13 +58,13 @@ my $opt = Optconfig->new( 'driver=s' => 'mysql', 'dbuser=s' => 'dbuser', 'dbpass=s' => 'dbpass', - 'dbhost' => 'localhost', + 'dbhost=s' => 'localhost', 'database' => 'inventory', 'debug' => 1, - 'prism_domain' => 'prism.ppops.net', - 'logconfig' => '/var/www/cmdb_api/log4perl.conf', - 'lexicon' => '/var/www/cmdb_api/lexicon.json', - 'ipaddress_attribute' => "ip_address", + 'prism_domain=s' => 'prism.ppops.net', + 'logconfig=s' => '/var/www/cmdb_api/log4perl.conf', + 'lexicon=s' => '/var/www/cmdb_api/lexicon.json', + 'ipaddress_attribute=s' => "ip_address", "traffic_control_search_fields" => [ "fqdn", "macaddress", "ipaddress" ], 'entities' => { acl => 'Acl', @@ -1758,13 +1758,12 @@ sub doEnvironmentsServicesGET() my $svc = $hash{ $data->{'name'} }; my $key = $data->{'data_key'}; my $value = $data->{'data_value'}; - next if ( not defined $key || grep(/^$key$/,['name','note','type','svc_id','environment_name'] ) ); + next if ( not defined $key || grep(/^$key$/,@{&getFieldList('service_instance')}) ); # next if ( ( not defined $key ) || exists $svc->{$key} ); if ($environment_tag) { my $inherited=0; - $logger->debug("TEST $key " . $svc->{$key}); if(exists $svc->{$key}) { $inherited = $svc->{$key}; @@ -1918,7 +1917,7 @@ sub doEnvironmentsServicesPUT() eval { # Get service_instance record for the requested service - $sql = "select svc_id,type,name,environment_name,note from service_instance where environment_name=? and name=? and type!='environment'"; + $sql = "select svc_id,type,name,environment_name,note from service_instance where environment_name=? and name=?"; $sth = $dbh->prepare($sql); executeDbStatement( $sth, $sql, $environment, $service ); $lkup_data = $sth->fetchall_arrayref( {}, undef ); @@ -2236,7 +2235,7 @@ sub doEnvironmentsServicesPOST() return $error if ( defined $error ); - $$requestObject{'headers_out'} = [ 'Location', "/cmndb_api/v1/environments/" . $environment . "/services/" . $service ]; + $$requestObject{'headers_out'} = [ 'Location', "/cmdb_api/v1/environments/" . $environment . "/services/" . $service ]; return; } From e518baaed9daf16e66a6c263806a9ceb68b30a68 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 8 Jan 2015 17:11:59 -0800 Subject: [PATCH 54/82] service_instance type is regular attribute not meta --- lexicon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lexicon.json b/lexicon.json index e4dd2ca..98995d1 100644 --- a/lexicon.json +++ b/lexicon.json @@ -513,7 +513,7 @@ "_label": "SVC ID", "_required": "true" }, - "_type": { + "type": { "_label": "Type", "_required": "false" } From 0a811616b9b49dca039f7f4f4081520313629b9b Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 8 Jan 2015 17:12:18 -0800 Subject: [PATCH 55/82] adding tests for environment services api --- cmdb_tests.csv | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmdb_tests.csv b/cmdb_tests.csv index 2487f93..81cb9c8 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -39,9 +39,20 @@ delete nonexistenet role,,role,norole,,DELETE,http://localhost:80,/cmdb_api/v1/, add acl,,acl,999,"{""acl_group"":""readonly"",""acl_id"":999,""entity"":""system"",""field"":""drac"",""logic"":""1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/acl/999 verify acl,,acl,999,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,entity=system make change against acl,,system,test.test.com,"{""drac"":""20.20.20.20""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,403,, -verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,,drac=10.10.10.1 +create test env,,environments,,"{""name"":""testenv1"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv1 +create environment/service,,environments/testenv1/services,,"{""name"":""servicetest"",""type"":""service"",""testattribute"":""12345"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv1/services/servicetest +create child env,,environments,,"{""name"":""testenv2"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv2 +validate inherited service,,environments/testenv2/services,servicetest,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,environment_name=testenv1 +create service override,,environments/testenv2/services,,"{""name"":""servicetest"",""type"":""service"",""environment_name"":""testenv2""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv2/services/servicetest +create service attribute override,,environments/testenv2/services,servicetest,"{""testattribute"":""testvalue""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,testattribute=testvalue +verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,drac=10.10.10.1 delete acl,,acl,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,,,,,, ,,,,,,,,,,, +delete child env,,environments,testenv2,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete test service,,environments/testenv1/services,servicetest,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete test service override,,environments/testenv2/services,servicetest,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete test env,,environments,testenv1,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, delete system,,system,test.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, +delete renamed non existant test system,,system,test2.test.com,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, delete datacenter,,data_center,DC1,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, From 92b7cae9115ae0e8b052fe1635f20dc355d7325d Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 12 Jan 2015 15:40:58 -0800 Subject: [PATCH 56/82] adding foreign key constriant so data records get deleted when the service record is deleted --- initial_schema.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/initial_schema.sql b/initial_schema.sql index f19a2e0..6472598 100644 --- a/initial_schema.sql +++ b/initial_schema.sql @@ -241,8 +241,10 @@ CREATE TABLE `service_instance_data` ( `svc_id` mediumint(9) NOT NULL, `data_key` varchar(45) NOT NULL, `data_value` varchar(255) DEFAULT NULL, - PRIMARY KEY (`data_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; + PRIMARY KEY (`data_id`), + KEY `svc_id` (`svc_id`), + CONSTRAINT `service_instance_data_ibfk_1` FOREIGN KEY (`svc_id`) REFERENCES `service_instance` (`svc_id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=10079 DEFAULT CHARSET=utf8 -- -- Table structure for table `snat` From 49536b86dab6f920c242461f59e194a81f8fcd94 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 20 Jan 2015 10:24:06 -0800 Subject: [PATCH 57/82] make meta attr default_value be _default_value --- cmdb_api.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index d35b2d2..8d771be 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2847,10 +2847,10 @@ sub applyDefaults() my $fields = &getFieldList( $entity ); foreach (@$fields) { - if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{default_value} ) + if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{_default_value} ) { - $logger->debug("using ($entity) default value '$tree_extended->{entities}->{ $entity }->{$_}->{default_value}' for $_"); - $$data{$_} = $tree_extended->{entities}->{ $entity }->{$_}->{default_value}; + $logger->debug("using ($entity) default value '$tree_extended->{entities}->{ $entity }->{$_}->{_default_value}' for $_"); + $$data{$_} = $tree_extended->{entities}->{ $entity }->{$_}->{_default_value}; } } return $data; From 4fd5a4f12ca336d38aa9425e00416204606cce97 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Tue, 20 Jan 2015 13:14:31 -0800 Subject: [PATCH 58/82] adding uuid to default lexicon --- lexicon.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lexicon.json b/lexicon.json index 98995d1..fa66c26 100644 --- a/lexicon.json +++ b/lexicon.json @@ -868,6 +868,10 @@ "_label": "JSON Tags", "_required": "false" }, + "uuid": { + "_flavor": "fact", + "_label": "UUID" + }, "virtual": { "_flavor": "_fact", "_label": "VM Type", @@ -912,4 +916,4 @@ } } } -} \ No newline at end of file +} From 6daaccd179bd392dd21c6c375279e8213cc5e7a1 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Wed, 21 Jan 2015 15:21:21 -0800 Subject: [PATCH 59/82] adjusting default_value to _default_value --- lexicon.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lexicon.json b/lexicon.json index fa66c26..565754f 100644 --- a/lexicon.json +++ b/lexicon.json @@ -166,7 +166,7 @@ "_required": "true" }, "inventory_component_type": { - "default_value": "system", + "_default_value": "system", "_enumeration": { "enumerator": [ "blade_chassis", @@ -619,7 +619,7 @@ "_required": "false" }, "environment_name": { - "default_value": "production", + "_default_value": "production", "_enumeration": { "attribute": "name", "entity": "environments", From 256adb1948232d98c0f35f570bed8eca6f5396cf Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Wed, 21 Jan 2015 15:22:09 -0800 Subject: [PATCH 60/82] removing incoming date_created and date_modified to prevent them from being updated. stub function to test for value change. better empty string / null handling to come --- cmdb_api.pm | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 8d771be..341ec52 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -147,7 +147,7 @@ eval { } }; -# show error and die if xml parsing of the lexicon failed +# show error and die if parsing of the lexicon failed if ($@) { $logger->fatal("error parsing $lexicon\n$@"); @@ -2440,6 +2440,11 @@ sub doEnvironmentsDELETE() } } +sub isChanged() +{ + return 1; +} + sub doSystemDELETE() { my $requestObject = shift; @@ -2615,6 +2620,8 @@ sub doSystemPUT() return "stored record has already been modified"; } } + delete $$data{'date_modified'}; + delete $$data{'date_created'}; if ( $$lkup_data{'metaData'} ) { $lkup_data = $$lkup_data{'records'}[0]; @@ -2713,7 +2720,7 @@ sub doSystemPUT() } #audit - if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} ) + if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} && &isChanged($lkup_data,$data,$_) ) { $dbs->do( 'insert into inv_audit set @@ -2797,7 +2804,7 @@ sub doSystemPUT() } #audit - if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} ) + if ( !$tree_extended->{entities}->{'system'}->{$_}->{meta} && &isChanged($lkup_data,$data,$_)) { $dbs->do( 'insert into inv_audit set From 5fd5660c4d9bff13f5a546e9ef88fb62de1e696f Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 22 Jan 2015 00:05:41 -0800 Subject: [PATCH 61/82] "Better" detection of fields being the same basic emptiness, null = "" in this case --- cmdb_api.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index 341ec52..b92b8ac 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2442,6 +2442,12 @@ sub doEnvironmentsDELETE() sub isChanged() { + my ($old,$new,$field)=@_; + # $logger->info("TESTCHANGED--$field--$$old{$field}=$$new{$field}--"); + if ("$$old{$field}" eq "$$new{$field}") + { + return 0; + } return 1; } From 519b35f76f08e45d294f4dfe33dcf0cd63e0b283 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 22 Jan 2015 12:03:53 -0800 Subject: [PATCH 62/82] add link to wiki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae225e7..58a4dc5 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ https://github.com/evernote/noms-client/wiki The API works very well with the UI project CMDB-UI (https://github.com/isaacfinnegan/cmdb-ui) - Web-based GUI for the NOMS CMDB - +More information can be found on the cmdb api wiki: https://github.com/isaacfinnegan/cmdb-api/wiki From 1c5542db181f112e3a52d7b1c72cf6c2b3e96703 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 22 Jan 2015 12:04:36 -0800 Subject: [PATCH 63/82] remove system_type proofpoint specific field, add default_value=idle to status --- lexicon.json | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/lexicon.json b/lexicon.json index 565754f..c1e42de 100644 --- a/lexicon.json +++ b/lexicon.json @@ -283,24 +283,7 @@ "_format": "string", "_label": "Status", "_required": "false", - "_validation": { - "is_string": {} - } - }, - "system_type": { - "_enumeration": { - "enumerator": [ - "POD", - "Archiving", - "Core", - "EDN", - "Eng" - ] - }, - "_flavor": "config", - "_format": "string", - "_label": "Business", - "_required": "false", + "_default_value": "idle", "_validation": { "is_string": {} } From 0c8e40269f1c8d689eae7506530be245568215ac Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 16 Mar 2015 16:58:13 -0700 Subject: [PATCH 64/82] patching to detect undef db handle and attempt to handle gracefully --- cmdb_api.pm | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index b92b8ac..978adff 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -38,6 +38,7 @@ use XML::Simple; use Date::Manip; use Optconfig; use DBI; +use Data::Dumper; use NOMS::JCEL; sub eat_json @@ -131,6 +132,9 @@ unless ($lexicon) # database connection our $dbh; +# apache request +our $r; + #valid api types. these must exist and be parsable in the lexicon if they are 'Generic' # or have provided doGET/PUT/POST functions @@ -192,7 +196,7 @@ sub lkupLexiconPath() sub handler() { $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); - my $r = shift; + $r = shift; my $up_uri = $r->unparsed_uri(); $up_uri =~ s/.+\?//; my $uri = uri_unescape($up_uri); @@ -208,6 +212,7 @@ sub handler() $$requestObject{'pathstr'} = $req->uri(); $$requestObject{'user'} = &doGenericGET( { entity => 'user', path => [ $req->user ] } ) if $req->user; $$requestObject{'http_auth_user'} = $req->user if $req->user; + $$requestObject{'request_object'} = $r; # apache 2.4 doesn't have this anymore if( $connection->can('remote_ip') ) { @@ -283,7 +288,6 @@ sub handler() $r->print( &make_json( $tree->{entities}->{ $requestObject->{'entity'} }, { pretty => 1, allow_nonref => 1 } ) ); return Apache2::Const::OK; } - } # check for valid entity @@ -312,6 +316,11 @@ sub handler() { $r->headers_out->add( $$requestObject{'headers_out'}[0] => $$requestObject{'headers_out'}[1] ); } + if($data eq 'killchild') + { + $r->child_terminate(); + undef $data; + } #TODO make output format based on accept content header if ( !defined $data && keys( %{ $$requestObject{'query'} } ) > 0 ) @@ -910,6 +919,27 @@ sub doSql() my $parms = shift; my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); + if ($dbh == undef) + { + + $logger->error("detected bad db handle, redirecting to self and terminating apache child"); + print STDERR "detected bad db handle, redirecting to self and terminating apache child"; + # check for bad db handle and redirect to self + $r->headers_out->set(Location => $r->unparsed_uri() ); + no strict 'subs'; + $r->status(Apache2::Const::HTTP_MOVED_TEMPORARILY); + + # no kill of this child, situations have been seen where this apache child will + # never again get a good db handle + $r->child_terminate(); + + # exiting here, as returning anything will require refactoring all methods that + # call this to handles the error and bubble it up to handler() + exit; + + + } + my $sth = $dbh->prepare($sql); my $sql_out; $logger->debug( "executing: $sql with " . &make_json( $parms, { allow_nonref => 1 } ) ) if ( $logger->is_debug() ); From c4be30dd297823dc91416285e4d14560351f9003 Mon Sep 17 00:00:00 2001 From: Eric Robbins Date: Tue, 17 Mar 2015 19:38:53 -0700 Subject: [PATCH 65/82] clean up error log spam --- cmdb_api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 978adff..9276a83 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -919,7 +919,7 @@ sub doSql() my $parms = shift; my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); - if ($dbh == undef) + if (!defined($dbh)) { $logger->error("detected bad db handle, redirecting to self and terminating apache child"); From ffb4ef5b5cf57e9fdaaa93fef89500dc33e881fa Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Wed, 18 Mar 2015 15:57:02 -0700 Subject: [PATCH 66/82] fixing missing _ on meta attribute property, this left meta attribute changes being logged to inv_audit. also fix for warning on undef $key --- cmdb_api.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 978adff..56ceab1 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -1788,7 +1788,8 @@ sub doEnvironmentsServicesGET() my $svc = $hash{ $data->{'name'} }; my $key = $data->{'data_key'}; my $value = $data->{'data_value'}; - next if ( not defined $key || grep(/^$key$/,@{&getFieldList('service_instance')}) ); + next if ( not defined $key ); + next if ( grep(/^$key$/,@{&getFieldList('service_instance')}) ); # next if ( ( not defined $key ) || exists $svc->{$key} ); if ($environment_tag) @@ -2697,13 +2698,13 @@ sub doSystemPUT() foreach (@$device_fields) { $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; - $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'_meta'} ); delete $$data{$_} if ( defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); } foreach (@$meta_fields) { $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ) if exists $$data{$_}; - $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'meta'} ); + $mtime = $$now[0] if ( exists $$data{$_} && !$tree_extended->{'entities'}->{'system'}->{$_}->{'_meta'} ); delete $$data{$_} if ( defined $$data{$_} && defined $$lkup_data{$_} && $$data{$_} eq $$lkup_data{$_} ); } my $blocked_changes = {}; From fdea41067471d9d5c42578e720423e1077f65698 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 19 Mar 2015 11:21:51 -0700 Subject: [PATCH 67/82] fixing error for invalid entity method, and changing inv_audit to use Audit methods. should NOT be using generic as it would allow PUT/DELETE. config files will need updating. --- cmdb_api.pm | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 56ceab1..e1a979f 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -85,7 +85,7 @@ my $opt = Optconfig->new( nccsystemname => 'ProvisionNcc', user => 'Generic', currentUser => 'User', - inv_audit => 'Generic', + inv_audit => 'Audit', audit => 'Audit', inv_normalizer => 'Generic', fact => 'TrafficControl', @@ -505,7 +505,7 @@ sub ProcessRequest() else { $logger->error("no function found $func"); - $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + $$requestObject{'stat'} = Apache2::Const::HTTP_METHOD_NOT_ALLOWED; return 'no entity function'; } } @@ -548,14 +548,7 @@ sub doColumn_lkupGET() sub doAuditGET() { my $requestObject = shift; - my $entity = $$requestObject{'path'}[0]; - if ( $$valid_entities{$entity} eq 'System' ) - { - $entity = 'device'; - } - my $lkup = $$requestObject{'path'}[1]; - my $sql = 'select * from inv_audit where entity_name=? and entity_key=? order by change_time'; - return &recordFetch( $requestObject, $sql, [ $entity, $lkup ] ); + return doGenericGET($requestObject); } #special api to check current user for write access From 30232dfb44e156ecbc88ab8e776f4a7b57a702b2 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Thu, 19 Mar 2015 14:02:55 -0700 Subject: [PATCH 68/82] fixing config --- cmdb_api.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdb_api.conf b/cmdb_api.conf index 780bf81..d7e09e3 100644 --- a/cmdb_api.conf +++ b/cmdb_api.conf @@ -27,7 +27,7 @@ "pcmsystemname":"ProvisionPcm", "user":"Generic", "currentUser":"User", - "inv_audit":"Generic", + "inv_audit":"Audit", "audit":"Audit", "inv_normalizer":"Generic", "fact":"TrafficControl", From a2516f1dac3106bd637ac2cf8c6cedd81bf7c998 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 30 Mar 2015 15:16:55 -0700 Subject: [PATCH 69/82] fix for detecting bad db handle and properly sending 302. --- cmdb_api.pm | 195 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 81 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index e1a979f..c427e22 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -135,6 +135,8 @@ our $dbh; # apache request our $r; +our $db_retry=0; + #valid api types. these must exist and be parsable in the lexicon if they are 'Generic' # or have provided doGET/PUT/POST functions @@ -211,6 +213,17 @@ sub handler() @{ $$requestObject{'path'} } = split( '/', $req->uri() ); $$requestObject{'pathstr'} = $req->uri(); $$requestObject{'user'} = &doGenericGET( { entity => 'user', path => [ $req->user ] } ) if $req->user; + if($db_retry) + { + $db_retry=0; + $logger->error("(handler 1)detected bad db handle, redirecting to self and terminating apache child"); + $r->headers_out->set('Location' => ($r->subprocess_env('HTTPS') eq 'on' ? 'https' : 'http' ) . '://' . $r->hostname() . $r->unparsed_uri() ); + no strict 'subs'; + $r->status(302); + $r->child_terminate(); + return Apache2::Const::REDIRECT; + } + $$requestObject{'http_auth_user'} = $req->user if $req->user; $$requestObject{'request_object'} = $r; # apache 2.4 doesn't have this anymore @@ -264,9 +277,9 @@ sub handler() && ref( $lex->{$_}->{$attr} ) eq 'HASH' && defined $lex->{$_}->{$attr}->{'_enumeration'} && defined $lex->{$_}->{$attr}->{'_enumeration'}->{'entity'} - && defined $lex->{$_}->{$attr}->{'_enumeration'}->{'attribute'} ) + && defined $lex->{$_}->{$attr}->{'_enumeration'}->{'attribute'} ) { - $lex->{$_}->{$attr}->{'_enumeration'}->{'enumerator'} = &doColumn_lkupGET( $requestObject, $lex->{$_}{$attr}{'_enumeration'}{'entity'}, $lex->{$_}{$attr}{'_enumeration'}->{'attribute'} ); + $lex->{$_}->{$attr}->{'_enumeration'}->{'enumerator'} = &doColumn_lkupGET( $requestObject, $lex->{$_}{$attr}{'_enumeration'}{'entity'}, $lex->{$_}{$attr}{'_enumeration'}->{'attribute'} ); } } } @@ -301,6 +314,18 @@ sub handler() #deal with the connection and produce data $data = &ProcessRequest($requestObject); + + if($db_retry) + { + $db_retry=0; + $logger->error("(handler 2)detected bad db handle, redirecting to self and terminating apache child"); + $r->headers_out->set('Location' => $r->unparsed_uri() ); + no strict 'subs'; + $r->status(Apache2::Const::HTTP_MOVED_TEMPORARILY); + $r->child_terminate(); + return Apache2::Const::REDIRECT; + } + $r->status( $$requestObject{'stat'} ); if ( $$requestObject{'stat'} eq '500' ) { @@ -706,8 +731,8 @@ sub doProvisionGET() # unless($$requestObject{'query'}{'serial_number'}) # { - # $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; - # return 'missing data (serial_number)'; + # $$requestObject{'stat'}=Apache2::Const::HTTP_FAILED_DEPENDENCY; + # return 'missing data (serial_number)'; # } # lkup system my $data = $sth->fetchall_arrayref( {}, undef ); @@ -915,12 +940,12 @@ sub doSql() if ($dbh == undef) { - $logger->error("detected bad db handle, redirecting to self and terminating apache child"); + $logger->error("(doSql)detected bad db handle, redirecting to self and terminating apache child"); print STDERR "detected bad db handle, redirecting to self and terminating apache child"; # check for bad db handle and redirect to self - $r->headers_out->set(Location => $r->unparsed_uri() ); + $r->headers_out->set('Location' => ($r->subprocess_env('HTTPS') eq 'on' ? 'https' : 'http' ) . '://' . $r->hostname() . $r->unparsed_uri() ); no strict 'subs'; - $r->status(Apache2::Const::HTTP_MOVED_TEMPORARILY); + $r->status(302); # no kill of this child, situations have been seen where this apache child will # never again get a good db handle @@ -928,9 +953,7 @@ sub doSql() # exiting here, as returning anything will require refactoring all methods that # call this to handles the error and bubble it up to handler() - exit; - - + return 'badhandle'; } my $sth = $dbh->prepare($sql); @@ -957,6 +980,17 @@ sub recordFetch() my $return; my $rtn = &doSql( $sql, $parms ); + if ($rtn eq 'badhandle') + { + $db_retry =1; + $r->headers_out->set(Location => $r->unparsed_uri() ); + no strict 'subs'; + $r->status(302); + # no kill of this child, situations have been seen where this apache child will + # never again get a good db handle + $r->child_terminate(); + return 'badhandle'; + } if ( $$rtn{'err'} ) { $$requestObject{'stat'} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; @@ -1085,14 +1119,14 @@ sub doGenericPUT { $dbs->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?', {}, ( $entity, @@ -1198,14 +1232,14 @@ sub doGenericPOST #audit entry for create $dbh->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?', {}, ( $entity, @@ -1399,14 +1433,14 @@ sub doAclPUT { $dbs->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?', {}, ( $entity, @@ -1632,14 +1666,14 @@ sub doGenericDELETE { $dbh->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=now(), - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=now(), + change_user=?, + change_ip=?', {}, ( $requestObject->{'entity'}, @@ -1664,7 +1698,7 @@ sub parseQueryParams my @ranges = split( /[&;]/, $data ); foreach my $range (@ranges) { - # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; next unless $range =~ /(\w+)([!~>=<]+)(.*)/; my $key = $1; my $op = $2; @@ -1881,14 +1915,14 @@ sub insertAuditEntry my ( $dbh, $requestObject, $entity, $key, $name, $old, $new, $time ) = @_; my $sql = 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?'; + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?'; $dbh->do( $sql, {}, ( $entity, $key, $name, $old, $new, $time, $requestObject->{user}->{username}, $$requestObject{ip_address} ) ); } @@ -2503,7 +2537,7 @@ sub doSystemGET() my @ranges = split( /[&;]/, $$requestObject{getparams} ); foreach my $range (@ranges) { - # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; + # next unless $range =~ /(\w+)([!~>=<]+)(.+)/; next unless $range =~ /(\w+)([!~>=<]+)(.*)/; my $key = $1; my $op = $2; @@ -2754,14 +2788,14 @@ sub doSystemPUT() { $dbs->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?', {}, ( 'device', @@ -2838,14 +2872,14 @@ sub doSystemPUT() { $dbs->do( 'insert into inv_audit set - entity_name=?, - entity_key=?, - field_name=?, - old_value=?, - new_value=?, - change_time=?, - change_user=?, - change_ip=?', + entity_name=?, + entity_key=?, + field_name=?, + old_value=?, + new_value=?, + change_time=?, + change_user=?, + change_ip=?', {}, ( 'device', @@ -2985,17 +3019,17 @@ sub lookupDC() my $ip = shift; my $dc = $dbh->selectcol_arrayref( 'select data_center_code from - datacenter_subnet s - where - INET_ATON(?) BETWEEN INET_ATON( - LEFT(s.subnet, - INSTR(subnet, "/")-1)) AND - INET_ATON( - LEFT(s.subnet, - INSTR(subnet, "/")-1))+ POW(2, 32-SUBSTRING(subnet, - INSTR(subnet - , "/")+1 - ))-1', {}, ($ip) + datacenter_subnet s + where + INET_ATON(?) BETWEEN INET_ATON( + LEFT(s.subnet, + INSTR(subnet, "/")-1)) AND + INET_ATON( + LEFT(s.subnet, + INSTR(subnet, "/")-1))+ POW(2, 32-SUBSTRING(subnet, + INSTR(subnet + , "/")+1 + ))-1', {}, ($ip) ); return $$dc[0]; } @@ -3098,5 +3132,4 @@ sub doUpdateIps #define HTTP_VERSION_NOT_SUPPORTED 505 #define HTTP_VARIANT_ALSO_VARIES 506 #define HTTP_INSUFFICIENT_STORAGE 507 -#define HTTP_NOT_EXTENDED 510 - +#define HTTP_NOT_EXTENDED 510 \ No newline at end of file From 49b0a112b94c245f9dfb85856d6acf5de523e8bd Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 13 Apr 2015 09:27:12 -0700 Subject: [PATCH 70/82] adjusting tags field to prep for future changes in ui --- lexicon.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lexicon.json b/lexicon.json index c1e42de..d95339a 100644 --- a/lexicon.json +++ b/lexicon.json @@ -618,7 +618,7 @@ "_required": "false" }, "_extends": "/lexicon/entities/device", - "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,notes", + "_field_order": "fqdn,status,inventory_component_type,roles,ipaddress,environment_name,svc_id,tags,notes", "guest_fqdns": { "fqdn": { "_required": "false" @@ -848,7 +848,7 @@ "_required": "false" }, "tags": { - "_label": "JSON Tags", + "_label": "Tags", "_required": "false" }, "uuid": { From 8d3f1b75cdecffc21cc11af1514082b63aef5040 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 13 Apr 2015 09:35:37 -0700 Subject: [PATCH 71/82] bad use of == for undefine produces warnings under strict --- cmdb_api.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index c427e22..03410ed 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -937,7 +937,7 @@ sub doSql() my $parms = shift; my $dbh = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS ); - if ($dbh == undef) + if (!defined($dbh)) { $logger->error("(doSql)detected bad db handle, redirecting to self and terminating apache child"); @@ -3132,4 +3132,4 @@ sub doUpdateIps #define HTTP_VERSION_NOT_SUPPORTED 505 #define HTTP_VARIANT_ALSO_VARIES 506 #define HTTP_INSUFFICIENT_STORAGE 507 -#define HTTP_NOT_EXTENDED 510 \ No newline at end of file +#define HTTP_NOT_EXTENDED 510 From 20a945015a3b1049169f0e83ee8f053685f4d509 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Wed, 15 Apr 2015 09:50:26 -0700 Subject: [PATCH 72/82] fixes for various use strict or other warnings being emitted in apache error log Change-Id: Ie21602923b1e9535d21ceeb6669fa96c7165c98b --- cmdb_api.pm | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/cmdb_api.pm b/cmdb_api.pm index 03410ed..85fa329 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -341,7 +341,7 @@ sub handler() { $r->headers_out->add( $$requestObject{'headers_out'}[0] => $$requestObject{'headers_out'}[1] ); } - if($data eq 'killchild') + if(defined $data && $data eq 'killchild') { $r->child_terminate(); undef $data; @@ -561,11 +561,18 @@ sub doColumn_lkupGET() $sql = 'select distinct metadata_value from device_metadata where metadata_name=? order by 1 limit 2000'; } my $res = $dbh->selectcol_arrayref( $sql, {}, ($col) ); - my @new; + my @return; - foreach (@$res) { $_ =~ s/\"//g; push( @new, $_ ) if $_; } + # only adds value to return set if non empty after removing quotes + foreach my $ll (@$res) { + if(defined($ll)) + { + $ll =~ s/\"//g; + push( @return, $ll ); + } + } - return \@new; + return \@return; } #audit info retreival @@ -1035,8 +1042,8 @@ sub doGenericPUT my $requestObject = shift; my $entity = $$requestObject{'entity'}; $logger->info("processing PUT"); - my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); - $dbs->begin_work; + my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 0 } ); + # $dbs->begin_work; # this is a noop when AutoCommit = 0 my ( @sql, $parms, @errors ); my $data = &eat_json( $$requestObject{'body'}, { allow_nonref => 1 } ); @@ -2396,7 +2403,7 @@ sub doEnvironmentsGET() my $service; my $get_services = 0; - if ( $path[1] eq 'services' ) + if ( defined ($path[1]) && $path[1] eq 'services' ) { return &doEnvironmentsServicesGET($requestObject); } @@ -2453,7 +2460,7 @@ sub doEnvironmentsPOST() my $environment = $path[0]; my $service = $path[2]; - if ( $path[1] eq 'services' ) + if ( defined $path[1] && $path[1] eq 'services' ) { return &doEnvironmentsServicesPOST($requestObject); } @@ -2477,7 +2484,7 @@ sub doEnvironmentsDELETE() my $environment = $path[0]; my $service = $path[2]; - if ( $path[1] eq 'services' ) + if ( defined $path[1] && $path[1] eq 'services' ) { if ( defined $service ) { @@ -2502,7 +2509,7 @@ sub isChanged() { my ($old,$new,$field)=@_; # $logger->info("TESTCHANGED--$field--$$old{$field}=$$new{$field}--"); - if ("$$old{$field}" eq "$$new{$field}") + if (defined $$old{$field} && defined $$new{$field} && "$$old{$field}" eq "$$new{$field}") { return 0; } @@ -2705,7 +2712,7 @@ sub doSystemPUT() || ( !exists( $data->{'data_center_code'} ) - #&& defined($lkup_data->{'data_center_code'}) + && defined($lkup_data->{'data_center_code'}) && length( $lkup_data->{'data_center_code'} ) == 0 ) ) @@ -2918,7 +2925,7 @@ sub applyDefaults() my $fields = &getFieldList( $entity ); foreach (@$fields) { - if ( ( !exists $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{_default_value} ) + if ( ( !defined $$data{$_} || $$data{$_} eq '' ) && $tree_extended->{entities}->{ $entity }->{$_}->{_default_value} ) { $logger->debug("using ($entity) default value '$tree_extended->{entities}->{ $entity }->{$_}->{_default_value}' for $_"); $$data{$_} = $tree_extended->{entities}->{ $entity }->{$_}->{_default_value}; @@ -2955,7 +2962,7 @@ sub doSystemPOST() { $$data{$_} = &doFieldNormalization( 'system', $_, $$data{$_} ); $set_sql .= "," if $set_sql; - if ( length( $$data{$_} ) == 0 ) + if ( !defined $$data{$_} || length( $$data{$_} ) == 0 ) { $set_sql .= " $_=NULL"; From fc035e37e060113ba6b10fb8abb383a3f18641ec Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Wed, 15 Apr 2015 14:22:39 -0700 Subject: [PATCH 73/82] OPS-10974 adding some comments to see if autobuilding in jenkins works Change-Id: Ic5601535c5458bf483f8a1b29b369b4b6717b7eb --- cmdb_api.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index 85fa329..765bfd7 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -2667,6 +2667,8 @@ sub doSystemGET() sub doSystemPUT() { my $requestObject = shift; + + # this should likely be changed to AutoCommit =>0 if $dbs->begin_work doesn't prooperly enter a transaction my $dbs = DBI->connect( "DBI:$DRIVER:database=$DATABASE;host=$DBHOST", $DBUSER, $DBPASS, { AutoCommit => 1 } ); my $x = 0; my $fqdn = $$requestObject{'path'}[0]; From 454445187fd1e8ff79b38551cc69d530fcb5fb4c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 27 Apr 2015 11:25:38 -0700 Subject: [PATCH 74/82] adjusting test script to take arguments to test aginst non-localhost Change-Id: I4e06320879ac738f5fd33c13668204f55420e969 --- cmdb_test.pl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cmdb_test.pl b/cmdb_test.pl index a615f66..be84038 100644 --- a/cmdb_test.pl +++ b/cmdb_test.pl @@ -22,12 +22,14 @@ use LWP::UserAgent; use Getopt::Std; my %opt; -getopts('f:dprH',\%opt); +getopts('f:dprHh:R:',\%opt); my $DEBUG = $opt{'d'} ? 1 : 0; my $FILE = $opt{'f'} ? $opt{'f'} : ""; my $PARSEONLY = $opt{'p'} ? 1 : 0; my $RUNTESTS = $opt{'r'} ? 1 : 0; my $HALT = $opt{'H'} ? 1 : 0; +my $HOSTPORT= $opt{'h'} ? $opt{'h'} : undef; +my $REALM = $opt{'R'} ? $opt{'R'} : 'Operations Only'; if(length($FILE) == 0) { @@ -36,6 +38,7 @@ -p: parse testcase file only -r: run tests -d: debug + -h: alternate host/port (ex: https://localhost) "; exit; } @@ -123,7 +126,8 @@ () my @expected_result; ### assemble request from test data - my $testurl=$test->{'host:port'} . $test->{'baseurl'} . $test->{'entity'} . '/'; + my $hostport_config= $HOSTPORT || $test->{'host:port'}; + my $testurl=$hostport_config . $test->{'baseurl'} . $test->{'entity'} . '/'; if ($test->{'method'} ne 'POST') { $testurl.=$test->{'entity_key'}; @@ -132,9 +136,9 @@ () my $user=$1; my $pass=$2; my $result=''; - $test->{'host:port'}=~m|//(.*)|; + $hostport_config=~m|//(.*)|; my $hostport=$1; - $ua->credentials($hostport,'Operations Only',$user,$pass); + $ua->credentials($hostport,$REALM,$user,$pass); if($test->{'method'} eq 'GET') { if($test->{'data'}) From 11e3dea99acfaa28aa2ddc79cb3d9ba09104ef95 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 27 Apr 2015 11:29:17 -0700 Subject: [PATCH 75/82] adjusting test script to take arguments to test aginst non-localhost Change-Id: I95e7bcf3fb8872c1d7e2da5f845d848ece3276d5 --- cmdb_test.pl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmdb_test.pl b/cmdb_test.pl index be84038..a0dbecf 100644 --- a/cmdb_test.pl +++ b/cmdb_test.pl @@ -104,6 +104,14 @@ print "\n"; } print "Complete: Total: " . ($$testResults{'pass'} + $$testResults{'fail'}) . " Pass: $$testResults{'pass'} Fail: $$testResults{'fail'}\n"; + if($$testResults{'fail'}) + { + exit 0; + } + else + { + exit 1; + } } sub makeUrlQueryStr() From 09318c1d8fde575477761cb3b33df16ed769ea2c Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 27 Apr 2015 11:31:46 -0700 Subject: [PATCH 76/82] adjusting test script to take arguments to test aginst non-localhost Change-Id: I412ee729803ca2d68553fb8320e58069d956e04d --- cmdb_test.pl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cmdb_test.pl b/cmdb_test.pl index a0dbecf..aa3caa1 100644 --- a/cmdb_test.pl +++ b/cmdb_test.pl @@ -104,11 +104,7 @@ print "\n"; } print "Complete: Total: " . ($$testResults{'pass'} + $$testResults{'fail'}) . " Pass: $$testResults{'pass'} Fail: $$testResults{'fail'}\n"; - if($$testResults{'fail'}) - { - exit 0; - } - else + if($$testResults{'fail'} > 0) { exit 1; } From 18d86808c38484ea59d95d6d0a33da7f7e0751a8 Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 27 Apr 2015 12:22:11 -0700 Subject: [PATCH 77/82] OPS-11305 fixing tests to run clean with slightly different lexicon Change-Id: Id3a7efb45e1c453fe3cb74ff21418f2b31257130 --- cmdb_test.pl | 2 +- cmdb_tests.csv | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmdb_test.pl b/cmdb_test.pl index aa3caa1..b773fa3 100644 --- a/cmdb_test.pl +++ b/cmdb_test.pl @@ -159,7 +159,7 @@ () $response = $ua->request($request); } print "### executing test: " . $test->{'testname'} . " with url (" . $test->{'method'} . "): $testurl" ; - + print "DEBUG: got status: $response->code content: \n" . $response->content if $DEBUG; if($response->code == 501) { $result.="\nHTTP ERROR: " . $response->status_line; diff --git a/cmdb_tests.csv b/cmdb_tests.csv index 81cb9c8..e15fd37 100644 --- a/cmdb_tests.csv +++ b/cmdb_tests.csv @@ -1,5 +1,5 @@ testname,notes,entity,entity_key,data,method,host:port,baseurl,user/pass,returncode,result type,result check -create system,,system,test.test.com,"{""fqdn"":""test.test.com"",""ipaddress"":""10.10.0.1"",""drac"":""10.10.10.1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/system/test.test.com +create system,,system,test.test.com,"{""fqdn"":""test.test.com"",""ipaddress"":""10.10.0.1"",""ipmi_ip"":""10.10.10.1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/system/test.test.com get system,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,ipaddress=10.10.0.1 modify system,,system,test.test.com,"{""notes"":""testnote""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,notes=testnote lookup nonexistent system,,system,no.system.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, @@ -36,16 +36,16 @@ modify role,,role,test::role,"{""role_name"":""test role for unit testing""}",PU delete role,,role,test::role,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, delete nonexistenet role,,role,norole,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,404,, ,,,,,,http://localhost:80,/cmdb_api/v1/,readonly/readonly,,, -add acl,,acl,999,"{""acl_group"":""readonly"",""acl_id"":999,""entity"":""system"",""field"":""drac"",""logic"":""1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/acl/999 +add acl,,acl,999,"{""acl_group"":""readonly"",""acl_id"":999,""entity"":""system"",""field"":""ipmi_ip"",""logic"":""1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/acl/999 verify acl,,acl,999,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,entity=system -make change against acl,,system,test.test.com,"{""drac"":""20.20.20.20""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,403,, +make change against acl,,system,test.test.com,"{""ipmi_ip"":""20.20.20.20"",""note"":""testnoate""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,403,, create test env,,environments,,"{""name"":""testenv1"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv1 create environment/service,,environments/testenv1/services,,"{""name"":""servicetest"",""type"":""service"",""testattribute"":""12345"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv1/services/servicetest create child env,,environments,,"{""name"":""testenv2"",""environment_name"":""testenv1""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv2 validate inherited service,,environments/testenv2/services,servicetest,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,environment_name=testenv1 create service override,,environments/testenv2/services,,"{""name"":""servicetest"",""type"":""service"",""environment_name"":""testenv2""}",POST,http://localhost:80,/cmdb_api/v1/,readonly/readonly,201,header,Location=/cmdb_api/v1/environments/testenv2/services/servicetest create service attribute override,,environments/testenv2/services,servicetest,"{""testattribute"":""testvalue""}",PUT,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,testattribute=testvalue -verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,drac=10.10.10.1 +verify acl blocked change,,system,test.test.com,,GET,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,data,ipmi_ip=10.10.10.1 delete acl,,acl,999,,DELETE,http://localhost:80,/cmdb_api/v1/,readonly/readonly,200,, ,,,,,,,,,,, ,,,,,,,,,,, From c44d7f7b675dc8dedaa654e122f96e74c41d91be Mon Sep 17 00:00:00 2001 From: Isaac Finnegan Date: Mon, 27 Apr 2015 13:51:04 -0700 Subject: [PATCH 78/82] OPS-11305 adding some readme text about the tests requirements hoping to trigger new build editing commit message to try to trigger build (and again) (and again) Change-Id: I760aa6dd9bc341804dcfba9a88b8d15554fcc034 --- readme.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.txt b/readme.txt index 663c9fc..c75b0c3 100644 --- a/readme.txt +++ b/readme.txt @@ -1,3 +1,8 @@ +testing with cmdb_test.pl +-the existing test in cmdb_tests.csv depend on a 'readonly' user being setup in a 'readonly' group. otherwise acl tests will fail +-these tests also depend on the lexicon having a 'ipmi_ip' field and the config item 'traffic_control_search_fields' containing + the 'ipaddress' field + new dependencies: -log4perl -perl-DateManip From eb1f2960326418c08d6ea0fe0084e1fc5e317abb Mon Sep 17 00:00:00 2001 From: esa Date: Tue, 2 Jun 2015 14:35:07 -0700 Subject: [PATCH 79/82] OPS-10327: log db handle errors to syslog Change-Id: I27d2a4cccf0320fdf5c9540dfeba865503da7919 --- cmdb_api.pm | 5 +++++ log4perl.conf | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/cmdb_api.pm b/cmdb_api.pm index 765bfd7..d47c4e9 100644 --- a/cmdb_api.pm +++ b/cmdb_api.pm @@ -123,6 +123,8 @@ my $log_config_file = $opt->{'logconfig'}; Log::Log4perl::init($log_config_file); my $logger = Log::Log4perl->get_logger('inventory.cmdb_api'); +my $syslog = Log::Log4perl->get_logger('inventory.syslog'); + unless ($lexicon) { #TODO this hardcoded path is bad fix it @@ -217,6 +219,7 @@ sub handler() { $db_retry=0; $logger->error("(handler 1)detected bad db handle, redirecting to self and terminating apache child"); + $syslog->error("(handler 1)detected bad db handle, redirecting to self and terminating apache child"); $r->headers_out->set('Location' => ($r->subprocess_env('HTTPS') eq 'on' ? 'https' : 'http' ) . '://' . $r->hostname() . $r->unparsed_uri() ); no strict 'subs'; $r->status(302); @@ -319,6 +322,7 @@ sub handler() { $db_retry=0; $logger->error("(handler 2)detected bad db handle, redirecting to self and terminating apache child"); + $syslog->error("(handler 2)detected bad db handle, redirecting to self and terminating apache child"); $r->headers_out->set('Location' => $r->unparsed_uri() ); no strict 'subs'; $r->status(Apache2::Const::HTTP_MOVED_TEMPORARILY); @@ -948,6 +952,7 @@ sub doSql() { $logger->error("(doSql)detected bad db handle, redirecting to self and terminating apache child"); + $syslog->error("(doSql)detected bad db handle, redirecting to self and terminating apache child"); print STDERR "detected bad db handle, redirecting to self and terminating apache child"; # check for bad db handle and redirect to self $r->headers_out->set('Location' => ($r->subprocess_env('HTTPS') eq 'on' ? 'https' : 'http' ) . '://' . $r->hostname() . $r->unparsed_uri() ); diff --git a/log4perl.conf b/log4perl.conf index 5eb975a..2d3aed8 100644 --- a/log4perl.conf +++ b/log4perl.conf @@ -23,3 +23,14 @@ log4perl.appender.FileAppndr1.filename = /var/log/httpd/cmdb_api.log log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.FileAppndr1.layout.ConversionPattern=%d %p:%P> %F{1}:%L %M - %m%n ###################################################### + +############################################################ +# Log::Log4perl conf - Syslog # +############################################################ +log4perl.logger.inventory.syslog = WARN, syslog +log4perl.appender.syslog = Log::Dispatch::Syslog +log4perl.appender.syslog.min_level = debug +log4perl.appender.syslog.ident = cmdb_api +log4perl.appender.syslog.facility = daemon +log4perl.appender.syslog.layout = Log::Log4perl::Layout::PatternLayout +log4perl.appender.syslog.layout.ConversionPattern=PID:%P %F{1}:%L %M - %m%n From 9995ca23665af2789af047682882e920256e02c5 Mon Sep 17 00:00:00 2001 From: esa Date: Fri, 5 Jun 2015 09:09:48 -0700 Subject: [PATCH 80/82] OPS-10327: rename lexicon.json to lexicon.json.sample Renaming it to avoid any risk of a package upgrade clobbering the most recent puppet-managed one Change-Id: I1c0b5f5649af0551dd2e5b90171a9b2b937881da --- lexicon.json => lexicon.json.sample | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lexicon.json => lexicon.json.sample (100%) diff --git a/lexicon.json b/lexicon.json.sample similarity index 100% rename from lexicon.json rename to lexicon.json.sample From 037adc5663c90175b3192979a79a5384189157e5 Mon Sep 17 00:00:00 2001 From: Jeremy Brinkley Date: Mon, 14 Dec 2015 11:13:39 -0800 Subject: [PATCH 81/82] [OPS-12045] Capturing existing log4perl configuration from current 0.24 package Change-Id: I0ff6a5157aa2a3bebfd2ecb329d5aa15bce8efa8 --- log4perl.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/log4perl.conf b/log4perl.conf index 2d3aed8..d8fb8aa 100644 --- a/log4perl.conf +++ b/log4perl.conf @@ -15,11 +15,11 @@ ############ #####/etc/log4perl.conf############################### -#log4perl.logger.inventory.cmdb_api = WARN, FileAppndr1 -log4perl.logger.inventory.cmdb_api = DEBUG, FileAppndr1 +log4perl.logger.inventory.cmdb_api = WARN, FileAppndr1 log4perl.appender.FileAppndr1 = Log::Log4perl::Appender::File -log4perl.appender.FileAppndr1.filename = /var/log/httpd/cmdb_api.log +log4perl.appender.FileAppndr1.filename = /var/log/apache2/cmdb_api.log +#log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::SimpleLayout log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.FileAppndr1.layout.ConversionPattern=%d %p:%P> %F{1}:%L %M - %m%n ###################################################### From 1ec6e0005711ce4da3a8e4a88313c25785a815f3 Mon Sep 17 00:00:00 2001 From: Jeremy Brinkley Date: Mon, 14 Dec 2015 12:49:25 -0800 Subject: [PATCH 82/82] [OPS-12045] Add dist and clean scripts to prepare a tarball Change-Id: Ifd1219e38b5cc2ef5e3729cf26ca6a438dff93ff --- clean | 3 +++ dist | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100755 clean create mode 100755 dist diff --git a/clean b/clean new file mode 100755 index 0000000..3655622 --- /dev/null +++ b/clean @@ -0,0 +1,3 @@ +#!/bin/bash + +rm -f *.tar.gz diff --git a/dist b/dist new file mode 100755 index 0000000..2c36adc --- /dev/null +++ b/dist @@ -0,0 +1,17 @@ +#!/bin/bash + +# Prepare 'distribution' (source tarball) +# This is a placeholder for what should really be a Perl module +# prepared with Module::Build + +set -e + +NAME=cmdb-api +VERSION=0.25 + +rm -rf $NAME-$VERSION $NAME-*.tar.gz +files=$(ls -1 | grep -v ^dist\$ | grep -v ^clean\$) +mkdir $NAME-$VERSION +echo "+++ files:" $files +cp -R $files $NAME-$VERSION +tar cvzf $NAME-$VERSION.tar.gz $NAME-$VERSION && rm -rf $NAME-$VERSION