Save a project milestone in the current Iguana instance

Here’s a module that will allow you to save a project milestone for any project on the current Iguana instance.

Note: This module will not work if the target project exists on a different Iguana instance.

local translator = {}

function node.getChannel(Config, Name) 
   for i=1,#Config.iguana_config.channel_config do
      local Channel = Config.iguana_config.channel_config[i]
      if Channel.name:nodeValue() == Name then
         return Channel
      end
   end
   return nil
end

function node.S(ANode)
   return tostring(ANode)
end

function Split(s, d)		
   local t = {}		
   local i = 0		
   local f		
   local match = '(.-)' .. d .. '()'		
   if string.find(s, d) == nil then		
      return {s}		
   end		
   for sub, j in string.gfind(s, match) do		
      i = i + 1		
      t[i] = sub		
      f = j		
   end		
   if i~= 0 then		
      t[i+1]=string.sub(s,f)		
   end		
   return t		
end		

local function GetSandbox(Root, WorkingDirectory)
   os.execute('mkdir "'..Root..'"')
   os.execute('cd "'..Root..'" & "'..WorkingDirectory..'fossil" open "'..WorkingDirectory..'vcs_repo.sqlite"')
end

local function CheckGuidArguments(T)
   if not T.channel then
      error('Parameter 'channel' is required.', 3)
   elseif not T.component_type then
      error('Parameter 'component_type' is required.', 3)
   end
end

local function CheckSaveMilestoneArguments(T)
   if not T.text then
      error('Parameter 'text' is required.', 3)
   elseif not  T.guid then
      error('Parameter 'guid' is required.', 3)
   elseif not T.url then
      error('Parameter 'url' is required.', 3)
   elseif not T.user then
      error('Parameter 'user' is required.', 3)
   elseif not T.password then
      error('Parameter 'password' is required.', 3)
   end
end

local function ScriptGuid(ScriptType, ChannelName, Config)
   local Channel = Config:getChannel(ChannelName)
   if not Channel then 
      error('Channel '..ChannelName..' does not exist', 3) 
   end

   if ScriptType == translator.TO then
      local Component = Channel.to_mapper
      if not Component then
         error('There is no "To Translator" component for this channel', 3)
      end
      return Component.guid:nodeValue() 
   end
   if ScriptType == translator.FROM then
      local Component = Channel.from_mapper
      if not Component then 
         error('There is no "From Translator" component for this channel', 3)
      end
      return Component.guid:nodeValue() 
   end
   if ScriptType == translator.FILTER then
      local Component= Channel.message_filter
      if not Component then 
         error('There is no "Translator Filter" component for this channel', 3)
      end
      return Component.translator_guid:nodeValue() 
   end
   if ScriptType == translator.ACK then
      local Component = Channel.from_llp_listener
      if not Component or not Component.ack_script then
         error('There is no "Custom ACK Translator" component for this channel', 3) 
      end
      return Component.ack_script:nodeValue() 
   end
   if ScriptType == translator.HTTPS then
      local Component = Channel.from_http
      if not Component or not Component.from_http_guid then
         error('There is no "From HTTPS translator" component for this channel', 3)
      end
      return Channel.from_http.guid:nodeValue() 
   end
end

--[[
   Function takes user credentials and returns a valid session id upon a 
   successful login.
]]
local function GetLoginKey(Url, User, Password)
   local Success, Result, _, Headers = pcall(
      net.http.get,
      {
         url=Url..'login.html', 
         parameters={
            username=User, 
            password=Password
         },
         live=true
      }
   )

   if not Success then
      return nil, Result
   end

   -- That gives us our session cookie which we use for our
   -- login credentials.  The login interface was not quite
   -- meant for an API - but the problem is solvable.
   local SessionId = Headers["Set-Cookie"]
   if not SessionId then
      error('Username and/or password wrong',3)
   end

   return Split(SessionId,' ')[1]
end

