{"action":"created","comment":{"url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/comments/99794462","pull_request_review_id":20463323,"id":99794462,"diff_hunk":"@@ -0,0 +1,1538 @@\n+#\n+# Copyright 2015-2016 Cloudbase Solutions Srl\n+#\n+\n+$ErrorActionPreference = 'Stop'\n+\n+Import-Module JujuUtils\n+Import-Module JujuHooks\n+Import-Module JujuLogging\n+Import-Module JujuHelper\n+Import-Module JujuWindowsUtils\n+Import-Module ADCharmUtils\n+\n+$NEUTRON_GIT           = \"https://github.com/openstack/neutron.git\"\n+$NOVA_GIT              = \"https://github.com/openstack/nova.git\"\n+$NETWORKING_HYPERV_GIT = \"https://github.com/openstack/networking-hyperv.git\"\n+$COMPUTE_HYPERV_GIT    = \"https://github.com/openstack/compute-hyperv.git\"\n+$OSWIN_GIT             = \"https://git.openstack.org/openstack/os-win.git\"\n+\n+$OPENSTACK_DIR  = Join-Path $env:SystemDrive \"OpenStack\"\n+$PYTHON_DIR     = Join-Path $env:SystemDrive \"Python27\"\n+$LIB_DIR        = Join-Path $PYTHON_DIR \"lib\\site-packages\"\n+$BUILD_DIR      = Join-Path $OPENSTACK_DIR \"build\"\n+$INSTANCES_DIR  = Join-Path $OPENSTACK_DIR \"Instances\"\n+$BIN_DIR        = Join-Path $OPENSTACK_DIR \"bin\"\n+$CONFIG_DIR     = Join-Path $OPENSTACK_DIR \"etc\"\n+$LOG_DIR        = Join-Path $OPENSTACK_DIR \"log\"\n+$SERVICE_DIR    = Join-Path $OPENSTACK_DIR \"service\"\n+$FILES_DIR      = Join-Path ${env:CHARM_DIR} \"files\"\n+$OVS_DIR        = \"${env:ProgramFiles}\\Cloudbase Solutions\\Open vSwitch\"\n+$OVS_VSCTL      = Join-Path $OVS_DIR \"bin\\ovs-vsctl.exe\"\n+$env:OVS_RUNDIR = Join-Path $env:ProgramData \"openvswitch\"\n+$OVS_EXT_NAME   = \"Open vSwitch Extension\"\n+\n+function Get-TemplatesDir {\n+    return (Join-Path (Get-JujuCharmDir) \"templates\")\n+}\n+\n+\n+function Get-DevStackContext {\n+    $requiredCtx =  @{\n+        \"devstack_ip\" = $null;\n+        \"password\"    = $null;\n+        \"rabbit_user\" = $null;\n+    }\n+    $ctx = Get-JujuRelationContext -Relation 'devstack' -RequiredContext $requiredCtx\n+\n+    # Required context not found\n+    if(!$ctx.Count) {\n+        return @{}\n+    }\n+\n+    return $ctx\n+}\n+\n+\n+function Get-SystemContext {\n+    $systemCtxt = @{\n+        \"instances_path\"      = Join-Path $OPENSTACK_DIR \"Instances\";\n+        \"interfaces_template\" = Join-Path $CONFIG_DIR \"interfaces.template\";\n+        \"policy_file\"         = Join-Path $CONFIG_DIR \"policy.json\";\n+        \"mkisofs_exe\"         = Join-Path $BIN_DIR \"mkisofs.exe\";\n+        \"log_directory\"       = $LOG_DIR;\n+        \"qemu_img_exe\"        = Join-Path $BIN_DIR \"qemu-img.exe\";\n+        \"compute_driver\"      = Get-ComputeDriver\n+        \"vswitch_name\"        = Get-JujuVMSwitchName\n+        \"local_ip\"            = (Get-CharmState -Namespace \"novahyperv\" -Key \"local_ip\");\n+        \"etc_directory\"       = $CONFIG_DIR;\n+        \"bin_directory\"       = $BIN_DIR;\n+    }\n+    return $systemCtxt\n+}\n+\n+\n+function Get-CharmServices {\n+    $services = @{\n+        'nova' = @{\n+            'description'  = \"OpenStack nova Compute Service\";\n+            'binary' = Join-Path $PYTHON_DIR \"Scripts\\nova-compute.exe\";\n+            'config' = Join-Path $CONFIG_DIR \"nova.conf\";\n+            'template' = Join-Path (Get-TemplatesDir) \"nova.conf\";\n+            'service_name' = 'nova-compute';\n+            \"context_generators\" = @(\n+                @{\n+                    \"generator\" = \"Get-DevStackContext\";\n+                    \"relation\"  = \"devstack\";\n+                },\n+                @{\n+                    \"generator\" = \"Get-SystemContext\";\n+                    \"relation\"  = \"system\";\n+                }\n+            );\n+        };\n+        'neutron' = @{\n+            'description' = \"OpenStack Neutron Hyper-V Agent Service\";\n+            'binary' = (Join-Path $PYTHON_DIR \"Scripts\\neutron-hyperv-agent.exe\");\n+            'config' = (Join-Path $CONFIG_DIR \"neutron_hyperv_agent.conf\");\n+            'template' = Join-Path (Get-TemplatesDir) \"neutron_hyperv_agent.conf\";\n+            'service_name' = \"neutron-hyperv-agent\";\n+            \"context_generators\" = @(\n+                @{\n+                    \"generator\" = \"Get-DevStackContext\";\n+                    \"relation\"  = \"devstack\";\n+                },\n+                @{\n+                    \"generator\" = \"Get-SystemContext\";\n+                    \"relation\"  = \"system\";\n+                }\n+            );\n+        };\n+        'neutron-ovs' = @{\n+            'description' = \"OpenStack Neutron Open vSwitch Agent Service\";\n+            'binary' = (Join-Path $PYTHON_DIR \"Scripts\\neutron-openvswitch-agent.exe\");\n+            'config' = (Join-Path $CONFIG_DIR \"ml2_conf.ini\");\n+            'template' = Join-Path (Get-TemplatesDir) \"ml2_conf.ini\";\n+            'service_name' = \"neutron-openvswitch-agent\";\n+            \"context_generators\" = @(\n+                @{\n+                    \"generator\" = \"Get-DevStackContext\";\n+                    \"relation\"  = \"devstack\";\n+                },\n+                @{\n+                    \"generator\" = \"Get-SystemContext\";\n+                    \"relation\"  = \"system\";\n+                }\n+            );\n+        }\n+    }\n+    return $services\n+}\n+\n+\n+function Write-ConfigFile {\n+    param (\n+        [Parameter(Mandatory=$true)]\n+        [string]$ServiceName\n+    )\n+    $JujuCharmServices = Get-CharmServices\n+    $should_restart = $true\n+    $service = $JujuCharmServices[$ServiceName]\n+    if (!$service){\n+        Write-JujuWarning \"No such service $ServiceName. Not generating config\"\n+        return $false\n+    }\n+    $config = gc $service[\"template\"]\n+    # populate config with variables from context\n+    \n+    $incompleteContexts = [System.Collections.Generic.List[object]](New-Object \"System.Collections.Generic.List[object]\")\n+    $allContexts = [System.Collections.Generic.List[object]](New-Object \"System.Collections.Generic.List[object]\")\n+\n+    foreach ($context in $service['context_generators']){\n+        Write-JujuInfo (\"Getting context for {0}\" -f $context[\"relation\"])\n+        $allContexts.Add($context[\"relation\"])\n+        $ctx = & $context[\"generator\"]\n+        Write-JujuInfo (\"Got {0} context: {1}\" -f @($context[\"relation\"], $ctx.Keys))\n+        if (!$ctx.Count){\n+            # Context is empty. Probably peer not ready\n+            Write-JujuWarning \"Context for $context is EMPTY\"\n+            $incompleteContexts.Add($context[\"relation\"])\n+            $should_restart = $false\n+            continue\n+        }\n+        foreach ($val in $ctx.GetEnumerator()) {\n+            $regex = \"{{[\\s]{0,}\" + $val.Name + \"[\\s]{0,}}}\"\n+            $config = $config -Replace $regex,$val.Value\n+        }\n+    }\n+    Set-IncompleteStatusContext -ContextSet $allContexts -Incomplete $incompleteContexts\n+    # Any variables not available in context we remove\n+    $config = $config -Replace \"{{[\\s]{0,}[a-zA-Z0-9_-]{0,}[\\s]{0,}}}\",\"\"\n+    Set-Content $service[\"config\"] $config\n+    # Restart-Service $service[\"service\"]\n+    return $should_restart\n+}\n+\n+\n+function Set-IncompleteStatusContext {\n+    Param(\n+        [array]$ContextSet=@(),\n+        [array]$Incomplete=@()\n+    )\n+    $status = Get-JujuStatus -Full\n+    $currentIncomplete = @()\n+    if($status[\"message\"]){\n+        $msg = $status[\"message\"].Split(\":\")\n+        if($msg.Count -ne 2){\n+            return\n+        }\n+        if($msg[0] -eq \"Incomplete contexts\") {\n+            $currentIncomplete = $msg[1].Split(\", \")\n+        }\n+    }\n+    $newIncomplete = [System.Collections.Generic.List[object]](New-Object \"System.Collections.Generic.List[object]\")\n+    if(!$Incomplete){\n+        foreach($i in $currentIncomplete) {\n+            if ($i -in $ContextSet){\n+                continue\n+            }\n+            $newIncomplete.Add($i)\n+        }\n+    } else {\n+        foreach($i in $currentIncomplete) {\n+            if($i -in $ContextSet -and !($i -in $Incomplete)){\n+                continue\n+            } else {\n+                $newIncomplete.Add($i)\n+            }\n+        }\n+        foreach($i in $Incomplete) {\n+            if ($i -in $newIncomplete) {\n+                continue\n+            }\n+            $newIncomplete.Add($i)\n+        }\n+    }\n+    if($newIncomplete){\n+        $msg = \"Incomplete contexts: {0}\" -f ($newIncomplete -Join \", \")\n+        Set-JujuStatus -Status blocked -Message $msg\n+    } else {\n+        Set-JujuStatus -Status waiting -Message \"Contexts are complete\"\n+    }\n+}\n+\n+\n+# Returns an HashTable with the download URL, the checksum (with the hashing\n+# algorithm) for a specific package. The URL config option for that package\n+# is parsed. In case the checksum is not specified, 'CHECKSUM' and\n+# 'HASHING_ALGORITHM' fields will be $null.\n+function Get-URLChecksum {\n+    Param(\n+        [string]$URLConfigKey\n+    )\n+\n+    $url = Get-JujuCharmConfig -Scope $URLConfigKey\n+    if ($url.contains('#')) {\n+        $urlSplit = $url.split('#')\n+        $algorithm = $urlSplit[1]\n+        if (!$algorithm.contains('=')) {\n+            Throw (\"Invalid algorithm format! \" +\n+                   \"Use the format: <hashing_algorithm>=<checksum>\")\n+        }\n+\n+        $validHashingAlgorithms = @('SHA1', 'SHA256', 'SHA384', 'SHA512',\n+                                    'MACTripleDES', 'MD5', 'RIPEMD160')\n+        $algorithmSplit = $algorithm.split('=')\n+        $hashingAlgorithm = $algorithmSplit[0]\n+        if ($hashingAlgorithm -notin $validHashingAlgorithms) {\n+            Throw (\"Invalid hashing algorithm format! \" +\n+                   \"Valid formats are: \" + $validHashingAlgorithms)\n+        }\n+\n+        $checksum = $algorithmSplit[1]\n+        return @{ 'URL' = $urlSplit[0];\n+                  'CHECKSUM' = $checksum;\n+                  'HASHING_ALGORITHM' = $hashingAlgorithm }\n+    }\n+\n+    return @{ 'URL' = $url;\n+              'CHECKSUM' = $null;\n+              'HASHING_ALGORITHM' = $null }\n+}\n+\n+\n+function Start-DownloadFile {\n+    Param(\n+        [Parameter(Mandatory=$True)]\n+        [System.Uri]$Uri,\n+        [string]$OutFile,\n+        [switch]$SkipIntegrityCheck=$false\n+    )\n+\n+    if(!$OutFile) {\n+        $OutFile = $Uri.PathAndQuery.Substring($Uri.PathAndQuery.LastIndexOf(\"/\") + 1)\n+        if(!$OutFile) {\n+            throw \"The \"\"OutFile\"\" parameter needs to be specified\"\n+        }\n+    }\n+\n+    $webClient = New-Object System.Net.WebClient\n+    Start-ExecuteWithRetry { $webClient.DownloadFile($Uri, $OutFile) }\n+\n+    if(!$SkipIntegrityCheck) {\n+        $fragment = $Uri.Fragment.Trim('#')\n+        if (!$fragment){\n+            return\n+        }\n+        $details = $fragment.Split(\"=\")\n+        $algorithm = $details[0]\n+        $hash = $details[1]\n+        if($algorithm -in @(\"SHA1\", \"SHA256\", \"SHA384\", \"SHA512\", \"MACTripleDES\", \"MD5\", \"RIPEMD160\")){\n+            Test-FileIntegrity -File $OutFile -Algorithm $algorithm -ExpectedHash $hash\n+        } else {\n+            Throw \"Hash algorithm $algorithm not recognized.\"\n+        }\n+    }\n+}\n+\n+\n+# Returns the full path of the package after it is downloaded using\n+# the URL parameter (a checksum may optionally be specified). The\n+# package is cached on the disk until the installation successfully finishes.\n+# If the hook fails, on the second run this function will return the cached\n+# package path if checksum is given and it matches.\n+function Get-PackagePath {\n+    Param(\n+        [Parameter(Mandatory=$True)]\n+        [string]$URL,\n+        [string]$Checksum=\"\",\n+        [string]$HashingAlgorithm=\"\"\n+    )\n+\n+    $packagePath = Join-Path $env:TEMP $URL.Split('/')[-1]\n+    if (Test-Path $packagePath) {\n+        if ($Checksum -and $HashingAlgorithm) {\n+            if (Test-FileIntegrity -File $packagePath -Algorithm $HashingAlgorithm -ExpectedHash $Checksum) {\n+                return $packagePath\n+            }\n+        }\n+        Remove-Item -Recurse -Force -Path $packagePath\n+    }\n+\n+    if ($Checksum -and $HashingAlgorithm) {\n+        Start-DownloadFile -Uri \"$URL#$HashingAlgorithm=$Checksum\" -OutFile $packagePath\n+    } else {\n+        Start-DownloadFile -Uri $URL -OutFile $packagePath -SkipIntegrityCheck\n+    }\n+\n+    return $packagePath\n+}\n+\n+\n+# Installs a package after it is downloaded from the Internet and checked for\n+# integrity with SHA1 checksum. Accepts as parameters: an URL, an optional\n+# 'Checksum' with its 'HashingAlgorithm' and 'ArgumentList' which can be passed\n+# if the installer requires unattended installation.\n+# Supported packages formats are: '.exe' and '.msi'\n+function Install-Package {\n+    Param(\n+        [string]$URL,\n+        [string]$Checksum=\"\",\n+        [string]$HashingAlgorithm=\"\",\n+        [array]$ArgumentList\n+    )\n+\n+    Write-JujuLog \"Installing package '$URL'\"\n+\n+    $packageFormat = $URL.Split('.')[-1]\n+    $acceptedFormats = @('msi', 'exe')\n+    if ($packageFormat -notin $acceptedFormats) {\n+        Throw (\"Cannot install the package found at this URL: $URL \" +\n+               \"Unsupported installer format.\")\n+    }\n+\n+    $installerPath = Get-PackagePath $URL $Checksum $HashingAlgorithm\n+    $stat = Start-Process -FilePath $installerPath -ArgumentList $ArgumentList `\n+                          -PassThru -Wait\n+    if ($stat.ExitCode -ne 0) {\n+        throw \"Package '$URL' failed to install.\"\n+    }\n+    Remove-Item $installerPath\n+\n+    Write-JujuLog \"Finished installing package.\"\n+}\n+\n+\n+function Start-GitClonePull {\n+    Param(\n+        [string]$Path,\n+        [string]$URL,\n+        [string]$Branch=\"master\"\n+    )\n+\n+    if (!(Test-Path -Path $Path)) {\n+        Start-ExecuteWithRetry {\n+            Start-ExternalCommand -ScriptBlock { git clone $URL $Path } `\n+                                  -ErrorMessage \"Git clone failed\"\n+        }\n+        pushd $Path\n+        Start-ExternalCommand -ScriptBlock { git checkout $Branch } `\n+                              -ErrorMessage \"Git checkout failed\"\n+        popd\n+    } else {\n+        pushd $Path\n+        try {\n+            $gitPath = Join-Path $Path \".git\"\n+            if (!(Test-Path -Path $gitPath)) {\n+                Remove-Item -Recurse -Force *\n+                Start-ExecuteWithRetry {\n+                    Start-ExternalCommand -ScriptBlock { git clone $URL $Path } `\n+                                          -ErrorMessage \"Git clone failed\"\n+                }\n+            } else {\n+                Start-ExecuteWithRetry {\n+                    Start-ExternalCommand -ScriptBlock { git fetch --all } `\n+                                          -ErrorMessage \"Git fetch failed\"\n+                }\n+            }\n+            Start-ExecuteWithRetry {\n+                Start-ExternalCommand -ScriptBlock { git checkout $Branch } `\n+                                      -ErrorMessage \"Git checkout failed\"\n+            }\n+            Get-ChildItem . -Include *.pyc -Recurse | foreach ($_) { Remove-Item $_.fullname }\n+            Start-ExternalCommand -ScriptBlock { git reset --hard } `\n+                                  -ErrorMessage \"Git reset failed\"\n+            Start-ExternalCommand -ScriptBlock { git clean -f -d } `\n+                                  -ErrorMessage \"Git clean failed\"\n+            Start-ExecuteWithRetry {\n+                Start-ExternalCommand -ScriptBlock { git pull } `\n+                                      -ErrorMessage \"Git pull failed\"\n+            }\n+        } finally {\n+            popd\n+        }\n+    }\n+}\n+\n+\n+function Install-OpenStackProjectFromRepo {\n+    Param(\n+        [string]$ProjectPath\n+    )\n+\n+    Start-ExternalCommand -ScriptBlock { pip install -e $ProjectPath } `\n+                          -ErrorMessage \"Failed to install $ProjectPath from repo.\"\n+}\n+\n+\n+function Start-GerritGitPrep {\n+    Param(\n+        [Parameter(Mandatory=$True)]\n+        [string]$ZuulUrl,\n+        [Parameter(Mandatory=$True)]\n+        [string]$GerritSite,\n+        [Parameter(Mandatory=$True)]\n+        [string]$ZuulRef,\n+        [Parameter(Mandatory=$True)]\n+        [string]$ZuulChange,\n+        [Parameter(Mandatory=$True)]\n+        [string]$ZuulProject,\n+        [string]$GitOrigin,\n+        [string]$ZuulNewrev\n+    )\n+\n+    if (!$ZuulRef -or !$ZuulChange -or !$ZuulProject) {\n+        Throw \"ZUUL_REF ZUUL_CHANGE ZUUL_PROJECT are mandatory\"\n+    }\n+    if (!$ZuulUrl) {\n+        Throw \"The zuul site name (eg 'http://zuul.openstack.org/p') must be the first argument.\"\n+    }\n+    if (!$GerritSite) {\n+        Throw \"The gerrit site name (eg 'https://review.openstack.org') must be the second argument.\"\n+    }\n+    if (!$GitOrigin -or !$ZuulNewrev) {\n+        $GitOrigin=\"$GerritSite/p\"\n+    }\n+\n+    Write-JujuLog \"Triggered by: $GerritSite/$ZuulChange\"\n+\n+    if (!(Test-Path -Path $BUILD_DIR -PathType Container)) {\n+        mkdir $BUILD_DIR\n+    }\n+\n+    $projectDir = Join-Path $BUILD_DIR $ZuulProject\n+    if (!(Test-Path -Path $projectDir -PathType Container)) {\n+        mkdir $projectDir\n+        try {\n+            Start-ExternalCommand -ScriptBlock { git clone \"$GitOrigin/$ZuulProject\" $projectDir } `\n+                                  -ErrorMessage \"Failed to clone $GitOrigin/$ZuulProject\"\n+        } catch {\n+            rm -Recurse -Force $projectDir\n+            Throw $_\n+        }\n+    }\n+\n+    pushd $projectDir\n+\n+    Start-ExternalCommand { git remote set-url origin \"$GitOrigin/$ZuulProject\" } `\n+        -ErrorMessage \"Failed to set origin: $GitOrigin/$ZuulProject\"\n+\n+    try {\n+        Start-ExternalCommand { git remote update } -ErrorMessage \"Failed to update remote\"\n+    } catch {\n+        Write-JujuLog \"The remote update failed, so garbage collecting before trying again.\"\n+        Start-ExternalCommand { git gc } -ErrorMessage \"Failed to run git gc.\"\n+        Start-ExternalCommand { git remote update } -ErrorMessage \"Failed to update remote\"\n+    }\n+\n+    Start-ExternalCommand { git reset --hard } -ErrorMessage \"Failed to git reset\"\n+    try {\n+        Start-ExternalCommand { git clean -x -f -d -q } -ErrorMessage \"Failed to git clean\"\n+    } catch {\n+        sleep 1\n+        Start-ExternalCommand { git clean -x -f -d -q } -ErrorMessage \"Failed to git clean\"\n+    }\n+\n+    echo \"Before doing git checkout:\"\n+    echo \"Git branch output:\"\n+    Start-ExternalCommand { git branch } -ErrorMessage \"Failed to show git branch.\"\n+    echo \"Git log output:\"\n+    Start-ExternalCommand { git log -10 --pretty=format:\"%h - %an, %ae, %ar : %s\" } `\n+        -ErrorMessage \"Failed to show git log.\"\n+\n+    $ret = echo \"$ZuulRef\" | Where-Object { $_ -match \"^refs/tags/\" }\n+    if ($ret) {\n+        Start-ExternalCommand { git fetch --tags \"$ZuulUrl/$ZuulProject\" } `\n+            -ErrorMessage \"Failed to fetch tags from: $ZuulUrl/$ZuulProject\"\n+        Start-ExternalCommand { git checkout $ZuulRef } `\n+            -ErrorMessage \"Failed to fetch tags to: $ZuulRef\"\n+        Start-ExternalCommand { git reset --hard $ZuulRef } `\n+            -ErrorMessage \"Failed to hard reset to: $ZuulRef\"\n+    } elseif (!$ZuulNewrev) {\n+        Start-ExternalCommand { git fetch \"$ZuulUrl/$ZuulProject\" $ZuulRef } `\n+            -ErrorMessage \"Failed to fetch: $ZuulUrl/$ZuulProject $ZuulRef\"\n+        Start-ExternalCommand { git checkout FETCH_HEAD } `\n+            -ErrorMessage \"Failed to checkout FETCH_HEAD\"\n+        Start-ExternalCommand { git reset --hard FETCH_HEAD } `\n+            -ErrorMessage \"Failed to hard reset FETCH_HEAD\"\n+    } else {\n+        Start-ExternalCommand { git checkout $ZuulNewrev } `\n+            -ErrorMessage \"Failed to checkout $ZuulNewrev\"\n+        Start-ExternalCommand { git reset --hard $ZuulNewrev } `\n+            -ErrorMessage \"Failed to hard reset $ZuulNewrev\"\n+    }\n+\n+    try {\n+        Start-ExternalCommand { git clean -x -f -d -q } -ErrorMessage \"Failed to git clean\"\n+    } catch {\n+        sleep 1\n+        Start-ExternalCommand { git clean -x -f -d -q } -ErrorMessage \"Failed to git clean\"\n+    }\n+\n+    if (Test-Path .gitmodules) {\n+        Start-ExternalCommand { git submodule init } -ErrorMessage \"Failed to init submodule\"\n+        Start-ExternalCommand { git submodule sync } -ErrorMessage \"Failed to sync submodule\"\n+        Start-ExternalCommand { git submodule update --init } -ErrorMessage \"Failed to update submodule\"\n+    }\n+\n+    echo \"Final result:\"\n+    echo \"Git branch output:\"\n+    Start-ExternalCommand { git branch } -ErrorMessage \"Failed to show git branch.\"\n+    echo \"Git log output:\"\n+    Start-ExternalCommand { git log -10 --pretty=format:\"%h - %an, %ae, %ar : %s\" } `\n+        -ErrorMessage \"Failed to show git log.\"\n+\n+    popd\n+}\n+\n+\n+function Install-Projects ($buildFor) {\n+    # Shared for all projects\n+    $projectDir = $buildFor -replace 'openstack/'\n+    Write-JujuLog \"Installing $projectDir\"\n+    $openstackBuild = Join-Path $BUILD_DIR \"openstack\"\n+    Start-ExecuteWithRetry {\n+        Install-OpenStackProjectFromRepo \"$openstackBuild\\$projectDir\"\n+    }\n+    \n+    # Individual projects\n+    # Nova\n+    if ($buildFor -eq \"openstack/nova\") {\n+        $novaBin = (Get-CharmServices)['nova']['binary']\n+        if (!(Test-Path $novaBin)) {\n+            Throw \"$novaBin was not found.\"\n+        }\n+\n+        Write-JujuLog \"Copying default config files\"\n+        $defaultConfigFiles = @('rootwrap.d', 'api-paste.ini', 'cells.json',\n+                                'rootwrap.conf')\n+        foreach ($config in $defaultConfigFiles) {\n+            Copy-Item -Recurse -Force \"$openstackBuild\\nova\\etc\\nova\\$config\" $CONFIG_DIR\n+        }\n+        Copy-Item -Force (Join-Path (Get-TemplatesDir) \"policy.json\") $CONFIG_DIR\n+        Copy-Item -Force (Join-Path (Get-TemplatesDir) \"interfaces.template\") $CONFIG_DIR\n+    }\n+    # Networking-Hyperv\n+    if ($buildFor -eq \"openstack/networking-hyperv\") {\n+        $neutronBin = (Get-CharmServices)['neutron']['binary']\n+        if (!(Test-Path $neutronBin)) {\n+            Throw \"$neutronBin was not found.\"\n+        }\n+    }\n+}\n+\n+function Get-ComputeDriver {\n+    if ($buildFor -eq \"openstack/compute-hyperv\") {\n+        $compute_driver = \"hyperv.nova.driver.HyperVDriver\"\n+    }\n+    else {\n+        $compute_driver = \"hyperv.driver.HyperVDriver\"\n+    }\n+    return $compute_driver\n+}\n+\n+function Install-NetworkType {\n+    $buildFor = Get-JujuCharmConfig -Scope 'zuul-project'\n+    $networkType = Get-JujuCharmConfig -Scope 'network-type'\n+    if (($networkType -eq 'ovs') -and ($buildFor -eq 'openstack/networking-hyperv')) {\n+        Throw \"'$networkType' cannot be used when building for '$buildFor'\"\n+    }\n+    if ($networkType -eq 'hyperv') {\n+#        Install-NetworkingHyperV\n+        Install-Projects \"openstack/networking-hyperv\"\n+    }\n+    elseif ($networkType -eq 'ovs') {\n+        Check-OVSPrerequisites\n+        Enable-OVS\n+        Ensure-InternalOVSInterfaces\n+    }\n+    else {\n+        Throw \"Wrong network type config: '$networkType'\"\n+    }\n+}\n+\n+#function Install-NetworkingHyperV {\n+#    Write-JujuLog \"Installing networking-hyperv\"\n+#\n+#    $openstackBuild = Join-Path $BUILD_DIR \"openstack\"\n+#    Start-ExecuteWithRetry {\n+#        Install-OpenStackProjectFromRepo \"$openstackBuild\\networking-hyperv\"\n+#    }\n+#    $neutronBin = (Get-CharmServices)['neutron']['binary']\n+#    if (!(Test-Path $neutronBin)) {\n+#        Throw \"$neutronBin was not found.\"\n+#    }\n+#}\n+\n+\n+function Install-OVS {\n+    param(\n+        [Parameter(Mandatory=$true)]\n+        [string]$InstallerPath\n+    )\n+\n+    Write-JujuInfo \"Running OVS install\"\n+    $ovs = Get-ManagementObject -Class Win32_Product | Where-Object {$_.Name -match \"open vswitch\"}\n+    if ($ovs){\n+        Write-JujuInfo \"OVS is already installed\"\n+        return $true\n+    }\n+\n+    $hasInstaller = Test-Path $InstallerPath\n+    if($hasInstaller -eq $false){\n+        $InstallerPath = Get-OVSInstaller\n+    }\n+\n+    Write-JujuInfo \"Installing from $InstallerPath\"\n+    $ret = Start-Process -FilePath msiexec.exe -ArgumentList \"INSTALLDIR=`\"$OVS_DIR`\"\",\"/qb\",\"/l*v\",\"$env:APPDATA\\ovs-log.txt\",\"/i\",\"$InstallerPath\" -Wait -PassThru\n+    if($ret.ExitCode) {\n+        Throw \"Failed to install OVS: $LASTEXITCODE\"\n+    }\n+    Remove-Item $InstallerPath\n+    return $true\n+}\n+\n+\n+function Import-OVSCertificate {\n+\tparam(\n+        [string]$CertificatePath\n+    )\n+    if (!(Test-Path $CertificatePath)) {\n+        $CertificatePath = Get-OVSCertificate\n+    }\n+    Write-JujuInfo \"Importing certificate from $CertificatePath\"\n+    Import-Certificate $CertificatePath -StoreLocation LocalMachine -StoreName TrustedPublisher\n+    Import-Certificate $CertificatePath -StoreLocation LocalMachine -StoreName Root\n+    Remove-Item $CertificatePath\n+    return $true\n+}\n+\n+\n+function Get-OVSInstaller {\n+    $urlChecksum = Get-URLChecksum \"ovs-installer-url\"\n+    $location = Get-PackagePath $urlChecksum['URL'] $urlChecksum['CHECKSUM'] `\n+                                $urlChecksum['HASHING_ALGORITHM']\n+    return $location\n+}\n+\n+function Get-OVSCertificate {\n+    $urlChecksum = Get-URLChecksum \"ovs-certificate-url\"\n+    $location = Get-PackagePath $urlChecksum['URL'] $urlChecksum['CHECKSUM'] `\n+                                $urlChecksum['HASHING_ALGORITHM']\n+    return $location\n+}\n+\n+function Check-OVSPrerequisites {\n+    try {\n+        $ovsdbSvc = Get-Service \"ovsdb-server\"\n+        $ovsSwitchSvc = Get-Service \"ovs-vswitchd\"\n+    } catch {\n+        $driverCertificate = Get-JujuCharmConfig -Scope \"ovs-certificate-url\"\n+        if ($driverCertificate) {\n+            $CertificatePath = Get-OVSCertificate\n+            Import-OVSCertificate $CertificatePath\n+        }\n+        else {\n+            Write-JujuLog \"No OVS driver certificate specified. Be sure to use an offically signed driver.\"\n+        }\n+        $InstallerPath = Get-OVSInstaller\n+        Install-OVS $InstallerPath\n+    }\n+    if(!(Test-Path $OVS_VSCTL)){\n+        Throw \"Could not find ovs-vsctl.exe in location: $OVS_VSCTL\"\n+    }\n+}\n+\n+\n+function Enable-OVSExtension {\n+    $ext = Get-OVSExtStatus\n+    if (!$ext){\n+       Throw \"Cannot enable OVS extension. Not installed\"\n+    }\n+    if (!$ext.Enabled) {\n+        Enable-VMSwitchExtension $OVS_EXT_NAME $ext.SwitchName\n+    }\n+    return $true\n+}\n+\n+\n+function Get-OVSExtStatus {\n+    $br = Get-JujuVMSwitchName\n+    Write-JujuInfo \"Switch name is $br\"\n+    $ext = Get-VMSwitchExtension -VMSwitchName $br -Name $OVS_EXT_NAME\n+\n+    if (!$ext){\n+        Write-JujuInfo \"Open vSwitch extension not installed\"\n+        return $null\n+    }\n+\n+    return $ext\n+}\n+\n+\n+function Enable-Service {\n+    Param(\n+        [Parameter(Mandatory=$true)]\n+        [string]$ServiceName\n+    )\n+\n+    Get-Service $ServiceName | Set-Service -StartupType Automatic\n+}\n+\n+\n+function Set-HyperVMACS {\n+    $b1 = \"0x{0:x}\" -f (get-random -minimum 1 -maximum 255)\n+    $b2 = \"0x{0:x}\" -f (get-random -minimum 1 -maximum 255)\n+    $reg = \"HKLM:\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\"\n+    set-ItemProperty -path $reg -name minimummacaddress -type binary -value ([byte[]](0x00,0x15,0x5d,$b1,$b2,0x00))\n+    set-ItemProperty -path $reg -name maximummacaddress -type binary -value ([byte[]](0x00,0x15,0x5d,$b1,$b2,0xff))\n+}\n+\n+\n+function Enable-OVS {\n+    Start-ExternalCommand { pip install -U \"ovs\" } -ErrorMessage \"Failed to install $ovs_pip\"\n+    \n+    Start-ExternalCommand { cmd.exe /c \"sc triggerinfo ovs-vswitchd start/strcustom/6066F867-7CA1-4418-85FD-36E3F9C0600C/VmmsWmiEventProvider\" } -ErrorMessage \"Failed to modify ovs-vswitchd service.\"\n+    Start-ExternalCommand { cmd.exe /c \"sc config ovs-vswitchd start=demand\" } -ErrorMessage \"Failed to modify ovs-vswitchd service.\"\n+\n+    Enable-OVSExtension\n+\n+    Enable-Service \"ovsdb-server\"\n+    #Enable-Service \"ovs-vswitchd\"\n+\n+    Start-Service \"ovsdb-server\"\n+    #Start-Service \"ovs-vswitchd\"\n+}\n+\n+\n+function Ensure-InternalOVSInterfaces {\n+    $ifIndex = Get-CharmState -Namespace \"novahyperv\" -Key \"dataNetworkIfindex\"\n+    $lip = Get-CharmState -Namespace \"novahyperv\" -Key \"local_ip\"\n+    $ifName = (Get-NetAdapter -ifindex $ifIndex).Name\n+    $lip_mask = Get-CharmState -Namespace \"novahyperv\" -Key \"local_ip_mask\"\n+\n+    Invoke-JujuCommand -Command @($ovs_vsctl, \"--may-exist\", \"add-br\", \"juju-br\")\n+    Invoke-JujuCommand -Command @($ovs_vsctl, \"--may-exist\", \"add-port\", \"juju-br\", $ifName)\n+\n+    Restart-Service \"ovs-vswitchd\"\n+    Enable-NetAdapter -Name \"juju-br\" -Confirm:$false\n+\n+    New-NetIPAddress -interfacealias \"juju-br\" -AddressFamily \"ipv4\" -IPAddress $lip -PrefixLength $lip_mask\n+    Set-CharmState -Namespace \"novahyperv\" -Key \"local_ip\" -Value $lip\n+    return\n+}\n+ \n+\n+function Get-CherryPicksObject {\n+    $cfgOption = Get-JujuCharmConfig -Scope 'cherry-picks'\n+    if (!$cfgOption) {\n+        return @{}\n+    }\n+    $ret = @{\n+        'nova' = @();\n+        'networking-hyperv' = @();\n+        'compute-hyperv' = @();\n+        'neutron' = @();\n+        'os-win' = @()\n+    }\n+    $splitCfgOption = $cfgOption.Split(',')\n+    $validProjects = @('nova', 'networking-hyperv', 'neutron', 'compute-hyperv', 'os-win')\n+    foreach ($item in $splitCfgOption) {\n+        $splitItem = $item.Split('|')\n+        if ($splitItem.Count -ne 4) {\n+            Throw \"ERROR: Wrong 'cherry-picks' config option format\"\n+        }\n+        $projectName = $splitItem[0]\n+        if ($projectName -notin $validProjects) {\n+            Throw \"ERROR: Invalid git project name '$projectName'\"\n+        }\n+        $ret[$projectName] += @{\n+            'git_url' = $splitItem[1];\n+            'branch_name' = $splitItem[2];\n+            'commit_id' = $splitItem[3]\n+        }\n+    }\n+    return $ret\n+}\n+\n+\n+function Initialize-GitRepository {\n+    Param(\n+        [string]$BuildFolder,\n+        [string]$GitURL,\n+        [string]$BranchName,\n+        [array]$CherryPicks=@()\n+    )\n+\n+    Write-JujuLog \"Cloning $GitURL from $BranchName\"\n+    Start-ExecuteWithRetry { Start-GitClonePull $BuildFolder $GitURL $BranchName }\n+    foreach ($commit in $CherryPicks) {\n+        Write-JujuLog (\"Cherry-picking commit {0} from {1}, branch {2}\" -f\n+                       @($commit['commit_id'], $commit['git_url'], $commit['branch_name']))\n+        pushd $BuildFolder\n+        $currentCommit = Start-ExternalCommand { git rev-parse HEAD }\n+        $cherryCommit = $commit['commit_id']\n+        if ($currentCommit -eq $cherryCommit) {\n+            Write-JujuLog \"Already in cherry-pick commit\"\n+            popd\n+        }\n+        else {\n+            Start-ExternalCommand { git fetch $commit['git_url'] $commit['branch_name'] }\n+            Start-ExternalCommand { git cherry-pick $commit['commit_id'] }\n+            popd\n+        }\n+    }\n+}\n+\n+\n+function Initialize-GitRepositories {\n+    Param(\n+        [ValidateSet(\"hyperv\", \"ovs\")]\n+        [string]$NetworkType,\n+        [string]$BranchName,\n+        [string]$BuildFor\n+    )\n+\n+    Write-JujuLog \"Cloning the required Git repositories\"\n+    \n+    $standardProjects = 'openstack/nova', 'openstack/neutron', 'openstack/os-win'\n+    $cherryPicks = Get-CherryPicksObject\n+    $openstackBuild = Join-Path $BUILD_DIR \"openstack\"\n+    if ($NetworkType -eq 'hyperv') {\n+        if ($BuildFor -ne \"openstack/networking-hyperv\") {\n+            Initialize-GitRepository \"$openstackBuild\\networking-hyperv\" $NETWORKING_HYPERV_GIT \"master\" $cherryPicks['networking-hyperv']\n+        }\n+    }\n+\n+    if ($BuildFor -eq \"openstack/nova\") {\n+        Initialize-GitRepository \"$openstackBuild\\neutron\" $NEUTRON_GIT $BranchName $cherryPicks['neutron']\n+        Initialize-GitRepository \"$openstackBuild\\os-win\" $OSWIN_GIT \"master\" $cherryPicks['os-win']\n+    }\n+\n+    if ($BuildFor -eq \"openstack/neutron\") {\n+        Initialize-GitRepository \"$openstackBuild\\nova\" $NOVA_GIT $BranchName $cherryPicks['nova']\n+        Initialize-GitRepository \"$openstackBuild\\os-win\" $OSWIN_GIT \"master\" $cherryPicks['os-win']\n+    }\n+    \n+    if ($BuildFor -eq \"openstack/os-win\") {\n+        Initialize-GitRepository \"$openstackBuild\\nova\" $NOVA_GIT $BranchName $cherryPicks['nova']\n+        Initialize-GitRepository \"$openstackBuild\\neutron\" $NEUTRON_GIT $BranchName $cherryPicks['neutron']\n+    }\n+    \n+    if ($BuildFor -notin $standardProjects) {\n+        Initialize-GitRepository \"$openstackBuild\\nova\" $NOVA_GIT $BranchName $cherryPicks['nova']\n+        Initialize-GitRepository \"$openstackBuild\\neutron\" $NEUTRON_GIT $BranchName $cherryPicks['neutron']\n+        Initialize-GitRepository \"$openstackBuild\\os-win\" $OSWIN_GIT \"master\" $cherryPicks['os-win']\n+    }\n+}\n+\n+\n+function Initialize-Environment {\n+    Param(\n+        [string]$BranchName='master',\n+        [string]$BuildFor\n+    )\n+\n+    $dirs = @($CONFIG_DIR, $BIN_DIR, $INSTANCES_DIR, $LOG_DIR, $SERVICE_DIR)\n+    foreach($dir in $dirs) {\n+        if (!(Test-Path $dir)) {\n+            Write-JujuLog \"Creating $dir folder.\"\n+            mkdir $dir\n+        }\n+    }\n+\n+    $mkisofsPath = Join-Path $BIN_DIR \"mkisofs.exe\"\n+    $qemuimgPath = Join-Path $BIN_DIR \"qemu-img.exe\"\n+    if (!(Test-Path $mkisofsPath) -or !(Test-Path $qemuimgPath)) {\n+        Write-JujuLog \"Extracting OpenStack binaries\"\n+        $zipPath = Join-Path $FILES_DIR \"openstack_bin.zip\"\n+        Expand-ZipArchive $zipPath $BIN_DIR\n+    }\n+  \n+    $networkType = Get-JujuCharmConfig -Scope 'network-type'\n+    Initialize-GitRepositories $networkType $BranchName $BuildFor\n+\n+    $standardProjects = 'openstack/nova', 'openstack/neutron', 'openstack/os-win'\n+    if ($buildFor -in $standardProjects) {\n+        $defaultProjects = $standardProjects -notmatch $buildFor\n+        foreach ($project in $defaultProjects) {\n+            Install-Projects $project\n+        }\n+        Install-NetworkType\n+        Install-Projects $buildFor\n+    }\n+    else {\n+        foreach ($project in $standardProjects) {\n+            Install-Projects $project\n+        }\n+        if (($buildFor -eq \"none\") -or ($buildFor -eq \"openstack/networking-hyperv\")) {\n+            Install-NetworkType\n+        }\n+        else {\n+            Install-NetworkType\n+            Install-Projects $buildFor\n+        }\n+    }\n+\n+    Write-JujuLog \"Environment initialization done.\"\n+}\n+\n+function Set-ServiceAcountCredentials {\n+    Param(\n+        [string]$ServiceName,\n+        [string]$ServiceUser,\n+        [string]$ServicePassword\n+    )\n+\n+    $filter = 'Name=' + \"'\" + $ServiceName + \"'\" + ''\n+    $service = Get-WMIObject -Namespace \"root\\cimv2\" -Class Win32_Service -Filter $filter\n+    $service.StopService()\n+    while ($service.Started) {\n+        Start-Sleep -Seconds 2\n+        $service = Get-WMIObject -Namespace \"root\\cimv2\" -Class Win32_Service -Filter $filter\n+    }\n+\n+    Grant-Privilege $ServiceUser \"SeServiceLogonRight\"\n+    Set-ServiceLogon -Services @($ServiceName) -UserName $ServiceUser -Password $ServicePassword\n+}\n+\n+\n+function New-OpenStackService {\n+    Param(\n+        [string]$ServiceName,\n+        [string]$ServiceDescription,\n+        [string]$ServiceExecutable,\n+        [string]$ServiceConfig,\n+        [string]$ServiceUser,\n+        [string]$ServicePassword\n+    )\n+\n+    $filter='Name=' + \"'\" + $ServiceName + \"'\"\n+\n+    $service = Get-WmiObject -Namespace \"root\\cimv2\" -Class Win32_Service -Filter $filter\n+    if($service) {\n+        Write-JujuLog \"Service $ServiceName is already created.\"\n+        return $true\n+    }\n+\n+    $serviceFileName = \"OpenStackService.exe\"\n+    if(!(Test-Path \"$SERVICE_DIR\\$serviceFileName\")) {\n+        Copy-Item \"$FILES_DIR\\$serviceFileName\" \"$SERVICE_DIR\\$serviceFileName\"\n+    }\n+\n+    New-Service -Name \"$ServiceName\" -DisplayName \"$ServiceName\" -Description \"$ServiceDescription\" -StartupType \"Manual\" `\n+                -BinaryPathName \"$SERVICE_DIR\\$serviceFileName $ServiceName $ServiceExecutable --config-file $ServiceConfig\" `\n+\n+    if((Get-Service -Name $ServiceName).Status -eq \"Running\") {\n+        Stop-Service $ServiceName\n+    }\n+\n+    Set-ServiceAcountCredentials $ServiceName $ServiceUser $ServicePassword\n+}\n+\n+\n+function Watch-ServiceStatus {\n+    Param(\n+        [string]$ServiceName,\n+        [int]$IntervalSeconds\n+    )\n+\n+    $count = 0\n+    while ($count -lt $IntervalSeconds) {\n+        if ((Get-Service -Name $ServiceName).Status -ne \"Running\") {\n+            Throw \"$ServiceName has errors. Please check the logs.\"\n+        }\n+        $count += 1\n+        Start-Sleep -Seconds 1\n+    }\n+}\n+\n+\n+function Get-JujuVMSwitchName {\n+    $VMswitchName = Get-JujuCharmConfig -Scope \"vmswitch-name\"\n+    if (!$VMswitchName){\n+        return \"br100\"\n+    }\n+    return $VMswitchName\n+}\n+\n+\n+function Get-InterfaceFromConfig {\n+    Param (\n+        [string]$ConfigOption=\"data-port\",\n+        [switch]$MustFindAdapter=$false\n+    )\n+\n+    $nic = $null\n+    $DataInterfaceFromConfig = Get-JujuCharmConfig -Scope $ConfigOption\n+    Write-JujuInfo \"Looking for $DataInterfaceFromConfig\"\n+    if (!$DataInterfaceFromConfig){\n+        if($MustFindAdapter) {\n+            Throw \"No data-port was specified\"\n+        }\n+        return $null\n+    }\n+    $byMac = @()\n+    $byName = @()\n+    $macregex = \"^([a-f-A-F0-9]{2}:){5}([a-fA-F0-9]{2})$\"\n+    foreach ($i in $DataInterfaceFromConfig.Split()){\n+        if ($i -match $macregex){\n+            $byMac += $i.Replace(\":\", \"-\")\n+        }else{\n+            $byName += $i\n+        }\n+    }\n+    if ($byMac.Length){\n+        $nicByMac = Get-NetAdapter | Where-Object { $_.MacAddress -in $byMac -and $_.DriverFileName -ne \"vmswitch.sys\" }\n+    }\n+    if ($byName.Length){\n+        $nicByName = Get-NetAdapter | Where-Object { $_.Name -in $byName }\n+    }\n+    if ($nicByMac -ne $null -and $nicByMac.GetType() -ne [System.Array]){\n+        $nicByMac = @($nicByMac)\n+    }\n+    if ($nicByName -ne $null -and $nicByName.GetType() -ne [System.Array]){\n+        $nicByName = @($nicByName)\n+    }\n+    $ret = $nicByMac + $nicByName\n+    if ($ret.Length -eq 0 -and $MustFindAdapter){\n+        Throw \"Could not find network adapters\"\n+    }\n+    return $ret\n+}\n+\n+\n+function Get-DataPortFromDataNetwork {\n+    $dataNetwork = Get-JujuCharmConfig -Scope \"os-data-network\"\n+    if (!$dataNetwork) {\n+        Write-JujuInfo \"os-data-network is not defined\"\n+        return $false\n+    }\n+\n+    $local_ip = Get-CharmState -Namespace \"novahyperv\" -Key \"local_ip\"\n+    $ifIndex = Get-CharmState -Namespace \"novahyperv\" -Key \"dataNetworkIfindex\"\n+\n+    if($local_ip -and $ifIndex){\n+        if((Confirm-LocalIP -IPaddress $ifIndex -ifIndex $ifIndex)){\n+            return Get-NetAdapter -ifindex $ifIndex\n+        }\n+    }\n+\n+    # If there is any network interface configured to use DHCP and did not get an IP address\n+    # we manually renew its lease and try to get an IP address before searching for the data network\n+    $interfaces = Get-CimInstance -Class win32_networkadapterconfiguration | Where-Object { \n+        $_.IPEnabled -eq $true -and $_.DHCPEnabled -eq $true -and $_.DHCPServer -eq \"255.255.255.255\"\n+    }\n+    if($interfaces){\n+        $interfaces.InterfaceIndex | Invoke-DHCPRenew -ErrorAction SilentlyContinue\n+    }\n+    $netDetails = $dataNetwork.Split(\"/\")\n+    $decimalMask = ConvertTo-Mask $netDetails[1]\n+\n+    $configuredAddresses = Get-NetIPAddress -AddressFamily IPv4\n+    foreach ($i in $configuredAddresses) {\n+        Write-JujuInfo (\"Checking {0} on interface {1}\" -f @($i.IPAddress, $i.InterfaceAlias))\n+        if ($i.PrefixLength -ne $netDetails[1]){\n+            continue\n+        }\n+        $network = Get-NetworkAddress $i.IPv4Address $decimalMask\n+        Write-JujuInfo (\"Network address for {0} is {1}\" -f @($i.IPAddress, $network))\n+        if ($network -eq $netDetails[0]){\n+            Set-CharmState -Namespace \"novahyperv\" -Key \"local_ip_mask\" -Value $i.PrefixLength\n+            Set-CharmState -Namespace \"novahyperv\" -Key \"local_ip\" -Value $i.IPAddress\n+            Set-CharmState -Namespace \"novahyperv\" -Key \"dataNetworkIfindex\" -Value $i.IfIndex\n+            return Get-NetAdapter -ifindex $i.IfIndex\n+        }\n+    }\n+    return $false\n+}\n+\n+\n+function Get-RealInterface {\n+    [CmdletBinding()]\n+    Param(\n+        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]\n+        [Microsoft.Management.Infrastructure.CimInstance]$interface\n+    )\n+    PROCESS {\n+        if($interface.DriverFileName -ne \"vmswitch.sys\") {\n+            return $interface\n+        }\n+        $realInterface = Get-NetAdapter | Where-Object {\n+            $_.MacAddress -eq $interface.MacAddress -and $_.ifIndex -ne $interface.ifIndex\n+        }\n+\n+        if(!$realInterface){\n+            Throw \"Failed to find interface attached to VMSwitch\"\n+        }\n+        return $realInterface[0]\n+    }\n+}\n+\n+\n+function Get-FallbackNetadapter {\n+    $name = Get-MainNetadapter\n+    $net = Get-NetAdapter -Name $name\n+    return $net\n+}\n+\n+\n+function Get-OVSDataPort {\n+    $dataPort = Get-DataPortFromDataNetwork\n+    if ($dataPort){\n+        return Get-RealInterface $dataPort\n+    }else{\n+        $port = Get-FallbackNetadapter\n+        $local_ip = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $port.IfIndex -ErrorAction SilentlyContinue\n+        if(!$local_ip){\n+            Throw \"failed to get fallback adapter IP address\"\n+        }\n+        Set-CharmState -Namespace \"novahyperv\" -Key \"local_ip_mask\" -Value $i.PrefixLength\n+        Set-CharmState -Namespace \"novahyperv\" -Key \"local_ip\" -Value $local_ip[0]\n+        Set-CharmState -Namespace \"novahyperv\" -Key \"dataNetworkIfindex\" -Value $port.IfIndex\n+    }\n+\n+    return Get-RealInterface $port\n+}\n+\n+\n+function Get-DataPort {\n+    $managementOS = Get-JujuCharmConfig -Scope \"vmswitch-management\"\n+    $networkType = Get-JujuCharmConfig -Scope 'network-type'\n+\n+    if ($networkType -eq \"ovs\"){\n+        Write-JujuInfo \"Trying to fetch OVS data port\"\n+        $dataPort = Get-OVSDataPort\n+        return @($dataPort[0], $managementOS)\n+    }\n+\n+    Write-JujuInfo \"Trying to fetch data port from config\"\n+    $nic = Get-InterfaceFromConfig\n+    if(!$nic) {\n+        $nic = Get-FallbackNetadapter\n+        $managementOS = $true\n+    }\n+    $nic = Get-RealInterface $nic[0]\n+    return @($nic, $managementOS)\n+}\n+\n+\n+function Start-ConfigureVMSwitch {\n+    $VMswitchName = Get-JujuVMSwitchName\n+    $vmswitch = Get-VMSwitch -SwitchType External -Name $VMswitchName -ErrorAction SilentlyContinue\n+\n+    if($vmswitch){\n+        return $true\n+    }\n+\n+    Set-HyperVMACS\n+\n+    $dataPort, $managementOS = Get-DataPort\n+    $VMswitches = Get-VMSwitch -SwitchType External -ErrorAction SilentlyContinue\n+    if ($VMswitches -and $VMswitches.Count -gt 0){\n+        foreach($i in $VMswitches){\n+            if ($i.NetAdapterInterfaceDescription -eq $dataPort.InterfaceDescription) {\n+                Rename-VMSwitch $i -NewName $VMswitchName\n+                Set-VMSwitch -Name $VMswitchName -AllowManagementOS $managementOS\n+                return $true\n+            }\n+        }\n+    }\n+\n+    Write-JujuInfo \"Adding new vmswitch: $VMswitchName\"\n+    New-VMSwitch -Name $VMswitchName -NetAdapterName $dataPort.Name -AllowManagementOS $managementOS\n+    return $true\n+}\n+\n+\n+function Install-Dependency {\n+    Param(\n+        [string]$URLConfigKey,\n+        [array]$ArgumentList\n+    )\n+\n+    $urlChecksum = Get-URLChecksum $URLConfigKey\n+    if ($urlChecksum['CHECKSUM'] -and $urlChecksum['HASHING_ALGORITHM']) {\n+        Install-Package -URL $urlChecksum['URL'] -Checksum $urlChecksum['CHECKSUM'] `\n+                        -HashingAlgorithm $urlChecksum['HASHING_ALGORITHM'] `\n+                        -ArgumentList $ArgumentList\n+    } else {\n+        Install-Package -URL $urlChecksum['URL'] -ArgumentList $ArgumentList\n+    }\n+}\n+\n+\n+function Install-FreeRDPConsole {\n+    Write-JujuLog \"Installing FreeRDP\"\n+\n+    Install-Dependency 'vc-2012-url' @('/q')\n+\n+    $freeRDPZip = Join-Path $FILES_DIR \"FreeRDP_powershell.zip\"\n+    $charmLibDir = Join-Path (Get-JujuCharmDir) \"lib\"\n+    Expand-ZipArchive $freeRDPZip $charmLibDir\n+\n+    # Copy wfreerdp.exe and DLL file to Windows folder\n+    $freeRDPFiles = @('wfreerdp.exe', 'libeay32.dll', 'ssleay32.dll')\n+    $windows = Join-Path $env:SystemDrive \"Windows\"\n+    foreach ($file in $freeRDPFiles) {\n+        Copy-Item \"$charmLibDir\\FreeRDP\\$file\" $windows\n+    }\n+\n+    $freeRDPModuleFolder = Join-Path $windows \"system32\\WindowsPowerShell\\v1.0\\Modules\\FreeRDP\"\n+    if (!(Test-Path $freeRDPModuleFolder)) {\n+        mkdir $freeRDPModuleFolder\n+    }\n+    Copy-Item \"$charmLibDir\\FreeRDP\\FreeRDP.psm1\" $freeRDPModuleFolder\n+    Remove-Item -Recurse \"$charmLibDir\\FreeRDP\"\n+\n+    Write-JujuLog \"Finished installing FreeRDP.\"\n+}\n+\n+\n+function Write-PipConfigFile {\n+    $pipDir = Join-Path $env:APPDATA \"pip\"\n+    if (Test-Path $pipDir){\n+        Remove-Item -Force -Recurse $pipDir\n+    }\n+\n+    $pypiMirror = Get-JujuCharmConfig -scope 'pypi-mirror'\n+    if ($pypiMirror -eq $null -or $pypiMirror.Length -eq 0) {\n+        Write-JujuLog (\"pypi-mirror config is not present. \" +\n+                       \"Will not generate the pip.ini file.\")\n+        return\n+    }\n+    mkdir $pipDir\n+    $pipIni = Join-Path $pipDir \"pip.ini\"\n+    New-Item -ItemType File $pipIni\n+\n+    $mirrors = $pypiMirror.Split()\n+    $hosts = @()\n+    foreach ($i in $mirrors){\n+        $h = ([System.Uri]$i).Host\n+        if ($h -in $hosts) {\n+            continue\n+        }\n+        $hosts += $h\n+    }\n+\n+    Set-IniFileValue \"index-url\" \"global\" $mirrors[0] $pipIni\n+    if ($mirrors.Length -gt 1){\n+        Set-IniFileValue \"extra-index-url\" \"global\" ($mirrors[1..$mirrors.Length] -Join \" \") $pipIni\n+    }\n+    Set-IniFileValue \"trusted-host\" \"install\" ($hosts -Join \" \") $pipIni\n+}\n+\n+\n+function Get-HypervADUser {\n+    $adUsername = Get-JujuCharmConfig -scope 'ad-user-name'\n+    if (!$adUsername) {\n+        $adUsername = \"hyper-v-user\"\n+    }\n+    return $adUsername\n+}\n+\n+\n+function Set-DevStackRelationParams {\n+    Param(\n+        [HashTable]$RelationParams\n+    )\n+\n+    $rids = Get-JujuRelationIds -Relation \"devstack\"\n+    foreach ($rid in $rids) {\n+        try {\n+            Set-JujuRelation -Settings $RelationParams -RelationId $rid\n+        } catch {\n+            Write-JujuError \"Failed to set DevStack relation parameters.\"\n+        }\n+    }\n+}\n+\n+\n+function Import-CloudbaseCert {\n+    $crt = Join-Path $FILES_DIR \"Cloudbase_signing.cer\"\n+    if (!(Test-Path $crt)){\n+        return $false\n+    }\n+    Import-Certificate $crt -StoreLocation LocalMachine -StoreName TrustedPublisher\n+}\n+\n+\n+# HOOKS FUNCTIONS\n+\n+function Start-InstallHook {\n+    # Set machine to use high performance settings.\n+    try {\n+        Set-PowerProfile -PowerProfile Performance\n+    } catch {\n+        # No need to error out the hook if this fails.\n+        Write-JujuWarning \"Failed to set power scheme.\"\n+    }\n+\n+    Start-TimeResync\n+\n+    if (!(Get-WindowsFeature hyper-v).Installed) {\n+        Install-WindowsFeature -Name Hyper-V -includemanagementtools\n+        Invoke-JujuReboot -Now\n+    }\n+\n+    # Disable firewall\n+    Start-ExternalCommand { netsh.exe advfirewall set allprofiles state off } -ErrorMessage \"Failed to disable firewall.\"\n+    # Disable automatic updates\n+    Write-JujuLog \"Disabling automatic updates\"\n+    $update_service = Get-WmiObject Win32_Service -Filter 'Name=\"wuauserv\"'\n+    $update_service.ChangeStartMode(\"Disabled\")\n+    $update_service.StopService()\n+\n+    # Allow self-signed drivers installation\n+    $bcdedit = \"C:\\bcdedit.txt\"\n+    $signingStatus = Get-JujuCharmConfig -Scope \"test-signing\"\n+    if ((!(Test-Path -path $bcdedit)) -and ($signingStatus)){\n+        bcdedit -set loadoptions DDISABLE_INTEGRITY_CHECKS\n+        bcdedit -set TESTSIGNING ON\n+        New-Item -ItemType file $bcdedit\n+        Invoke-JujuReboot\n+    }\n+    \n+    # Set Administrator user password\n+    $admin_user = \"Administrator\"\n+    $admin_password = Get-JujuCharmConfig -Scope 'administrator-password'\n+    net user $admin_user \"$admin_password\"\n+    \n+    Write-JujuLog \"Enable and start MSiSCSI\"\n+    $msiscsi_service = Get-WmiObject Win32_Service -Filter 'Name=\"MSiSCSI\"'\n+    $msiscsi_service.ChangeStartMode(\"Automatic\")\n+    $msiscsi_service.StartService()\n+\n+    Import-CloudbaseCert\n+    Start-ConfigureVMSwitch\n+    Write-PipConfigFile\n+\n+    # Install Git\n+    Install-Dependency 'git-url' @('/SILENT')\n+    Add-ToUserPath \"${env:ProgramFiles(x86)}\\Git\\cmd\"\n+    Add-ToSystemPath \"${env:ProgramFiles(x86)}\\Git\\cmd\"\n+\n+    # Install Python 2.7.x (x86)\n+    Install-Dependency 'python27-url' @('/qn')\n+    Add-ToUserPath \"${env:SystemDrive}\\Python27;${env:SystemDrive}\\Python27\\scripts\"\n+    Add-ToSystemPath \"${env:SystemDrive}\\Python27;${env:SystemDrive}\\Python27\\scripts\"\n+\n+    # Install FreeRDP Hyper-V console access\n+    $enableFreeRDP = Get-JujuCharmConfig -Scope 'enable-freerdp-console'\n+    if ($enableFreeRDP -eq $true) {\n+        Install-FreeRDPConsole\n+    }\n+\n+    Write-JujuLog \"Installing pip\"\n+    $conf_pip_version = Get-JujuCharmConfig -Scope 'pip-version'\n+    $tmpPath = Join-Path $env:TEMP \"get-pip.py\"\n+    Start-DownloadFile -Uri \"https://bootstrap.pypa.io/get-pip.py\" -SkipIntegrityCheck -OutFile $tmpPath\n+    Start-ExternalCommand -ScriptBlock { python $tmpPath $conf_pip_version } -ErrorMessage \"Failed to install pip.\"\n+    Remove-Item $tmpPath\n+    $version = Start-ExternalCommand { pip.exe --version } -ErrorMessage \"Failed to get pip version.\"\n+    Write-JujuLog \"Pip version: $version\"\n+\n+    Write-JujuLog \"Installing pip dependencies\"\n+    $pythonPkgs = Get-JujuCharmConfig -Scope 'extra-python-packages'\n+    if ($pythonPkgs) {\n+        $pythonPkgsArr = $pythonPkgs.Split()\n+        foreach ($pythonPkg in $pythonPkgsArr) {\n+            Write-JujuLog \"Installing $pythonPkg\"\n+            Start-ExternalCommand -ScriptBlock { pip install -U $pythonPkg } `\n+                                    -ErrorMessage \"Failed to install $pythonPkg\"\n+        }\n+    }\n+\n+    Write-JujuLog \"Installing posix_ipc library\"\n+    $zipPath = Join-Path $FILES_DIR \"posix_ipc.zip\"\n+    $posixIpcEgg = Join-Path $LIB_DIR \"posix_ipc-0.9.8-py2.7.egg-info\"\n+    if (!(Test-Path $posixIpcEgg)) {\n+        Expand-ZipArchive $zipPath $LIB_DIR\n+    }\n+\n+    Write-JujuLog \"Installing pywin32\"\n+    Start-ExternalCommand -ScriptBlock { pip install pywin32 } `\n+                          -ErrorMessage \"Failed to install pywin32.\"\n+    Start-ExternalCommand {\n+        python \"$PYTHON_DIR\\Scripts\\pywin32_postinstall.py\" -install\n+    } -ErrorMessage \"Failed to run pywin32_postinstall.py\"\n+    \n+    $zuulUrl = Get-JujuCharmConfig -Scope 'zuul-url'\n+    $zuulRef = Get-JujuCharmConfig -Scope 'zuul-ref'\n+    $zuulChange = Get-JujuCharmConfig -Scope 'zuul-change'\n+    $zuulProject = Get-JujuCharmConfig -Scope 'zuul-project'\n+\n+    if (!$zuulProject) {\n+        Write-JujuLog \"zuulProject is empty. Setting it to 'none'. Not running Git Prep\"\n+        $zuulProject = \"none\"\n+    }\n+    else {\n+        $gerritSite = $zuulUrl.Trim('/p')\n+        Start-GerritGitPrep -ZuulUrl $zuulUrl -GerritSite $gerritSite -ZuulRef $zuulRef `\n+                            -ZuulChange $zuulChange -ZuulProject $zuulProject\n+    }\n+    \n+    $gitEmail = Get-JujuCharmConfig -scope 'git-user-email'\n+    $gitName = Get-JujuCharmConfig -scope 'git-user-name'\n+    Start-ExternalCommand { git config --global user.email $gitEmail } `\n+        -ErrorMessage \"Failed to set git global user.email\"\n+    Start-ExternalCommand { git config --global user.name $gitName } `\n+        -ErrorMessage \"Failed to set git global user.name\"\n+    $zuulBranch = Get-JujuCharmConfig -scope 'zuul-branch'\n+    if (!$zuulBranch) {\n+        Write-JujuLog \"zuulBranch is empty. Setting branch to master\"\n+        $zuulBranch = \"master\"\n+    }\n+\n+    Write-JujuLog \"Initializing the environment\"\n+    Initialize-Environment -BranchName $zuulBranch -BuildFor $zuulProject\n+}\n+\n+\n+function Start-ADRelationJoinedHook {\n+    $hypervADUser = Get-HypervADUser\n+    $userGroup = @{$hypervADUser = @(\"CN=Users\")}\n+    $encUserGroup = Get-MarshaledObject $userGroup\n+    $constraintsList = @(\"Microsoft Virtual System Migration Service\", \"cifs\")\n+    $relationParams = @{\n+        'computername' = [System.Net.Dns]::GetHostName()\n+        'constraints' = Get-MarshaledObject $constraintsList\n+        'adusers' = $encUserGroup\n+    }\n+\n+    $rids = Get-JujuRelationIds -Relation \"ad-join\"\n+    foreach ($rid in $rids) {\n+        try {\n+            Set-JujuRelation -Settings $relationParams -RelationId $rid\n+        } catch {\n+            Write-JujuError \"Failed to set AD relation parameters.\"\n+        }\n+    }\n+}\n+\n+\n+function Start-RelationHooks {\n+    $charmServices = Get-CharmServices\n+    $networkType = Get-JujuCharmConfig -Scope 'network-type'\n+    if ($networkType -eq \"hyperv\") {\n+        $charmServices.Remove('neutron-ovs')\n+    } elseif ($networkType -eq \"ovs\") {\n+        $charmServices.Remove('neutron')\n+    } else {\n+        Throw \"ERROR: Unknown network type: '$networkType'.\"\n+    }\n+\n+    $adCtx = Get-ActiveDirectoryContext\n+    if (!$adCtx.Count) {\n+        Write-JujuLog \"AD context is not ready.\"\n+    } else {\n+        Start-JoinDomain\n+\n+        try {\n+            # Enable Live Migration\n+            Start-ExternalCommand { Enable-VMMigration } -ErrorMessage \"Failed to enable live migation.\" -ErrorAction silentlycontinue\n+            Start-ExternalCommand { Set-VMHost -useanynetworkformigration $true } -ErrorMessage \"Failed setting using any network for migration\" -ErrorAction silentlycontinue\n+            Start-ExternalCommand { Set-VMHost -VirtualMachineMigrationAuthenticationType Kerberos } -ErrorMessage \"Failed setting VM migartion authentication type\" -ErrorAction silentlycontinue\n+        } Catch {\n+            # TODO: better handling maybe move this to start-joindomain function\n+            Write-JujuLog \"Failed configuring migration, might be already set from a previous run\"\n+        }     \n+\n+        $adUserCred = @{\n+            'domain'   = $adCtx[\"domainName\"];\n+            'username' = $adCtx['adcredentials'][0]['username'];\n+            'password' = $adCtx['adcredentials'][0]['password']\n+        }\n+        $relationParams = @{'ad_credentials' = (Get-MarshaledObject $adUserCred);}\n+        Set-DevStackRelationParams $relationParams","path":"lib/Modules/HyperVCIHooks/HyperVCIHooks.psm1","position":1507,"original_position":1507,"commit_id":"91d753c0baac6369249402db8cb48e1c6f052308","original_commit_id":"91d753c0baac6369249402db8cb48e1c6f052308","user":{"login":"gabriel-samfira","id":903160,"avatar_url":"https://avatars.githubusercontent.com/u/903160?v=3","gravatar_id":"","url":"https://api.github.com/users/gabriel-samfira","html_url":"https://github.com/gabriel-samfira","followers_url":"https://api.github.com/users/gabriel-samfira/followers","following_url":"https://api.github.com/users/gabriel-samfira/following{/other_user}","gists_url":"https://api.github.com/users/gabriel-samfira/gists{/gist_id}","starred_url":"https://api.github.com/users/gabriel-samfira/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabriel-samfira/subscriptions","organizations_url":"https://api.github.com/users/gabriel-samfira/orgs","repos_url":"https://api.github.com/users/gabriel-samfira/repos","events_url":"https://api.github.com/users/gabriel-samfira/events{/privacy}","received_events_url":"https://api.github.com/users/gabriel-samfira/received_events","type":"User","site_admin":false},"body":"Why does devstack require AD user information?","created_at":"2017-02-07T10:54:41Z","updated_at":"2017-02-07T10:56:16Z","html_url":"https://github.com/cloudbase/hyperv-charm/pull/14#discussion_r99794462","pull_request_url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14","_links":{"self":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/comments/99794462"},"html":{"href":"https://github.com/cloudbase/hyperv-charm/pull/14#discussion_r99794462"},"pull_request":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14"}}},"pull_request":{"url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14","id":104864601,"html_url":"https://github.com/cloudbase/hyperv-charm/pull/14","diff_url":"https://github.com/cloudbase/hyperv-charm/pull/14.diff","patch_url":"https://github.com/cloudbase/hyperv-charm/pull/14.patch","issue_url":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/14","number":14,"state":"open","locked":false,"title":"New Branch Cambridge-2016","user":{"login":"capsali","id":15943551,"avatar_url":"https://avatars.githubusercontent.com/u/15943551?v=3","gravatar_id":"","url":"https://api.github.com/users/capsali","html_url":"https://github.com/capsali","followers_url":"https://api.github.com/users/capsali/followers","following_url":"https://api.github.com/users/capsali/following{/other_user}","gists_url":"https://api.github.com/users/capsali/gists{/gist_id}","starred_url":"https://api.github.com/users/capsali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/capsali/subscriptions","organizations_url":"https://api.github.com/users/capsali/orgs","repos_url":"https://api.github.com/users/capsali/repos","events_url":"https://api.github.com/users/capsali/events{/privacy}","received_events_url":"https://api.github.com/users/capsali/received_events","type":"User","site_admin":false},"body":"","created_at":"2017-02-06T19:26:59Z","updated_at":"2017-02-07T10:56:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9a80b626039c7d82c506bdc5dcb0e65a0e8793d9","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14/commits","review_comments_url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14/comments","review_comment_url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/14/comments","statuses_url":"https://api.github.com/repos/cloudbase/hyperv-charm/statuses/91d753c0baac6369249402db8cb48e1c6f052308","head":{"label":"capsali:cambridge-2016","ref":"cambridge-2016","sha":"91d753c0baac6369249402db8cb48e1c6f052308","user":{"login":"capsali","id":15943551,"avatar_url":"https://avatars.githubusercontent.com/u/15943551?v=3","gravatar_id":"","url":"https://api.github.com/users/capsali","html_url":"https://github.com/capsali","followers_url":"https://api.github.com/users/capsali/followers","following_url":"https://api.github.com/users/capsali/following{/other_user}","gists_url":"https://api.github.com/users/capsali/gists{/gist_id}","starred_url":"https://api.github.com/users/capsali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/capsali/subscriptions","organizations_url":"https://api.github.com/users/capsali/orgs","repos_url":"https://api.github.com/users/capsali/repos","events_url":"https://api.github.com/users/capsali/events{/privacy}","received_events_url":"https://api.github.com/users/capsali/received_events","type":"User","site_admin":false},"repo":{"id":80551171,"name":"hyperv-charm","full_name":"capsali/hyperv-charm","owner":{"login":"capsali","id":15943551,"avatar_url":"https://avatars.githubusercontent.com/u/15943551?v=3","gravatar_id":"","url":"https://api.github.com/users/capsali","html_url":"https://github.com/capsali","followers_url":"https://api.github.com/users/capsali/followers","following_url":"https://api.github.com/users/capsali/following{/other_user}","gists_url":"https://api.github.com/users/capsali/gists{/gist_id}","starred_url":"https://api.github.com/users/capsali/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/capsali/subscriptions","organizations_url":"https://api.github.com/users/capsali/orgs","repos_url":"https://api.github.com/users/capsali/repos","events_url":"https://api.github.com/users/capsali/events{/privacy}","received_events_url":"https://api.github.com/users/capsali/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/capsali/hyperv-charm","description":"Hyper-V Charm for CI","fork":true,"url":"https://api.github.com/repos/capsali/hyperv-charm","forks_url":"https://api.github.com/repos/capsali/hyperv-charm/forks","keys_url":"https://api.github.com/repos/capsali/hyperv-charm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/capsali/hyperv-charm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/capsali/hyperv-charm/teams","hooks_url":"https://api.github.com/repos/capsali/hyperv-charm/hooks","issue_events_url":"https://api.github.com/repos/capsali/hyperv-charm/issues/events{/number}","events_url":"https://api.github.com/repos/capsali/hyperv-charm/events","assignees_url":"https://api.github.com/repos/capsali/hyperv-charm/assignees{/user}","branches_url":"https://api.github.com/repos/capsali/hyperv-charm/branches{/branch}","tags_url":"https://api.github.com/repos/capsali/hyperv-charm/tags","blobs_url":"https://api.github.com/repos/capsali/hyperv-charm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/capsali/hyperv-charm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/capsali/hyperv-charm/git/refs{/sha}","trees_url":"https://api.github.com/repos/capsali/hyperv-charm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/capsali/hyperv-charm/statuses/{sha}","languages_url":"https://api.github.com/repos/capsali/hyperv-charm/languages","stargazers_url":"https://api.github.com/repos/capsali/hyperv-charm/stargazers","contributors_url":"https://api.github.com/repos/capsali/hyperv-charm/contributors","subscribers_url":"https://api.github.com/repos/capsali/hyperv-charm/subscribers","subscription_url":"https://api.github.com/repos/capsali/hyperv-charm/subscription","commits_url":"https://api.github.com/repos/capsali/hyperv-charm/commits{/sha}","git_commits_url":"https://api.github.com/repos/capsali/hyperv-charm/git/commits{/sha}","comments_url":"https://api.github.com/repos/capsali/hyperv-charm/comments{/number}","issue_comment_url":"https://api.github.com/repos/capsali/hyperv-charm/issues/comments{/number}","contents_url":"https://api.github.com/repos/capsali/hyperv-charm/contents/{+path}","compare_url":"https://api.github.com/repos/capsali/hyperv-charm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/capsali/hyperv-charm/merges","archive_url":"https://api.github.com/repos/capsali/hyperv-charm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/capsali/hyperv-charm/downloads","issues_url":"https://api.github.com/repos/capsali/hyperv-charm/issues{/number}","pulls_url":"https://api.github.com/repos/capsali/hyperv-charm/pulls{/number}","milestones_url":"https://api.github.com/repos/capsali/hyperv-charm/milestones{/number}","notifications_url":"https://api.github.com/repos/capsali/hyperv-charm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/capsali/hyperv-charm/labels{/name}","releases_url":"https://api.github.com/repos/capsali/hyperv-charm/releases{/id}","deployments_url":"https://api.github.com/repos/capsali/hyperv-charm/deployments","created_at":"2017-01-31T18:55:59Z","updated_at":"2017-01-31T18:56:01Z","pushed_at":"2017-02-06T19:26:17Z","git_url":"git://github.com/capsali/hyperv-charm.git","ssh_url":"git@github.com:capsali/hyperv-charm.git","clone_url":"https://github.com/capsali/hyperv-charm.git","svn_url":"https://github.com/capsali/hyperv-charm","homepage":null,"size":3231,"stargazers_count":0,"watchers_count":0,"language":"PowerShell","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"cloudbase:cambridge-2016","ref":"cambridge-2016","sha":"2bbcb84f63b362f50c5fd3c339c6ca7a127ba31c","user":{"login":"cloudbase","id":2104303,"avatar_url":"https://avatars.githubusercontent.com/u/2104303?v=3","gravatar_id":"","url":"https://api.github.com/users/cloudbase","html_url":"https://github.com/cloudbase","followers_url":"https://api.github.com/users/cloudbase/followers","following_url":"https://api.github.com/users/cloudbase/following{/other_user}","gists_url":"https://api.github.com/users/cloudbase/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudbase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudbase/subscriptions","organizations_url":"https://api.github.com/users/cloudbase/orgs","repos_url":"https://api.github.com/users/cloudbase/repos","events_url":"https://api.github.com/users/cloudbase/events{/privacy}","received_events_url":"https://api.github.com/users/cloudbase/received_events","type":"Organization","site_admin":false},"repo":{"id":37482946,"name":"hyperv-charm","full_name":"cloudbase/hyperv-charm","owner":{"login":"cloudbase","id":2104303,"avatar_url":"https://avatars.githubusercontent.com/u/2104303?v=3","gravatar_id":"","url":"https://api.github.com/users/cloudbase","html_url":"https://github.com/cloudbase","followers_url":"https://api.github.com/users/cloudbase/followers","following_url":"https://api.github.com/users/cloudbase/following{/other_user}","gists_url":"https://api.github.com/users/cloudbase/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudbase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudbase/subscriptions","organizations_url":"https://api.github.com/users/cloudbase/orgs","repos_url":"https://api.github.com/users/cloudbase/repos","events_url":"https://api.github.com/users/cloudbase/events{/privacy}","received_events_url":"https://api.github.com/users/cloudbase/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cloudbase/hyperv-charm","description":"Hyper-V Charm for CI","fork":false,"url":"https://api.github.com/repos/cloudbase/hyperv-charm","forks_url":"https://api.github.com/repos/cloudbase/hyperv-charm/forks","keys_url":"https://api.github.com/repos/cloudbase/hyperv-charm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cloudbase/hyperv-charm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cloudbase/hyperv-charm/teams","hooks_url":"https://api.github.com/repos/cloudbase/hyperv-charm/hooks","issue_events_url":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/events{/number}","events_url":"https://api.github.com/repos/cloudbase/hyperv-charm/events","assignees_url":"https://api.github.com/repos/cloudbase/hyperv-charm/assignees{/user}","branches_url":"https://api.github.com/repos/cloudbase/hyperv-charm/branches{/branch}","tags_url":"https://api.github.com/repos/cloudbase/hyperv-charm/tags","blobs_url":"https://api.github.com/repos/cloudbase/hyperv-charm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cloudbase/hyperv-charm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cloudbase/hyperv-charm/git/refs{/sha}","trees_url":"https://api.github.com/repos/cloudbase/hyperv-charm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cloudbase/hyperv-charm/statuses/{sha}","languages_url":"https://api.github.com/repos/cloudbase/hyperv-charm/languages","stargazers_url":"https://api.github.com/repos/cloudbase/hyperv-charm/stargazers","contributors_url":"https://api.github.com/repos/cloudbase/hyperv-charm/contributors","subscribers_url":"https://api.github.com/repos/cloudbase/hyperv-charm/subscribers","subscription_url":"https://api.github.com/repos/cloudbase/hyperv-charm/subscription","commits_url":"https://api.github.com/repos/cloudbase/hyperv-charm/commits{/sha}","git_commits_url":"https://api.github.com/repos/cloudbase/hyperv-charm/git/commits{/sha}","comments_url":"https://api.github.com/repos/cloudbase/hyperv-charm/comments{/number}","issue_comment_url":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/comments{/number}","contents_url":"https://api.github.com/repos/cloudbase/hyperv-charm/contents/{+path}","compare_url":"https://api.github.com/repos/cloudbase/hyperv-charm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cloudbase/hyperv-charm/merges","archive_url":"https://api.github.com/repos/cloudbase/hyperv-charm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cloudbase/hyperv-charm/downloads","issues_url":"https://api.github.com/repos/cloudbase/hyperv-charm/issues{/number}","pulls_url":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls{/number}","milestones_url":"https://api.github.com/repos/cloudbase/hyperv-charm/milestones{/number}","notifications_url":"https://api.github.com/repos/cloudbase/hyperv-charm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cloudbase/hyperv-charm/labels{/name}","releases_url":"https://api.github.com/repos/cloudbase/hyperv-charm/releases{/id}","deployments_url":"https://api.github.com/repos/cloudbase/hyperv-charm/deployments","created_at":"2015-06-15T18:21:34Z","updated_at":"2016-03-16T08:56:03Z","pushed_at":"2017-02-06T19:27:00Z","git_url":"git://github.com/cloudbase/hyperv-charm.git","ssh_url":"git@github.com:cloudbase/hyperv-charm.git","clone_url":"https://github.com/cloudbase/hyperv-charm.git","svn_url":"https://github.com/cloudbase/hyperv-charm","homepage":null,"size":3193,"stargazers_count":0,"watchers_count":0,"language":"PowerShell","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":2,"forks":4,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14"},"html":{"href":"https://github.com/cloudbase/hyperv-charm/pull/14"},"issue":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/14"},"comments":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/issues/14/comments"},"review_comments":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14/comments"},"review_comment":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/pulls/14/commits"},"statuses":{"href":"https://api.github.com/repos/cloudbase/hyperv-charm/statuses/91d753c0baac6369249402db8cb48e1c6f052308"}}}}
{"actor":{"display_login":"gabriel-samfira"}}