Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2019/a-second-attempt-at-installing-solr-with-sif

A Second attempt at installing Solr with SIF

Published 04 March 2019

Recently Steve McGill asked me if I'd tried using SIF's certificate creation when automating Solr setup for Sitecore. I realised I'd not put any effort into how this might work – and that seemed like an excellent excuse for some research...

So what needs to change from my original attempt at installing Solr via SIF to make this work?

If you look at the standard SIF V2 scripts, then they make use of the createcert.json file to generate certificates. The standard setup passes in some parameters like the name for the certificate and where it should be saved, and the script deals with the generation work and ensuring it's trusted. So it could be called a third time to generate a certificate for Solr and write it out to disk. That would mean changing my original SIF script for Solr so that instead of generating the certificiate it needs, it can take a file that had already been generated, and install it.

That means making a few adjustments to the PowerShell...

Changing my script... url copied!

When I was checking that my original code was still usable for this, I noticed that I got a lot of warnings from PowerShell telling me that the `Write-TaskInfo` had been depreciated, and I should use `Write-Information` instead. So I did a quick search and replace to make that change across the `SolrInstall-SIF-Extension.psm1` file.

The biggest logical change to the SIF extension for Solr is that we no longer need to generate a certificate. If we just need to copy a certificate that was already generated - hence the code for Invoke-EnsureSSLCertificateTask gets a lot simpler:

function Invoke-EnsureSSLCertificateTask
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [parameter(Mandatory=$true)]
        [bool]$solrSSL,
        [parameter(Mandatory=$true)]
        [string]$solrName,
        [parameter(Mandatory=$true)]
        [string]$solrHost,
        [parameter(Mandatory=$true)]
        [string]$certificateStore,
        [parameter(Mandatory=$true)]
        [string]$sourceCert
    )

    PROCESS
    {
        if($solrSSL)
        {
            Write-Information -Message "$sourceCert" -Tag "Copying the site cert to $certificateStore"
            copy $sourceCert $certificateStore
        }
    }
}

					

A new parameter $sourceCert is added specifying the path to find the generated certificate. And the logic gets changed to get rid of all the original "generate and trust a certificate" code, and it's replaced with a simple copy operation from the new source location to the same place it went before. I could also get rid of the un-needed parameters that used to be used to generate a certificate, but I realise I didn't get around to that. A task for another day...

The location of the new certificate and its password also needs to be passed in to Invoke-ConfigureSolrTask that SIF calls to modify the default Solr config, so it can be passed down the chain...

function Invoke-ConfigureSolrTask
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [parameter(Mandatory=$true)]
        [bool]$solrSSL,
        [parameter(Mandatory=$true)]
        [string]$solrHost,
        [parameter(Mandatory=$true)]
        [string]$solrRoot,
        [parameter(Mandatory=$true)]
        [string]$certificateStore,
        [parameter(Mandatory=$true)]
        [string]$CertificatePassword
    )

    PROCESS
    {
        if($solrSSL)
        {
            Write-Information -Message "HTTPS" -Tag "Configuring Solr for HTTPS access"
            configureHTTPS $solrHost $solrRoot $certificateStore $CertificatePassword
        }
        else
        {
            Write-Information -Message "HTTP" -Tag "Configuring Solr for HTTP access"
            configureHTTP $solrHost $solrRoot
        }
    }
}

					

...to the configureHTTPS method. It already knows how to point Solr at the certificate, but in my original code I'd hardcoded the certificate password for simplicity. SIF doesn't use a simple default password, so we need to be able to pass in the right password, and it needs to be put into the config:

