Andrew Bogott has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/405373 )

Change subject: openstack horizon: rough in manifests for source deploy of 
Horizon 'ocata'
......................................................................

openstack horizon: rough in manifests for source deploy of Horizon 'ocata'

At the moment I'm only trying to get a stock Horizon install; will add our
customizations later (ideally via scap rather than puppet)

Bug: T168470
Change-Id: Ie3cc3df3b48a4933672f98d574551768e4f82314
---
M hieradata/role/common/deployment_server.yaml
A modules/openstack/files/ocata/horizon/disabled_policy.json
A modules/openstack/files/ocata/horizon/glance_policy.json
A modules/openstack/files/ocata/horizon/keystone_policy.json
A modules/openstack/files/ocata/horizon/nova_policy.json
A modules/openstack/manifests/horizon/source_deploy.pp
A modules/openstack/templates/ocata/horizon/labtesthorizon.wikimedia.org.erb
A modules/openstack/templates/ocata/horizon/local_settings.py.erb
A modules/openstack/templates/ocata/horizon/newhorizon.wikimedia.org.erb
9 files changed, 1,069 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/puppet 
refs/changes/73/405373/1

diff --git a/hieradata/role/common/deployment_server.yaml 
b/hieradata/role/common/deployment_server.yaml
index 1921c65..d8ed935 100644
--- a/hieradata/role/common/deployment_server.yaml
+++ b/hieradata/role/common/deployment_server.yaml
@@ -141,6 +141,8 @@
     repository: operations/software/servermon
   striker/deploy:
     repository: labs/striker/deploy
+  horizon/deploy:
+    repository: openstack/horizon/deploy
   tilerator/deploy:
     repository: maps/tilerator/deploy
 #    lvs_service: tilerator
