Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Publish Verb to Control Panel #404

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 88 additions & 9 deletions doc/PowerShell Remote Scripting/Unicorn.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ $global:unicornWarnings = @{ }
[int]$Global:totalProjectCount

Function Sync-Unicorn {
[cmdletbinding()]
Param(
[Parameter(Mandatory = $True)]
[string]$ControlPanelUrl,
Expand All @@ -19,7 +20,7 @@ Function Sync-Unicorn {

[string[]]$Configurations,

[string]$Verb = 'Sync',
[string]$Verb = "Sync",

[switch]$SkipTransparentConfigs,

Expand All @@ -42,10 +43,88 @@ Function Sync-Unicorn {
$skipValue = 1
}

$url = "{0}?verb={1}&configuration={2}&skipTransparentConfigs={3}" -f $ControlPanelUrl, $Verb, $parsedConfigurations, $skipValue
$url = "{0}?verb={1}&configuration={2}&skipTransparentConfigs={3}" -f $ControlPanelUrl, $Verb, $parsedConfigurations, $skipValue

$params = @{
ControlPanelUrl = $ControlPanelUrl
VerbUrl = $url
SharedSecret = $SharedSecret
SleepTime = $SleepTime
}
if ($DebugSecurity) {
$params["DebugSecurity"] = $true
}
if ($StreamLogs) {
$params["StreamLogs"] = $true
}

Invoke-UnicornControlPanel @params
}

Function Publish-Unicorn {
[cmdletbinding()]
Param(
[Parameter(Mandatory = $True)]
[string]$ControlPanelUrl,

[Parameter(Mandatory = $True)]
[string]$SharedSecret,

[string]$Verb = "Publish",

# Item ID or path to an item used as Publish Trigger
[string]$TriggerItem = "/sitecore/templates/common/folder",

# List of Publish target database names
[string[]]$Targets = @("web"),

[switch]$DebugSecurity,

# defines, if logs shall be streamed to output
[switch]$StreamLogs,
[int]$SleepTime = 30
)

$parsedTargets = $Targets -join ","

$url = "{0}?verb={1}&trigger={2}&targets={3}" -f $ControlPanelUrl, $Verb, $TriggerItem, $parsedTargets

$params = @{
ControlPanelUrl = $ControlPanelUrl
VerbUrl = $url
SharedSecret = $SharedSecret
SleepTime = $SleepTime
}
if ($DebugSecurity) {
$params["DebugSecurity"] = $true
}
if ($StreamLogs) {
$params["StreamLogs"] = $true
}

Invoke-UnicornControlPanel @params
}

Function Invoke-UnicornControlPanel {
Param(
[Parameter(Mandatory = $True)]
[string]$ControlPanelUrl,

[Parameter(Mandatory = $True)]
[string]$VerbUrl,

[Parameter(Mandatory = $True)]
[string]$SharedSecret,

[switch]$DebugSecurity,

# defines, if logs shall be streamed to output
[switch]$StreamLogs,
[int]$SleepTime = 30
)

if ($DebugSecurity) {
Write-Host "Sync-Unicorn: Preparing authorization for $url"
Write-Host "Sync-Unicorn: Preparing authorization for $VerbUrl"
}

# GET AN AUTH CHALLENGE
Expand All @@ -58,7 +137,7 @@ Function Sync-Unicorn {
# CREATE A SIGNATURE WITH THE SHARED SECRET AND CHALLENGE
$signatureService = New-Object MicroCHAP.SignatureService -ArgumentList $SharedSecret

$signature = $signatureService.CreateSignature($challenge, $url, $null)
$signature = $signatureService.CreateSignature($challenge, $VerbUrl, $null)

if ($DebugSecurity) {
Write-Host "Sync-Unicorn: MAC '$($signature.SignatureSource)'"
Expand All @@ -74,19 +153,19 @@ Function Sync-Unicorn {

# USING THE SIGNATURE, EXECUTE UNICORN
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$result = Invoke-StreamingWebRequest -Uri $url -Mac $signature.SignatureHash -Nonce $challenge -RequestVerb $Verb
$result = Invoke-StreamingWebRequest -Uri $VerbUrl -Mac $signature.SignatureHash -Nonce $challenge -RequestVerb $Verb

while ($result.Trim().ToLowerInvariant() -eq "Sync in progress".ToLowerInvariant()) {
Write-Host "Sync is still running, sleeping for $SleepTime seconds"
Start-Sleep $SleepTime
# renew challenge and signature
$challenge = Get-Challenge -ControlPanelUrl $ControlPanelUrl
$signature = $signatureService.CreateSignature($challenge, $url, $null)
$result = Invoke-StreamingWebRequest -Uri $url -Mac $signature.SignatureHash -Nonce $challenge -RequestVerb $Verb
$signature = $signatureService.CreateSignature($challenge, $VerbUrl, $null)
$result = Invoke-StreamingWebRequest -Uri $VerbUrl -Mac $signature.SignatureHash -Nonce $challenge -RequestVerb $Verb
}

if ($result.TrimEnd().EndsWith('****ERROR OCCURRED****')) {
throw "Unicorn $Verb to $url returned an error. See the preceding log for details."
throw "Unicorn Control Panel invoke of $VerbUrl returned an error. See the preceding log for details."
}

# Uncomment this if you want the console results to be returned by the function
Expand Down Expand Up @@ -232,4 +311,4 @@ Function Invoke-StreamingWebRequest($Uri, $MAC, $Nonce, $RequestVerb) {
return $resultingData
}

Export-ModuleMember -Function Sync-Unicorn
Export-ModuleMember -Function Sync-Unicorn,Publish-Unicorn
9 changes: 9 additions & 0 deletions doc/PowerShell Remote Scripting/sample.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ Sync-Unicorn -ControlPanelUrl 'https://localhost/unicorn.aspx' -SharedSecret 'yo
# SYNC ALL CONFIGURATIONS WITHOUT KEEPING CONNECTION OPEN ALL THE TIME (specify configurations if you need only some of them). This is introduced to fix edge case, described https://github.com/SitecoreUnicorn/Unicorn/issues/387
Sync-Unicorn -ControlPanelUrl 'https://localhost/unicorn.aspx' -SharedSecret 'your-sharedsecret-here' -Verb 'SyncSilent'

# PUBLISH UNICORN MANUAL QUEUE
Publish-Unicorn -ControlPanelUrl 'https://localhost/unicorn.aspx' -SharedSecret 'your-sharedsecret-here'

# PUBLISH UNICORN MANUAL QUEUE, CUSTOM TARGETS
Publish-Unicorn -ControlPanelUrl 'https://localhost/unicorn.aspx' -SharedSecret 'your-sharedsecret-here' -Targets @('web','preview')

# PUBLISH UNICORN MANUAL QUEUE, SPECIFIC TRIGGER ITEM
Publish-Unicorn -ControlPanelUrl 'https://localhost/unicorn.aspx' -SharedSecret 'your-sharedsecret-here' -TriggerItem 'item-path-or-id'

# Note: you may pass -Verb 'Reserialize' for remote reserialize. Usually not needed though.

# Note: If you are having authorization issues, add -DebugSecurity to your cmdlet invocation; this will display the raw signatures being used to compare to the server.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Linq;
using System.Web;

using Kamsar.WebConsole;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

using Unicorn.ControlPanel.Headings;
using Unicorn.ControlPanel.Responses;
using Unicorn.Logging;
using Unicorn.Publishing;

namespace Unicorn.ControlPanel.Pipelines.UnicornControlPanelRequest
{
public class PublishVerb : UnicornControlPanelRequestPipelineProcessor
{
public PublishVerb() : base("Publish")
{
}

protected override IResponse CreateResponse(UnicornControlPanelRequestPipelineArgs args)
{
return new WebConsoleResponse("Publish Unicorn Queue", args.SecurityState.IsAutomatedTool, new HeadingService(), progress => Process(progress, new WebConsoleLogger(progress, args.Context.Request.QueryString["log"])));
}

protected virtual void Process(IProgressStatus progress, ILogger logger)
{
try
{
if (ManualPublishQueueHandler.HasItemsToPublish)
{
Item trigger = GetTriggerItem();
Database[] targets = GetTargets();

Log.Info("Unicorn: initiated synchronous publishing of synced items.", this);
if (ManualPublishQueueHandler.PublishQueuedItems(trigger, targets, logger))
{
Log.Info("Unicorn: publishing of synced items is complete.", this);
}
}
else
{
logger.Warn("[Unicorn Publish] There were no items to publish.");
}
}
catch (Exception ex)
{
logger.Error(ex);
}
}

protected virtual Item GetTriggerItem()
{
Item result;
string triggerIdOrPath = HttpContext.Current.Request.QueryString["trigger"];
if (!string.IsNullOrWhiteSpace(triggerIdOrPath))
{
result = Factory.GetDatabase("master").GetItem(triggerIdOrPath);
if (result == null || result.Empty)
{
throw new ArgumentException($"No item found for '{triggerIdOrPath}'.", "trigger");
}
}
else
{
throw new ArgumentNullException("trigger");
}

return result;
}

protected virtual Database[] GetTargets()
{
Database[] result;
string targets = HttpContext.Current.Request.QueryString["targets"];
if (!string.IsNullOrWhiteSpace(targets))
{
string[] targetNames = targets.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
result = targetNames.Select(Factory.GetDatabase).ToArray();
if (result.Length <= 0)
{
throw new ArgumentException("At least 1 valid target should be specified.", "targets");
}
}
else
{
throw new ArgumentNullException("targets");
}

return result;
}
}
}
18 changes: 18 additions & 0 deletions src/Unicorn/Pipelines/Initialize/ManualQueueLoad.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Sitecore.Diagnostics;
using Sitecore.Pipelines;

using Unicorn.Publishing;

namespace Unicorn.Pipelines.Initialize
{
public class ManualQueueLoad
{
public void Process(PipelineArgs args)
{
if (ManualPublishQueueHandler.LoadFromPersistentStore())
{
Log.Info("Loaded persisted unicorn queue.", this);
}
}
}
}
16 changes: 16 additions & 0 deletions src/Unicorn/Pipelines/UnicornSyncEnd/ManualQueuePersistence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Unicorn.Publishing;

namespace Unicorn.Pipelines.UnicornSyncEnd
{
/// <summary>
/// Persists the Manual Publish Queue generated by Unicorn for its synched items.
/// This is required due to the app pool recycle that will be caused by swapping between sync and publish.
/// </summary>
public class ManualQueuePersistence : IUnicornSyncEndProcessor
{
public void Process(UnicornSyncEndPipelineArgs args)
{
ManualPublishQueueHandler.Persist();
}
}
}
Loading