function configureHTTPS
{
    Param(
        [string]$solrHost,
        [string]$solrRoot,
        [string]$certStore,
        [string]$CertificatePassword
    )

    $solrConfig = "$solrRoot\bin\solr.in.cmd"
    if(!(Test-Path -Path "$solrConfig.old"))
    {
        if($pscmdlet.ShouldProcess("$solrConfig", "Rewriting Solr config file for HTTPS"))
        {
            $cfg = Get-Content $solrConfig
            Rename-Item $solrConfig "$solrRoot\bin\solr.in.cmd.old"
            $newCfg = $cfg | % { $_ -replace "REM set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks", "set SOLR_SSL_KEY_STORE=$certStore" }
            $newCfg = $newCfg | % { $_ -replace "REM set SOLR_SSL_KEY_STORE_PASSWORD=secret", "set SOLR_SSL_KEY_STORE_PASSWORD=$CertificatePassword" }
            $newCfg = $newCfg | % { $_ -replace "REM set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks", "set SOLR_SSL_TRUST_STORE=$certStore" }
            $newCfg = $newCfg | % { $_ -replace "REM set SOLR_SSL_TRUST_STORE_PASSWORD=secret", "set SOLR_SSL_TRUST_STORE_PASSWORD=$CertificatePassword" }
            $newCfg = $newCfg | % { $_ -replace "REM set SOLR_HOST=192.168.1.1", "set SOLR_HOST=$solrHost" }
            $newCfg | Set-Content $solrConfig
        }

        Write-Information -Message "$solrConfig" -Tag "Solr config updated for HTTPS access"
    }
    else
    {
        Write-Information -Message "$solrConfig" -Tag "Solr config already updated for HTTPS access - skipping"
    }
}

					

So that's the code changes necessary for doing the install of Solr. I realise another tweak I could make here is to get rid of the "install Solr without HTTPS" code path from this file, as we're always using HTTPS. But again, that's a job for when I have some more free time.

So next is the configuration that binds this into SIF:

Adjusting the Solr install extension for SIF url copied!

Last time I looked at installing Solr alongside Sitecore using SIF it was against an earlier version of Sitecore. So that's going to require some changes even before we get to the certificates bit...

The version of Solr required by v9.1 is Solr 7.2.1. That gets specified in the json file that describes our Solr requirements to SIF. So lets update that parameter. I also have a newer version of Java running on the VM I tested this on, so that needs updating too:

{
    "Parameters" : {
        "JREVersion": {
            "Type": "string",
            "DefaultValue": "1.8.0_202",
            "Description": "What version of the Java Runtime should "
        },
        "SolrVersion": {
            "Type": "string",
            "DefaultValue": "7.2.1",
            "Description": "What version of Solr should be downloaded for install"
        }
    }
}

					

I also decided I wanted a way around the issue of the path for the JRE. Depending on whether you're using 64 bit or 32 bit Java, you get your Java program files folder in a different place. So I decided to add a parameter for which Program Files folder it is using, and configured it for the 64-bit version of Java I was using:

{
    "Parameters" : {
        "ProgFilesFolder": {
            "Type": "string",
            "DefaultValue": "C:\\Program Files",
            "Description": "Where certificates got exported to after generation"
        }
    }
}

					

That parameter then gets used to help work out the correct JRE path variable from the original script:

{
    "Variables" : {
        "JREPath":             "[concat(parameter('ProgFilesFolder'), '\\Java\\jre', parameter('JREVersion'))]"
    },
}

					

Since we changed some method signatures in the PowerShell, the Tasks binding needs updating:

{
    "Tasks" : {
        "Rewrite Solr config file": {
            "Type": "ConfigureSolr",
            "Params": {
                "solrSSL":             "[parameter('SolrUseSSL')]",
                "solrHost":             "[parameter('SolrHost')]",
                "solrRoot":             "[variable('SolrInstallFolder')]",
                "certificateStore":  "[variable('CertStoreFile')]",
                "CertificatePassword": "[parameter('CertificatePassword')]"
            }
        },
        "Ensure trusted SSL certificate exists (if required)": {
            "Type": "EnsureSSLCertificate",
            "Params": {
                "solrSSL":             "[parameter('SolrUseSSL')]",
                "solrName":             "[variable('SolrName')]",
                "solrHost":             "[parameter('SolrHost')]",
                "certificateStore":     "[variable('CertStoreFile')]",
                "sourceCert":           "[variable('SourceCert')]"
            }
        }
    }
}

					

Those changes require some extra data to be passed in. The source certificate name is going to be a combination of a path, the host name we're using for Solr and the ".pfx" extension. Since that involves calculation, it means we need to set it up as a Variable:

    "Variables" : {
        "SourceCert":        "[concat(parameter('CertFilePath'), parameter('SolrHost'), '.pfx')]"
    }

					

And that means we also have two new parameters to the overall SIF script, in the folder we'll find the certificate in, and the password for the certificate file:

{
    "Parameters" : {
        "CertFilePath": {
            "Type": "string",
            "DefaultValue": "c:\\certificates\\",
            "Description": "Where certificates got exported to after generation"
        },
        "CertificatePassword": {
            "Type": "string",
            "DefaultValue": "SIF-Default",
            "Description": "The password set on the certificate"
    }
}

					

And that's the changes necessary to my original SIF process. The json file and the PowerShell file are available in a gist.

Adjusting the base SIF config url copied!

Now that the SIF extension to add Solr is in place, the next thing that needs doing is wiring that into the overall SIF process for Sitecore. That involves changing the overall SIF process json file and the powershell script which involves it. I'm working on a developer install here, so I'm working on the `XP0-SingleDeveloper` files.

The first change to make in XP0-SingleDeveloper.json is that we need to trigger the creation of a certificate for Solr and then to call the Solr install we created above. These need inserting at the top of the Includes section so that they happen first:

{
    "Includes": {
        "SolrCertificates": {
            "Source": ".\\createcert.json"
        },
        "Solr": {
            "Source": ".\\SolrServer.json"
        }
    }
}

					

For those to work, some further parameters are required. First up, there are some parameters that define how the rest of the script can work:

{
    "Parameters": {
        "SolrExportPassword": {
            "Type": "String",
            "DefaultValue": "testpassword",
            "Description": "Password to export the solr cert with."
        },
        "CertPath": {
            "Type": "String",
            "DefaultValue": "c:\\certificates",
            "Description": "where exported certs go"
        },
        "SolrHost": {
            "Type": "String",
            "DefaultValue": "localhost",
            "Description": "The Solr host name to use."
        }
    }
}

					

We don't want SIF to generate a password for the Solr certificate, so we need to define a value for that. And we need to tell the Solr install script where it can find the generated certificate and what the host name for Solr should be.

Some of those values then need passing in to other bits of the script. SIF defines a special naming convention when you want a value to be passed to a specific included script. Where a parameter name is prefixed with the name of an include and a colon, it is specific to that include. Also, instead of defining a default value for these parameters, they define a reference to another parameter's name – effectively copying that value:

{
    "Parameters": {
        "SolrCertificates:CertificateName": {
            "Type": "String",
            "Reference": "SolrHost",
            "Description": "Override to pass SolrCertificateName value to SolrCertificates config."
        },
        "SolrCertificates:ExportPassword": {
            "Type": "String",
            "Reference": "SolrExportPassword",
            "Description": "Password to export the solr cert with."
        },
        "Solr:CertificatePassword": {
            "Type": "String",
            "Reference": "SolrExportPassword",
            "Description": "Password the solr cert was exported with."
        }
    }
}

					

Once these changes are made, you then need to tweak the PowerShell that invokes the JSON data. The change I made here was to pass in the SolrHost value, but you could pass in the password as well if you wanted to change the default above:

# Solr host name
$SolrHost = "solr";

# Install XP0 via combined partials file.
$singleDeveloperParams = @{
    Path = "$SCInstallRoot\XP0-SingleDeveloper.json"
    SqlServer = $SqlServer
    SqlAdminUser = $SqlAdminUser
    SqlAdminPassword = $SqlAdminPassword
    SitecoreAdminPassword = $SitecoreAdminPassword
    SolrHost = $SolrHost
    SolrUrl = $SolrUrl
    SolrRoot = $SolrRoot
    SolrService = $SolrService
    Prefix = $Prefix
    XConnectCertificateName = $XConnectSiteName
    IdentityServerCertificateName = $IdentityServerSiteName
    IdentityServerSiteName = $IdentityServerSiteName
    LicenseFile = $LicenseFile
    XConnectPackage = $XConnectPackage
    SitecorePackage = $SitecorePackage
    IdentityServerPackage = $IdentityServerPackage
    XConnectSiteName = $XConnectSiteName
    SitecoreSitename = $SitecoreSiteName
    PasswordRecoveryUrl = $PasswordRecoveryUrl
    SitecoreIdentityAuthority = $SitecoreIdentityAuthority
    XConnectCollectionService = $XConnectCollectionService
    ClientSecret = $ClientSecret
    AllowedCorsOrigins = $AllowedCorsOrigins
}

					

Again, these changes to the SIF json and the powershell are in the gist for this post.

Conclusions url copied!

After a fair amount of hacking about, a lot of reverting my VM, and a pile of fixing my own silly mistakes (Don't forget to use `Get-Content YourFileNameHere.json | ConvertFrom-Json` to check if your json is well formed if SIF complains about it!) it works:

Solr Runs

I suspect there's a bit more tweaking that could be done here to make it prettier and more flexible, but this is a good start...

↑ Back to top