diff --git a/modules/openstack/files/ocata/horizon/disabled_policy.json 
b/modules/openstack/files/ocata/horizon/disabled_policy.json
new file mode 100644
index 0000000..92503af
--- /dev/null
+++ b/modules/openstack/files/ocata/horizon/disabled_policy.json
@@ -0,0 +1,4 @@
+{
+    "context_is_admin":  "!",
+    "default": "!"
+}
diff --git a/modules/openstack/files/ocata/horizon/glance_policy.json 
b/modules/openstack/files/ocata/horizon/glance_policy.json
new file mode 100644
index 0000000..ce70040
--- /dev/null
+++ b/modules/openstack/files/ocata/horizon/glance_policy.json
@@ -0,0 +1,52 @@
+{
+    "context_is_admin":  "role:admin",
+    "admin_or_owner":  "is_admin:True or project_id:%(project_id)s",
+    "default": "role:admin",
+
+    "add_image": "role:admin",
+    "delete_image": "role:admin",
+    "get_image": "",
+    "get_images": "",
+    "modify_image": "role:admin",
+    "publicize_image": "role:admin",
+    "copy_from": "role:admin",
+
+    "download_image": "",
+    "upload_image": "role:admin",
+
+    "delete_image_location": "role:admin",
+    "get_image_location": "",
+    "set_image_location": "role:admin",
+
+    "add_member": "role:admin",
+    "delete_member": "role:admin",
+    "get_member": "",
+    "get_members": "",
+    "modify_member": "role:admin",
+
+    "manage_image_cache": "role:admin",
+
+    "get_task": "",
+    "get_tasks": "",
+    "add_task": "role:admin",
+    "modify_task": "role:admin",
+
+    "get_metadef_namespace": "",
+    "get_metadef_namespaces":"",
+    "modify_metadef_namespace":"role:admin",
+    "add_metadef_namespace":"role:admin",
+    "delete_metadef_namespace":"role:admin",
+
+    "get_metadef_object":"",
+    "get_metadef_objects":"",
+    "modify_metadef_object":"role:admin",
+    "add_metadef_object":"role:admin",
+
+    "list_metadef_resource_types":"rule:admin_or_owner",
+    "add_metadef_resource_type_association":"",
+
+    "get_metadef_property":"",
+    "get_metadef_properties":"",
+    "modify_metadef_property":"role:admin",
+    "add_metadef_property":"role:admin"
+}
diff --git a/modules/openstack/files/ocata/horizon/keystone_policy.json 
b/modules/openstack/files/ocata/horizon/keystone_policy.json
new file mode 100644
index 0000000..0bd9546
--- /dev/null
+++ b/modules/openstack/files/ocata/horizon/keystone_policy.json
@@ -0,0 +1,183 @@
+{
+    "admin_required": "role:admin or is_admin:1",
+    "service_role": "role:service",
+    "service_or_admin": "rule:admin_required or rule:service_role",
+    "owner" : "user_id:%(user_id)s",
+    "admin_or_owner": "rule:admin_required or rule:owner",
+    "token_subject": "user_id:%(target.token.user_id)s",
+    "admin_or_token_subject": "rule:admin_required or rule:token_subject",
+ 
+    "default": "rule:admin_required",
+ 
+    "identity:get_region": "",
+    "identity:list_regions": "rule:admin_required",
+    "identity:create_region": "rule:admin_required",
+    "identity:update_region": "rule:admin_required",
+    "identity:delete_region": "rule:admin_required",
+ 
+    "identity:get_service": "",
+    "identity:list_services": "",
+    "identity:create_service": "rule:admin_required",
+    "identity:update_service": "rule:admin_required",
+    "identity:delete_service": "rule:admin_required",
+ 
+    "identity:get_endpoint": "",
+    "identity:list_endpoints": "",
+    "identity:create_endpoint": "rule:admin_required",
+    "identity:update_endpoint": "rule:admin_required",
+    "identity:delete_endpoint": "rule:admin_required",
+ 
+    "identity:get_domain": "rule:admin_required",
+    "identity:list_domains": "rule:admin_required",
+    "identity:create_domain": "rule:admin_required",
+    "identity:update_domain": "rule:admin_required",
+    "identity:delete_domain": "rule:admin_required",
+ 
+    "identity:get_project": "rule:admin_required",
+    "identity:list_projects": "role:admin_required",
+    "identity:list_user_projects": "",
+    "identity:create_project": "rule:admin_required",
+    "identity:update_project": "rule:admin_required",
+    "identity:delete_project": "rule:admin_required",
+ 
+    "identity:get_user": "rule:admin_required",
+    "identity:list_users": "rule:admin_required",
+    "identity:create_user": "rule:admin_required",
+    "identity:update_user": "rule:admin_required",
+    "identity:delete_user": "rule:admin_required",
+    "identity:change_password": "rule:admin_or_owner",
+ 
+    "identity:get_group": "rule:admin_required",
+    "identity:list_groups": "rule:admin_required",
+    "identity:list_groups_for_user": "rule:admin_or_owner",
+    "identity:create_group": "rule:admin_required",
+    "identity:update_group": "rule:admin_required",
+    "identity:delete_group": "rule:admin_required",
+    "identity:list_users_in_group": "rule:admin_required",
+    "identity:remove_user_from_group": "rule:admin_required",
+    "identity:check_user_in_group": "rule:admin_required",
+    "identity:add_user_to_group": "rule:admin_required",
+ 
+    "identity:get_credential": "rule:admin_required",
+    "identity:list_credentials": "rule:admin_required",
+    "identity:create_credential": "rule:admin_required",
+    "identity:update_credential": "rule:admin_required",
+    "identity:delete_credential": "rule:admin_required",
+ 
+    "identity:ec2_get_credential": "rule:admin_required or (rule:owner and 
user_id:%(target.credential.user_id)s)",
+    "identity:ec2_list_credentials": "rule:admin_or_owner",
+    "identity:ec2_create_credential": "rule:admin_or_owner",
+    "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and 
user_id:%(target.credential.user_id)s)",
+ 
+    "identity:get_role": "",
+    "identity:list_roles": "rule:admin_required",
+    "identity:create_role": "rule:admin_required",
+    "identity:update_role": "rule:admin_required",
+    "identity:delete_role": "rule:admin_required",
+ 
+    "identity:check_grant": "rule:admin_required",
+    "identity:list_grants": "rule:admin_required",
+    "identity:create_grant": "rule:admin_required",
+    "identity:revoke_grant": "rule:admin_required",
+ 
+    "identity:list_role_assignments": "rule:admin_required",
+ 
+    "identity:get_policy": "rule:admin_required",
+    "identity:list_policies": "rule:admin_required",
+    "identity:create_policy": "rule:admin_required",
+    "identity:update_policy": "rule:admin_required",
+    "identity:delete_policy": "rule:admin_required",
+ 
+    "identity:check_token": "rule:admin_or_token_subject",
+    "identity:validate_token": "rule:service_admin_or_token_subject",
+    "identity:validate_token_head": "rule:service_or_admin",
+    "identity:revocation_list": "rule:service_or_admin",
+    "identity:revoke_token": "rule:admin_or_token_subject",
+ 
+    "identity:create_trust": "user_id:%(trust.trustor_user_id)s",
+    "identity:list_trusts": "rule:admin_required",
+    "identity:list_roles_for_trust": "",
+    "identity:get_role_for_trust": "",
+    "identity:delete_trust": "",
+ 
+    "identity:create_consumer": "rule:admin_required",
+    "identity:get_consumer": "rule:admin_required",
+    "identity:list_consumers": "rule:admin_required",
+    "identity:delete_consumer": "rule:admin_required",
+    "identity:update_consumer": "rule:admin_required",
+ 
+    "identity:authorize_request_token": "rule:admin_required",
+    "identity:list_access_token_roles": "rule:admin_required",
+    "identity:get_access_token_role": "rule:admin_required",
+    "identity:list_access_tokens": "rule:admin_required",
+    "identity:get_access_token": "rule:admin_required",
+    "identity:delete_access_token": "rule:admin_required",
+ 
+    "identity:list_projects_for_endpoint": "rule:admin_required",
+    "identity:add_endpoint_to_project": "rule:admin_required",
+    "identity:check_endpoint_in_project": "rule:admin_required",
+    "identity:list_endpoints_for_project": "rule:admin_required",
+    "identity:remove_endpoint_from_project": "rule:admin_required",
+ 
+    "identity:create_endpoint_group": "rule:admin_required",
+    "identity:list_endpoint_groups": "rule:admin_required",
+    "identity:get_endpoint_group": "rule:admin_required",
+    "identity:update_endpoint_group": "rule:admin_required",
+    "identity:delete_endpoint_group": "rule:admin_required",
+    "identity:list_projects_associated_with_endpoint_group": 
"rule:admin_required",
+    "identity:list_endpoints_associated_with_endpoint_group": 
"rule:admin_required",
+    "identity:get_endpoint_group_in_project": "rule:admin_required",
+    "identity:list_endpoint_groups_for_project": "rule:admin_required",
+    "identity:add_endpoint_group_to_project": "rule:admin_required",
+    "identity:remove_endpoint_group_from_project": "rule:admin_required",
+ 
+    "identity:create_identity_provider": "rule:admin_required",
+    "identity:list_identity_providers": "rule:admin_required",
+    "identity:get_identity_providers": "rule:admin_required",
+    "identity:update_identity_provider": "rule:admin_required",
+    "identity:delete_identity_provider": "rule:admin_required",
+ 
+    "identity:create_protocol": "rule:admin_required",
+    "identity:update_protocol": "rule:admin_required",
+    "identity:get_protocol": "rule:admin_required",
+    "identity:list_protocols": "rule:admin_required",
+    "identity:delete_protocol": "rule:admin_required",
+ 
+    "identity:create_mapping": "rule:admin_required",
+    "identity:get_mapping": "rule:admin_required",
+    "identity:list_mappings": "rule:admin_required",
+    "identity:delete_mapping": "rule:admin_required",
+    "identity:update_mapping": "rule:admin_required",
+ 
+    "identity:create_service_provider": "rule:admin_required",
+    "identity:list_service_providers": "rule:admin_required",
+    "identity:get_service_provider": "rule:admin_required",
+    "identity:update_service_provider": "rule:admin_required",
+    "identity:delete_service_provider": "rule:admin_required",
+ 
+    "identity:get_auth_catalog": "",
+    "identity:get_auth_projects": "",
+    "identity:get_auth_domains": "",
+ 
+    "identity:list_projects_for_groups": "",
+    "identity:list_domains_for_groups": "",
+ 
+    "identity:list_revoke_events": "",
+ 
+    "identity:create_policy_association_for_endpoint": "rule:admin_required",
+    "identity:check_policy_association_for_endpoint": "rule:admin_required",
+    "identity:delete_policy_association_for_endpoint": "rule:admin_required",
+    "identity:create_policy_association_for_service": "rule:admin_required",
+    "identity:check_policy_association_for_service": "rule:admin_required",
+    "identity:delete_policy_association_for_service": "rule:admin_required",
+    "identity:create_policy_association_for_region_and_service": 
"rule:admin_required",
+    "identity:check_policy_association_for_region_and_service": 
"rule:admin_required",
+    "identity:delete_policy_association_for_region_and_service": 
"rule:admin_required",
+    "identity:get_policy_for_endpoint": "rule:admin_required",
+    "identity:list_endpoints_for_policy": "rule:admin_required",
+ 
+    "identity:create_domain_config": "rule:admin_required",
+    "identity:get_domain_config": "rule:admin_required",
+    "identity:update_domain_config": "rule:admin_required",
+    "identity:delete_domain_config": "rule:admin_required"
+}
diff --git a/modules/openstack/files/ocata/horizon/nova_policy.json 
b/modules/openstack/files/ocata/horizon/nova_policy.json
new file mode 100644
index 0000000..92ff69d
--- /dev/null
+++ b/modules/openstack/files/ocata/horizon/nova_policy.json
@@ -0,0 +1,138 @@
+{
+    "context_is_admin":  [["role:admin"]],
+    "admin_or_member":  [["is_admin:True"], ["project_id:%(project_id)s"]],
+    "admin_or_projectadmin":  [["is_admin:True"], ["role:projectadmin"]],
+    "default": [["rule:admin_or_projectadmin"]],
+
+    "compute:create": "rule:admin_or_projectadmin",
+    "compute:delete": "rule:admin_or_projectadmin",
+    "compute:create:attach_network": "role:admin",
+    "compute:create:attach_volume": "role:admin",
+    "compute:rebuild": "!",
+    "compute:start": "rule:admin_or_projectadmin",
+    "compute:stop": "rule:admin_or_projectadmin",
+    "compute:snapshot": "!",
+    "compute:get_all": [],
+    "compute:get_console_output": "rule:admin_or_member",
+
+    "sudorule:list": "",
+    "sudorule:delete": "rule:admin_or_projectadmin",
+    "sudorule:create": "rule:admin_or_projectadmin",
+    "sudorule:modify": "rule:admin_or_projectadmin",
+
+    "admin_api": [["is_admin:True"]],
+    "compute_extension:accounts": [["rule:admin_api"]],
+    "compute_extension:admin_actions": [["rule:admin_api"]],
+    "compute_extension:admin_actions:pause": [["rule:admin_or_projectadmin"]],
+    "compute_extension:admin_actions:unpause": 
[["rule:admin_or_projectadmin"]],
+    "compute_extension:admin_actions:suspend": "!",
+    "compute_extension:shelve": "!",
+    "compute_extension:admin_actions:resume": [["rule:admin_or_projectadmin"]],
+    "compute_extension:admin_actions:lock": [["rule:admin_api"]],
+    "compute_extension:admin_actions:unlock": [["rule:admin_api"]],
+    "compute_extension:admin_actions:resetNetwork": [["rule:admin_api"]],
+    "compute_extension:admin_actions:injectNetworkInfo": [["rule:admin_api"]],
+    "compute_extension:admin_actions:createBackup": [["role:admin"]],
+    "compute_extension:admin_actions:migrateLive": [["rule:admin_api"]],
+    "compute_extension:admin_actions:resetState": [["rule:admin_api"]],
+    "compute_extension:admin_actions:migrate": [["rule:admin_api"]],
+    "compute_extension:aggregates": [["rule:admin_api"]],
+    "compute_extension:certificates": [],
+    "compute_extension:cloudpipe": [["rule:admin_api"]],
+    "compute_extension:console_output": [["rule:admin_or_projectadmin"]],
+    "compute_extension:consoles": [["rule:admin_api"]],
+    "compute_extension:createserverext": [["rule:admin_or_projectadmin"]],
+    "compute_extension:deferred_delete": [["rule:admin_or_projectadmin"]],
+    "compute_extension:disk_config": [["rule:admin_or_projectadmin"]],
+    "compute_extension:extended_server_attributes": [],
+    "compute_extension:extended_status": [],
+    "compute_extension:flavor_access": [],
+    "compute_extension:flavor_disabled": [],
+    "compute_extension:flavor_rxtx": [],
+    "compute_extension:flavor_swap": [],
+    "compute_extension:flavorextradata": [],
+    "compute_extension:flavorextraspecs": [],
+    "compute_extension:flavormanage": [["rule:admin_api"]],
+    "compute_extension:floating_ip_dns": [["rule:admin_or_projectadmin"]],
+    "compute_extension:floating_ip_pools": [["rule:admin_or_projectadmin"]],
+    "compute_extension:floating_ips": [["rule:admin_or_projectadmin"]],
+    "compute_extension:hosts": [["rule:admin_api"]],
+    "compute_extension:hypervisors": [["rule:admin_api"]],
+    "compute_extension:instance_usage_audit_log": [["rule:admin_api"]],
+    "compute_extension:keypairs": "!",
+    "compute_extension:keypairs:create": "!",
+    "compute_extension:multinic": [["rule:admin_or_projectadmin"]],
+    "compute_extension:networks": [],
+    "compute_extension:networks:view": [],
+    "compute_extension:quotas:show": [["rule:admin_or_member"]],
+    "compute_extension:quotas:update": [["rule:admin_api"]],
+    "compute_extension:quota_classes": [["rule:admin_or_projectadmin"]],
+    "compute_extension:rescue": [["rule:admin_or_projectadmin"]],
+    "compute_extension:security_groups": [["rule:admin_or_projectadmin"]],
+    "compute_extension:server_diagnostics": [["rule:admin_api"]],
+    "compute_extension:simple_tenant_usage:show": [["rule:admin_or_member"]],
+    "compute_extension:simple_tenant_usage:list": [["rule:admin_api"]],
+    "compute_extension:users": [["rule:admin_api"]],
+    "compute_extension:virtual_interfaces": [["rule:admin_or_projectadmin"]],
+    "compute_extension:virtual_storage_arrays": 
[["rule:admin_or_projectadmin"]],
+    "compute_extension:volumes": [["rule:admin_or_projectadmin"]],
+    "compute_extension:volumetypes": [["rule:admin_or_projectadmin"]],
+
+    "compute:volume_snapshot_create": "!",
+    "compute:volume_snapshot_delete": "!",
+    "compute:resize": "!",
+    "compute:confirm_resize": "!",
+    "compute:revert_resize": "!",
+
+    "volume:create": [["rule:admin_or_projectadmin"]],
+    "volume:get_all": [],
+    "volume:get_volume_metadata": [],
+    "volume:get_snapshot": "!",
+    "volume:get_all_snapshots": "!",
+    "volume:create_snapshot": "!",
+
+
+    "volume_extension:types_manage": [["rule:admin_api"]],
+    "volume_extension:types_extra_specs": [["rule:admin_api"]],
+    "volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
+    "volume_extension:snapshot_admin_actions:reset_status": 
[["rule:admin_api"]],
+    "volume_extension:volume_admin_actions:force_delete": [["rule:admin_api"]],
+
+
+    "network:get_all_networks": [],
+    "network:get_network": [],
+    "network:delete_network": [["rule:admin_api"]],
+    "network:disassociate_network": [["rule:admin_or_projectadmin"]],
+    "network:get_vifs_by_instance": [],
+    "network:allocate_for_instance": [["rule:admin_or_projectadmin"]],
+    "network:deallocate_for_instance": [["rule:admin_or_projectadmin"]],
+    "network:validate_networks": [],
+    "network:get_instance_uuids_by_ip_filter": [],
+
+    "network:get_floating_ip": [],
+    "network:get_floating_ip_pools": [],
+    "network:get_floating_ip_by_address": [],
+    "network:get_floating_ips_by_project": [],
+    "network:get_floating_ips_by_fixed_address": [],
+    "network:allocate_floating_ip": [["rule:admin_or_projectadmin"]],
+    "network:deallocate_floating_ip": [["rule:admin_or_projectadmin"]],
+    "network:associate_floating_ip": [["rule:admin_or_projectadmin"]],
+    "network:disassociate_floating_ip": [["rule:admin_or_projectadmin"]],
+
+    "network:get_fixed_ip": [],
+    "network:get_fixed_ip_by_address": [],
+    "network:add_fixed_ip_to_instance": [["rule:admin_or_projectadmin"]],
+    "network:remove_fixed_ip_from_instance": [["rule:admin_or_projectadmin"]],
+    "network:add_network_to_project": [["rule:admin_or_projectadmin"]],
+    "network:get_instance_nw_info": [],
+
+    "network:get_dns_domains": [],
+    "network:add_dns_entry": [["rule:admin_or_projectadmin"]],
+    "network:modify_dns_entry": [["rule:admin_or_projectadmin"]],
+    "network:delete_dns_entry": [["rule:admin_or_projectadmin"]],
+    "network:get_dns_entries_by_address": [],
+    "network:get_dns_entries_by_name": [],
+    "network:create_private_dns_domain": [["rule:admin_or_projectadmin"]],
+    "network:create_public_dns_domain": [["rule:admin_or_projectadmin"]],
+    "network:delete_dns_domain": [["rule:admin_or_projectadmin"]]
+}
diff --git a/modules/openstack/manifests/horizon/source_deploy.pp 
b/modules/openstack/manifests/horizon/source_deploy.pp
new file mode 100644
index 0000000..ecb285c
--- /dev/null
+++ b/modules/openstack/manifests/horizon/source_deploy.pp
@@ -0,0 +1,95 @@
+# The OpenStack Das2hboard Project
+# http://docs.openstack.org/developer/horizon/
+class openstack::horizon::service(
+    $version,
+    $nova_controller,
+    $wmflabsdotorg_admin,
+    $wmflabsdotorg_pass,
+    $dhcp_domain,
+    $ldap_user_pass,
+    $deploy_dir    = '/srv/deployment/horizon/deploy',
+    $venv_dir      = '/srv/deployment/horizon/venv',
+    $webserver_hostname = 'newhorizon.wikimedia.org'
+) {
+
+    include ::apache
+    include ::apache::mod::ssl
+    include ::apache::mod::wsgi
+    include ::apache::mod::rewrite
+    include ::apache::mod::headers
+    include ::memcached
+
+    require_package([
+        'python-wheel',
+        'python-virtualenv',
+        ]
+    )
+
+    file { '/etc/openstack-dashboard/local_settings.py':
+        content => 
template("openstack/${version}/horizon/local_settings.py.erb"),
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => [Service['apache2'], Exec['djangorefresh']],
+    }
+
+    # In the perfect future, Horizon policies will be the same
+    #  files that the respective services use.  In the meantime, though
+    #  it's useful to be able to disable not-yet-supported horizon features.
+    file { '/etc/openstack-dashboard/nova_policy.json':
+        source  => 
"puppet:///modules/openstack/${version}/horizon/nova_policy.json",
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => Service['apache2'],
+    }
+    file { '/etc/openstack-dashboard/glance_policy.json':
+        source  => 
"puppet:///modules/openstack/${version}/horizon/glance_policy.json",
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => Service['apache2'],
+    }
+
+    # We need a horizon-specific keystone policy because horizon does 
weird/special
+    #  things for admin_required policies which I don't totally understand.  
In particular,
+    #  some permissive policies here (e.g. "") cause Horizon to panic, not ask 
Keystone for permission,
+    #  and log out the user.
+    file { '/etc/openstack-dashboard/keystone_policy.json':
+        source  => 
"puppet:///modules/openstack/${version}/horizon/keystone_policy.json",
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => Service['apache2'],
+    }
+
+    file { '/etc/openstack-dashboard/designate_policy.json':
+        source  => 
"puppet:///modules/openstack/${version}/designate/policy.json",
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => Service['apache2'],
+    }
+
+    # This is a trivial policy file that forbids everything.  We'll use it
+    #  for services that we don't support to prevent Horizon from
+    #  displaying spurious panels.
+    file { '/etc/openstack-dashboard/disabled_policy.json':
+        source  => 
"puppet:///modules/openstack/${version}/horizon/disabled_policy.json",
+        owner   => 'horizon',
+        group   => 'horizon',
+        mode    => '0440',
+        require => Package['openstack-dashboard'],
+        notify  => Service['apache2'],
+    }
+
+    apache::site { $webserver_hostname:
+        content => 
template("openstack/${version}/horizon/${webserver_hostname}.erb"),
+        require => File['/etc/openstack-dashboard/local_settings.py'],
+    }
+}
diff --git 
a/modules/openstack/templates/ocata/horizon/labtesthorizon.wikimedia.org.erb 
b/modules/openstack/templates/ocata/horizon/labtesthorizon.wikimedia.org.erb
new file mode 100644
index 0000000..2f87666
--- /dev/null
+++ b/modules/openstack/templates/ocata/horizon/labtesthorizon.wikimedia.org.erb
@@ -0,0 +1,23 @@
+#####################################################################
+### THIS FILE IS MANAGED BY PUPPET
+#####################################################################
+# vim: filetype=apache
+
+# This config is behind misc-web which handles ssl for us.
+<VirtualHost *:80>
+    ServerName <%= @webserver_hostname %>
+
+    RewriteEngine on
+    RewriteCond %{HTTP:X-Forwarded-Proto} !https
+    RewriteCond %{REQUEST_URI} !^/status$
+    RewriteRule ^/(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} 
[R=301,E=ProtoRedirect]
+    Header always merge Vary X-Forwarded-Proto env=ProtoRedirect
+
+    WSGIScriptAlias / 
/usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
+    WSGIDaemonProcess horizon user=horizon group=horizon processes=3 threads=10
+    WSGIProcessGroup horizon
+    Alias /static /usr/share/openstack-dashboard/openstack_dashboard/static/
+    <Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi>
+        Require all granted
+    </Directory>
+</VirtualHost>
diff --git a/modules/openstack/templates/ocata/horizon/local_settings.py.erb 
b/modules/openstack/templates/ocata/horizon/local_settings.py.erb
new file mode 100644
index 0000000..1c0681b
--- /dev/null
+++ b/modules/openstack/templates/ocata/horizon/local_settings.py.erb
@@ -0,0 +1,549 @@
+#####################################################################
+### THIS FILE IS MANAGED BY PUPPET
+#####################################################################
+import os
+
+from django.utils.translation import ugettext_lazy as _
+
+from openstack_dashboard import exceptions
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+# Required for Django 1.5.
+# If horizon is running in production (DEBUG is False), set this
+# with the list of host/domain names that the application can serve.
+# For more information see:
+# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = ['horizon.wikimedia.org', ]
+
+AUTHENTICATION_PLUGINS = ['openstack_auth.plugin.wmtotp.WmtotpPlugin', 
'openstack_auth.plugin.token.TokenPlugin']
+
+# Set SSL proxy settings:
+# For Django 1.4+ pass this header from the proxy after terminating the SSL,
+# and don't forget to strip it from the client's request.
+# For more information see:
+# https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header
+# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
+
+# SESSION_TIMEOUT is in seconds and defaults to 1800.  Change to 7 days
+#  by default, and also support SESSION_SHORT_TIMEOUT of 30 minutes.
+#
+# SESSION_SHORT is a WMF hack.  SESSION_TIMEOUT is used in multiple
+#  places as a maximum in Horizon code, so it was easier to add
+#  a special case extra-short token for when the user does not
+#  check 'Remember me'.
+SESSION_SHORT_TIMEOUT = 1800
+SESSION_TIMEOUT = 604800
+SESSION_COOKIE_AGE = 604800
+CSRF_COOKIE_SECURE = True
+SESSION_COOKIE_SECURE = True
+
+# Overrides for OpenStack API versions. Use this setting to force the
+# OpenStack dashboard to use a specific API version for a given service API.
+# NOTE: The version should be formatted as it appears in the URL for the
+# service API. For example, The identity service APIs have inconsistent
+# use of the decimal point, so valid options would be "2.0" or "3".
+# OPENSTACK_API_VERSIONS = {
+#     "identity": 3,
+#     "volume": 2
+# }
+
+OPENSTACK_API_VERSIONS = {
+     "identity": 3,
+}
+
+# Set this to True if running on multi-domain model. When this is enabled, it
+# will require user to enter the Domain name in addition to username for login.
+# OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False
+
+# Overrides the default domain used when running on single-domain model
+# with Keystone V3. All entities will be created in the default domain.
+# OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default'
+
+# Set Console type:
+# valid options would be "AUTO", "VNC", "SPICE" or "RDP"
+# CONSOLE_TYPE = "AUTO"
+
+# Default OpenStack Dashboard configuration.
+HORIZON_CONFIG = {
+    'user_home': 'openstack_dashboard.views.get_user_home',
+    'ajax_queue_limit': 10,
+    'auto_fade_alerts': {
+        'delay': 3000,
+        'fade_duration': 1500,
+        'types': ['alert-success', 'alert-info']
+    },
+    'help_url': "https://wikitech.wikimedia.org/wiki/Horizon";,
+    'exceptions': {'recoverable': exceptions.RECOVERABLE,
+                   'not_found': exceptions.NOT_FOUND,
+                   'unauthorized': exceptions.UNAUTHORIZED},
+    'customization_module': 'horizon.overrides',
+}
+
+# Specify a regular expression to validate user passwords.
+# HORIZON_CONFIG["password_validator"] = {
+#     "regex": '.*',
+#     "help_text": _("Your password does not meet the requirements.")
+# }
+
+# Disable simplified floating IP address management for deployments with
+# multiple floating IP pools or complex network requirements.
+# HORIZON_CONFIG["simple_ip_management"] = False
+
+# Turn on browser autocompletion for the login form
+HORIZON_CONFIG["password_autocomplete"] = "on"
+
+LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))
+
+# Set custom secret key:
+# You can either set it to a specific value or you can let horizion generate a
+# default secret key that is unique on this machine, e.i. regardless of the
+# amount of Python WSGI workers (if used behind Apache+mod_wsgi): However, 
there
+# may be situations where you would want to set this explicitly, e.g. when
+# multiple dashboard instances are distributed on different machines (usually
+# behind a load-balancer). Either you have to make sure that a session gets all
+# requests routed to the same dashboard instance or you set the same SECRET_KEY
+# for all of them.
+from horizon.utils import secret_key
+SECRET_KEY = 
secret_key.generate_or_read_from_file('/var/lib/openstack-dashboard/secret_key')
+
+# We recommend you use memcached for development; otherwise after every reload
+# of the django development server, you will have to login again.
+SESSION_ENGINE='django.contrib.sessions.backends.cache'
+CACHES = {
+   'default': {
+       'BACKEND' : 'django.core.cache.backends.memcached.MemcachedCache',
+       'LOCATION' : '127.0.0.1:11000',
+   }
+}
+
+# Enable the Ubuntu theme if it is present.
+try:
+       from ubuntu_theme import *
+except ImportError:
+       pass
+
+# Default Ubuntu apache configuration uses /horizon as the application root.
+# Configure auth redirects here accordingly.
+LOGIN_URL='/auth/login/'
+LOGOUT_URL='/auth/logout/'
+LOGIN_REDIRECT_URL='/'
+
+# The Ubuntu package includes pre-compressed JS and compiled CSS to allow
+# offline compression by default.  To enable online compression, install
+# the node-less package and enable the following option.
+COMPRESS_OFFLINE = True
+COMPRESS_ENABLED = True
+
+# By default, validation of the HTTP Host header is disabled.  Production
+# installations should have this set accordingly.  For more information
+# see https://docs.djangoproject.com/en/dev/ref/settings/.
+ALLOWED_HOSTS = '*'
+
+# Send email to the console by default
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+# Or send them to /dev/null
+#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
+
+# Configure these for your outgoing email host
+# EMAIL_HOST = 'smtp.my-company.com'
+# EMAIL_PORT = 25
+# EMAIL_HOST_USER = 'djangomail'
+# EMAIL_HOST_PASSWORD = 'top-secret!'
+
+# For multiple regions uncomment this configuration, and add (endpoint, title).
+# AVAILABLE_REGIONS = [
+#     ('http://cluster1.example.com:5000/v2.0', 'cluster1'),
+#     ('http://cluster2.example.com:5000/v2.0', 'cluster2'),
+# ]
+
+OPENSTACK_HOST = "<%= @nova_controller %>"
+OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3"; % OPENSTACK_HOST
+OPENSTACK_KEYSTONE_DEFAULT_ROLE = "user"
+
+# Disable SSL certificate checks (useful for self-signed certificates):
+# OPENSTACK_SSL_NO_VERIFY = True
+
+# The CA certificate to use to verify SSL connections
+# OPENSTACK_SSL_CACERT = '/path/to/cacert.pem'
+
+# The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the
+# capabilities of the auth backend for Keystone.
+# If Keystone has been configured to use LDAP as the auth backend then set
+# can_edit_user to False and name to 'ldap'.
+#
+# TODO(tres): Remove these once Keystone has an API to identify auth backend.
+OPENSTACK_KEYSTONE_BACKEND = {
+    'name': 'native',
+    'can_edit_user': True,
+    'can_edit_group': True,
+    'can_edit_project': True,
+    'can_edit_domain': True,
+    'can_edit_role': True
+}
+
+#Setting this to True, will add a new "Retrieve Password" action on instance,
+#allowing Admin session password retrieval/decryption.
+#OPENSTACK_ENABLE_PASSWORD_RETRIEVE = False
+
+# The Xen Hypervisor has the ability to set the mount point for volumes
+# attached to instances (other Hypervisors currently do not). Setting
+# can_set_mount_point to True will add the option to set the mount point
+# from the UI.
+OPENSTACK_HYPERVISOR_FEATURES = {
+    'can_set_mount_point': False,
+    'can_set_password': False,
+}
+
+# The OPENSTACK_NEUTRON_NETWORK settings can be used to enable optional
+# services provided by neutron. Options currently available are load
+# balancer service, security groups, quotas, VPN service.
+OPENSTACK_NEUTRON_NETWORK = {
+    'enable_lb': False,
+    'enable_firewall': False,
+    'enable_quotas': True,
+    'enable_vpn': False,
+    # The profile_support option is used to detect if an external router can be
+    # configured via the dashboard. When using specific plugins the
+    # profile_support can be turned on if needed.
+    'profile_support': None,
+    #'profile_support': 'cisco',
+}
+
+# The OPENSTACK_IMAGE_BACKEND settings can be used to customize features
+# in the OpenStack Dashboard related to the Image service, such as the list
+# of supported image formats.
+# OPENSTACK_IMAGE_BACKEND = {
+#     'image_formats': [
+#         ('', ''),
+#         ('aki', _('AKI - Amazon Kernel Image')),
+#         ('ami', _('AMI - Amazon Machine Image')),
+#         ('ari', _('ARI - Amazon Ramdisk Image')),
+#         ('iso', _('ISO - Optical Disk Image')),
+#         ('qcow2', _('QCOW2 - QEMU Emulator')),
+#         ('raw', _('Raw')),
+#         ('vdi', _('VDI')),
+#         ('vhd', _('VHD')),
+#         ('vmdk', _('VMDK'))
+#     ]
+# }
+
+# The IMAGE_CUSTOM_PROPERTY_TITLES settings is used to customize the titles for
+# image custom property attributes that appear on image detail pages.
+IMAGE_CUSTOM_PROPERTY_TITLES = {
+    "architecture": _("Architecture"),
+    "kernel_id": _("Kernel ID"),
+    "ramdisk_id": _("Ramdisk ID"),
+    "image_state": _("Euca2ools state"),
+    "project_id": _("Project ID"),
+    "image_type": _("Image Type")
+}
+
+# OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints
+# in the Keystone service catalog. Use this setting when Horizon is running
+# external to the OpenStack environment. The default is 'publicURL'.
+#OPENSTACK_ENDPOINT_TYPE = "publicURL"
+
+# SECONDARY_ENDPOINT_TYPE specifies the fallback endpoint type to use in the
+# case that OPENSTACK_ENDPOINT_TYPE is not present in the endpoints
+# in the Keystone service catalog. Use this setting when Horizon is running
+# external to the OpenStack environment. The default is None.  This
+# value should differ from OPENSTACK_ENDPOINT_TYPE if used.
+#SECONDARY_ENDPOINT_TYPE = "publicURL"
+
+# The number of objects (Swift containers/objects or images) to display
+# on a single page before providing a paging element (a "more" link)
+# to paginate results.
+API_RESULT_LIMIT = 1000
+API_RESULT_PAGE_SIZE = 20
+
+# The timezone of the server. This should correspond with the timezone
+# of your entire OpenStack installation, and hopefully be in UTC.
+TIME_ZONE = "UTC"
+
+# When launching an instance, the menu of available flavors is
+# sorted by RAM usage, ascending. If you would like a different sort order,
+# you can provide another flavor attribute as sorting key. Alternatively, you
+# can provide a custom callback method to use for sorting. You can also provide
+# a flag for reverse sort. For more info, see
+# http://docs.python.org/2/library/functions.html#sorted
+# CREATE_INSTANCE_FLAVOR_SORT = {
+#     'key': 'name',
+#      # or
+#     'key': my_awesome_callback_method,
+#     'reverse': False,
+# }
+
+# The Horizon Policy Enforcement engine uses these values to load per service
+# policy rule files. The content of these files should match the files the
+# OpenStack services are using to determine role based access control in the
+# target installation.
+
+# Path to directory containing policy.json files
+POLICY_FILES_PATH = '/etc/openstack-dashboard/'
+# Map of local copy of service policy files
+POLICY_FILES = {
+    'identity': 'keystone_policy.json',
+    'compute': 'nova_policy.json',
+    'image': 'glance_policy.json',
+    'dns': 'designate_policy.json',
+    'volume': 'disabled_policy.json',
+    'telemetry': 'disabled_policy.json',
+    'orchestration': 'disabled_policy.json',
+    'network': 'disabled_policy.json',
+}
+
+# Trove user and database extension support. By default support for
+# creating users and databases on database instances is turned on.
+# To disable these extensions set the permission here to something
+# unusable such as ["!"].
+# TROVE_ADD_USER_PERMS = []
+# TROVE_ADD_DATABASE_PERMS = []
+
+LOGGING = {
+    'version': 1,
+    # When set to True this will disable all logging except
+    # for loggers specified in this configuration dictionary. Note that
+    # if nothing is specified here and disable_existing_loggers is True,
+    # django.db.backends will still log unless it is disabled explicitly.
+    'disable_existing_loggers': False,
+    'handlers': {
+        'null': {
+            'level': 'DEBUG',
+            'class': 'django.utils.log.NullHandler',
+        },
+        'console': {
+            # Set the level to "DEBUG" for verbose output logging.
+            'level': 'INFO',
+            'class': 'logging.StreamHandler',
+        },
+    },
+    'loggers': {
+        # Logging from django.db.backends is VERY verbose, send to null
+        # by default.
+        'django.db.backends': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'requests': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'horizon': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'openstack_dashboard': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'novaclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'cinderclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'keystoneclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'glanceclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'neutronclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'heatclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'ceilometerclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'troveclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'swiftclient': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'openstack_auth': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'nose.plugins.manager': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'django': {
+            'handlers': ['console'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+        'iso8601': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+    }
+}
+
+# 'direction' should not be specified for all_tcp/udp/icmp.
+# It is specified in the form.
+SECURITY_GROUP_RULES = {
+    'all_tcp': {
+        'name': 'ALL TCP',
+        'ip_protocol': 'tcp',
+        'from_port': '1',
+        'to_port': '65535',
+    },
+    'all_udp': {
+        'name': 'ALL UDP',
+        'ip_protocol': 'udp',
+        'from_port': '1',
+        'to_port': '65535',
+    },
+    'all_icmp': {
+        'name': 'ALL ICMP',
+        'ip_protocol': 'icmp',
+        'from_port': '-1',
+        'to_port': '-1',
+    },
+    'ssh': {
+        'name': 'SSH',
+        'ip_protocol': 'tcp',
+        'from_port': '22',
+        'to_port': '22',
+    },
+    'smtp': {
+        'name': 'SMTP',
+        'ip_protocol': 'tcp',
+        'from_port': '25',
+        'to_port': '25',
+    },
+    'dns': {
+        'name': 'DNS',
+        'ip_protocol': 'tcp',
+        'from_port': '53',
+        'to_port': '53',
+    },
+    'http': {
+        'name': 'HTTP',
+        'ip_protocol': 'tcp',
+        'from_port': '80',
+        'to_port': '80',
+    },
+    'pop3': {
+        'name': 'POP3',
+        'ip_protocol': 'tcp',
+        'from_port': '110',
+        'to_port': '110',
+    },
+    'imap': {
+        'name': 'IMAP',
+        'ip_protocol': 'tcp',
+        'from_port': '143',
+        'to_port': '143',
+    },
+    'ldap': {
+        'name': 'LDAP',
+        'ip_protocol': 'tcp',
+        'from_port': '389',
+        'to_port': '389',
+    },
+    'https': {
+        'name': 'HTTPS',
+        'ip_protocol': 'tcp',
+        'from_port': '443',
+        'to_port': '443',
+    },
+    'smtps': {
+        'name': 'SMTPS',
+        'ip_protocol': 'tcp',
+        'from_port': '465',
+        'to_port': '465',
+    },
+    'imaps': {
+        'name': 'IMAPS',
+        'ip_protocol': 'tcp',
+        'from_port': '993',
+        'to_port': '993',
+    },
+    'pop3s': {
+        'name': 'POP3S',
+        'ip_protocol': 'tcp',
+        'from_port': '995',
+        'to_port': '995',
+    },
+    'ms_sql': {
+        'name': 'MS SQL',
+        'ip_protocol': 'tcp',
+        'from_port': '1433',
+        'to_port': '1433',
+    },
+    'mysql': {
+        'name': 'MYSQL',
+        'ip_protocol': 'tcp',
+        'from_port': '3306',
+        'to_port': '3306',
+    },
+    'rdp': {
+        'name': 'RDP',
+        'ip_protocol': 'tcp',
+        'from_port': '3389',
+        'to_port': '3389',
+    },
+}
+
+FLAVOR_EXTRA_KEYS = {
+    'flavor_keys': [
+        ('quota:read_bytes_sec', _('Quota: Read bytes')),
+        ('quota:write_bytes_sec', _('Quota: Write bytes')),
+        ('quota:cpu_quota', _('Quota: CPU')),
+        ('quota:cpu_period', _('Quota: CPU period')),
+        ('quota:inbound_average', _('Quota: Inbound average')),
+        ('quota:outbound_average', _('Quota: Outbound average')),
+    ]
+}
+
+# WMF-specific branding
+SITE_BRANDING = 'Manage Wikimedia Labs'
+SITE_BRANDING_LINK = 'https://horizon.wikimedia.org'
+
+# WMF-specific security limits
+HORIZON_IMAGES_ALLOW_UPLOAD = False
+
+DESIGNATE = { 'records_use_fips': True }
+
+WMFLABSDOTORG_ADMIN_USERNAME = '<%= @wmflabsdotorg_admin %>'
+WMFLABSDOTORG_ADMIN_PASSWORD = '<%= @wmflabsdotorg_pass %>'
+INSTANCE_TLD = '<%= @dhcp_domain %>'
+
+# Disable instance consoles for Horizon users
+CONSOLE_TYPE = False
+
+PUPPETMASTER_API  = "https://<%= scope.call_function(:hiera, 
['labs_puppet_master']) %>:8140/future"
+PUPPET_CONFIG_BACKEND  = "http://<%= scope.call_function(:hiera, 
['labs_puppet_master']) %>:8101/v1"
+
+LDAP_USER = "uid=novaadmin,ou=people,dc=wikimedia,dc=org"
+LDAP_PROJECTS_BASE = "ou=projects,dc=wikimedia,dc=org"
+LDAP_USER_PASSWORD = "<%= @ldap_user_pass %>"
diff --git 
a/modules/openstack/templates/ocata/horizon/newhorizon.wikimedia.org.erb 
b/modules/openstack/templates/ocata/horizon/newhorizon.wikimedia.org.erb
new file mode 100644
index 0000000..c8c3ed2
--- /dev/null
+++ b/modules/openstack/templates/ocata/horizon/newhorizon.wikimedia.org.erb
@@ -0,0 +1,23 @@
+#####################################################################
+### THIS FILE IS MANAGED BY PUPPET
+#####################################################################
+# vim: filetype=apache
+
+# This config is behind misc-web which handles ssl for us.
+<VirtualHost *:80>
+    ServerName <%= @webserver_hostname %>
+
+    RewriteEngine on
+    RewriteCond %{HTTP:X-Forwarded-Proto} !https
+    RewriteCond %{REQUEST_URI} !^/status$
+    RewriteRule ^/(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} 
[R=301,E=ProtoRedirect]
+    Header always merge Vary X-Forwarded-Proto env=ProtoRedirect
+
+    WSGIScriptAlias / <%= @venv_dir 
%>/usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
+    WSGIDaemonProcess horizon user=horizon group=horizon processes=3 threads=10
+    WSGIProcessGroup horizon
+    Alias /static <%= @venv_dir 
%>/usr/share/openstack-dashboard/openstack_dashboard/static/
+    <Directory <%= @venv_dir 
%>/usr/share/openstack-dashboard/openstack_dashboard/wsgi>
+        Require all granted
+    </Directory>
+</VirtualHost>

-- 
To view, visit https://gerrit.wikimedia.org/r/405373
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ie3cc3df3b48a4933672f98d574551768e4f82314
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Andrew Bogott <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to