--[[
   Function returns the dependency list of the Translator project identified by 
   the inputted GUID.
]]
local function GetDependencies(Guid, User, WorkingDirectory)
   local PathSeparator = WorkingDirectory:sub(#WorkingDirectory)
   local Sandbox = 'edit'..PathSeparator..User..PathSeparator
   local Project = Sandbox..Guid..PathSeparator..'project.prj'

   local ProjectFile = io.open(Project, 'r')

   -- The user's sandbox may not yet exist.  If not, let's create it.
   if not ProjectFile then
      GetSandbox(Sandbox, WorkingDirectory)
      ProjectFile = io.open(Project, 'r')
   end

   local ProjectInfo = json.parse{data=ProjectFile:read('*a')}
   local Dependencies = ProjectInfo.LuaDependencies
   local Size = #Dependencies

   for k,v in pairs(ProjectInfo.OtherDependencies) do Dependencies[Size + k] = v end

   io.close(ProjectFile)

   return Dependencies
end

--[[
    Translator interface
]]

translator.FROM = 'from'
translator.TO = 'to'
translator.FILTER = 'filter' 
translator.ACK = 'ack'
translator.HTTPS = 'https'

--[[
   Function returns the GUID associated the Translator project 
   identified by the inputted channel name and component type.
]]
function translator.guid(T)
   CheckGuidArguments(T)

   local ChannelName = T.channel
   local ComponentType = T.component_type

   -- Read the config file.
   local ConfigFile = io.open('IguanaConfiguration.xml', 'r')
   Config = xml.parse{data=ConfigFile:read('*a')}
   io.close(ConfigFile)

   return ScriptGuid(ComponentType, ChannelName, Config)
end

local GuidHelpData = {
   Title='translator.guid',
   Usage=[[translator.guid{channel=<value>, component_type=<value>}]],
   Desc=[[Returns the GUID associated with the Translator project identified by the 
   inputted channel name and component type.]],
   Returns='The GUID of interest.',
   ParameterTable=true,
   Parameters={
      {channel={Desc='The channel name containing the Translator project.'}},
      {component_type={Desc='The component type in which the Translator project is defined.'}}
   },   
   Examples={
      [[translator.guid{channel='Example', component_type=translator.FROM}]]
   },
   SeeAlso=nil
}

help.set{input_function=translator.guid, help_data=GuidHelpData}

--[[
   Function commits the project identified by the inputted GUID under the given milestone name. 
]]
function translator.saveMilestone(T)
   CheckSaveMilestoneArguments(T)

   local Text = T.text
   local Guid = T.guid
   local Url = T.url
   local User = T.user
   local Password = T.password
   local Live = T.live or not iguana.isTest()

   local LoginKey, LoginResult = GetLoginKey(Url, User, Password)

   if not LoginKey then
      error(LoginResult, 2)
   end

   local WorkingDirectory = iguana.workingDir()
   local Dependencies = GetDependencies(Guid, User, WorkingDirectory)
   local DependencyString = 'mainnproject settingsn'..table.concat(Dependencies, 'n')

   if not Live then
      return 'Set live=true to really run the code'
   end

   local Success, Result = pcall(
      net.http.post,
      {
         url=Url..'source_control', 
         parameters={
            RequestType='save_milestone',
            FileLabels=DependencyString,
            MilestoneName=Text, 
            ChannelGuid=Guid
         }, 
         headers={Cookie=LoginKey}, 
         live=Live
      }
   )

   if not Success then
      error(Result, 2)
   end

   Success, Result = pcall(json.parse,{data=Result})

   if not Success then
      error(Result, 2)
   elseif Result.ErrorMessage then
      error(Result.ErrorMessage, 2)
   elseif Result.PermissionIssue then
      error(Result.PermissionIssue, 2)
   end

   return "Saved milestone "..T.text
end

local SaveHelpData = {
   Title='translator.saveMilestone',
   Usage=[[translator.saveMilestone{text=<value>, guid=<value>, url=<value>, 
   user=<value>, password=<value>, live=<value>}]],
   Desc=[[Commits the Translator project associated with the inputted GUID.<br><br>Important Note: This API
   can only save milestones of projects that exist on the same instance of Iguana that uses this API.]],
   Returns=nil,
   ParameterTable=true,
   Parameters={
      {text={Desc='The milestone name to use for the commit.'}},
      {guid={Desc='The GUID associated with the project being committed.'}},
      {url={Desc=[[The URL of the Iguana server running the project that uses this API.]]}},
      {user={Desc='User name to login with.'}},
      {password={Desc='Password to login with.'}},
      {live={Desc='Flag that determines if the code will be run.'}}
   },   
   Examples={
      [[translator.saveMilestone{
                          text='Milestone 1', 
                          guid='123456',
                          url='http://localhost:6543/', 
                          user='admin', 
                          password='password', 
                          live=true
                       }]]
   },
   SeeAlso=nil
}

help.set{input_function=translator.saveMilestone, help_data=SaveHelpData}

return translator

Here’s a main module that uses the module above.

translator = require 'translator'

function main() 
   translator.saveMilestone{
      text='New milestone at '..os.date('%Y%m%d %H:%M:%S'), 
      guid=translator.guid{
         channel='XDS.b RET', 
         component_type=translator.FROM
      }, 
      url='http://10.211.55.8:6543/', 
      user='admin', 
      password='password', 
      live=true
   }
end