aboutsummaryrefslogtreecommitdiff
path: root/kolla/docker
diff options
context:
space:
mode:
Diffstat (limited to 'kolla/docker')
-rw-r--r--kolla/docker/aodh/aodh-api/Dockerfile.j216
-rw-r--r--kolla/docker/aodh/aodh-api/extend_start.sh8
-rw-r--r--kolla/docker/aodh/aodh-base/Dockerfile.j238
-rw-r--r--kolla/docker/aodh/aodh-base/aodh_sudoers1
-rwxr-xr-xkolla/docker/aodh/aodh-base/extend_start.sh15
-rw-r--r--kolla/docker/aodh/aodh-evaluator/Dockerfile.j215
-rw-r--r--kolla/docker/aodh/aodh-expirer/Dockerfile.j215
-rw-r--r--kolla/docker/aodh/aodh-listener/Dockerfile.j215
-rw-r--r--kolla/docker/aodh/aodh-notifier/Dockerfile.j215
-rw-r--r--kolla/docker/barbican/barbican-api/Dockerfile.j219
-rwxr-xr-xkolla/docker/barbican/barbican-api/extend_start.sh8
-rw-r--r--kolla/docker/barbican/barbican-base/Dockerfile.j236
-rw-r--r--kolla/docker/barbican/barbican-base/barbican_sudoers1
-rwxr-xr-xkolla/docker/barbican/barbican-base/extend_start.sh15
-rw-r--r--kolla/docker/barbican/barbican-keystone-listener/Dockerfile.j215
-rw-r--r--kolla/docker/barbican/barbican-worker/Dockerfile.j215
-rw-r--r--kolla/docker/base/Dockerfile.j2346
-rw-r--r--kolla/docker/base/apt_preferences1
-rw-r--r--kolla/docker/base/apt_preferences.debian4
-rw-r--r--kolla/docker/base/apt_preferences.ubuntu3
-rw-r--r--kolla/docker/base/ci-centos.repo75
-rw-r--r--kolla/docker/base/copy_cacerts.sh48
-rw-r--r--kolla/docker/base/curlrc8
-rw-r--r--kolla/docker/base/debian.sources13
-rw-r--r--kolla/docker/base/dnf.conf12
-rw-r--r--kolla/docker/base/dpkg_reducing_disk_footprint9
-rwxr-xr-xkolla/docker/base/healthcheck_curl12
-rwxr-xr-xkolla/docker/base/healthcheck_filemod15
-rwxr-xr-xkolla/docker/base/healthcheck_listen8
-rwxr-xr-xkolla/docker/base/healthcheck_port8
-rwxr-xr-xkolla/docker/base/healthcheck_socket10
-rw-r--r--kolla/docker/base/install_projects.sh14
-rw-r--r--kolla/docker/base/kolla_bashrc5
-rwxr-xr-xkolla/docker/base/kolla_patch.sh55
-rw-r--r--kolla/docker/base/set_configs.py759
-rw-r--r--kolla/docker/base/sources.list1
-rw-r--r--kolla/docker/base/start.sh27
-rw-r--r--kolla/docker/base/sudoers26
-rw-r--r--kolla/docker/base/ubuntu.sources24
-rw-r--r--kolla/docker/base/ubuntu.sources.arm6424
-rw-r--r--kolla/docker/bifrost/bifrost-base/Dockerfile.j276
-rw-r--r--kolla/docker/bifrost/bifrost-base/bifrost_sudoers1
-rw-r--r--kolla/docker/bifrost/bifrost-base/build_arg.yml7
-rw-r--r--kolla/docker/bifrost/bifrost-deploy/Dockerfile.j235
-rw-r--r--kolla/docker/blazar/blazar-api/Dockerfile.j218
-rw-r--r--kolla/docker/blazar/blazar-api/extend_start.sh8
-rw-r--r--kolla/docker/blazar/blazar-base/Dockerfile.j229
-rw-r--r--kolla/docker/blazar/blazar-base/extend_start.sh10
-rw-r--r--kolla/docker/blazar/blazar-manager/Dockerfile.j215
-rw-r--r--kolla/docker/ceilometer/ceilometer-base/Dockerfile.j243
-rw-r--r--kolla/docker/ceilometer/ceilometer-base/ceilometer_sudoers1
-rw-r--r--kolla/docker/ceilometer/ceilometer-base/extend_start.sh15
-rw-r--r--kolla/docker/ceilometer/ceilometer-central/Dockerfile.j215
-rw-r--r--kolla/docker/ceilometer/ceilometer-compute/Dockerfile.j234
-rw-r--r--kolla/docker/ceilometer/ceilometer-ipmi/Dockerfile.j227
-rw-r--r--kolla/docker/ceilometer/ceilometer-notification/Dockerfile.j218
-rw-r--r--kolla/docker/ceilometer/ceilometer-notification/extend_start.sh11
-rw-r--r--kolla/docker/cinder/cinder-api/Dockerfile.j222
-rw-r--r--kolla/docker/cinder/cinder-api/extend_start.sh14
-rw-r--r--kolla/docker/cinder/cinder-backup/Dockerfile.j233
-rw-r--r--kolla/docker/cinder/cinder-base/Dockerfile.j260
-rw-r--r--kolla/docker/cinder/cinder-base/cinder_sudoers1
-rw-r--r--kolla/docker/cinder/cinder-base/extend_start.sh13
-rw-r--r--kolla/docker/cinder/cinder-scheduler/Dockerfile.j215
-rw-r--r--kolla/docker/cinder/cinder-volume/Dockerfile.j258
-rw-r--r--kolla/docker/cinder/cinder-volume/cinder_sudoers5
-rw-r--r--kolla/docker/cinder/cinder-volume/extend_start.sh14
-rw-r--r--kolla/docker/cloudkitty/cloudkitty-api/Dockerfile.j221
-rw-r--r--kolla/docker/cloudkitty/cloudkitty-api/extend_start.sh9
-rw-r--r--kolla/docker/cloudkitty/cloudkitty-base/Dockerfile.j229
-rw-r--r--kolla/docker/cloudkitty/cloudkitty-base/extend_start.sh10
-rw-r--r--kolla/docker/cloudkitty/cloudkitty-processor/Dockerfile.j220
-rw-r--r--kolla/docker/collectd/Dockerfile.j281
-rw-r--r--kolla/docker/collectd/extend_start.sh8
-rw-r--r--kolla/docker/cron/Dockerfile.j235
-rw-r--r--kolla/docker/cron/extend_start.sh21
-rwxr-xr-xkolla/docker/cron/logrotate18
-rw-r--r--kolla/docker/cyborg/cyborg-agent/Dockerfile.j222
-rw-r--r--kolla/docker/cyborg/cyborg-api/Dockerfile.j219
-rw-r--r--kolla/docker/cyborg/cyborg-api/extend_start.sh8
-rw-r--r--kolla/docker/cyborg/cyborg-base/Dockerfile.j231
-rw-r--r--kolla/docker/cyborg/cyborg-base/cyborg_sudoers1
-rwxr-xr-xkolla/docker/cyborg/cyborg-base/extend_start.sh15
-rw-r--r--kolla/docker/cyborg/cyborg-conductor/Dockerfile.j213
-rw-r--r--kolla/docker/designate/designate-api/Dockerfile.j215
-rw-r--r--kolla/docker/designate/designate-backend-bind9/Dockerfile.j231
-rw-r--r--kolla/docker/designate/designate-base/Dockerfile.j233
-rw-r--r--kolla/docker/designate/designate-base/designate_sudoers1
-rw-r--r--kolla/docker/designate/designate-base/extend_start.sh10
-rw-r--r--kolla/docker/designate/designate-central/Dockerfile.j218
-rw-r--r--kolla/docker/designate/designate-central/extend_start.sh13
-rw-r--r--kolla/docker/designate/designate-mdns/Dockerfile.j215
-rw-r--r--kolla/docker/designate/designate-producer/Dockerfile.j215
-rw-r--r--kolla/docker/designate/designate-sink/Dockerfile.j215
-rw-r--r--kolla/docker/designate/designate-worker/Dockerfile.j227
-rw-r--r--kolla/docker/dnsmasq/Dockerfile.j219
-rw-r--r--kolla/docker/dnsmasq/extend_start.sh31
-rw-r--r--kolla/docker/etcd/Dockerfile.j231
-rw-r--r--kolla/docker/etcd/etcd_sudoers1
-rw-r--r--kolla/docker/etcd/extend_start.sh13
-rw-r--r--kolla/docker/fluentd/Dockerfile.j262
-rw-r--r--kolla/docker/fluentd/extend_start.sh20
-rw-r--r--kolla/docker/fluentd/fluentd_sudoers8
-rw-r--r--kolla/docker/glance/glance-api/Dockerfile.j238
-rw-r--r--kolla/docker/glance/glance-api/extend_start.sh27
-rw-r--r--kolla/docker/glance/glance-base/Dockerfile.j254
-rw-r--r--kolla/docker/glance/glance-base/etc/glance/rootwrap.conf27
-rw-r--r--kolla/docker/glance/glance-base/etc/glance/rootwrap.d/glance_cinder_store.filters12
-rw-r--r--kolla/docker/glance/glance-base/extend_start.sh10
-rw-r--r--kolla/docker/glance/glance-base/glance_sudoers1
-rw-r--r--kolla/docker/gnocchi/gnocchi-api/Dockerfile.j216
-rw-r--r--kolla/docker/gnocchi/gnocchi-api/extend_start.sh8
-rw-r--r--kolla/docker/gnocchi/gnocchi-base/Dockerfile.j256
-rw-r--r--kolla/docker/gnocchi/gnocchi-base/extend_start.sh15
-rw-r--r--kolla/docker/gnocchi/gnocchi-base/gnocchi_sudoers1
-rw-r--r--kolla/docker/gnocchi/gnocchi-metricd/Dockerfile.j215
-rw-r--r--kolla/docker/gnocchi/gnocchi-statsd/Dockerfile.j215
-rw-r--r--kolla/docker/grafana/Dockerfile.j242
-rw-r--r--kolla/docker/grafana/extend_start.sh8
-rw-r--r--kolla/docker/grafana/grafana_sudoers1
-rw-r--r--kolla/docker/hacluster/hacluster-base/Dockerfile.j222
-rw-r--r--kolla/docker/hacluster/hacluster-base/extend_start.sh8
-rw-r--r--kolla/docker/hacluster/hacluster-corosync/Dockerfile.j215
-rw-r--r--kolla/docker/hacluster/hacluster-pacemaker-remote/Dockerfile.j221
-rw-r--r--kolla/docker/hacluster/hacluster-pacemaker/Dockerfile.j221
-rw-r--r--kolla/docker/hacluster/hacluster-pcs/Dockerfile.j215
-rw-r--r--kolla/docker/haproxy/haproxy-ssh/Dockerfile.j242
-rw-r--r--kolla/docker/haproxy/haproxy-ssh/extend_start.sh37
-rwxr-xr-xkolla/docker/haproxy/haproxy-ssh/update-loadbalancer-cert.sh125
-rw-r--r--kolla/docker/haproxy/haproxy/Dockerfile.j226
-rw-r--r--kolla/docker/haproxy/haproxy/ensure_latest_config.sh14
-rw-r--r--kolla/docker/heat/heat-api-cfn/Dockerfile.j213
-rw-r--r--kolla/docker/heat/heat-api/Dockerfile.j216
-rw-r--r--kolla/docker/heat/heat-api/extend_start.sh17
-rw-r--r--kolla/docker/heat/heat-base/Dockerfile.j241
-rw-r--r--kolla/docker/heat/heat-base/extend_start.sh13
-rw-r--r--kolla/docker/heat/heat-engine/Dockerfile.j215
-rw-r--r--kolla/docker/horizon/Dockerfile.j274
-rw-r--r--kolla/docker/horizon/extend_start.sh248
-rw-r--r--kolla/docker/httpd/Dockerfile.j242
-rw-r--r--kolla/docker/httpd/extend_start.sh1
-rw-r--r--kolla/docker/httpd/httpd_setup.sh51
-rw-r--r--kolla/docker/ironic/ironic-api/Dockerfile.j220
-rw-r--r--kolla/docker/ironic/ironic-api/extend_start.sh19
-rw-r--r--kolla/docker/ironic/ironic-base/Dockerfile.j232
-rw-r--r--kolla/docker/ironic/ironic-base/extend_start.sh22
-rw-r--r--kolla/docker/ironic/ironic-base/ironic_sudoers3
-rw-r--r--kolla/docker/ironic/ironic-conductor/Dockerfile.j276
-rw-r--r--kolla/docker/ironic/ironic-prometheus-exporter/Dockerfile.j217
-rw-r--r--kolla/docker/ironic/ironic-pxe-filter/Dockerfile.j224
-rw-r--r--kolla/docker/ironic/ironic-pxe-filter/extend_start.sh6
-rw-r--r--kolla/docker/ironic/ironic-pxe/Dockerfile.j255
-rw-r--r--kolla/docker/ironic/ironic-pxe/extend_start.sh103
-rw-r--r--kolla/docker/ironic/ironic-pxe/tftp-map-file-template4
-rw-r--r--kolla/docker/iscsid/Dockerfile.j236
-rw-r--r--kolla/docker/iscsid/extend_start.sh7
-rw-r--r--kolla/docker/keepalived/Dockerfile.j234
-rw-r--r--kolla/docker/keepalived/check_alive.sh33
-rw-r--r--kolla/docker/keepalived/extend_start.sh6
-rw-r--r--kolla/docker/kerbside/kerbside-api/Dockerfile.j29
-rw-r--r--kolla/docker/kerbside/kerbside-api/extend_start.sh4
-rw-r--r--kolla/docker/kerbside/kerbside-base/Dockerfile.j238
-rw-r--r--kolla/docker/kerbside/kerbside-proxy/Dockerfile.j29
-rw-r--r--kolla/docker/kerbside/kerbside-proxy/extend_start.sh4
-rw-r--r--kolla/docker/keystone/keystone-base/Dockerfile.j245
-rw-r--r--kolla/docker/keystone/keystone-fernet/Dockerfile.j234
-rw-r--r--kolla/docker/keystone/keystone-fernet/extend_start.sh12
-rw-r--r--kolla/docker/keystone/keystone-fernet/fetch_fernet_tokens.py83
-rw-r--r--kolla/docker/keystone/keystone-fernet/keystone_bootstrap.sh43
-rw-r--r--kolla/docker/keystone/keystone-ssh/Dockerfile.j238
-rw-r--r--kolla/docker/keystone/keystone-ssh/extend_start.sh17
-rw-r--r--kolla/docker/keystone/keystone/Dockerfile.j245
-rw-r--r--kolla/docker/keystone/keystone/extend_start.sh46
-rw-r--r--kolla/docker/keystone/keystone/keystone_bootstrap.sh66
-rw-r--r--kolla/docker/kolla-toolbox/Dockerfile.j2129
-rw-r--r--kolla/docker/kolla-toolbox/ansible.cfg4
-rw-r--r--kolla/docker/kolla-toolbox/ansible_sudoers3
l---------kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.debian1
l---------kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.ubuntu1
-rw-r--r--kolla/docker/kolla-toolbox/extend_start.sh13
-rw-r--r--kolla/docker/kolla-toolbox/kolla_toolbox.sh16
-rw-r--r--kolla/docker/kolla-toolbox/requirements.yml14
-rw-r--r--kolla/docker/letsencrypt/letsencrypt-base/Dockerfile.j214
-rw-r--r--kolla/docker/letsencrypt/letsencrypt-base/extend_start.sh69
-rwxr-xr-xkolla/docker/letsencrypt/letsencrypt-lego/Dockerfile.j241
-rwxr-xr-xkolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh226
-rw-r--r--kolla/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh93
-rw-r--r--kolla/docker/macros.j297
-rw-r--r--kolla/docker/magnum/magnum-api/Dockerfile.j218
-rw-r--r--kolla/docker/magnum/magnum-api/extend_start.sh8
-rw-r--r--kolla/docker/magnum/magnum-base/Dockerfile.j231
-rw-r--r--kolla/docker/magnum/magnum-base/extend_start.sh10
-rw-r--r--kolla/docker/magnum/magnum-conductor/Dockerfile.j218
-rw-r--r--kolla/docker/manila/manila-api/Dockerfile.j223
-rw-r--r--kolla/docker/manila/manila-api/extend_start.sh9
-rw-r--r--kolla/docker/manila/manila-base/Dockerfile.j248
-rw-r--r--kolla/docker/manila/manila-base/extend_start.sh10
-rw-r--r--kolla/docker/manila/manila-base/manila_sudoers1
-rw-r--r--kolla/docker/manila/manila-data/Dockerfile.j231
-rw-r--r--kolla/docker/manila/manila-scheduler/Dockerfile.j215
-rw-r--r--kolla/docker/manila/manila-share/Dockerfile.j234
-rw-r--r--kolla/docker/mariadb-server/Dockerfile.j296
-rw-r--r--kolla/docker/mariadb-server/backup.sh73
-rw-r--r--kolla/docker/mariadb-server/backup_replica.sh149
-rw-r--r--kolla/docker/mariadb-server/extend_start.sh61
-rwxr-xr-xkolla/docker/mariadb-server/healthcheck_mariadb19
-rw-r--r--kolla/docker/mariadb-server/mariadb_sudoers1
-rw-r--r--kolla/docker/mariadb-server/security_reset.expect67
-rw-r--r--kolla/docker/masakari/masakari-api/Dockerfile.j216
-rwxr-xr-xkolla/docker/masakari/masakari-api/extend_start.sh8
-rw-r--r--kolla/docker/masakari/masakari-base/Dockerfile.j243
-rwxr-xr-xkolla/docker/masakari/masakari-base/extend_start.sh13
-rw-r--r--kolla/docker/masakari/masakari-engine/Dockerfile.j215
-rw-r--r--kolla/docker/masakari/masakari-monitors/Dockerfile.j255
-rw-r--r--kolla/docker/masakari/masakari-monitors/masakari_monitors_sudoers5
-rw-r--r--kolla/docker/memcached/Dockerfile.j220
-rw-r--r--kolla/docker/mistral/mistral-api/Dockerfile.j220
-rw-r--r--kolla/docker/mistral/mistral-api/extend_start.sh9
-rw-r--r--kolla/docker/mistral/mistral-base/Dockerfile.j238
-rw-r--r--kolla/docker/mistral/mistral-base/extend_start.sh10
-rw-r--r--kolla/docker/mistral/mistral-engine/Dockerfile.j215
-rw-r--r--kolla/docker/mistral/mistral-event-engine/Dockerfile.j215
-rw-r--r--kolla/docker/mistral/mistral-executor/Dockerfile.j215
-rw-r--r--kolla/docker/multipathd/Dockerfile.j221
-rw-r--r--kolla/docker/networking-baremetal/ironic-neutron-agent/Dockerfile.j215
-rw-r--r--kolla/docker/neutron/neutron-base/Dockerfile.j2104
-rw-r--r--kolla/docker/neutron/neutron-base/copy-wrappers9
-rw-r--r--kolla/docker/neutron/neutron-base/delete-wrappers6
-rw-r--r--kolla/docker/neutron/neutron-base/dnsmasq45
-rw-r--r--kolla/docker/neutron/neutron-base/extend_start.sh34
-rw-r--r--kolla/docker/neutron/neutron-base/haproxy45
-rw-r--r--kolla/docker/neutron/neutron-base/keepalived47
-rw-r--r--kolla/docker/neutron/neutron-base/neutron-keepalived-state-change40
-rw-r--r--kolla/docker/neutron/neutron-base/neutron_sudoers11
-rw-r--r--kolla/docker/neutron/neutron-base/radvd42
-rw-r--r--kolla/docker/neutron/neutron-bgp-dragent/Dockerfile.j224
-rw-r--r--kolla/docker/neutron/neutron-dhcp-agent/Dockerfile.j222
-rw-r--r--kolla/docker/neutron/neutron-dhcp-agent/extend_start.sh17
-rw-r--r--kolla/docker/neutron/neutron-l3-agent/Dockerfile.j232
-rw-r--r--kolla/docker/neutron/neutron-l3-agent/extend_start.sh39
-rw-r--r--kolla/docker/neutron/neutron-metadata-agent/Dockerfile.j222
-rw-r--r--kolla/docker/neutron/neutron-metadata-agent/extend_start.sh18
-rw-r--r--kolla/docker/neutron/neutron-metering-agent/Dockerfile.j215
-rw-r--r--kolla/docker/neutron/neutron-mlnx-agent/Dockerfile.j227
-rw-r--r--kolla/docker/neutron/neutron-openvswitch-agent/Dockerfile.j217
-rw-r--r--kolla/docker/neutron/neutron-ovn-agent/Dockerfile.j220
-rw-r--r--kolla/docker/neutron/neutron-ovn-vpn-agent/Dockerfile.j231
-rw-r--r--kolla/docker/neutron/neutron-server/Dockerfile.j233
-rw-r--r--kolla/docker/neutron/neutron-server/extend_start.sh34
-rw-r--r--kolla/docker/neutron/neutron-sriov-agent/Dockerfile.j217
-rw-r--r--kolla/docker/nova/nova-api/Dockerfile.j220
-rw-r--r--kolla/docker/nova/nova-api/extend_start.sh30
-rw-r--r--kolla/docker/nova/nova-base/Dockerfile.j286
-rw-r--r--kolla/docker/nova/nova-base/extend_start.sh10
-rw-r--r--kolla/docker/nova/nova-base/nova_sudoers1
-rw-r--r--kolla/docker/nova/nova-compute-ironic/Dockerfile.j223
-rw-r--r--kolla/docker/nova/nova-compute/Dockerfile.j2112
-rw-r--r--kolla/docker/nova/nova-compute/extend_start.sh5
-rw-r--r--kolla/docker/nova/nova-conductor/Dockerfile.j218
-rw-r--r--kolla/docker/nova/nova-conductor/extend_start.sh17
-rw-r--r--kolla/docker/nova/nova-libvirt-spice/Dockerfile.j2120
-rw-r--r--kolla/docker/nova/nova-libvirt-spice/extend_start.sh11
-rw-r--r--kolla/docker/nova/nova-libvirt-spice/kolla_nova_libvirt_start.sh12
-rw-r--r--kolla/docker/nova/nova-libvirt/Dockerfile.j2100
-rw-r--r--kolla/docker/nova/nova-libvirt/extend_start.sh11
-rw-r--r--kolla/docker/nova/nova-libvirt/kolla_nova_libvirt_start.sh12
-rw-r--r--kolla/docker/nova/nova-novncproxy/Dockerfile.j221
-rw-r--r--kolla/docker/nova/nova-scheduler/Dockerfile.j215
-rw-r--r--kolla/docker/nova/nova-serialproxy/Dockerfile.j215
-rw-r--r--kolla/docker/nova/nova-spicehtml5proxy/Dockerfile.j233
-rw-r--r--kolla/docker/nova/nova-ssh/Dockerfile.j234
-rw-r--r--kolla/docker/nova/nova-ssh/extend_start.sh16
-rw-r--r--kolla/docker/octavia/octavia-api/Dockerfile.j231
-rw-r--r--kolla/docker/octavia/octavia-api/extend_start.sh9
-rw-r--r--kolla/docker/octavia/octavia-base/Dockerfile.j248
-rw-r--r--kolla/docker/octavia/octavia-base/extend_start.sh17
-rw-r--r--kolla/docker/octavia/octavia-driver-agent/Dockerfile.j229
-rw-r--r--kolla/docker/octavia/octavia-health-manager/Dockerfile.j215
-rw-r--r--kolla/docker/octavia/octavia-housekeeping/Dockerfile.j215
-rw-r--r--kolla/docker/octavia/octavia-worker/Dockerfile.j215
-rw-r--r--kolla/docker/opensearch/opensearch-dashboards/Dockerfile.j226
-rw-r--r--kolla/docker/opensearch/opensearch-dashboards/extend_start.sh17
-rw-r--r--kolla/docker/opensearch/opensearch/Dockerfile.j232
-rw-r--r--kolla/docker/opensearch/opensearch/extend_start.sh8
-rw-r--r--kolla/docker/openstack-base/Dockerfile.j2213
-rw-r--r--kolla/docker/openvswitch/openvswitch-base/Dockerfile.j245
-rw-r--r--kolla/docker/openvswitch/openvswitch-base/extend_start.sh12
-rw-r--r--kolla/docker/openvswitch/openvswitch-base/ovs_wrapper.sh19
-rw-r--r--kolla/docker/openvswitch/openvswitch-db-server/Dockerfile.j216
-rw-r--r--kolla/docker/openvswitch/openvswitch-db-server/extend_start.sh15
-rw-r--r--kolla/docker/openvswitch/openvswitch-db-server/releasenotes/notes/drop-ovs-ensure-configured-7973d36969e157fa.yaml5
-rw-r--r--kolla/docker/openvswitch/openvswitch-vswitchd/Dockerfile.j216
-rw-r--r--kolla/docker/openvswitch/openvswitch-vswitchd/extend_start.sh8
-rw-r--r--kolla/docker/ovn/ovn-base/Dockerfile.j229
-rw-r--r--kolla/docker/ovn/ovn-base/extend_start.sh13
-rw-r--r--kolla/docker/ovn/ovn-controller/Dockerfile.j229
-rw-r--r--kolla/docker/ovn/ovn-controller/extend_start.sh6
-rw-r--r--kolla/docker/ovn/ovn-nb-db-server/Dockerfile.j231
-rw-r--r--kolla/docker/ovn/ovn-nb-db-server/extend_start.sh6
-rwxr-xr-xkolla/docker/ovn/ovn-nb-db-server/start_nb_db_server.sh28
-rw-r--r--kolla/docker/ovn/ovn-northd/Dockerfile.j229
-rw-r--r--kolla/docker/ovn/ovn-northd/extend_start.sh5
-rw-r--r--kolla/docker/ovn/ovn-sb-db-relay/Dockerfile.j218
-rw-r--r--kolla/docker/ovn/ovn-sb-db-relay/extend_start.sh6
-rw-r--r--kolla/docker/ovn/ovn-sb-db-server/Dockerfile.j231
-rw-r--r--kolla/docker/ovn/ovn-sb-db-server/extend_start.sh6
-rwxr-xr-xkolla/docker/ovn/ovn-sb-db-server/start_sb_db_server.sh29
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk-db/Dockerfile.j218
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk-db/extend_start.sh6
-rwxr-xr-xkolla/docker/ovsdpdk/ovsdpdk-db/start_ovsdb_server.sh30
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk-vswitchd/Dockerfile.j216
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk-vswitchd/extend_start.sh6
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk/Dockerfile.j240
-rw-r--r--kolla/docker/ovsdpdk/ovsdpdk/extend_start.sh10
-rw-r--r--kolla/docker/placement/placement-api/Dockerfile.j219
-rw-r--r--kolla/docker/placement/placement-api/extend_start.sh21
-rw-r--r--kolla/docker/placement/placement-base/Dockerfile.j244
-rw-r--r--kolla/docker/placement/placement-base/extend_start.sh15
-rw-r--r--kolla/docker/prometheus/prometheus-alertmanager/Dockerfile.j221
-rw-r--r--kolla/docker/prometheus/prometheus-base/Dockerfile.j217
-rw-r--r--kolla/docker/prometheus/prometheus-base/extend_start.sh9
-rw-r--r--kolla/docker/prometheus/prometheus-blackbox-exporter/Dockerfile.j236
-rw-r--r--kolla/docker/prometheus/prometheus-cadvisor/Dockerfile.j220
-rw-r--r--kolla/docker/prometheus/prometheus-elasticsearch-exporter/Dockerfile.j223
-rw-r--r--kolla/docker/prometheus/prometheus-libvirt-exporter/Dockerfile.j225
-rw-r--r--kolla/docker/prometheus/prometheus-memcached-exporter/Dockerfile.j223
-rw-r--r--kolla/docker/prometheus/prometheus-mtail/Dockerfile.j225
-rw-r--r--kolla/docker/prometheus/prometheus-mysqld-exporter/Dockerfile.j223
-rw-r--r--kolla/docker/prometheus/prometheus-node-exporter/Dockerfile.j223
-rw-r--r--kolla/docker/prometheus/prometheus-openstack-exporter/Dockerfile.j222
-rw-r--r--kolla/docker/prometheus/prometheus-openstack-network-exporter/Dockerfile.j224
-rw-r--r--kolla/docker/prometheus/prometheus-server/Dockerfile.j224
-rw-r--r--kolla/docker/prometheus/prometheus-valkey-exporter/Dockerfile.j225
-rw-r--r--kolla/docker/proxysql/Dockerfile.j243
-rw-r--r--kolla/docker/proxysql/kolla_extend_start21
-rw-r--r--kolla/docker/proxysql/kolla_proxysql_config_sync162
-rw-r--r--kolla/docker/rabbitmq/Dockerfile.j269
-rw-r--r--kolla/docker/rabbitmq/apt_preferences_rabbitmq.debian7
-rw-r--r--kolla/docker/rabbitmq/apt_preferences_rabbitmq.ubuntu7
-rw-r--r--kolla/docker/rabbitmq/extend_start.sh26
-rw-r--r--kolla/docker/rabbitmq/healthcheck_rabbitmq8
-rw-r--r--kolla/docker/skyline/skyline-apiserver/Dockerfile.j227
-rw-r--r--kolla/docker/skyline/skyline-apiserver/extend_start.sh11
-rw-r--r--kolla/docker/skyline/skyline-base/Dockerfile.j219
-rw-r--r--kolla/docker/skyline/skyline-base/extend_start.sh23
-rw-r--r--kolla/docker/skyline/skyline-console/Dockerfile.j230
-rw-r--r--kolla/docker/tacker/tacker-base/Dockerfile.j243
-rw-r--r--kolla/docker/tacker/tacker-base/extend_start.sh10
-rw-r--r--kolla/docker/tacker/tacker-conductor/Dockerfile.j215
-rw-r--r--kolla/docker/tacker/tacker-server/Dockerfile.j218
-rw-r--r--kolla/docker/tacker/tacker-server/extend_start.sh8
-rw-r--r--kolla/docker/tgtd/Dockerfile.j226
-rw-r--r--kolla/docker/trove/trove-api/Dockerfile.j216
-rw-r--r--kolla/docker/trove/trove-api/extend_start.sh8
-rw-r--r--kolla/docker/trove/trove-base/Dockerfile.j230
-rw-r--r--kolla/docker/trove/trove-base/extend_start.sh10
-rw-r--r--kolla/docker/trove/trove-conductor/Dockerfile.j215
-rw-r--r--kolla/docker/trove/trove-guestagent/Dockerfile.j215
-rw-r--r--kolla/docker/trove/trove-taskmanager/Dockerfile.j215
-rw-r--r--kolla/docker/valkey/valkey-base/Dockerfile.j217
-rw-r--r--kolla/docker/valkey/valkey-base/extend_start.sh9
-rw-r--r--kolla/docker/valkey/valkey-sentinel/Dockerfile.j226
-rw-r--r--kolla/docker/valkey/valkey-sentinel/extend_start.sh32
-rw-r--r--kolla/docker/valkey/valkey-server/Dockerfile.j223
-rw-r--r--kolla/docker/watcher/watcher-api/Dockerfile.j218
-rw-r--r--kolla/docker/watcher/watcher-api/extend_start.sh8
-rw-r--r--kolla/docker/watcher/watcher-applier/Dockerfile.j215
-rw-r--r--kolla/docker/watcher/watcher-base/Dockerfile.j229
-rw-r--r--kolla/docker/watcher/watcher-base/extend_start.sh10
-rw-r--r--kolla/docker/watcher/watcher-engine/Dockerfile.j215
370 files changed, 10879 insertions, 0 deletions
diff --git a/kolla/docker/aodh/aodh-api/Dockerfile.j2 b/kolla/docker/aodh/aodh-api/Dockerfile.j2
new file mode 100644
index 000000000..b420df814
--- /dev/null
+++ b/kolla/docker/aodh/aodh-api/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}aodh-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_aodh_extend_start
+RUN chmod 644 /usr/local/bin/kolla_aodh_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/aodh/aodh-api/extend_start.sh b/kolla/docker/aodh/aodh-api/extend_start.sh
new file mode 100644
index 000000000..575b0e584
--- /dev/null
+++ b/kolla/docker/aodh/aodh-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ aodh-dbsync
+ exit 0
+fi
diff --git a/kolla/docker/aodh/aodh-base/Dockerfile.j2 b/kolla/docker/aodh/aodh-base/Dockerfile.j2
new file mode 100644
index 000000000..519537468
--- /dev/null
+++ b/kolla/docker/aodh/aodh-base/Dockerfile.j2
@@ -0,0 +1,38 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='aodh') }}
+
+{% set aodh_base_packages = [
+] %}
+
+{{ macros.install_packages(aodh_base_packages | customizable("packages")) }}
+
+ADD aodh-base-archive /aodh-base-source
+
+{% set aodh_base_pip_packages = [
+ '/aodh',
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY aodh_sudoers /etc/sudoers.d/kolla_aodh_sudoers
+
+RUN ln -s aodh-base-source/* aodh \
+ && {{ macros.install_pip(aodh_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/aodh \
+ && cp /aodh/aodh/api/api-paste.ini /etc/aodh/ \
+ && chmod 644 /etc/aodh/api-paste.ini \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 640 /etc/sudoers.d/kolla_aodh_sudoers \
+ && touch /usr/local/bin/kolla_aodh_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_aodh_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_base_footer %}{% endblock %}
diff --git a/kolla/docker/aodh/aodh-base/aodh_sudoers b/kolla/docker/aodh/aodh-base/aodh_sudoers
new file mode 100644
index 000000000..2ec268c9d
--- /dev/null
+++ b/kolla/docker/aodh/aodh-base/aodh_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R aodh\: /var/lib/aodh/, /bin/chown -R aodh\: /var/lib/aodh/
diff --git a/kolla/docker/aodh/aodh-base/extend_start.sh b/kolla/docker/aodh/aodh-base/extend_start.sh
new file mode 100755
index 000000000..f3b1d2d49
--- /dev/null
+++ b/kolla/docker/aodh/aodh-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Create log dir for Aodh logs
+AODH_LOG_DIR="/var/log/kolla/aodh"
+if [[ ! -d "${AODH_LOG_DIR}" ]]; then
+ mkdir -p ${AODH_LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${AODH_LOG_DIR}) != "aodh:kolla" ]]; then
+ chown aodh:kolla ${AODH_LOG_DIR}
+fi
+if [[ $(stat -c %a ${AODH_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${AODH_LOG_DIR}
+fi
+
+. /usr/local/bin/kolla_aodh_extend_start
diff --git a/kolla/docker/aodh/aodh-evaluator/Dockerfile.j2 b/kolla/docker/aodh/aodh-evaluator/Dockerfile.j2
new file mode 100644
index 000000000..3d1f6a8b4
--- /dev/null
+++ b/kolla/docker/aodh/aodh-evaluator/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}aodh-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_evaluator_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_evaluator_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER aodh
diff --git a/kolla/docker/aodh/aodh-expirer/Dockerfile.j2 b/kolla/docker/aodh/aodh-expirer/Dockerfile.j2
new file mode 100644
index 000000000..9c426f82c
--- /dev/null
+++ b/kolla/docker/aodh/aodh-expirer/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}aodh-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_expirer_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_expirer_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER aodh
diff --git a/kolla/docker/aodh/aodh-listener/Dockerfile.j2 b/kolla/docker/aodh/aodh-listener/Dockerfile.j2
new file mode 100644
index 000000000..a6f4f3c0e
--- /dev/null
+++ b/kolla/docker/aodh/aodh-listener/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}aodh-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_listener_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_listener_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER aodh
diff --git a/kolla/docker/aodh/aodh-notifier/Dockerfile.j2 b/kolla/docker/aodh/aodh-notifier/Dockerfile.j2
new file mode 100644
index 000000000..c49f37441
--- /dev/null
+++ b/kolla/docker/aodh/aodh-notifier/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}aodh-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block aodh_notifier_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block aodh_notifier_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER aodh
diff --git a/kolla/docker/barbican/barbican-api/Dockerfile.j2 b/kolla/docker/barbican/barbican-api/Dockerfile.j2
new file mode 100644
index 000000000..e78f3ea63
--- /dev/null
+++ b/kolla/docker/barbican/barbican-api/Dockerfile.j2
@@ -0,0 +1,19 @@
+FROM {{ namespace }}/{{ image_prefix }}barbican-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block barbican_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_barbican_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_barbican_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block barbican_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER barbican
diff --git a/kolla/docker/barbican/barbican-api/extend_start.sh b/kolla/docker/barbican/barbican-api/extend_start.sh
new file mode 100755
index 000000000..c541d0343
--- /dev/null
+++ b/kolla/docker/barbican/barbican-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ barbican-db-manage upgrade
+ exit 0
+fi
diff --git a/kolla/docker/barbican/barbican-base/Dockerfile.j2 b/kolla/docker/barbican/barbican-base/Dockerfile.j2
new file mode 100644
index 000000000..89c44ef9a
--- /dev/null
+++ b/kolla/docker/barbican/barbican-base/Dockerfile.j2
@@ -0,0 +1,36 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block barbican_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='barbican', groups='nfast,hsmusers') }}
+
+{{ macros.install_packages(barbican_base_packages | customizable("packages")) }}
+
+{% set barbican_base_pip_packages = [
+ '/barbican',
+ 'pastedeploy',
+ 'python-barbicanclient',
+] %}
+
+ADD barbican-base-archive /barbican-base-source
+
+COPY barbican_sudoers /etc/sudoers.d/kolla_barbican_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s barbican-base-source/* barbican \
+ && {{ macros.install_pip(barbican_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/barbican \
+ && cp -r /barbican/etc/barbican/* /etc/barbican/ \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 640 /etc/sudoers.d/kolla_barbican_sudoers \
+ && touch /usr/local/bin/kolla_barbican_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_barbican_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block barbican_base_footer %}{% endblock %}
diff --git a/kolla/docker/barbican/barbican-base/barbican_sudoers b/kolla/docker/barbican/barbican-base/barbican_sudoers
new file mode 100644
index 000000000..26ae4710e
--- /dev/null
+++ b/kolla/docker/barbican/barbican-base/barbican_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R barbican\: /var/lib/barbican/, /bin/chown -R barbican\: /var/lib/barbican/
diff --git a/kolla/docker/barbican/barbican-base/extend_start.sh b/kolla/docker/barbican/barbican-base/extend_start.sh
new file mode 100755
index 000000000..16b41ad09
--- /dev/null
+++ b/kolla/docker/barbican/barbican-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Create log dir for Barbican logs
+LOG_DIR="/var/log/kolla/barbican"
+if [[ ! -d "${LOG_DIR}" ]]; then
+ mkdir -p ${LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${LOG_DIR}) != "barbican:kolla" ]]; then
+ chown barbican:kolla ${LOG_DIR}
+fi
+if [[ $(stat -c %a ${LOG_DIR}) != "755" ]]; then
+ chmod 755 ${LOG_DIR}
+fi
+
+. /usr/local/bin/kolla_barbican_extend_start
diff --git a/kolla/docker/barbican/barbican-keystone-listener/Dockerfile.j2 b/kolla/docker/barbican/barbican-keystone-listener/Dockerfile.j2
new file mode 100644
index 000000000..3329aed1b
--- /dev/null
+++ b/kolla/docker/barbican/barbican-keystone-listener/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}barbican-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block barbican_keystone_listener_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block barbican_keystone_listener_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER barbican
diff --git a/kolla/docker/barbican/barbican-worker/Dockerfile.j2 b/kolla/docker/barbican/barbican-worker/Dockerfile.j2
new file mode 100644
index 000000000..9cba55b6b
--- /dev/null
+++ b/kolla/docker/barbican/barbican-worker/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}barbican-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block barbican_worker_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block barbican_worker_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER barbican
diff --git a/kolla/docker/base/Dockerfile.j2 b/kolla/docker/base/Dockerfile.j2
new file mode 100644
index 000000000..0e2f3c1ab
--- /dev/null
+++ b/kolla/docker/base/Dockerfile.j2
@@ -0,0 +1,346 @@
+FROM {{ base_image }}:{{ base_distro_tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+RUN . /etc/os-release;\
+ if [ "${PRETTY_NAME#{{ supported_distro_name }}}" = "$PRETTY_NAME" ]; then \
+ echo "Only releases \"{{ supported_distro_name }}\" are supported on {{ base_distro }}"; false; \
+ fi
+
+# We use curl in this dockerfile so let configure it before first use
+COPY curlrc /root/.curlrc
+
+{% block base_lang %}
+# NOTE(yoctozepto): use a UTF-8 (Unicode) locale like standard image installs do
+# fixes issues arising from ascii fallback usage
+ENV LANG en_US.UTF-8
+{% endblock %}
+
+LABEL kolla_version="{{ kolla_version }}"
+
+{% import "macros.j2" as macros with context %}
+{% block base_header %}{% endblock %}
+
+ENV KOLLA_BASE_DISTRO={{ base_distro }} \
+ KOLLA_BASE_ARCH={{ base_arch }}
+
+#### Customize PS1 to be used with bash shell
+COPY kolla_bashrc /tmp/
+RUN cat /tmp/kolla_bashrc >> /etc/skel/.bashrc \
+ && cat /tmp/kolla_bashrc >> /root/.bashrc
+
+# PS1 var when used /bin/sh shell
+ENV PS1="$(tput bold)($(printenv KOLLA_SERVICE_NAME))$(tput sgr0)[$(id -un)@$(hostname -s) $(pwd)]$ "
+
+{% if base_package_type == 'rpm' %}
+# For RPM Variants, enable the correct repositories - this should all be done
+# in the base image so repos are consistent throughout the system. This also
+# enables to provide repo overrides at a later date in a simple fashion if we
+# desire such functionality. I think we will :)
+
+ENV KOLLA_RPM_OVS_VERSION=3.5 \
+ KOLLA_RPM_OVN_VERSION=26.03
+
+RUN cat /tmp/kolla_bashrc >> /etc/bashrc \
+ && sed -i 's|^\(override_install_langs=.*\)|# \1|' /etc/dnf/dnf.conf
+
+{% block base_dnf_conf %}
+{% block base_yum_conf %}
+
+COPY dnf.conf /etc/dnf/dnf.conf
+
+{% endblock %}
+{% endblock %}
+
+#### BEGIN REPO ENABLEMENT
+{% set base_yum_url_packages = [
+] %}
+
+{% set base_yum_repo_keys = [
+] %}
+
+{% block base_centos_repo_overrides_post_copy %}{% endblock %}
+
+# Install what is needed for en_US.UTF-8
+{% block base_centos_distro_sync_and_languages %}
+
+{% set base_centos_language_packages = [
+ 'langpacks-en',
+ 'glibc-all-langpacks'
+] %}
+
+# NOTE(hrw): this macro file drops all languages other than C.UTF-8 so horizon fails
+# https://bugzilla.redhat.com/show_bug.cgi?id=1729770
+RUN rm -f /etc/rpm/macros.image-language-conf \
+ && {{ macros.install_packages(base_centos_language_packages | customizable("centos_language_packages"), chain=True, clean=False) }} \
+ && {{ macros.rpm_security_update(clean_package_cache) }}
+
+{% endblock %}
+
+{{ macros.install_packages(base_yum_url_packages | customizable("yum_url_packages")) }}
+
+{% for key in base_yum_repo_keys | customizable('yum_repo_keys') %}
+{%- if loop.first %}RUN {% else %} && {% endif -%}
+ rpm --import {{ key }}
+{%- if not loop.last %} \{% endif %}
+{% endfor -%}
+
+{% for cmd in rpm_setup %}
+{{ cmd }}
+{% endfor %}
+
+{% block base_centos_repo_overrides_post_rpm %}{% endblock %}
+
+{% block base_centos_gpg_key_import %}
+{% endblock %}
+
+{% set base_centos_yum_repo_keys = [
+] %}
+
+{% set base_centos_yum_repo_packages = [
+ 'centos-release-ceph-squid',
+ 'centos-release-nfv-openvswitch',
+ 'epel-release',
+] %}
+
+# We need 'dnf-plugins-core' for 'dnf config-manager'
+{% set base_centos_yum_repo_packages = base_centos_yum_repo_packages + [
+ 'dnf-plugins-core'
+] %}
+
+RUN {{ macros.install_packages(base_centos_yum_repo_packages | customizable("centos_yum_repo_packages"), chain=True, clean=False) }}
+
+{%- for key in base_centos_yum_repo_keys | customizable('centos_yum_repo_keys') %} && rpm --import {{ key }} {% endfor %} \
+{% block base_centos_repo_overrides_post_yum %}{% endblock -%}
+ && {{ macros.rpm_security_update(clean_package_cache) }}
+
+#### END REPO ENABLEMENT
+
+{# We are back to the basic if conditional here which is:
+ if base_package_type == 'rpm' #}
+
+{% block base_redhat_binary_versionlock %}{% endblock %}
+
+{# NOTE(hrw): CentOS Stream 9 has curl-minimal, Rocky Linux 9 has curl so we do not install any #}
+{% set base_centos_packages = [
+ 'ca-certificates',
+ 'crypto-policies-scripts',
+ 'dumb-init',
+ 'findutils',
+ 'hostname',
+ 'iproute',
+ 'iscsi-initiator-utils',
+ 'lsof',
+ 'lvm2',
+ 'ncurses',
+ 'openssl',
+ 'procps-ng',
+ 'python3',
+ 'python3-pip',
+ 'python3-pyyaml',
+ 'socat',
+ 'sudo',
+ 'tar',
+ 'util-linux',
+ 'util-linux-user',
+ 'which',
+ 'patch'
+] %}
+
+# Install base packages
+{{ macros.enable_extra_repos(['epel']) }}
+{{ macros.install_packages(base_centos_packages | customizable("centos_packages") | customizable("centos_binary_packages") | customizable("centos_source_packages")) }}
+
+{# endif for base_package_type rpm #}
+{% elif base_package_type == 'deb' %}
+# This will prevent questions from being asked during the install
+ENV DEBIAN_FRONTEND noninteractive
+
+# Reducing disk footprint
+COPY dpkg_reducing_disk_footprint /etc/dpkg/dpkg.cfg.d/dpkg_reducing_disk_footprint
+
+{% block base_ubuntu_package_pre %}
+# curl and ca-certificates to fetch remote keys via http
+# gnupg to fetch keys directly from keyserver
+{% set base_ubuntu_package_pre_packages = [
+ 'adduser',
+ 'ca-certificates',
+ 'curl',
+ 'gnupg'
+] %}
+# ubuntu-cloud-keyring to install UCA packages
+{% if base_distro == 'ubuntu' %}
+{% set base_ubuntu_package_pre_packages = base_ubuntu_package_pre_packages + [
+ 'ubuntu-cloud-keyring'
+] %}
+{% endif %}
+{{ macros.install_packages(base_ubuntu_package_pre_packages | customizable("base_ubuntu_package_pre_packages")) }}
+{% endblock %}
+
+# Customize PS1 bash shell
+# - enlarge 'system users' range so 'haproxy' package will not complain
+# see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=939470
+# - enlarge 'system groups' range so 'hacluster' user added in
+# https://review.opendev.org/c/openstack/kolla/+/802671
+# can be in 'haclient' group with same high uid
+RUN cat /tmp/kolla_bashrc >> /etc/bash.bashrc \
+ && sed -i -e s/#*LAST_SYSTEM_UID=999/LAST_SYSTEM_UID=59999/g \
+ -e s/#*LAST_SYSTEM_GID=999/LAST_SYSTEM_GID=59999/g /etc/adduser.conf
+
+{# NOTE(mnasiadka): Ubuntu arm64 uses ports.ubuntu.com #}
+{% block base_ubuntu_package_sources_list %}
+{% if base_distro == "ubuntu" and base_arch == 'aarch64' %}
+COPY {{ base_distro }}.sources.arm64 /etc/apt/sources.list.d/{{ base_distro }}.sources
+{% else %}
+COPY {{ base_distro }}.sources /etc/apt/sources.list.d/
+{% endif %}
+COPY sources.list /etc/apt/sources.list.d/kolla-custom.list
+{% endblock %}
+
+{% block base_debian_after_sources_list %}{% endblock %}
+
+{# install Debian Openstack repos - they are not mirrored on CI #}
+{% if base_distro == 'debian' %}
+
+RUN apt update \
+ && apt install -y --no-install-recommends extrepo \
+ && extrepo enable openstack_{{ openstack_release_codename | lower }} \
+ && apt purge -y extrepo \
+ && apt --purge autoremove -y \
+ && apt clean
+
+{% endif %}
+
+{% block base_ubuntu_package_apt_preferences %}
+COPY apt_preferences.{{ base_distro }} /etc/apt/preferences
+COPY apt_preferences /etc/apt/preferences.d/kolla-custom
+{% endblock %}
+
+{% set base_apt_packages = [
+ 'apt-utils',
+ 'dumb-init',
+ 'systemd-standalone-sysusers',
+ 'gawk',
+ 'iproute2',
+ 'kmod',
+ 'lsof',
+ 'lvm2',
+ 'netbase',
+ 'open-iscsi',
+ 'procps',
+ 'python3',
+ 'python3-pip',
+ 'python3-yaml',
+ 'socat',
+ 'sudo',
+ 'tgt',
+ 'patch'
+] %}
+
+{% set base_apt_keys = [
+ {'name': 'erlang-ppa', 'keyid': 'F77F1EDA57EBB1CC'},
+ {'name': 'rabbitmq', 'keyid': '6B73A36E6026DFCA'},
+ {'name': 'haproxy', 'keyid': 'CFFB779AADC995E4F350A060505D97A41C61B9CD'},
+] %}
+
+{# NOTE(hrw): type field defaults to 'asc' which is used for single keys #}
+{% set base_remote_apt_keys = [
+ {'name': 'docker-ce', 'url': 'https://download.docker.com/linux/debian/gpg'},
+ {'name': 'fluentd', 'url': 'https://fluentd.cdn.cncf.io/GPG-KEY-fluent-package'},
+ {'name': 'grafana', 'url': 'https://rpm.grafana.com/gpg.key'},
+ {'name': 'mariadb', 'url': 'https://downloads.mariadb.com/MariaDB/mariadb-keyring-2019.gpg', 'type': 'gpg'},
+ {'name': 'opensearch', 'url': 'https://artifacts.opensearch.org/publickeys/opensearch-release.pgp'},
+ {'name': 'proxysql', 'url': 'https://repo.proxysql.com/ProxySQL/proxysql-3.0.x/repo_pub_key'},
+] %}
+
+{% block base_ubuntu_package_installation %}
+ {%- block base_ubuntu_package_key_installation %}
+ {% for key in base_apt_keys | customizable('apt_keys') %}
+ {%- if loop.first %}RUN mkdir -p /etc/kolla/apt-keys/{% endif %} \
+ && gpg --keyserver hkp://keyserver.ubuntu.com:80 \
+ {% if env.http_proxy %} --keyserver-options "http-proxy={{ env.http_proxy }}" {% endif %}\
+ --recv-keys {{ key.keyid }} \
+ && gpg --export {{ key.keyid }} >/etc/kolla/apt-keys/{{ key.name }}.gpg
+ {%- if not loop.last %} \
+ {% endif -%}
+ {% endfor %}
+ {% for key in base_remote_apt_keys | customizable('remote_apt_keys') %}
+ {%- if loop.first %} RUN mkdir -p /etc/kolla/apt-keys/ {% endif %} \
+ && curl {{ key.url }} -o /etc/kolla/apt-keys/{{ key.name }}.{{ key.type | default('asc') }}
+ {%- if not loop.last %} \
+ {% endif -%}
+ {% endfor %}
+ {% endblock %}
+RUN apt-get --error-on=any update \
+ && apt-get -y install locales \
+ && sed -e "s/# $LANG UTF-8/$LANG UTF-8/g" /etc/locale.gen -i \
+ && locale-gen "$LANG" \
+ && apt-get -y upgrade \
+ && apt-get -y dist-upgrade \
+ && {{ macros.install_packages(base_apt_packages | customizable('apt_packages'), True) }} \
+ # NOTE: python3-pip installs dependent tzdata package and blocks mount in docker - 2091161
+ && unlink /etc/localtime
+
+{% endblock %}
+
+{# endif base_package_type deb #}
+{% endif %}
+
+{# NOTE(SamYaple): Avoid uid/gid conflicts by creating each user/group up front. #}
+{# Specifics required such as homedir or shell are configured within the service specific image #}
+{%- for name, user in users | dictsort() %}
+{% if loop.first -%}RUN {% else %} && {% endif -%}
+ groupadd --gid {{ user.gid }} {{ user.group }} \
+ && useradd -l -M --shell /usr/sbin/nologin --uid {{ user.uid }} --gid {{ user.gid }} {{ name }}
+ {%- if not loop.last %} \{% endif -%}
+{%- endfor %}
+
+{% if base_distro == 'centos' %}
+RUN sed -ri '/-session(\s+)optional(\s+)pam_systemd.so/d' /etc/pam.d/system-auth \
+ && sed -ri '/^[^#]/ s/systemd//g' /etc/nsswitch.conf
+{% endif %}
+
+COPY set_configs.py /usr/local/bin/kolla_set_configs
+COPY start.sh /usr/local/bin/kolla_start
+COPY copy_cacerts.sh /usr/local/bin/kolla_copy_cacerts
+COPY install_projects.sh /usr/local/bin/kolla_install_projects
+COPY kolla_patch.sh /usr/local/bin/kolla_patch
+COPY sudoers /etc/sudoers
+
+{% if use_dumb_init %}
+ENTRYPOINT ["dumb-init", "--single-child", "--"]
+{% endif %}
+
+{% if docker_healthchecks %}
+{% block healthcheck_installation %}
+
+COPY healthcheck_curl healthcheck_filemod healthcheck_listen healthcheck_port healthcheck_socket /usr/local/bin/
+RUN chmod 755 /usr/local/bin/healthcheck_*
+
+{% endblock %}
+{% endif %}
+
+RUN touch /usr/local/bin/kolla_extend_start \
+ && chmod 755 /usr/local/bin/kolla_start \
+ /usr/local/bin/kolla_set_configs \
+ /usr/local/bin/kolla_copy_cacerts \
+ /usr/local/bin/kolla_install_projects \
+ /usr/local/bin/kolla_patch \
+ && chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 440 /etc/sudoers \
+ && mkdir -p /var/log/kolla \
+ && chown :kolla /var/log/kolla \
+ && chmod 2775 /var/log/kolla \
+ && rm -f /tmp/kolla_bashrc
+
+{% block base_pip_conf %}
+# the variables like PIP_INDEX_URL, PIP_EXTRA_INDEX_URL, PIP_TRUSTED_HOST etc. should be defined here.
+# ENV PIP_INDEX_URL=https://pypi.python.org/simple
+# ENV PIP_TRUSTED_HOST=pypi.python.org
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block base_footer %}{% endblock %}
+CMD ["kolla_start"]
diff --git a/kolla/docker/base/apt_preferences b/kolla/docker/base/apt_preferences
new file mode 100644
index 000000000..dc5b993f1
--- /dev/null
+++ b/kolla/docker/base/apt_preferences
@@ -0,0 +1 @@
+# used for adding custom options by 'apt_preferences' option of kolla-build.conf
diff --git a/kolla/docker/base/apt_preferences.debian b/kolla/docker/base/apt_preferences.debian
new file mode 100644
index 000000000..08e21ff22
--- /dev/null
+++ b/kolla/docker/base/apt_preferences.debian
@@ -0,0 +1,4 @@
+# NOTE(hrw): we do not want backports unless requested
+Package: *
+Pin: release n=trixie-backports
+Pin-Priority: -1000
diff --git a/kolla/docker/base/apt_preferences.ubuntu b/kolla/docker/base/apt_preferences.ubuntu
new file mode 100644
index 000000000..7cc49b4ad
--- /dev/null
+++ b/kolla/docker/base/apt_preferences.ubuntu
@@ -0,0 +1,3 @@
+Package: *
+Pin: origin dlm.mariadb.com
+Pin-Priority: 1000
diff --git a/kolla/docker/base/ci-centos.repo b/kolla/docker/base/ci-centos.repo
new file mode 100644
index 000000000..4c08f1fea
--- /dev/null
+++ b/kolla/docker/base/ci-centos.repo
@@ -0,0 +1,75 @@
+[baseos]
+name=(OpenDev mirror) CentOS Stream $releasever - BaseOS
+baseurl=http://MIRROR/centos-stream/$stream/BaseOS/$basearch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=1
+repo_gpgcheck=0
+enabled=1
+
+[appstream]
+name=(OpenDev mirror) CentOS Stream $releasever - AppStream
+baseurl=http://MIRROR/centos-stream/$stream/AppStream/$basearch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=1
+repo_gpgcheck=0
+enabled=1
+
+[extras-common]
+name=(OpenDev mirror) CentOS Stream $releasever - Extras packages
+baseurl=http://MIRROR/centos-stream/SIGs/$stream/extras/$basearch/extras-common/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
+gpgcheck=1
+repo_gpgcheck=0
+enabled=1
+
+[crb]
+name=(OpenDev mirror) CentOS Stream $releasever - CRB
+baseurl=http://MIRROR/centos-stream/$stream/CRB/$basearch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=1
+repo_gpgcheck=0
+enabled=0
+
+[highavailability]
+name=(OpenDev mirror) CentOS Stream $releasever - HighAvailability
+baseurl=http://MIRROR/centos-stream/$stream/HighAvailability/$basearch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=1
+repo_gpgcheck=0
+enabled=0
+
+[centos-ceph-reef]
+name=(OpenDev mirror) CentOS-$stream - Ceph Reef
+baseurl=http://MIRROR/centos-stream/SIGs/$stream/storage/$basearch/ceph-reef/
+gpgcheck=1
+enabled=0
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage
+
+[centos-nfv-openvswitch]
+name=(OpenDev mirror) CentOS Stream $releasever - NFV OpenvSwitch
+baseurl=http://MIRROR/centos-stream/SIGs/$stream/nfv/$basearch/openvswitch-2/
+gpgcheck=1
+enabled=0
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-NFV
+module_hotfixes=1
+
+[centos-opstools]
+name=(OpenDev mirror) CentOS Stream $releasever - OpsTools - collectd
+baseurl=http://MIRROR/centos-stream/SIGs/$stream/opstools/$basearch/collectd-5/
+gpgcheck=1
+enabled=0
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-OpsTools
+
+[centos-rabbitmq-38]
+name=(OpenDev mirror) CentOS-9 - RabbitMQ 38
+baseurl=http://MIRROR/centos-stream/SIGs/$stream/messaging/$basearch/rabbitmq-38
+gpgcheck=1
+enabled=0
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Messaging
+
+[epel]
+name=(OpenDev mirror) Extra Packages for Enterprise Linux $releasever - $basearch
+baseurl=http://MIRROR/epel/$releasever/Everything/$basearch/
+enabled=0
+gpgcheck=1
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$releasever
diff --git a/kolla/docker/base/copy_cacerts.sh b/kolla/docker/base/copy_cacerts.sh
new file mode 100644
index 000000000..213033c0c
--- /dev/null
+++ b/kolla/docker/base/copy_cacerts.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+# Copy custom CA certificates to system trusted CA certificates folder
+# and run CA update utility
+
+if [[ -e "/etc/debian_version" ]]; then
+ ca_dst_path="/usr/local/share/ca-certificates"
+ update_command="update-ca-certificates"
+elif [[ -e "/etc/redhat-release" ]]; then
+ ca_dst_path="/etc/pki/ca-trust/source/anchors"
+ update_command="update-ca-trust"
+else
+ echo "Unsupported OS"
+ exit 1
+fi
+
+# Initialize update_needed variable
+update_needed="false"
+
+# Remove old certificates
+if find /etc/ssl/certs/ \
+ /usr/local/share/ca-certificates/ \
+ /etc/pki/ca-trust/source/anchors/ \
+ -name 'kolla*' -exec rm -f {} + 2>/dev/null; then
+ update_needed="true"
+fi
+
+# Determine source path for CA certificates
+if grep -q '"source": "/var/lib/kolla/share/ca-certificates"' /etc/kolla/defaults/state; then
+ ca_src_path="/var/lib/kolla/share/ca-certificates"
+else
+ ca_src_path="/var/lib/kolla/config_files/ca-certificates"
+fi
+
+# Check if the source path exists and is not empty
+if [[ -d ${ca_src_path} && $(ls -A "${ca_src_path}" 2>/dev/null) ]]; then
+ # Copy certificates and update CA
+ for cert in "${ca_src_path}"/*; do
+ file=$(basename "${cert}")
+ cp ${cert} ${ca_dst_path}/kolla-customca-${file}
+ update_needed="true"
+ done
+fi
+
+# Run the update command if needed
+if [[ "${update_needed}" == "true" ]]; then
+ ${update_command}
+fi
diff --git a/kolla/docker/base/curlrc b/kolla/docker/base/curlrc
new file mode 100644
index 000000000..3b1ecea06
--- /dev/null
+++ b/kolla/docker/base/curlrc
@@ -0,0 +1,8 @@
+# curl default options
+--fail
+--location
+--retry 5
+--retry-all-errors
+--silent
+--show-error
+--write-out "curl (%{url_effective}): response: %{http_code}, time: %{time_total}, size: %{size_download}\n"
diff --git a/kolla/docker/base/debian.sources b/kolla/docker/base/debian.sources
new file mode 100644
index 000000000..28c61c81d
--- /dev/null
+++ b/kolla/docker/base/debian.sources
@@ -0,0 +1,13 @@
+Types: deb
+# http://snapshot.debian.org/archive/debian/20251020T000000Z
+URIs: http://deb.debian.org/debian
+Suites: trixie trixie-updates trixie-backports
+Components: main
+Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
+
+Types: deb
+# http://snapshot.debian.org/archive/debian-security/20251020T000000Z
+URIs: http://deb.debian.org/debian-security
+Suites: trixie-security
+Components: main
+Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
diff --git a/kolla/docker/base/dnf.conf b/kolla/docker/base/dnf.conf
new file mode 100644
index 000000000..6ca687545
--- /dev/null
+++ b/kolla/docker/base/dnf.conf
@@ -0,0 +1,12 @@
+[main]
+keepcache=0
+logfile=/var/log/dnf.log
+exactarch=1
+obsoletes=1
+gpgcheck=1
+plugins=1
+installonly_limit=0
+skip_missing_names_on_install=False
+clean_requirements_on_remove=True
+tsflags=nodocs
+install_weak_deps=False
diff --git a/kolla/docker/base/dpkg_reducing_disk_footprint b/kolla/docker/base/dpkg_reducing_disk_footprint
new file mode 100644
index 000000000..87b8255f9
--- /dev/null
+++ b/kolla/docker/base/dpkg_reducing_disk_footprint
@@ -0,0 +1,9 @@
+path-exclude /usr/share/doc/*
+path-include /usr/share/doc/*/copyright
+path-exclude /usr/share/man/*
+path-exclude /usr/share/groff/*
+path-exclude /usr/share/info/*
+path-exclude /usr/share/lintian/*
+path-exclude /usr/share/linda/*
+path-exclude /usr/share/locale/*
+path-include /usr/share/locale/en*
diff --git a/kolla/docker/base/healthcheck_curl b/kolla/docker/base/healthcheck_curl
new file mode 100755
index 000000000..6df232fe8
--- /dev/null
+++ b/kolla/docker/base/healthcheck_curl
@@ -0,0 +1,12 @@
+#!/bin/bash
+: ${HEALTHCHECK_CURL_MAX_TIME:=10}
+: ${HEALTHCHECK_CURL_USER_AGENT:=curl-healthcheck}
+: ${HEALTHCHECK_CURL_WRITE_OUT:='\n%{http_code} %{remote_ip}:%{remote_port} %{time_total} seconds\n'}
+: ${HEALTHCHECK_CURL_OUTPUT:='/dev/null'}
+
+export NSS_SDB_USE_CACHE=no
+curl -q -g -k -s -S --fail -o "${HEALTHCHECK_CURL_OUTPUT}" \
+ --max-time "${HEALTHCHECK_CURL_MAX_TIME}" \
+ --user-agent "${HEALTHCHECK_CURL_USER_AGENT}" \
+ --write-out "${HEALTHCHECK_CURL_WRITE_OUT}" \
+ "$@" || exit 1
diff --git a/kolla/docker/base/healthcheck_filemod b/kolla/docker/base/healthcheck_filemod
new file mode 100755
index 000000000..007643d9b
--- /dev/null
+++ b/kolla/docker/base/healthcheck_filemod
@@ -0,0 +1,15 @@
+#!/bin/bash
+file_path=$1
+limit_seconds=$2
+
+# if the file doesn't exist, return 1
+if [ ! -f $file_path ]; then
+ echo "${file_path} does not exist for file modification check"
+ exit 1
+fi
+curr_time=$(date +%s)
+last_mod=$(stat -c '%Y' $file_path)
+limit_epoch=$(( curr_time-limit_seconds ))
+if [ ${limit_epoch} -gt ${last_mod} ]; then
+ exit 1
+fi
diff --git a/kolla/docker/base/healthcheck_listen b/kolla/docker/base/healthcheck_listen
new file mode 100755
index 000000000..70273339c
--- /dev/null
+++ b/kolla/docker/base/healthcheck_listen
@@ -0,0 +1,8 @@
+#!/bin/bash
+process=$1
+
+shift 1
+args=$@
+ports=${args// /|}
+pids=$(pgrep -d '|' -f $process)
+ss -lnp | grep -qE ":($ports).*,pid=($pids),"
diff --git a/kolla/docker/base/healthcheck_port b/kolla/docker/base/healthcheck_port
new file mode 100755
index 000000000..9531bd77a
--- /dev/null
+++ b/kolla/docker/base/healthcheck_port
@@ -0,0 +1,8 @@
+#!/bin/bash
+process=$1
+
+shift 1
+args=$@
+ports=${args// /|}
+pids=$(pgrep -d '|' -f $process)
+ss -ntp | grep -qE ":($ports).*,pid=($pids),"
diff --git a/kolla/docker/base/healthcheck_socket b/kolla/docker/base/healthcheck_socket
new file mode 100755
index 000000000..cd65bae69
--- /dev/null
+++ b/kolla/docker/base/healthcheck_socket
@@ -0,0 +1,10 @@
+#!/bin/bash
+process=$1
+socket=$2
+
+# lsof truncate command name to 15 characters and this behaviour
+# cannot be disabled
+if [ ${#process} -gt 15 ] ; then
+ process=${process:0:15}
+fi
+lsof -Fc -Ua $socket | grep -q "c$process"
diff --git a/kolla/docker/base/install_projects.sh b/kolla/docker/base/install_projects.sh
new file mode 100644
index 000000000..c3f7182b5
--- /dev/null
+++ b/kolla/docker/base/install_projects.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# This script finds all projects in /dev-mode folder and
+# installs them using pip
+# The projects are mounted there when dev mode is enabled for them
+# in kolla-ansible
+
+VENV_PATH="/var/lib/kolla/venv"
+
+if [ -d "/dev-mode" ] && [ -d "$VENV_PATH" ]; then
+ for project in /dev-mode/*; do
+ pip install -U "$project"
+ done
+fi
diff --git a/kolla/docker/base/kolla_bashrc b/kolla/docker/base/kolla_bashrc
new file mode 100644
index 000000000..09b19cc06
--- /dev/null
+++ b/kolla/docker/base/kolla_bashrc
@@ -0,0 +1,5 @@
+#### Custom data added by Kolla
+_KBOLD="\[$(tput bold)\]"
+_KRESET="\[$(tput sgr0)\]"
+
+PS1="${_KBOLD}(${KOLLA_SERVICE_NAME})${_KRESET}[\u@\h \W]\\$ "
diff --git a/kolla/docker/base/kolla_patch.sh b/kolla/docker/base/kolla_patch.sh
new file mode 100755
index 000000000..b17a5b61d
--- /dev/null
+++ b/kolla/docker/base/kolla_patch.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+# This script works as debian quilt patch.
+# So, patch files included in /patches/series
+# are applied and information what was applied
+# is stored in /etc/kolla/patched.
+#
+# No more, no less :)
+
+cd /
+
+# If exist /patches/series
+# let's try to apply patches
+if [ -e "/patches/series" ]; then
+ # If there is /patches/series.applied
+ # then it means previous run already applied
+ # some patches - from another intermediate container
+ #
+ # So, let's add patches again to /patches/series
+ # and let's script to handle it
+ if [ -e "/patches/series.applied" ]; then
+ grep -v '^#' /patches/series.applied > /tmp/series.tmp
+ grep -v '^#' /patches/series >> /tmp/series.tmp
+ rm -f /patches/series
+ mv /tmp/series.tmp /patches/series
+ fi
+ touch /etc/kolla/patched
+ for patchfile in $(grep -v '^#' /patches/series); do
+ # If patch is not applied, try to apply it, otherwise
+ # inform user that patchfile is already applied
+ if ! grep -q "$patchfile" /etc/kolla/patched; then
+ echo "[i] Applying /patches/${patchfile}"
+ patch -p0 --fuzz=0 --ignore-whitespace < /patches/${i}/${patchfile}
+ # If apply patch was successful inform user,
+ # otherwise fail build process and inform user
+ # to check/fix patch
+ if [ $? -eq 0 ]; then
+ echo "[i] Applied /patches/${patchfile}" >> /etc/kolla/patched
+ else
+ echo "[i] Patch /patches/${patchfile} failed, please fix your patchfiles."
+ exit 1
+ fi
+ else
+ echo "[i] /patches/${patchfile} already applied."
+ fi
+ done
+ # Ignore files which are commented and move
+ # to /patches/series.applied as /patch/series
+ # can be potentially replaced by another files
+ # from different intermediate container
+ grep -v '^#' /patches/series > /patches/series.applied
+ rm -f /patches/series
+else
+ echo "[i] No series file found, nothing to patch."
+fi
diff --git a/kolla/docker/base/set_configs.py b/kolla/docker/base/set_configs.py
new file mode 100644
index 000000000..4b025d55a
--- /dev/null
+++ b/kolla/docker/base/set_configs.py
@@ -0,0 +1,759 @@
+#!/usr/bin/env python3
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import glob
+import grp
+import json
+import logging
+import os
+import pwd
+import re
+import shutil
+import stat
+import sys
+import yaml
+
+
+# TODO(rhallisey): add docstring.
+logging.basicConfig(
+ format='%(asctime)s.%(msecs)03d %(levelname)s %(message)s',
+ level=logging.INFO,
+ datefmt='%Y-%m-%d %H:%M:%S'
+)
+LOG = logging.getLogger(__name__)
+LOG.setLevel(logging.INFO)
+
+KOLLA_DEFAULTS = "/etc/kolla/defaults"
+KOLLA_DEFAULTS_STATE = KOLLA_DEFAULTS + '/' + 'state'
+
+
+class ExitingException(Exception):
+ def __init__(self, message, exit_code=1):
+ super(ExitingException, self).__init__(message)
+ self.exit_code = exit_code
+
+
+class ImmutableConfig(ExitingException):
+ pass
+
+
+class InvalidConfig(ExitingException):
+ pass
+
+
+class MissingRequiredSource(ExitingException):
+ pass
+
+
+class UserNotFound(ExitingException):
+ pass
+
+
+class ConfigFileBadState(ExitingException):
+ pass
+
+
+class ConfigFileCommandDiffers(ExitingException):
+ pass
+
+
+class StateMismatch(ExitingException):
+ pass
+
+
+class ConfigFile(object):
+
+ def __init__(self, source, dest, owner=None, perm=None, optional=False,
+ preserve_properties=False, merge=False):
+ self.source = source
+ self.dest = dest
+ self.owner = owner
+ self.perm = perm
+ self.optional = optional
+ self.merge = merge
+ self.preserve_properties = preserve_properties
+
+ def __str__(self):
+ return '<ConfigFile source:"{}" dest:"{}">'.format(self.source,
+ self.dest)
+
+ def _copy_file(self, source, dest):
+ self._delete_path(dest)
+ # dest endswith / means copy the <source> to <dest> folder
+ LOG.info('Copying %s to %s', source, dest)
+ if self.merge and self.preserve_properties and os.path.islink(source):
+ link_target = os.readlink(source)
+ os.symlink(link_target, dest)
+ else:
+ shutil.copy(source, dest)
+ self._set_properties(source, dest)
+
+ def _merge_directories(self, source, dest):
+ if os.path.isdir(source):
+ if os.path.lexists(dest) and not os.path.isdir(dest):
+ self._delete_path(dest)
+ if not os.path.isdir(dest):
+ LOG.info('Creating directory %s', dest)
+ os.makedirs(dest)
+ self._set_properties(source, dest)
+
+ dir_content = os.listdir(source)
+ for to_copy in dir_content:
+ self._merge_directories(os.path.join(source, to_copy),
+ os.path.join(dest, to_copy))
+ else:
+ self._copy_file(source, dest)
+
+ def _delete_path(self, path):
+ if not os.path.lexists(path):
+ return
+ LOG.info('Deleting %s', path)
+ if os.path.isdir(path):
+ shutil.rmtree(path)
+ else:
+ os.remove(path)
+
+ def _create_parent_dirs(self, path):
+ parent_path = os.path.dirname(path)
+ if not os.path.exists(parent_path):
+ os.makedirs(parent_path)
+
+ def _set_properties(self, source, dest):
+ if self.preserve_properties:
+ self._set_properties_from_file(source, dest)
+ else:
+ self._set_properties_from_conf(dest)
+
+ def _set_properties_from_file(self, source, dest):
+ LOG.info('Copying permissions from %s onto %s', source, dest)
+ shutil.copystat(source, dest)
+ stat = os.stat(source)
+ os.chown(dest, stat.st_uid, stat.st_gid)
+
+ def _set_properties_from_conf(self, path):
+ config = {'permissions':
+ [{'owner': self.owner, 'path': path, 'perm': self.perm}]}
+ handle_permissions(config)
+
+ def copy(self):
+
+ sources = glob.glob(self.source)
+
+ if not self.optional and not sources:
+ raise MissingRequiredSource('%s file is not found' % self.source)
+ # skip when there is no sources and optional
+ elif self.optional and not sources:
+ return
+
+ for source in sources:
+ dest = self.dest
+ # dest endswith / means copy the <source> into <dest> folder,
+ # otherwise means copy the source to dest
+ if dest.endswith(os.sep):
+ dest = os.path.join(dest, os.path.basename(source))
+ if not self.merge:
+ self._delete_path(dest)
+ self._create_parent_dirs(dest)
+ try:
+ self._merge_directories(source, dest)
+ except OSError:
+ # If a source is tried to merge with a read-only mount, it
+ # may throw an OSError. Because we don't print the source or
+ # dest anywhere, let's catch the exception and log a better
+ # message to help with tracking down the issue.
+ LOG.error('Unable to merge %s with %s', source, dest)
+ raise
+
+ def _cmp_file(self, source, dest):
+ # check exist
+ if (os.path.exists(source) and
+ not os.path.exists(dest)):
+ return False
+ if (os.path.exists(dest) and
+ not os.path.exists(source)):
+ return False
+ # check content
+ with open(source, 'rb') as f1, open(dest, 'rb') as f2:
+ if f1.read() != f2.read():
+ LOG.error('The content of source file(%s) and'
+ ' dest file(%s) are not equal.', source, dest)
+ return False
+ # check perm
+ file_stat = os.stat(dest)
+ actual_perm = oct(file_stat.st_mode)[-4:]
+ if self.perm != actual_perm:
+ LOG.error('Dest file does not have expected perm: %s, actual: %s',
+ self.perm, actual_perm)
+ return False
+ # check owner
+ desired_user, desired_group = user_group(self.owner)
+ actual_user = pwd.getpwuid(file_stat.st_uid)
+ if actual_user.pw_name != desired_user:
+ LOG.error('Dest file does not have expected user: %s,'
+ ' actual: %s ', desired_user, actual_user.pw_name)
+ return False
+ actual_group = grp.getgrgid(file_stat.st_gid)
+ if actual_group.gr_name != desired_group:
+ LOG.error('Dest file does not have expected group: %s,'
+ ' actual: %s ', desired_group, actual_group.gr_name)
+ return False
+ return True
+
+ def _cmp_dir(self, source, dest):
+ for root, dirs, files in os.walk(source):
+ for dir_ in dirs:
+ full_path = os.path.join(root, dir_)
+ dest_full_path = os.path.join(dest, os.path.relpath(source,
+ full_path))
+ dir_stat = os.stat(dest_full_path)
+ actual_perm = oct(dir_stat.st_mode)[-4:]
+ if self.perm != actual_perm:
+ LOG.error('Dest dir does not have expected perm: %s,'
+ ' actual %s', self.perm, actual_perm)
+ return False
+ for file_ in files:
+ full_path = os.path.join(root, file_)
+ dest_full_path = os.path.join(dest, os.path.relpath(full_path,
+ source))
+ if not self._cmp_file(full_path, dest_full_path):
+ return False
+ return True
+
+ def check(self):
+ bad_state_files = []
+ sources = glob.glob(self.source)
+
+ if not sources and not self.optional:
+ raise MissingRequiredSource('%s file is not found' % self.source)
+ elif self.optional and not sources:
+ return
+
+ for source in sources:
+ dest = self.dest
+ # dest endswith / means copy the <source> into <dest> folder,
+ # otherwise means copy the source to dest
+ if dest.endswith(os.sep):
+ dest = os.path.join(dest, os.path.basename(source))
+ if os.path.isdir(source):
+ if not self._cmp_dir(source, dest):
+ bad_state_files.append(source)
+ elif not self._cmp_file(source, dest):
+ bad_state_files.append(source)
+ if len(bad_state_files) != 0:
+ msg = 'Following files are in bad state: %s' % bad_state_files
+ raise ConfigFileBadState(msg)
+
+
+def validate_config(config):
+ required_keys = {'source', 'dest'}
+ required_path_keys = {'path', 'owner', 'perm'}
+
+ if 'command' not in config:
+ raise InvalidConfig('Config is missing required "command" key')
+
+ # Validate config sections
+ for data in config.get('config_files', list()):
+ # Verify required keys exist.
+ if not set(data.keys()) >= required_keys:
+ message = 'Config is missing required keys: %s' % required_keys
+ raise InvalidConfig(message)
+ if ('owner' not in data or 'perm' not in data) \
+ and not data.get('preserve_properties', False):
+ raise InvalidConfig(
+ 'Config needs preserve_properties or owner and perm')
+
+ for section in ('directories', 'files'):
+ for data in config.get(section, list()):
+ missing = required_path_keys - set(data.keys())
+ if missing:
+ raise InvalidConfig(
+ '%s config is missing required keys: %s'
+ % (section, missing))
+
+ for key in required_path_keys:
+ if data.get(key) is None:
+ raise InvalidConfig(
+ '%s config key "%s" must not be null'
+ % (section, key))
+
+ if glob.has_magic(data['path']):
+ raise InvalidConfig(
+ '%s config does not support glob paths: %s'
+ % (section, data['path']))
+
+
+def validate_source(data):
+ source = data.get('source')
+
+ # Only check existence if no wildcard found
+ if '*' not in source:
+ if not os.path.exists(source):
+ if data.get('optional'):
+ LOG.info("%s does not exist, but is not required", source)
+ return False
+ else:
+ raise MissingRequiredSource(
+ "The source to copy does not exist: %s" % source)
+
+ return True
+
+
+def load_config():
+ def load_from_file():
+ yaml_config_file = '/var/lib/kolla/config_files/config.yaml'
+ json_config_file = '/var/lib/kolla/config_files/config.json'
+
+ if os.path.exists(yaml_config_file):
+ config_file = yaml_config_file
+ LOG.info("Loading config file at %s", config_file)
+ try:
+ with open(config_file) as f:
+ config = yaml.safe_load(f)
+
+ if config is None:
+ raise InvalidConfig(
+ "Invalid yaml file found at %s: "
+ "file is empty" % config_file)
+ return config
+ except yaml.YAMLError:
+ raise InvalidConfig(
+ "Invalid yaml file found at %s" % config_file)
+ except IOError as e:
+ raise InvalidConfig(
+ "Could not read file %s: %r" % (config_file, e))
+ else:
+ config_file = json_config_file
+ LOG.info("Loading config file at %s", config_file)
+ try:
+ with open(config_file) as f:
+ return json.load(f)
+ except ValueError:
+ raise InvalidConfig(
+ "Invalid json file found at %s" % config_file)
+ except IOError as e:
+ raise InvalidConfig(
+ "Could not read file %s: %r" % (config_file, e))
+
+ config = load_from_file()
+
+ LOG.info('Validating config file')
+ validate_config(config)
+ return config
+
+
+def copy_config(config):
+ if 'config_files' in config:
+ LOG.info('Copying service configuration files')
+ for data in config['config_files']:
+ config_file = ConfigFile(**data)
+ config_file.copy()
+ else:
+ LOG.debug('No files to copy found in config')
+
+ LOG.info('Writing out command to execute')
+ LOG.debug("Command is: %s", config['command'])
+ # The value from the 'command' key will be written to '/run_command'
+ cmd = '/run_command'
+ with open(cmd, 'w+') as f:
+ f.write(config['command'])
+ # Make sure the generated file is readable by all users
+ try:
+ os.chmod(cmd, 0o644)
+ except OSError:
+ LOG.exception('Failed to set permission of %s to 0o644', cmd)
+
+
+def create_directories(config):
+ if 'directories' not in config:
+ return
+ LOG.info('Creating directories')
+ for data in config['directories']:
+ path = data['path']
+ try:
+ os.makedirs(path, exist_ok=True)
+ except Exception:
+ raise OSError("Can't create destination directory (%s)" % (path))
+ handle_permissions({'permissions': [data]})
+
+
+def create_files(config):
+ if 'files' not in config:
+ return
+ LOG.info('Creating files')
+ for data in config['files']:
+ path = data['path']
+ if os.path.islink(path):
+ raise OSError(
+ "Path is a symbolic link, expected regular file (%s)" % path)
+ if os.path.isdir(path):
+ raise OSError(
+ "Path is a directory, expected file (%s)" % path)
+ parent = os.path.dirname(path)
+ if parent and not os.path.isdir(parent):
+ raise OSError(
+ "Parent directory does not exist (%s)" % parent)
+ if not os.path.exists(path):
+ with open(path, 'a'):
+ pass
+ handle_permissions({'permissions': [data]})
+
+
+def user_group(owner):
+ if ':' in owner:
+ user, group = owner.split(':', 1)
+ if not group:
+ group = user
+ else:
+ user, group = owner, owner
+ return user, group
+
+
+def handle_permissions(config):
+ for permission in config.get('permissions', list()):
+ path = permission.get('path')
+ owner = permission.get('owner')
+ recurse = permission.get('recurse', False)
+ perm = permission.get('perm')
+ exclude = permission.get('exclude', [])
+
+ desired_user, desired_group = user_group(owner)
+ uid = pwd.getpwnam(desired_user).pw_uid
+ gid = grp.getgrnam(desired_group).gr_gid
+
+ def set_perms(path, uid, gid, perm):
+ LOG.info('Setting permission for %s', path)
+ if not os.path.exists(path):
+ LOG.warning('%s does not exist', path)
+ return
+
+ try:
+ os.chown(path, uid, gid)
+ except OSError:
+ LOG.exception('Failed to change ownership of %s to %s:%s',
+ path, uid, gid)
+
+ if perm:
+ # Accept valid UNIX permission notation
+ # (e.g. 644, 0600, 1777, 2755)
+ # or equivalent Python octal literals
+ # (e.g. 0o644, 0o1777, 0o2755).
+ if re.fullmatch(r'[0-7]{3,4}', perm):
+ perm = int(perm, 8)
+ elif re.fullmatch(r'0o[0-7]{3,4}', perm):
+ perm = int(perm, 0)
+ else:
+ raise ValueError(
+ f"Invalid permission '{perm}'. Expected a valid UNIX "
+ "permission (e.g. 644, 0600, 2755) or a Python octal "
+ "literal (e.g. 0o644, 0o0600, 0o2755)."
+ )
+
+ # Ensure execute bit on directory if read bit is set
+ if os.path.isdir(path):
+ if perm & stat.S_IRUSR:
+ perm |= stat.S_IXUSR
+ if perm & stat.S_IRGRP:
+ perm |= stat.S_IXGRP
+ if perm & stat.S_IROTH:
+ perm |= stat.S_IXOTH
+
+ try:
+ os.chmod(path, perm)
+ except OSError:
+ LOG.exception('Failed to set permission of %s to %s',
+ path, perm)
+
+ def handle_exclusion(root, path_suffix):
+ full_path = os.path.join(root, path_suffix)
+ LOG.debug("Checking for exclusion: %s" % full_path)
+ if exclude:
+ for exclude_ in exclude:
+ if not re.search(exclude_, full_path):
+ set_perms(full_path, uid, gid, perm)
+ else:
+ set_perms(full_path, uid, gid, perm)
+
+ for dest in glob.glob(path):
+ set_perms(dest, uid, gid, perm)
+ if recurse and os.path.isdir(dest):
+ for root, dirs, files in os.walk(dest):
+ for dir_ in dirs:
+ handle_exclusion(root, dir_)
+ for file_ in files:
+ handle_exclusion(root, file_)
+
+
+def get_defaults_state():
+ """Retrieve the saved default configuration state from default state file.
+
+ This function creates the directory for Kolla defaults if it does not
+ exist, and then attempts to read the current configuration state from
+ a JSON file. If the file exists, it reads and returns the content.
+ If not, it returns an empty dictionary.
+
+ Simply said, when the container starts for the first time, the state file
+ doesn't exist, and it returns an empty dictionary.
+ However, if it has already been started before, it will contain the state
+ as it was when it first ran.
+
+ Returns:
+ dict: The configuration state stored in the Kolla defaults state file.
+
+ Example:
+ {
+ "/etc/cinder/cinder.conf": {
+ "source": "/etc/cinder/cinder.conf",
+ "preserve_properties": true,
+ "dest": null
+ },
+ "/etc/apache2/conf-enabled/cinder-wsgi.conf": {
+ "source": "/etc/apache2/conf-enabled/cinder-wsgi.conf",
+ "preserve_properties": true,
+ "dest": null
+ },
+ "/etc/cinder/cinder_audit_map.conf": {
+ "source": "/etc/cinder/cinder_audit_map.conf",
+ "preserve_properties": true,
+ "dest": "/etc/kolla/defaults/etc/cinder/cinder_audit_map.conf"
+ }
+ }
+
+ From above example:
+ /etc/cinder/cinder.conf didn't exist
+ /etc/apache2/conf-enabled/cinder-wsgi.conf didn't exist
+ /etc/cinder/cinder_audit_map.conf exists and saved
+ """
+ os.makedirs(KOLLA_DEFAULTS, exist_ok=True)
+ if os.path.exists(KOLLA_DEFAULTS_STATE):
+ with open(KOLLA_DEFAULTS_STATE, 'r') as f:
+ return json.load(f)
+ else:
+ return {}
+
+
+def set_defaults_state(state):
+ """Save the provided configuration state to the defaults state file.
+
+ This function writes the provided state (a dictionary) to a JSON file at
+ the specified Kolla defaults state location, ensuring that it is properly
+ formatted with indentation for readability.
+
+ Args:
+ state (dict): The configuration state to save to the Kolla defaults
+ state file.
+ """
+ with open(KOLLA_DEFAULTS_STATE, 'w') as f:
+ json.dump(state, f, indent=4)
+
+
+def remove_or_restore_configs(state):
+ """Remove or restore configuration files based on their current state.
+
+ This function iterates over the configuration files in the provided state.
+ If the destination is `None`, it removes the file or directory. Otherwise,
+ it swaps the source and destination, restoring the configuration file
+ by copying it back to its original location.
+
+ Args:
+ state (dict): The current default state of configuration files, mapping
+ file paths to their source and destination information.
+ """
+ for k, v in state.items():
+ if v['dest'] is None:
+ if os.path.exists(k):
+ if os.path.isfile(k):
+ os.remove(k)
+ else:
+ shutil.rmtree(k)
+ else:
+ v['source'], v['dest'] = v['dest'], v['source']
+ config_file = ConfigFile(**v)
+ config_file.copy()
+
+
+def backup_configs(config, state):
+ """Back up new configuration files and update the default state.
+
+ This function processes new configuration files provided in the
+ input `config`. For each file, it checks if the destination exists in the
+ current state. If not, it backs up the file by copying it to the default
+ directory. It then updates the state with the new configuration file's
+ information.
+
+ Args:
+ config (dict): The input configuration containing a list of config
+ files.
+ state (dict): The current default state to be updated with the new
+ config files.
+ """
+ if 'config_files' in config:
+ for data in config['config_files']:
+ if data['dest'] in state.keys():
+ continue
+ src = data['source']
+ if data['dest'].endswith('/'):
+ dst = data['dest'] + data['source'].split('/')[-1]
+ else:
+ dst = data['dest']
+ default = KOLLA_DEFAULTS + dst
+ if os.path.exists(src):
+ copy = {'source': dst, 'preserve_properties': True}
+ if os.path.exists(dst):
+ copy['dest'] = default
+ if dst not in state:
+ config_file = ConfigFile(**copy)
+ config_file.copy()
+ state[dst] = copy
+ else:
+ copy['dest'] = None
+ if dst not in state:
+ state[dst] = copy
+
+
+def handle_defaults(config):
+ """Handle the default config files by copying/removing them as needed.
+
+ This function loads the current default state and manages the configuration
+ files. It first processes existing configuration files in the default
+ state, either removing or restoring them based on their destination status.
+ It then backs up any new configuration files from the input config,
+ updating the default state accordingly.
+
+ Args:
+ config (dict): A dictionary containing the list of configuration files
+ to be handled.
+ """
+ state = get_defaults_state()
+ remove_or_restore_configs(state)
+ backup_configs(config, state)
+ set_defaults_state(state)
+
+
+def execute_config_strategy(config):
+ config_strategy = os.environ.get("KOLLA_CONFIG_STRATEGY")
+ LOG.info("Kolla config strategy set to: %s", config_strategy)
+ if config_strategy == "COPY_ALWAYS":
+ create_directories(config)
+ create_files(config)
+ handle_defaults(config)
+ copy_config(config)
+ handle_permissions(config)
+ elif config_strategy == "COPY_ONCE":
+ if os.path.exists('/configured'):
+ raise ImmutableConfig(
+ "The config strategy prevents copying new configs",
+ exit_code=0)
+ else:
+ create_directories(config)
+ create_files(config)
+ handle_defaults(config)
+ copy_config(config)
+ handle_permissions(config)
+ os.mknod('/configured')
+ else:
+ raise InvalidConfig('KOLLA_CONFIG_STRATEGY is not set properly')
+
+
+def execute_command_check(config):
+ cmd = config.get('command')
+ with open("/run_command", "r") as f:
+ cmd_running = f.read()
+ if cmd != cmd_running:
+ msg = "Running command differs. " + cmd + " != " + cmd_running
+ raise ConfigFileCommandDiffers(msg)
+
+
+def execute_config_check(config):
+ """Check configuration state consistency and validate config file entries.
+
+ This function compares the current config file destinations from the
+ provided config dictionary with those stored in the defaults state file.
+ If any destinations are found in the state file but not in the config,
+ a StateMismatch exception is raised. These missing files would otherwise
+ be restored or removed depending on their backup state.
+
+ After validating consistency, the function performs standard checks on
+ each declared configuration file, including content, permissions, and
+ ownership validation.
+
+ Args:
+ config (dict): The configuration dictionary containing 'config_files'
+ entries as expected by Kolla.
+
+ Raises:
+ StateMismatch: If there are entries in the defaults state not present
+ in the provided config.
+ """
+ state = get_defaults_state()
+
+ # Build a set of all current destination paths from the config
+ # If the destination is a directory, we append the
+ # basename of the source
+ current_dests = {
+ entry['dest'] if not entry['dest'].endswith('/') else
+ os.path.join(entry['dest'], os.path.basename(entry['source']))
+ for entry in config.get('config_files', [])
+ if entry.get('dest')
+ }
+
+ # Detect any paths that are present in the state file but
+ # missing from the config file.
+ # These would be either restored (if state[dest] has a backup)
+ # or removed (if dest is null)
+ removed_dests = [
+ path for path in state.keys()
+ if path not in current_dests
+ ]
+
+ if removed_dests:
+ raise StateMismatch(
+ f"The following config files are tracked in state but missing "
+ f"from the config file. "
+ f"They would be restored or removed: {sorted(removed_dests)}"
+ )
+
+ # Perform the regular content, permissions, and ownership
+ # checks on the declared files
+ for data in config.get('config_files', []):
+ config_file = ConfigFile(**data)
+ config_file.check()
+
+
+def main():
+ try:
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--check',
+ action='store_true',
+ required=False,
+ help='Check whether the configs changed')
+ args = parser.parse_args()
+ config = load_config()
+
+ if args.check:
+ execute_command_check(config)
+ execute_config_check(config)
+ else:
+ execute_config_strategy(config)
+ except ExitingException as e:
+ LOG.error("%s: %s", e.__class__.__name__, e)
+ return e.exit_code
+ except Exception:
+ LOG.exception('Unexpected error:')
+ return 2
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/kolla/docker/base/sources.list b/kolla/docker/base/sources.list
new file mode 100644
index 000000000..1acbbcb59
--- /dev/null
+++ b/kolla/docker/base/sources.list
@@ -0,0 +1 @@
+# used for adding custom repos by 'apt_sources_list' option of kolla-build.conf
diff --git a/kolla/docker/base/start.sh b/kolla/docker/base/start.sh
new file mode 100644
index 000000000..7ac505aea
--- /dev/null
+++ b/kolla/docker/base/start.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -o errexit
+set -o pipefail
+set -o nounset
+set -o xtrace
+
+# Processing /var/lib/kolla/config_files/config.json as root. This is necessary
+# to permit certain files to be controlled by the root user which should
+# not be writable by the dropped-privileged user, especially /run_command
+sudo -E kolla_set_configs
+CMD=$(cat /run_command)
+ARGS=""
+
+# Install/remove custom CA certificates
+sudo kolla_copy_cacerts
+
+# Install projects that are in /dev-mode
+sudo kolla_install_projects
+
+if [[ ! "${!KOLLA_SKIP_EXTEND_START[@]}" ]]; then
+ # Run additional commands if present
+ . kolla_extend_start
+fi
+
+echo "Running command: '${CMD}${ARGS:+ $ARGS}'"
+exec ${CMD} ${ARGS}
diff --git a/kolla/docker/base/sudoers b/kolla/docker/base/sudoers
new file mode 100644
index 000000000..eb31bc98b
--- /dev/null
+++ b/kolla/docker/base/sudoers
@@ -0,0 +1,26 @@
+# The idea here is a container service adds their UID to the kolla group
+# via usermod -a -G kolla <uid>. Then the kolla_start may run
+# kolla_set_configs via sudo as the root user which is necessary to protect
+# the immutability of the container
+
+# anyone in the kolla group may sudo -E (set the environment)
+Defaults: %kolla setenv
+
+Defaults secure_path="/var/lib/kolla/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+
+# root may run any commands via sudo as the network seervice user. This is
+# neededfor database migrations of existing services which have not been
+# converted to run as a non-root user, but instead do that via sudo -E glance
+root ALL=(ALL) ALL
+
+# anyone in the kolla group may run /usr/local/bin/kolla_set_configs as the
+# root user via sudo without password confirmation
+%kolla ALL=(root) NOPASSWD: /usr/local/bin/kolla_set_configs
+
+# Copy custom CA certificates to containers
+%kolla ALL=(root) NOPASSWD: /usr/local/bin/kolla_copy_cacerts
+
+# Install projects in dev-mode
+%kolla ALL=(root) NOPASSWD: /usr/local/bin/kolla_install_projects
+
+#includedir /etc/sudoers.d
diff --git a/kolla/docker/base/ubuntu.sources b/kolla/docker/base/ubuntu.sources
new file mode 100644
index 000000000..f8f609cee
--- /dev/null
+++ b/kolla/docker/base/ubuntu.sources
@@ -0,0 +1,24 @@
+Types: deb
+URIs: http://archive.ubuntu.com/ubuntu/
+Suites: noble noble-updates noble-backports
+Components: main universe
+Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
+
+## Ubuntu security updates. Aside from URIs and Suites,
+## this should mirror your choices in the previous section.
+Types: deb
+URIs: http://security.ubuntu.com/ubuntu/
+Suites: noble-security
+Components: main universe
+Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
+
+## Ubuntu Cloud Archive
+Types: deb
+URIs: http://ubuntu-cloud.archive.canonical.com/ubuntu
+Suites: noble-updates/gazpacho
+Components: main
+Signed-By: /usr/share/keyrings/ubuntu-cloud-keyring.gpg
+
+# NOTE(hrw): extra repositories are added into image when they are needed as
+# separate files in /etc/apt/sources.list.d/ directory. For that purpose they
+# are defined in kolla/template/repos.yaml file.
diff --git a/kolla/docker/base/ubuntu.sources.arm64 b/kolla/docker/base/ubuntu.sources.arm64
new file mode 100644
index 000000000..751af7ccf
--- /dev/null
+++ b/kolla/docker/base/ubuntu.sources.arm64
@@ -0,0 +1,24 @@
+Types: deb
+URIs: http://ports.ubuntu.com/ubuntu-ports/
+Suites: noble noble-updates noble-backports
+Components: main universe
+Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
+
+## Ubuntu security updates. Aside from URIs and Suites,
+## this should mirror your choices in the previous section.
+Types: deb
+URIs: http://ports.ubuntu.com/ubuntu-ports/
+Suites: noble-security
+Components: main universe
+Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
+
+## Ubuntu Cloud Archive
+Types: deb
+URIs: http://ubuntu-cloud.archive.canonical.com/ubuntu
+Suites: noble-updates/gazpacho
+Components: main
+Signed-By: /usr/share/keyrings/ubuntu-cloud-keyring.gpg
+
+# NOTE(hrw): extra repositories are added into image when they are needed as
+# separate files in /etc/apt/sources.list.d/ directory. For that purpose they
+# are defined in kolla/template/repos.yaml file.
diff --git a/kolla/docker/bifrost/bifrost-base/Dockerfile.j2 b/kolla/docker/bifrost/bifrost-base/Dockerfile.j2
new file mode 100644
index 000000000..3e82a9fd3
--- /dev/null
+++ b/kolla/docker/bifrost/bifrost-base/Dockerfile.j2
@@ -0,0 +1,76 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block bifrost_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set venv_path='/var/lib/kolla/venv' %}
+
+{{ macros.configure_user(name='bifrost') }}
+
+{% set bifrost_base_packages = [
+ 'cpio'
+] %}
+
+{% if base_package_type == 'rpm' %}
+
+{# NOTE(mgoddard): EPEL required for nginx #}
+{{ macros.enable_extra_repos(['epel']) }}
+{% endif %}
+
+{{ macros.install_packages(bifrost_base_packages | customizable("packages")) }}
+
+ENV VENV {{ venv_path }}
+
+{% set bifrost_base_pip_packages = [
+ '/bifrost'
+] %}
+
+ADD bifrost-base-archive /bifrost-base-source
+COPY build_arg.yml /tmp/build_arg.yml
+
+RUN mkdir -p /requirements \
+ && curl -o /requirements/upper-constraints.txt ${UPPER_CONSTRAINTS_FILE:-https://releases.openstack.org/constraints/upper/{{ openstack_release }}} \
+ && python3 -m venv --system-site-packages {{ venv_path }} \
+ && ln -s bifrost-base-source/* bifrost \
+ && {{ macros.install_pip(['pip', 'wheel', 'setuptools']) }} \
+ && {{ macros.install_pip(bifrost_base_pip_packages | customizable("pip_packages")) }}
+
+WORKDIR /bifrost
+
+{# ANSIBLE_GATHER_TIMEOUT is a workaround for https://github.com/ansible/ansible/issues/43884 #}
+ENV ANSIBLE_GATHER_TIMEOUT=30
+
+{% block bifrost_ansible_install %}
+{%- if base_package_type == 'deb' %}
+RUN apt-get --error-on=any update && \
+{%- else %}
+RUN echo " " && \
+{%- endif %}
+ bash -c '$VENV/bin/pip install "ansible>=12,<14" && \
+ $VENV/bin/ansible-galaxy collection install -r /bifrost/ansible-collections-requirements.yml && \
+ $VENV/bin/ansible-playbook -vvvv -i /bifrost/playbooks/inventory/target \
+ /bifrost/playbooks/install.yaml \
+ -e upper_constraints_file="/requirements/upper-constraints.txt" \
+ -e git_branch={{ openstack_branch_slashed }} \
+ -e ipa_upstream_release={{ openstack_branch }} \
+ -e @/tmp/build_arg.yml && \
+{%- if base_package_type == 'deb' %}
+ apt-get clean && rm -rf /var/lib/apt/lists/*'
+{%- else %}
+ dnf clean all && rm -rf /var/dnf/cache'
+{%- endif %}
+{% endblock %}
+
+COPY bifrost_sudoers /etc/sudoers.d/kolla_bifrost_sudoers
+
+RUN chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_bifrost_sudoers \
+ && chown -R bifrost:bifrost /bifrost
+
+{{ macros.kolla_patch_sources() }}
+
+{% block bifrost_base_footer %}{% endblock %}
diff --git a/kolla/docker/bifrost/bifrost-base/bifrost_sudoers b/kolla/docker/bifrost/bifrost-base/bifrost_sudoers
new file mode 100644
index 000000000..6c343a956
--- /dev/null
+++ b/kolla/docker/bifrost/bifrost-base/bifrost_sudoers
@@ -0,0 +1 @@
+bifrost ALL=(ALL) NOPASSWD:ALL
diff --git a/kolla/docker/bifrost/bifrost-base/build_arg.yml b/kolla/docker/bifrost/bifrost-base/build_arg.yml
new file mode 100644
index 000000000..8c810f133
--- /dev/null
+++ b/kolla/docker/bifrost/bifrost-base/build_arg.yml
@@ -0,0 +1,7 @@
+---
+skip_bootstrap: true
+skip_start: true
+install_dib: true
+create_image_via_dib: false
+ansible_python_interpreter: /var/lib/kolla/venv/bin/python
+internal_ip: "127.0.0.1"
diff --git a/kolla/docker/bifrost/bifrost-deploy/Dockerfile.j2 b/kolla/docker/bifrost/bifrost-deploy/Dockerfile.j2
new file mode 100644
index 000000000..04fc733fc
--- /dev/null
+++ b/kolla/docker/bifrost/bifrost-deploy/Dockerfile.j2
@@ -0,0 +1,35 @@
+FROM {{ namespace }}/{{ image_prefix }}bifrost-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block bifrost_deploy_header %}{% endblock %}
+
+ENV container docker
+
+{% import "macros.j2" as macros with context %}
+
+RUN rm -f $(find /lib/systemd/system/sysinit.target.wants/ ! -name systemd-tmpfiles-setup.service -type l); \
+ rm -f /lib/systemd/system/multi-user.target.wants/*; \
+ rm -f /etc/systemd/system/*.wants/*; \
+ rm -f /lib/systemd/system/local-fs.target.wants/*; \
+ rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
+ rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
+ rm -f /lib/systemd/system/basic.target.wants/*; \
+ rm -f /lib/systemd/system/anaconda.target.wants/*;
+
+{% set bifrost_deploy_packages = [
+ 'e2fsprogs',
+ 'systemd-sysv',
+] %}
+
+{{ macros.install_packages(bifrost_deploy_packages | customizable("packages")) }}
+
+# Clear any customisation by Kolla to entrypoint & command
+ENTRYPOINT []
+CMD [ "/sbin/init" ]
+
+{{ macros.kolla_patch_sources() }}
+
+{% block bifrost_deploy_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/blazar/blazar-api/Dockerfile.j2 b/kolla/docker/blazar/blazar-api/Dockerfile.j2
new file mode 100644
index 000000000..70d6ba22a
--- /dev/null
+++ b/kolla/docker/blazar/blazar-api/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}blazar-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block blazar_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_blazar_extend_start
+RUN chmod 644 /usr/local/bin/kolla_blazar_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block blazar_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER blazar
diff --git a/kolla/docker/blazar/blazar-api/extend_start.sh b/kolla/docker/blazar/blazar-api/extend_start.sh
new file mode 100644
index 000000000..ff4f2b801
--- /dev/null
+++ b/kolla/docker/blazar/blazar-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ blazar-db-manage --config-file /etc/blazar/blazar.conf upgrade head
+ exit 0
+fi
diff --git a/kolla/docker/blazar/blazar-base/Dockerfile.j2 b/kolla/docker/blazar/blazar-base/Dockerfile.j2
new file mode 100644
index 000000000..433842e41
--- /dev/null
+++ b/kolla/docker/blazar/blazar-base/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block blazar_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='blazar') }}
+
+ADD blazar-base-archive /blazar-base-source
+
+{% set blazar_base_pip_packages = [
+ '/blazar'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s blazar-base-source/* blazar \
+ && {{ macros.install_pip(blazar_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/blazar \
+ && cp -r /blazar/etc/blazar/* /etc/blazar \
+ && touch /usr/local/bin/kolla_blazar_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_blazar_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block blazar_base_footer %}{% endblock %}
diff --git a/kolla/docker/blazar/blazar-base/extend_start.sh b/kolla/docker/blazar/blazar-base/extend_start.sh
new file mode 100644
index 000000000..a96a4c113
--- /dev/null
+++ b/kolla/docker/blazar/blazar-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/blazar" ]]; then
+ mkdir -p /var/log/kolla/blazar
+fi
+if [[ $(stat -c %a /var/log/kolla/blazar) != "755" ]]; then
+ chmod 755 /var/log/kolla/blazar
+fi
+
+. /usr/local/bin/kolla_blazar_extend_start
diff --git a/kolla/docker/blazar/blazar-manager/Dockerfile.j2 b/kolla/docker/blazar/blazar-manager/Dockerfile.j2
new file mode 100644
index 000000000..5cde3db3b
--- /dev/null
+++ b/kolla/docker/blazar/blazar-manager/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}blazar-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block blazar_manager_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block blazar_manager_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER blazar
diff --git a/kolla/docker/ceilometer/ceilometer-base/Dockerfile.j2 b/kolla/docker/ceilometer/ceilometer-base/Dockerfile.j2
new file mode 100644
index 000000000..18bd7600a
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-base/Dockerfile.j2
@@ -0,0 +1,43 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ceilometer_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='ceilometer') }}
+
+{% set ceilometer_base_pip_packages = [
+ '/ceilometer'
+] %}
+
+{% set ceilometer_base_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+ADD ceilometer-base-archive /ceilometer-base-source
+ADD plugins-archive /
+
+COPY ceilometer_sudoers /etc/sudoers.d/kolla_ceilometer_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+# NOTE(egonzalez): Remove ceilometer from constraint or will fail.
+RUN ln -s ceilometer-base-source/* ceilometer \
+ && {{ macros.upper_constraints_remove("ceilometer") }} \
+ && {{ macros.install_pip(ceilometer_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/ceilometer \
+ && cp -r /ceilometer/etc/ceilometer/* /etc/ceilometer/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/ceilometer/rootwrap.conf \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(ceilometer_base_plugins_pip_packages) }}; \
+ fi \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_ceilometer_sudoers \
+ && touch /usr/local/bin/kolla_ceilometer_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_ceilometer_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ceilometer_base_footer %}{% endblock %}
diff --git a/kolla/docker/ceilometer/ceilometer-base/ceilometer_sudoers b/kolla/docker/ceilometer/ceilometer-base/ceilometer_sudoers
new file mode 100644
index 000000000..dd861bb58
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-base/ceilometer_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R ceilometer\: /var/lib/ceilometer/, /bin/chown -R ceilometer\: /var/lib/ceilometer/
diff --git a/kolla/docker/ceilometer/ceilometer-base/extend_start.sh b/kolla/docker/ceilometer/ceilometer-base/extend_start.sh
new file mode 100644
index 000000000..4d3fe6b61
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+CEILOMETER_LOG_DIR=/var/log/kolla/ceilometer
+
+if [[ ! -d "${CEILOMETER_LOG_DIR}" ]]; then
+ mkdir -p "${CEILOMETER_LOG_DIR}"
+fi
+if [[ $(stat -c %U:%G "${CEILOMETER_LOG_DIR}") != "ceilometer:kolla" ]]; then
+ chown ceilometer:kolla "${CEILOMETER_LOG_DIR}"
+fi
+if [[ $(stat -c %a "${CEILOMETER_LOG_DIR}") != "755" ]]; then
+ chmod 755 "${CEILOMETER_LOG_DIR}"
+fi
+
+. /usr/local/bin/kolla_ceilometer_extend_start
diff --git a/kolla/docker/ceilometer/ceilometer-central/Dockerfile.j2 b/kolla/docker/ceilometer/ceilometer-central/Dockerfile.j2
new file mode 100644
index 000000000..df96d8ed3
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-central/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}ceilometer-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ceilometer_central_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ceilometer_central_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER ceilometer
diff --git a/kolla/docker/ceilometer/ceilometer-compute/Dockerfile.j2 b/kolla/docker/ceilometer/ceilometer-compute/Dockerfile.j2
new file mode 100644
index 000000000..879c4fb01
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-compute/Dockerfile.j2
@@ -0,0 +1,34 @@
+FROM {{ namespace }}/{{ image_prefix }}ceilometer-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ceilometer_compute_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ceilometer_compute_packages = [
+ 'python3-libvirt'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set ceilometer_compute_packages = [
+ 'python3-libvirt'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(ceilometer_compute_packages | customizable("packages")) }}
+
+# NOTE(jeffrey4l): just for back compatibility
+{% set ceilometer_compute_pip_packages = [] %}
+
+RUN {{ macros.install_pip(ceilometer_compute_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ceilometer_compute_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+# NOTE(huikang): non-root user does not work with libvirt.sock.
+# Need configuration in nova-libvirt container to allow
+# USER ceilometer to access libvirt.sock
diff --git a/kolla/docker/ceilometer/ceilometer-ipmi/Dockerfile.j2 b/kolla/docker/ceilometer/ceilometer-ipmi/Dockerfile.j2
new file mode 100644
index 000000000..a767dc842
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-ipmi/Dockerfile.j2
@@ -0,0 +1,27 @@
+FROM {{ namespace }}/{{ image_prefix }}ceilometer-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ceilometer_ipmi_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ceilometer_ipmi_packages = [
+ 'ipmitool'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set ceilometer_ipmi_packages = [
+ 'ipmitool'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(ceilometer_ipmi_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ceilometer_ipmi_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER ceilometer
diff --git a/kolla/docker/ceilometer/ceilometer-notification/Dockerfile.j2 b/kolla/docker/ceilometer/ceilometer-notification/Dockerfile.j2
new file mode 100644
index 000000000..1fc03ffc4
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-notification/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}ceilometer-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ceilometer_notification_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_ceilometer_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ceilometer_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ceilometer_notification_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER ceilometer
diff --git a/kolla/docker/ceilometer/ceilometer-notification/extend_start.sh b/kolla/docker/ceilometer/ceilometer-notification/extend_start.sh
new file mode 100644
index 000000000..0abeeb1e1
--- /dev/null
+++ b/kolla/docker/ceilometer/ceilometer-notification/extend_start.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ if [[ "${CEILOMETER_DATABASE_TYPE}" == "gnocchi" ]]; then
+ ceilometer-upgrade ${CEILOMETER_UPGRADE_PARAMS}
+ fi
+ sudo chown -R ceilometer: /var/lib/ceilometer/
+ exit 0
+fi
diff --git a/kolla/docker/cinder/cinder-api/Dockerfile.j2 b/kolla/docker/cinder/cinder-api/Dockerfile.j2
new file mode 100644
index 000000000..b82fba6d3
--- /dev/null
+++ b/kolla/docker/cinder/cinder-api/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}cinder-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cinder_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set cinder_api_packages = [
+] %}
+
+{{ macros.install_packages(cinder_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_cinder_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_cinder_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cinder_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/cinder/cinder-api/extend_start.sh b/kolla/docker/cinder/cinder-api/extend_start.sh
new file mode 100644
index 000000000..94b0fe875
--- /dev/null
+++ b/kolla/docker/cinder/cinder-api/extend_start.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -o errexit
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ cinder-manage db sync
+ exit 0
+fi
+
+if [[ "${!KOLLA_OSM[@]}" ]]; then
+ cinder-manage db online_data_migrations
+ exit 0
+fi
diff --git a/kolla/docker/cinder/cinder-backup/Dockerfile.j2 b/kolla/docker/cinder/cinder-backup/Dockerfile.j2
new file mode 100644
index 000000000..15be355dd
--- /dev/null
+++ b/kolla/docker/cinder/cinder-backup/Dockerfile.j2
@@ -0,0 +1,33 @@
+FROM {{ namespace }}/{{ image_prefix }}cinder-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cinder_backup_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set cinder_backup_packages = [
+ 'device-mapper-multipath',
+ 'lsscsi',
+ 'nfs-utils',
+ 'sysfsutils'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set cinder_backup_packages = [
+ 'lsscsi',
+ 'multipath-tools',
+ 'nfs-common',
+ 'sysfsutils'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(cinder_backup_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cinder_backup_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER cinder
diff --git a/kolla/docker/cinder/cinder-base/Dockerfile.j2 b/kolla/docker/cinder/cinder-base/Dockerfile.j2
new file mode 100644
index 000000000..bd25d9b18
--- /dev/null
+++ b/kolla/docker/cinder/cinder-base/Dockerfile.j2
@@ -0,0 +1,60 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cinder_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='cinder') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['ceph', 'epel']) }}
+
+ {% set cinder_base_packages = [
+ 'ceph-common',
+ 'cryptsetup',
+ 'lvm2',
+ 'nvme-cli',
+ 'qemu-img',
+ 'systemd-udev'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set cinder_base_packages = [
+ 'ceph-common',
+ 'cryptsetup',
+ 'lvm2',
+ 'nvme-cli',
+ 'python3-cephfs',
+ 'python3-rados',
+ 'python3-rbd',
+ 'qemu-utils'
+ ] %}
+{% endif %}
+{{ macros.install_packages(cinder_base_packages | customizable("packages")) }}
+
+{% set cinder_base_pip_packages = [
+ '/cinder'
+] %}
+
+ADD cinder-base-archive /cinder-base-source
+
+COPY cinder_sudoers /etc/sudoers.d/kolla_cinder_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s cinder-base-source/* cinder \
+ && {{ macros.install_pip(cinder_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/cinder \
+ && cp -r /cinder/etc/cinder/* /etc/cinder/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/cinder_api_audit_map.conf /etc/cinder/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/cinder/rootwrap.conf \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_cinder_sudoers \
+ && touch /usr/local/bin/kolla_cinder_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_cinder_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cinder_base_footer %}{% endblock %}
diff --git a/kolla/docker/cinder/cinder-base/cinder_sudoers b/kolla/docker/cinder/cinder-base/cinder_sudoers
new file mode 100644
index 000000000..3750f31a0
--- /dev/null
+++ b/kolla/docker/cinder/cinder-base/cinder_sudoers
@@ -0,0 +1 @@
+cinder ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/cinder-rootwrap /etc/cinder/rootwrap.conf *
diff --git a/kolla/docker/cinder/cinder-base/extend_start.sh b/kolla/docker/cinder/cinder-base/extend_start.sh
new file mode 100644
index 000000000..ddf1469c9
--- /dev/null
+++ b/kolla/docker/cinder/cinder-base/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/cinder" ]]; then
+ mkdir -p /var/log/kolla/cinder
+fi
+if [[ $(stat -c %U:%G /var/log/kolla/cinder) != "cinder:kolla" ]]; then
+ chown -R cinder:kolla /var/log/kolla/cinder
+fi
+if [[ $(stat -c %a /var/log/kolla/cinder) != "755" ]]; then
+ chmod 755 /var/log/kolla/cinder
+fi
+
+. /usr/local/bin/kolla_cinder_extend_start
diff --git a/kolla/docker/cinder/cinder-scheduler/Dockerfile.j2 b/kolla/docker/cinder/cinder-scheduler/Dockerfile.j2
new file mode 100644
index 000000000..c43792dcc
--- /dev/null
+++ b/kolla/docker/cinder/cinder-scheduler/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}cinder-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cinder_scheduler_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cinder_scheduler_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER cinder
diff --git a/kolla/docker/cinder/cinder-volume/Dockerfile.j2 b/kolla/docker/cinder/cinder-volume/Dockerfile.j2
new file mode 100644
index 000000000..0e4f38a6e
--- /dev/null
+++ b/kolla/docker/cinder/cinder-volume/Dockerfile.j2
@@ -0,0 +1,58 @@
+FROM {{ namespace }}/{{ image_prefix }}cinder-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cinder_volume_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set cinder_volume_packages = [
+ 'lsscsi',
+ 'device-mapper-multipath',
+ 'nfs-utils',
+ 'nvmetcli',
+ 'sysfsutils',
+ 'targetcli'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set cinder_volume_packages = [
+ 'lsscsi',
+ 'multipath-tools',
+ 'nfs-common',
+ 'sysfsutils',
+ 'targetcli-fb',
+ 'thin-provisioning-tools',
+ 'tgt'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(cinder_volume_packages | customizable("packages")) }}
+
+{% if base_package_type == 'rpm' %}
+
+{% block cinder_volume_redhat_setup %}
+{% endblock %}
+
+{% elif base_package_type == 'deb' %}
+
+{% block cinder_volume_ubuntu_setup %}
+RUN sed -i '1 i include /var/lib/cinder/volumes/*' /etc/tgt/targets.conf
+{% endblock %}
+
+{% endif %}
+
+COPY cinder_sudoers /etc/sudoers.d/kolla_cinder_volume_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_cinder_extend_start
+
+RUN chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_cinder_volume_sudoers \
+ && chmod 644 /usr/local/bin/kolla_cinder_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cinder_volume_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER cinder
diff --git a/kolla/docker/cinder/cinder-volume/cinder_sudoers b/kolla/docker/cinder/cinder-volume/cinder_sudoers
new file mode 100644
index 000000000..f3bec8b13
--- /dev/null
+++ b/kolla/docker/cinder/cinder-volume/cinder_sudoers
@@ -0,0 +1,5 @@
+cinder ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/cinder-rootwrap /etc/cinder/rootwrap.conf *
+%kolla ALL=(root) NOPASSWD: /bin/chown -R cinder\:kolla /var/lib/cinder, /usr/bin/chown -R cinder\:kolla /var/lib/cinder
+%kolla ALL=(root) NOPASSWD: /bin/chmod 2775 /var/lib/cinder, /usr/bin/chmod 2775 /var/lib/cinder
+%kolla ALL=(root) NOPASSWD: /bin/chown -R cinder\:kolla /etc/iscsi, /usr/bin/chown -R cinder\:kolla /etc/iscsi
+%kolla ALL=(root) NOPASSWD: /bin/chmod 2775 /etc/iscsi, /usr/bin/chmod 2775 /etc/iscsi
diff --git a/kolla/docker/cinder/cinder-volume/extend_start.sh b/kolla/docker/cinder/cinder-volume/extend_start.sh
new file mode 100644
index 000000000..67e866143
--- /dev/null
+++ b/kolla/docker/cinder/cinder-volume/extend_start.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [[ $(stat -c %U:%G /var/lib/cinder) != "cinder:kolla" ]]; then
+ sudo chown -R cinder:kolla /var/lib/cinder
+fi
+if [[ $(stat -c %a /var/lib/cinder) != "2775" ]]; then
+ sudo chmod 2775 /var/lib/cinder
+fi
+if [[ $(stat -c %U:%G /etc/iscsi) != "cinder:kolla" ]]; then
+ sudo chown -R cinder:kolla /etc/iscsi
+fi
+if [[ $(stat -c %a /etc/iscsi) != "2775" ]]; then
+ sudo chmod 2775 /etc/iscsi
+fi
diff --git a/kolla/docker/cloudkitty/cloudkitty-api/Dockerfile.j2 b/kolla/docker/cloudkitty/cloudkitty-api/Dockerfile.j2
new file mode 100644
index 000000000..fd3d124f3
--- /dev/null
+++ b/kolla/docker/cloudkitty/cloudkitty-api/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}cloudkitty-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cloudkitty_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set cloudkitty_api_packages = [
+] %}
+
+{{ macros.install_packages(cloudkitty_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_cloudkitty_extend_start
+RUN chmod 644 /usr/local/bin/kolla_cloudkitty_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cloudkitty_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/cloudkitty/cloudkitty-api/extend_start.sh b/kolla/docker/cloudkitty/cloudkitty-api/extend_start.sh
new file mode 100644
index 000000000..26d4a8c36
--- /dev/null
+++ b/kolla/docker/cloudkitty/cloudkitty-api/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ cloudkitty-dbsync upgrade
+ cloudkitty-storage-init
+ exit 0
+fi
diff --git a/kolla/docker/cloudkitty/cloudkitty-base/Dockerfile.j2 b/kolla/docker/cloudkitty/cloudkitty-base/Dockerfile.j2
new file mode 100644
index 000000000..b545d4adf
--- /dev/null
+++ b/kolla/docker/cloudkitty/cloudkitty-base/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cloudkitty_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='cloudkitty') }}
+
+{% set cloudkitty_base_pip_packages = [
+ '/cloudkitty'
+] %}
+
+ADD cloudkitty-base-archive /cloudkitty-base-source
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s cloudkitty-base-source/* cloudkitty \
+ && {{ macros.install_pip(cloudkitty_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/cloudkitty \
+ && cp -r /cloudkitty/etc/cloudkitty/* /etc/cloudkitty/ \
+ && touch /usr/local/bin/kolla_cloudkitty_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_cloudkitty_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cloudkitty_base_footer %}{% endblock %}
diff --git a/kolla/docker/cloudkitty/cloudkitty-base/extend_start.sh b/kolla/docker/cloudkitty/cloudkitty-base/extend_start.sh
new file mode 100644
index 000000000..cf49f9caf
--- /dev/null
+++ b/kolla/docker/cloudkitty/cloudkitty-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/cloudkitty" ]]; then
+ mkdir -p /var/log/kolla/cloudkitty
+fi
+if [[ $(stat -c %a /var/log/kolla/cloudkitty) != "755" ]]; then
+ chmod 755 /var/log/kolla/cloudkitty
+fi
+
+. /usr/local/bin/kolla_cloudkitty_extend_start
diff --git a/kolla/docker/cloudkitty/cloudkitty-processor/Dockerfile.j2 b/kolla/docker/cloudkitty/cloudkitty-processor/Dockerfile.j2
new file mode 100644
index 000000000..33f30fd33
--- /dev/null
+++ b/kolla/docker/cloudkitty/cloudkitty-processor/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}cloudkitty-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cloudkitty_processor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set cloudkitty_processor_packages = [
+] %}
+
+{{ macros.install_packages(cloudkitty_processor_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cloudkitty_processor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER cloudkitty
diff --git a/kolla/docker/collectd/Dockerfile.j2 b/kolla/docker/collectd/Dockerfile.j2
new file mode 100644
index 000000000..32ded377e
--- /dev/null
+++ b/kolla/docker/collectd/Dockerfile.j2
@@ -0,0 +1,81 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block collectd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='collectd') }}
+
+{% set collectd_packages = [
+ 'collectd'
+] %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['opstools']) }}
+
+ {% set collectd_packages = collectd_packages + [
+ 'collectd-amqp',
+ 'collectd-amqp1',
+ 'collectd-apache',
+ 'collectd-bind',
+ 'collectd-ceph',
+ 'collectd-chrony',
+ 'collectd-connectivity',
+ 'collectd-curl',
+ 'collectd-curl_json',
+ 'collectd-curl_xml',
+ 'collectd-dbi',
+ 'collectd-disk',
+ 'collectd-dns',
+ 'collectd-ipmi',
+ 'collectd-logparser',
+ 'collectd-mcelog',
+ 'collectd-mysql',
+ 'collectd-netlink',
+ 'collectd-openldap',
+ 'collectd-ovs-events',
+ 'collectd-ovs-stats',
+ 'collectd-ping',
+ 'collectd-procevent',
+ 'collectd-python',
+ 'collectd-sensors',
+ 'collectd-smart',
+ 'collectd-snmp',
+ 'collectd-snmp-agent',
+ 'collectd-sysevent',
+ 'collectd-utils',
+ 'collectd-virt',
+ 'collectd-write_http',
+ 'collectd-write_kafka',
+ 'collectd-write_prometheus',
+ ] %}
+
+ {% if base_arch =='x86_64' %}
+ {% set collectd_packages = collectd_packages + [
+ 'collectd-hugepages',
+ 'collectd-pcie-errors',
+ 'collectd-pmu',
+ 'collectd-rdt',
+ 'collectd-turbostat'
+ ] %}
+ {% endif %}
+
+{% endif %}
+
+{{ macros.install_packages(collectd_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chown -R collectd /var/lib/collectd \
+ && chown -R collectd /var/run/
+
+{{ macros.kolla_patch_sources() }}
+
+{% block collectd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER collectd
diff --git a/kolla/docker/collectd/extend_start.sh b/kolla/docker/collectd/extend_start.sh
new file mode 100644
index 000000000..80ef63db3
--- /dev/null
+++ b/kolla/docker/collectd/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/collectd" ]]; then
+ mkdir -p /var/log/kolla/collectd
+fi
+if [[ $(stat -c %a /var/log/kolla/collectd) != "755" ]]; then
+ chmod 755 /var/log/kolla/collectd
+fi
diff --git a/kolla/docker/cron/Dockerfile.j2 b/kolla/docker/cron/Dockerfile.j2
new file mode 100644
index 000000000..d3a9213ac
--- /dev/null
+++ b/kolla/docker/cron/Dockerfile.j2
@@ -0,0 +1,35 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cron_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set cron_packages = [
+ 'cronie',
+ 'logrotate'
+ ] %}
+
+# NOTE(hrw): In RHEL 9 family it is done by systemd timer, we want cron to
+# handle it.
+COPY logrotate /etc/cron.daily/logrotate
+
+{% elif base_package_type == 'deb' %}
+ {% set cron_packages = [
+ 'cron',
+ 'logrotate'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(cron_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cron_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/cron/extend_start.sh b/kolla/docker/cron/extend_start.sh
new file mode 100644
index 000000000..3476302e1
--- /dev/null
+++ b/kolla/docker/cron/extend_start.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+CRON_LOGROTATE_CURRENT_PATH="$(find /etc/cron* -name logrotate)"
+CRON_LOGROTATE_CURRENT_SCHEDULE=$(echo "${CRON_LOGROTATE_CURRENT_PATH}" | sed -r 's/(.*)(hourly|daily|weekly|monthly)(.*)/\2/g')
+
+if [ -z $CRON_LOGROTATE_CURRENT_PATH ]; then
+ echo "logrotate is not handled by cron"
+else
+ # Pass only for hourly|daily|weekly|monthly
+ if [[ "${KOLLA_LOGROTATE_SCHEDULE:-undefined}" =~ hourly|daily|weekly|monthly ]]; then
+
+ CRON_LOGROTATE_DESIRED_PATH="/etc/cron.${KOLLA_LOGROTATE_SCHEDULE}/logrotate"
+
+ if [[ "${CRON_LOGROTATE_CURRENT_PATH}" != "${CRON_LOGROTATE_DESIRED_PATH}" ]]; then
+ mv ${CRON_LOGROTATE_CURRENT_PATH} ${CRON_LOGROTATE_DESIRED_PATH}
+ CRON_LOGROTATE_CURRENT_SCHEDULE="${KOLLA_LOGROTATE_SCHEDULE}"
+ fi
+ fi
+
+ echo "[i] Cron schedule for logrotate is currently set to: ${CRON_LOGROTATE_CURRENT_SCHEDULE}."
+fi
diff --git a/kolla/docker/cron/logrotate b/kolla/docker/cron/logrotate
new file mode 100755
index 000000000..1ac157001
--- /dev/null
+++ b/kolla/docker/cron/logrotate
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# skip in favour of systemd timer
+if [ -d /run/systemd/system ]; then
+ exit 0
+fi
+
+# this cronjob persists removals (but not purges)
+if [ ! -x /usr/sbin/logrotate ]; then
+ exit 0
+fi
+
+/usr/sbin/logrotate /etc/logrotate.conf
+EXITVALUE=$?
+if [ $EXITVALUE != 0 ]; then
+ /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
+fi
+exit $EXITVALUE
diff --git a/kolla/docker/cyborg/cyborg-agent/Dockerfile.j2 b/kolla/docker/cyborg/cyborg-agent/Dockerfile.j2
new file mode 100644
index 000000000..dfc1adc0b
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-agent/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}cyborg-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cyborg_agent_header %}{% endblock %}
+{% import "macros.j2" as macros with context %}
+
+{% set cyborg_agent_packages = [
+ 'pciutils',
+] %}
+
+{{ macros.install_packages(cyborg_agent_packages | customizable("packages")) }}
+
+{% set cyborg_agent_pip_packages = [ ] %}
+
+RUN {{ macros.install_pip(cyborg_agent_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cyborg_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/cyborg/cyborg-api/Dockerfile.j2 b/kolla/docker/cyborg/cyborg-api/Dockerfile.j2
new file mode 100644
index 000000000..d67cdeb28
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-api/Dockerfile.j2
@@ -0,0 +1,19 @@
+FROM {{ namespace }}/{{ image_prefix }}cyborg-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cyborg_api_header %}{% endblock %}
+{% import "macros.j2" as macros with context %}
+
+{% set cyborg_api_packages = [
+] %}
+{{ macros.install_packages(cyborg_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_cyborg_extend_start
+RUN chmod 644 /usr/local/bin/kolla_cyborg_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cyborg_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/cyborg/cyborg-api/extend_start.sh b/kolla/docker/cyborg/cyborg-api/extend_start.sh
new file mode 100644
index 000000000..b1c7af7af
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ cyborg-dbsync upgrade
+ exit 0
+fi
diff --git a/kolla/docker/cyborg/cyborg-base/Dockerfile.j2 b/kolla/docker/cyborg/cyborg-base/Dockerfile.j2
new file mode 100644
index 000000000..c3f3e602a
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-base/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cyborg_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='cyborg') }}
+
+ADD cyborg-base-archive /cyborg-base-source
+
+{% set cyborg_base_pip_packages = [
+ '/cyborg'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY cyborg_sudoers /etc/sudoers.d/kolla_cyborg_sudoers
+
+RUN ln -s cyborg-base-source/* cyborg \
+ && {{ macros.install_pip(cyborg_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/cyborg/ \
+ && cp -r /cyborg/etc/cyborg/* /etc/cyborg/ \
+ && chmod 750 /etc/sudoers.d \
+ && touch /usr/local/bin/kolla_cyborg_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_cyborg_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cyborg_footer %}{% endblock %}
diff --git a/kolla/docker/cyborg/cyborg-base/cyborg_sudoers b/kolla/docker/cyborg/cyborg-base/cyborg_sudoers
new file mode 100644
index 000000000..63440026d
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-base/cyborg_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R cyborg\: /var/lib/cyborg/, /bin/chown -R cyborg\: /var/lib/cyborg/
diff --git a/kolla/docker/cyborg/cyborg-base/extend_start.sh b/kolla/docker/cyborg/cyborg-base/extend_start.sh
new file mode 100755
index 000000000..10a58c626
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Create log dir for Cyborg logs
+CYBORG_LOG_DIR="/var/log/kolla/cyborg"
+if [[ ! -d "${CYBORG_LOG_DIR}" ]]; then
+ mkdir -p ${CYBORG_LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${CYBORG_LOG_DIR}) != "cyborg:kolla" ]]; then
+ chown cyborg:kolla ${CYBORG_LOG_DIR}
+fi
+if [[ $(stat -c %a ${CYBORG_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${CYBORG_LOG_DIR}
+fi
+
+. /usr/local/bin/kolla_cyborg_extend_start
diff --git a/kolla/docker/cyborg/cyborg-conductor/Dockerfile.j2 b/kolla/docker/cyborg/cyborg-conductor/Dockerfile.j2
new file mode 100644
index 000000000..d17d1cacd
--- /dev/null
+++ b/kolla/docker/cyborg/cyborg-conductor/Dockerfile.j2
@@ -0,0 +1,13 @@
+FROM {{ namespace }}/{{ image_prefix }}cyborg-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block cyborg_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block cyborg_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/designate/designate-api/Dockerfile.j2 b/kolla/docker/designate/designate-api/Dockerfile.j2
new file mode 100644
index 000000000..6ca9ac180
--- /dev/null
+++ b/kolla/docker/designate/designate-api/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/designate/designate-backend-bind9/Dockerfile.j2 b/kolla/docker/designate/designate-backend-bind9/Dockerfile.j2
new file mode 100644
index 000000000..cf1277aeb
--- /dev/null
+++ b/kolla/docker/designate/designate-backend-bind9/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_backend_bind9_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set designate_backend_bind9_packages = ['bind'] %}
+
+{{ macros.install_packages(designate_backend_bind9_packages | customizable("packages")) }}
+
+{% elif base_package_type == 'deb' %}
+ {% set designate_backend_bind9_packages = ['bind9'] %}
+
+{{ macros.install_packages(designate_backend_bind9_packages | customizable("packages")) }}
+
+{% endif %}
+
+{% set designate_backend_bind_name = 'bind' if base_package_type == 'deb' else 'named' %}
+
+RUN mkdir -p /var/lib/kolla/ /var/lib/{{ designate_backend_bind_name }}/ /run/{{ designate_backend_bind_name }} \
+ && chown -R root: /var/lib/{{ designate_backend_bind_name }} /run/{{ designate_backend_bind_name }} \
+ && chmod 755 /run/{{ designate_backend_bind_name }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_backend_bind9_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/designate/designate-base/Dockerfile.j2 b/kolla/docker/designate/designate-base/Dockerfile.j2
new file mode 100644
index 000000000..26a0beca1
--- /dev/null
+++ b/kolla/docker/designate/designate-base/Dockerfile.j2
@@ -0,0 +1,33 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='designate') }}
+
+{% set designate_base_pip_packages = [
+ '/designate'
+] %}
+
+ADD designate-base-archive /designate-base-source
+
+COPY designate_sudoers /etc/sudoers.d/kolla_designate_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s designate-base-source/* designate \
+ && {{ macros.install_pip(designate_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/designate \
+ && cp -r /designate/etc/designate/* /etc/designate/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/designate/rootwrap.conf \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 640 /etc/sudoers.d/kolla_designate_sudoers \
+ && touch /usr/local/bin/kolla_designate_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_designate_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_base_footer %}{% endblock %}
diff --git a/kolla/docker/designate/designate-base/designate_sudoers b/kolla/docker/designate/designate-base/designate_sudoers
new file mode 100644
index 000000000..d06679fe2
--- /dev/null
+++ b/kolla/docker/designate/designate-base/designate_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /var/lib/kolla/venv/bin/designate-rootwrap /etc/designate/rootwrap.conf *
diff --git a/kolla/docker/designate/designate-base/extend_start.sh b/kolla/docker/designate/designate-base/extend_start.sh
new file mode 100644
index 000000000..66b376c30
--- /dev/null
+++ b/kolla/docker/designate/designate-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/designate" ]]; then
+ mkdir -p /var/log/kolla/designate
+fi
+if [[ $(stat -c %a /var/log/kolla/designate) != "755" ]]; then
+ chmod 755 /var/log/kolla/designate
+fi
+
+. /usr/local/bin/kolla_designate_extend_start
diff --git a/kolla/docker/designate/designate-central/Dockerfile.j2 b/kolla/docker/designate/designate-central/Dockerfile.j2
new file mode 100644
index 000000000..81c3c901f
--- /dev/null
+++ b/kolla/docker/designate/designate-central/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_central_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_designate_extend_start
+RUN chmod 644 /usr/local/bin/kolla_designate_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_central_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/designate/designate-central/extend_start.sh b/kolla/docker/designate/designate-central/extend_start.sh
new file mode 100644
index 000000000..8e90f55fc
--- /dev/null
+++ b/kolla/docker/designate/designate-central/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -f "/var/log/kolla/designate/designate-manage.log" ]]; then
+ touch /var/log/kolla/designate/designate-manage.log
+ chmod 644 /var/log/kolla/designate/designate-manage.log
+fi
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ designate-manage database sync
+ exit 0
+fi
diff --git a/kolla/docker/designate/designate-mdns/Dockerfile.j2 b/kolla/docker/designate/designate-mdns/Dockerfile.j2
new file mode 100644
index 000000000..dd2e6df75
--- /dev/null
+++ b/kolla/docker/designate/designate-mdns/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_mdns_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_mdns_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/designate/designate-producer/Dockerfile.j2 b/kolla/docker/designate/designate-producer/Dockerfile.j2
new file mode 100644
index 000000000..bd1ce4e1d
--- /dev/null
+++ b/kolla/docker/designate/designate-producer/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_producer_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_producer_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/designate/designate-sink/Dockerfile.j2 b/kolla/docker/designate/designate-sink/Dockerfile.j2
new file mode 100644
index 000000000..8b8e50adf
--- /dev/null
+++ b/kolla/docker/designate/designate-sink/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_sink_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_sink_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/designate/designate-worker/Dockerfile.j2 b/kolla/docker/designate/designate-worker/Dockerfile.j2
new file mode 100644
index 000000000..b4e190df6
--- /dev/null
+++ b/kolla/docker/designate/designate-worker/Dockerfile.j2
@@ -0,0 +1,27 @@
+FROM {{ namespace }}/{{ image_prefix }}designate-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block designate_worker_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+# The bind9 package here is only to provide the rndc binary.
+{% if base_package_type == 'rpm' %}
+ {% set designate_worker_packages = [
+ 'bind'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set designate_worker_packages = [
+ 'bind9'
+ ] %}
+{% endif %}
+{{ macros.install_packages(designate_worker_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block designate_worker_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER designate
diff --git a/kolla/docker/dnsmasq/Dockerfile.j2 b/kolla/docker/dnsmasq/Dockerfile.j2
new file mode 100644
index 000000000..882613be7
--- /dev/null
+++ b/kolla/docker/dnsmasq/Dockerfile.j2
@@ -0,0 +1,19 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block dnsmasq_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set dnsmasq_packages = ['dnsmasq'] %}
+{{ macros.install_packages(dnsmasq_packages| customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{% block dnsmasq_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/dnsmasq/extend_start.sh b/kolla/docker/dnsmasq/extend_start.sh
new file mode 100644
index 000000000..0c9734fa1
--- /dev/null
+++ b/kolla/docker/dnsmasq/extend_start.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+DNSMASQ_PIDFILE=${DNSMASQ_PIDFILE:-/run/ironic/dnsmasq.pid}
+DNSMASQ_PIDFILE_DIR="$(dirname $DNSMASQ_PIDFILE)"
+
+if [[ ! -d "/var/log/kolla/ironic" ]]; then
+ mkdir -p /var/log/kolla/ironic
+fi
+if [[ $(stat -c %a /var/log/kolla/ironic) != "755" ]]; then
+ chmod 755 /var/log/kolla/ironic
+fi
+if [[ ! -r "/var/log/kolla/ironic/dnsmasq.log" ]]; then
+ touch /var/log/kolla/ironic/dnsmasq.log
+ chown ironic:ironic /var/log/kolla/ironic/dnsmasq.log
+fi
+
+if [[ ! -d "$DNSMASQ_PIDFILE_DIR" ]]; then
+ mkdir -p "$DNSMASQ_PIDFILE_DIR"
+fi
+
+# NOTE(wszumski): This writes the PID of dnsmasq out to a file. The PIDFILE can be used in
+# another container to send a signal to dnsmasq to reload its config (providing that the two
+# containers share a PID namespace). The concrete use case is for the Ironic PXE filter to
+# clean up stale host entries on startup as documented in:
+#
+# https://docs.openstack.org/ironic/latest/admin/inspection/pxe_filter.html
+#
+# We cannot use the pid-file option in dnsmasq, since it will only write the PIDFILE if you
+# run dnsmasq in its non-forking mode i.e you do use the --no-daemon or --keep-in-foreground
+# options.
+echo $$ > "$DNSMASQ_PIDFILE"
diff --git a/kolla/docker/etcd/Dockerfile.j2 b/kolla/docker/etcd/Dockerfile.j2
new file mode 100644
index 000000000..aafcd8666
--- /dev/null
+++ b/kolla/docker/etcd/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block etcd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='etcd') }}
+
+{% block etcd_install %}
+ADD etcd-archive /etcd-source
+
+RUN ln -s etcd-source/* etcd \
+ && cp /etcd/etcd /etcd/etcdctl /usr/bin
+{% endblock %}
+
+COPY etcd_sudoers /etc/sudoers.d/kolla_etcd_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_etcd_sudoers
+
+{{ macros.kolla_patch_sources() }}
+
+{% block etcd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER etcd
diff --git a/kolla/docker/etcd/etcd_sudoers b/kolla/docker/etcd/etcd_sudoers
new file mode 100644
index 000000000..d3e244be0
--- /dev/null
+++ b/kolla/docker/etcd/etcd_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown etcd\: /var/lib/etcd/, /bin/chown etcd\: /var/lib/etcd/
diff --git a/kolla/docker/etcd/extend_start.sh b/kolla/docker/etcd/extend_start.sh
new file mode 100644
index 000000000..c54023299
--- /dev/null
+++ b/kolla/docker/etcd/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Create log directory, with appropriate permissions
+if [[ ! -d "/var/log/kolla/etcd" ]]; then
+ mkdir -p /var/log/kolla/etcd
+fi
+if [[ $(stat -c %a /var/log/kolla/etcd) != "755" ]]; then
+ chmod 755 /var/log/kolla/etcd
+fi
+
+if [[ $(stat -c %U /var/lib/etcd/) != "etcd" ]]; then
+ sudo chown etcd: /var/lib/etcd/
+fi
diff --git a/kolla/docker/fluentd/Dockerfile.j2 b/kolla/docker/fluentd/Dockerfile.j2
new file mode 100644
index 000000000..c63e1b990
--- /dev/null
+++ b/kolla/docker/fluentd/Dockerfile.j2
@@ -0,0 +1,62 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+
+{% set fluentd_user = 'fluentd' %}
+
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" fluentd_binary="fluentd" fluentd_user="{{ fluentd_user }}"
+{% endblock %}
+
+{% block fluentd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.enable_extra_repos(['fluentd']) }}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set fluentd_packages = [
+ 'gcc-c++',
+ 'make',
+ 'fluent-package',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set fluentd_packages = [
+ 'g++',
+ 'make',
+ 'fluent-package',
+ ] %}
+
+{% endif %}
+
+{{ macros.configure_user(name=fluentd_user, groups='mysql') }}
+
+COPY fluentd_sudoers /etc/sudoers.d/kolla_fluentd_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{{ macros.install_packages(fluentd_packages | customizable("packages")) }} \
+ && chmod 440 /etc/sudoers.d/kolla_fluentd_sudoers \
+ && mkdir -p /etc/{{ fluentd_user }} \
+ && mkdir -p /var/run/{{ fluentd_user }} \
+ && chown -R {{ fluentd_user }}: /var/run/{{ fluentd_user }} \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+{% block fluentd_plugins_install %}
+
+{% set fluentd_plugins = [
+ 'fluent-plugin-grok-parser',
+ 'fluent-plugin-prometheus',
+ 'fluent-plugin-rewrite-tag-filter',
+] %}
+
+{{ macros.install_fluent_plugins(fluentd_plugins | customizable("plugins")) }}
+
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block fluentd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER {{ fluentd_user }}
diff --git a/kolla/docker/fluentd/extend_start.sh b/kolla/docker/fluentd/extend_start.sh
new file mode 100644
index 000000000..6b304b92c
--- /dev/null
+++ b/kolla/docker/fluentd/extend_start.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Give processes executed with the "kolla" group the permission to create files
+# and sub-directories in the /var/log/kolla directory.
+#
+# Also set the setgid permission on the /var/log/kolla directory so that new
+# files and sub-directories in that directory inherit its group id ("kolla").
+
+if [ ! -d /var/log/kolla ]; then
+ mkdir -p /var/log/kolla
+fi
+if [[ $(stat -c %U:%G /var/log/kolla) != "fluentd:kolla" ]]; then
+ sudo chown fluentd:kolla /var/log/kolla
+fi
+if [[ $(stat -c %a /var/log/kolla) != "2775" ]]; then
+ sudo chmod 2775 /var/log/kolla
+fi
+if [[ (-d /var/lib/fluentd) && ($(stat -c %U:%G /var/lib/fluentd) != "fluentd:kolla") ]]; then
+ sudo chown fluentd:kolla /var/lib/fluentd
+fi
diff --git a/kolla/docker/fluentd/fluentd_sudoers b/kolla/docker/fluentd/fluentd_sudoers
new file mode 100644
index 000000000..25ca93f5a
--- /dev/null
+++ b/kolla/docker/fluentd/fluentd_sudoers
@@ -0,0 +1,8 @@
+%kolla ALL=(root) NOPASSWD: /bin/chown fluentd\:kolla /var/log/kolla, /usr/bin/chown fluentd\:kolla /var/log/kolla
+%kolla ALL=(root) NOPASSWD: /bin/chown td-agent\:kolla /var/log/kolla, /usr/bin/chown td-agent\:kolla /var/log/kolla
+%kolla ALL=(root) NOPASSWD: /bin/chown fluentd\:kolla /var/lib/fluentd, /usr/bin/chown fluentd\:kolla /var/lib/fluentd
+%kolla ALL=(root) NOPASSWD: /bin/chown td-agent\:kolla /var/lib/td-agent, /usr/bin/chown td-agent\:kolla /var/lib/td-agent
+%kolla ALL=(root) NOPASSWD: /bin/chown fluentd\:kolla /var/lib/td-agent, /usr/bin/chown fluentd\:kolla /var/lib/td-agent
+%kolla ALL=(root) NOPASSWD: /bin/chmod 2775 /var/log/kolla, /usr/bin/chmod 2775 /var/log/kolla
+%kolla ALL=(root) NOPASSWD: /bin/chmod 2775 /var/lib/fluentd, /usr/bin/chmod 2775 /var/lib/fluentd
+%kolla ALL=(root) NOPASSWD: /bin/chmod 2775 /var/lib/td-agent, /usr/bin/chmod 2775 /var/lib/td-agent
diff --git a/kolla/docker/glance/glance-api/Dockerfile.j2 b/kolla/docker/glance/glance-api/Dockerfile.j2
new file mode 100644
index 000000000..e3efbfef3
--- /dev/null
+++ b/kolla/docker/glance/glance-api/Dockerfile.j2
@@ -0,0 +1,38 @@
+FROM {{ namespace }}/{{ image_prefix }}glance-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block glance_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set glance_api_packages = [
+ 'lsscsi',
+ 'device-mapper-multipath',
+ 'nvme-cli',
+ 'qemu-img',
+ 'systemd-udev'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set glance_api_packages = [
+ 'lsscsi',
+ 'multipath-tools',
+ 'nfs-common',
+ 'nvme-cli',
+ 'qemu-utils'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(glance_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_glance_extend_start
+RUN chmod 644 /usr/local/bin/kolla_glance_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block glance_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER glance
diff --git a/kolla/docker/glance/glance-api/extend_start.sh b/kolla/docker/glance/glance-api/extend_start.sh
new file mode 100644
index 000000000..3363539a5
--- /dev/null
+++ b/kolla/docker/glance/glance-api/extend_start.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ glance-manage db_sync
+ glance-manage db_load_metadefs
+ exit 0
+fi
+
+# Migrate database and exit if KOLLA_UPGRADE variable is set. This catches all cases
+# of the KOLLA_UPGRADE variable being set, including empty.
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ if [[ "${!GLANCE_DB_EXPAND[@]}" ]]; then
+ echo "Expanding database"
+ glance-manage db expand
+ fi
+ if [[ "${!GLANCE_DB_MIGRATE[@]}" ]]; then
+ echo "Migrating database"
+ glance-manage db migrate
+ fi
+ if [[ "${!GLANCE_DB_CONTRACT[@]}" ]]; then
+ echo "Contracting database"
+ glance-manage db contract
+ fi
+ exit 0
+fi
diff --git a/kolla/docker/glance/glance-base/Dockerfile.j2 b/kolla/docker/glance/glance-base/Dockerfile.j2
new file mode 100644
index 000000000..885cc4353
--- /dev/null
+++ b/kolla/docker/glance/glance-base/Dockerfile.j2
@@ -0,0 +1,54 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block glance_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='glance') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['ceph', 'epel']) }}
+
+ {% set glance_base_packages = [
+ 'python3-rados',
+ 'python3-rbd'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set glance_base_packages = [
+ 'python3-rados',
+ 'python3-rbd'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(glance_base_packages | customizable("packages")) }}
+
+ADD glance-base-archive /glance-base-source
+
+{% set glance_base_pip_packages = [
+ '/glance',
+ 'glance_store[cinder,vmware,swift,s3]'
+] %}
+
+# add missing rootwrap config present in glance_store repo
+COPY etc/glance /etc/glance
+COPY glance_sudoers /etc/sudoers.d/kolla_glance_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s glance-base-source/* glance \
+ && {{ macros.install_pip(glance_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/glance \
+ && cp -r /glance/etc/* /etc/glance/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/glance_api_audit_map.conf /etc/glance/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/glance/rootwrap.conf \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_glance_sudoers \
+ && touch /usr/local/bin/kolla_glance_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_glance_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block glance_base_footer %}{% endblock %}
diff --git a/kolla/docker/glance/glance-base/etc/glance/rootwrap.conf b/kolla/docker/glance/glance-base/etc/glance/rootwrap.conf
new file mode 100644
index 000000000..56e7529af
--- /dev/null
+++ b/kolla/docker/glance/glance-base/etc/glance/rootwrap.conf
@@ -0,0 +1,27 @@
+# Configuration for glance-rootwrap
+# This file should be owned by (and only-writable by) the root user
+
+[DEFAULT]
+# List of directories to load filter definitions from (separated by ',').
+# These directories MUST all be only writeable by root !
+filters_path=/etc/glance/rootwrap.d,/usr/share/glance/rootwrap
+
+# List of directories to search executables in, in case filters do not
+# explicitly specify a full path (separated by ',')
+# If not specified, defaults to system PATH environment variable.
+# These directories MUST all be only writeable by root !
+exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin
+
+# Enable logging to syslog
+# Default value is False
+use_syslog=False
+
+# Which syslog facility to use.
+# Valid values include auth, authpriv, syslog, local0, local1...
+# Default value is 'syslog'
+syslog_log_facility=syslog
+
+# Which messages to log.
+# INFO means log all usage
+# ERROR means only log unsuccessful attempts
+syslog_log_level=ERROR
diff --git a/kolla/docker/glance/glance-base/etc/glance/rootwrap.d/glance_cinder_store.filters b/kolla/docker/glance/glance-base/etc/glance/rootwrap.d/glance_cinder_store.filters
new file mode 100644
index 000000000..46c389b7e
--- /dev/null
+++ b/kolla/docker/glance/glance-base/etc/glance/rootwrap.d/glance_cinder_store.filters
@@ -0,0 +1,12 @@
+# glance-rootwrap command filters for glance cinder store
+# This file should be owned by (and only-writable by) the root user
+
+[Filters]
+# cinder store driver
+disk_chown: RegExpFilter, chown, root, chown, \d+, /dev/(?!.*/\.\.).*
+
+# os-brick library commands
+# os_brick.privileged.run_as_root oslo.privsep context
+# This line ties the superuser privs with the config files, context name,
+# and (implicitly) the actual python code invoked.
+privsep-rootwrap: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, os_brick.privileged.default, --privsep_sock_path, /tmp/.*
diff --git a/kolla/docker/glance/glance-base/extend_start.sh b/kolla/docker/glance/glance-base/extend_start.sh
new file mode 100644
index 000000000..4f5f5ecaf
--- /dev/null
+++ b/kolla/docker/glance/glance-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/glance" ]]; then
+ mkdir -p /var/log/kolla/glance
+fi
+if [[ $(stat -c %a /var/log/kolla/glance) != "755" ]]; then
+ chmod 755 /var/log/kolla/glance
+fi
+
+. /usr/local/bin/kolla_glance_extend_start
diff --git a/kolla/docker/glance/glance-base/glance_sudoers b/kolla/docker/glance/glance-base/glance_sudoers
new file mode 100644
index 000000000..9af870983
--- /dev/null
+++ b/kolla/docker/glance/glance-base/glance_sudoers
@@ -0,0 +1 @@
+glance ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/glance-rootwrap /etc/glance/rootwrap.conf *
diff --git a/kolla/docker/gnocchi/gnocchi-api/Dockerfile.j2 b/kolla/docker/gnocchi/gnocchi-api/Dockerfile.j2
new file mode 100644
index 000000000..c0cc2cc33
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-api/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}gnocchi-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block gnocchi_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_gnocchi_extend_start
+RUN chmod 644 /usr/local/bin/kolla_gnocchi_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block gnocchi_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/gnocchi/gnocchi-api/extend_start.sh b/kolla/docker/gnocchi/gnocchi-api/extend_start.sh
new file mode 100644
index 000000000..dda0461c6
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ sudo -H -u gnocchi gnocchi-upgrade --log-file /var/log/kolla/gnocchi/gnocchi-upgrade.log
+ exit 0
+fi
diff --git a/kolla/docker/gnocchi/gnocchi-base/Dockerfile.j2 b/kolla/docker/gnocchi/gnocchi-base/Dockerfile.j2
new file mode 100644
index 000000000..91199ac39
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-base/Dockerfile.j2
@@ -0,0 +1,56 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block gnocchi_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='gnocchi') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['ceph', 'epel']) }}
+
+ {% set gnocchi_base_packages = [
+ 'librados2-devel',
+ 'python3-rados',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set gnocchi_base_packages = [
+ 'librados-dev',
+ 'python3-rados',
+ ] %}
+
+
+{% endif %}
+
+{{ macros.install_packages(gnocchi_base_packages | customizable("packages")) }}
+
+ADD gnocchi-base-archive /gnocchi-base-source
+
+{% set gnocchi_base_pip_packages = [
+ 'gnocchiclient',
+ '/gnocchi[keystone,mysql,file,ceph,s3]'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY gnocchi_sudoers /etc/sudoers.d/kolla_gnocchi_sudoers
+
+RUN {{ macros.upper_constraints_version_change("Werkzeug", "3.0.1", "2.2.3") }}
+
+RUN ln -s gnocchi-base-source/* gnocchi \
+ && {{ macros.install_pip(gnocchi_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/gnocchi \
+ && cp /var/lib/kolla/venv/etc/pycadf/gnocchi_api_audit_map.conf /etc/gnocchi/ \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 640 /etc/sudoers.d/kolla_gnocchi_sudoers \
+ && touch /usr/local/bin/kolla_gnocchi_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_gnocchi_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block gnocchi_base_footer %}{% endblock %}
diff --git a/kolla/docker/gnocchi/gnocchi-base/extend_start.sh b/kolla/docker/gnocchi/gnocchi-base/extend_start.sh
new file mode 100644
index 000000000..adc05b1b3
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Create log dir for Gnocchi logs
+GNOCCHI_LOG_DIR="/var/log/kolla/gnocchi"
+if [[ ! -d "${GNOCCHI_LOG_DIR}" ]]; then
+ mkdir -p ${GNOCCHI_LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${GNOCCHI_LOG_DIR}) != "gnocchi:kolla" ]]; then
+ chown gnocchi:kolla ${GNOCCHI_LOG_DIR}
+fi
+if [[ $(stat -c %a ${GNOCCHI_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${GNOCCHI_LOG_DIR}
+fi
+
+. /usr/local/bin/kolla_gnocchi_extend_start
diff --git a/kolla/docker/gnocchi/gnocchi-base/gnocchi_sudoers b/kolla/docker/gnocchi/gnocchi-base/gnocchi_sudoers
new file mode 100644
index 000000000..c2a01d709
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-base/gnocchi_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R gnocchi\: /var/lib/gnocchi/, /bin/chown -R gnocchi\: /var/lib/gnocchi/
diff --git a/kolla/docker/gnocchi/gnocchi-metricd/Dockerfile.j2 b/kolla/docker/gnocchi/gnocchi-metricd/Dockerfile.j2
new file mode 100644
index 000000000..fe9978fa2
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-metricd/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}gnocchi-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block gnocchi_metricd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block gnocchi_metricd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER gnocchi
diff --git a/kolla/docker/gnocchi/gnocchi-statsd/Dockerfile.j2 b/kolla/docker/gnocchi/gnocchi-statsd/Dockerfile.j2
new file mode 100644
index 000000000..8f1574946
--- /dev/null
+++ b/kolla/docker/gnocchi/gnocchi-statsd/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}gnocchi-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block gnocchi_statsd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block gnocchi_statsd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER gnocchi
diff --git a/kolla/docker/grafana/Dockerfile.j2 b/kolla/docker/grafana/Dockerfile.j2
new file mode 100644
index 000000000..e401d91b6
--- /dev/null
+++ b/kolla/docker/grafana/Dockerfile.j2
@@ -0,0 +1,42 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block grafana_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.enable_extra_repos(['grafana']) }}
+
+{% set grafana_packages = ['grafana'] %}
+
+{{ macros.configure_user(name='grafana', homedir='/usr/share/grafana') }}
+
+{{ macros.install_packages(grafana_packages | customizable("packages")) }}
+
+WORKDIR /usr/share/grafana
+
+COPY grafana_sudoers /etc/sudoers.d/kolla_grafana_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{% block grafana_plugins_install %}
+
+{% set grafana_default_plugins = ['grafana-opensearch-datasource'] %}
+{% set grafana_plugins = grafana_default_plugins | customizable("plugins") %}
+
+{% if grafana_plugins | length > 0 %}
+RUN {{ (['grafana-cli plugins install %s'] * grafana_plugins | length ) | join(' && ') | format(*grafana_plugins) }}
+{% endif %}
+{% endblock %}
+
+RUN chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_grafana_sudoers \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block grafana_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER grafana
diff --git a/kolla/docker/grafana/extend_start.sh b/kolla/docker/grafana/extend_start.sh
new file mode 100644
index 000000000..5ed4dc56d
--- /dev/null
+++ b/kolla/docker/grafana/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/grafana" ]]; then
+ mkdir -p /var/log/kolla/grafana
+fi
+if [[ $(stat -c %a /var/log/kolla/grafana) != "755" ]]; then
+ chmod 755 /var/log/kolla/grafana
+fi
diff --git a/kolla/docker/grafana/grafana_sudoers b/kolla/docker/grafana/grafana_sudoers
new file mode 100644
index 000000000..386829969
--- /dev/null
+++ b/kolla/docker/grafana/grafana_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/bin/chown -R grafana\: /var/lib/grafana/, /bin/chown -R grafana\: /var/lib/grafana/
diff --git a/kolla/docker/hacluster/hacluster-base/Dockerfile.j2 b/kolla/docker/hacluster/hacluster-base/Dockerfile.j2
new file mode 100644
index 000000000..045f21e1e
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-base/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block hacluster_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type =='rpm' %}
+
+{{ macros.enable_extra_repos(['hacluster']) }}
+
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block hacluster_base_footer %}{% endblock %}
diff --git a/kolla/docker/hacluster/hacluster-base/extend_start.sh b/kolla/docker/hacluster/hacluster-base/extend_start.sh
new file mode 100644
index 000000000..c0e9301d0
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-base/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/hacluster" ]]; then
+ mkdir -p /var/log/kolla/hacluster
+fi
+if [[ $(stat -c %a /var/log/kolla/hacluster) != "755" ]]; then
+ chmod 755 /var/log/kolla/hacluster
+fi
diff --git a/kolla/docker/hacluster/hacluster-corosync/Dockerfile.j2 b/kolla/docker/hacluster/hacluster-corosync/Dockerfile.j2
new file mode 100644
index 000000000..74d47c380
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-corosync/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}hacluster-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block hacluster_corosync_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set hacluster_corosync_packages = ['corosync'] %}
+{{ macros.install_packages(hacluster_corosync_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block hacluster_corosync_footer %}{% endblock %}
diff --git a/kolla/docker/hacluster/hacluster-pacemaker-remote/Dockerfile.j2 b/kolla/docker/hacluster/hacluster-pacemaker-remote/Dockerfile.j2
new file mode 100644
index 000000000..64c381e1f
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-pacemaker-remote/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}hacluster-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block hacluster_pacemaker_remote_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set hacluster_pacemaker_remote_packages = ['pacemaker-remote'] %}
+{% if base_package_type == 'deb' %}
+ {% set hacluster_pacemaker_remote_packages = hacluster_pacemaker_remote_packages + [
+ 'pacemaker-cli-utils'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(hacluster_pacemaker_remote_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block hacluster_pacemaker_remote_footer %}{% endblock %}
diff --git a/kolla/docker/hacluster/hacluster-pacemaker/Dockerfile.j2 b/kolla/docker/hacluster/hacluster-pacemaker/Dockerfile.j2
new file mode 100644
index 000000000..33eeb3144
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-pacemaker/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}hacluster-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block hacluster_pacemaker_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set hacluster_pacemaker_packages = ['pacemaker'] %}
+{% if base_package_type == 'deb' %}
+ {% set hacluster_pacemaker_packages = hacluster_pacemaker_packages + [
+ 'pacemaker-cli-utils'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(hacluster_pacemaker_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block hacluster_pacemaker_footer %}{% endblock %}
diff --git a/kolla/docker/hacluster/hacluster-pcs/Dockerfile.j2 b/kolla/docker/hacluster/hacluster-pcs/Dockerfile.j2
new file mode 100644
index 000000000..a146d0d4b
--- /dev/null
+++ b/kolla/docker/hacluster/hacluster-pcs/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}hacluster-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block hacluster_pcs_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set hacluster_pcs_packages = ['pcs','crmsh'] %}
+{{ macros.install_packages(hacluster_pcs_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block hacluster_pcs_footer %}{% endblock %}
diff --git a/kolla/docker/haproxy/haproxy-ssh/Dockerfile.j2 b/kolla/docker/haproxy/haproxy-ssh/Dockerfile.j2
new file mode 100644
index 000000000..a674cc75e
--- /dev/null
+++ b/kolla/docker/haproxy/haproxy-ssh/Dockerfile.j2
@@ -0,0 +1,42 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block haproxy_ssh_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='haproxy', shell='/bin/bash') }}
+
+{% if base_package_type == 'rpm' %}
+ {% set haproxy_ssh_packages = [
+ 'openssh-server',
+ 'openssh-clients',
+ 'rsync',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set haproxy_ssh_packages = [
+ 'openssh-server',
+ 'openssh-client',
+ 'rsync',
+ ] %}
+
+RUN mkdir -p /var/run/sshd \
+ && chmod 0755 /var/run/sshd
+
+{% endif %}
+
+{{ macros.install_packages(haproxy_ssh_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && sed -ri 's/session(\s+)required(\s+)pam_loginuid.so/session\1optional\2pam_loginuid.so/' /etc/pam.d/sshd
+
+COPY update-loadbalancer-cert.sh /usr/bin/update-loadbalancer-cert
+
+{{ macros.kolla_patch_sources() }}
+
+{% block haproxy_ssh_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/haproxy/haproxy-ssh/extend_start.sh b/kolla/docker/haproxy/haproxy-ssh/extend_start.sh
new file mode 100644
index 000000000..4b2239100
--- /dev/null
+++ b/kolla/docker/haproxy/haproxy-ssh/extend_start.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+SSH_HOST_KEY_TYPES=( "rsa" "ecdsa" "ed25519" )
+
+for key_type in ${SSH_HOST_KEY_TYPES[@]}; do
+ KEY_PATH=/etc/ssh/ssh_host_${key_type}_key
+ if [[ ! -f "${KEY_PATH}" ]]; then
+ ssh-keygen -q -t ${key_type} -f ${KEY_PATH} -N ""
+ fi
+done
+
+mkdir -p /var/lib/haproxy/.ssh
+
+if [[ $(stat -c %U:%G /var/lib/haproxy/.ssh) != "haproxy:haproxy" ]]; then
+ chown haproxy: /var/lib/haproxy/.ssh
+fi
+
+FOLDERS_LEGO="/etc/letsencrypt /etc/letsencrypt/backups"
+USERGROUP="haproxy:haproxy"
+
+for folder in ${FOLDERS_LEGO}; do
+ mkdir -p ${folder}
+
+ if [[ $(stat -c %U:%G ${folder}) != "${USERGROUP}" ]]; then
+ chown ${USERGROUP} ${folder}
+ fi
+
+ if [[ "${folder}" == "/etc/letsencrypt" ]]; then
+ if [[ $(stat -c %a ${folder}) != "751" ]]; then
+ chmod 751 ${folder}
+ fi
+ else
+ if [[ $(stat -c %a ${folder}) != "755" ]]; then
+ chmod 755 ${folder}
+ fi
+ fi
+done
diff --git a/kolla/docker/haproxy/haproxy-ssh/update-loadbalancer-cert.sh b/kolla/docker/haproxy/haproxy-ssh/update-loadbalancer-cert.sh
new file mode 100755
index 000000000..7e4122c65
--- /dev/null
+++ b/kolla/docker/haproxy/haproxy-ssh/update-loadbalancer-cert.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+function log_info {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [INFO] ${message}"
+}
+
+function log_error {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [ERROR] ${message}"
+}
+
+function haproxy_transaction_start {
+ local cert_input=${1}
+ local cert_dest=${2}
+
+ local transaction_result=""
+ local transaction_grep_check=""
+
+ transaction_grep_check="Transaction (created|updated) for certificate $(echo $cert_dest | sed -e 's|/|\\/|g')!"
+ transaction_result=$(echo -e "set ssl cert ${cert_dest} <<\n$(cat ${cert_input})\n" | socat unix-connect:/var/lib/kolla/haproxy/haproxy.sock -)
+ if echo "${transaction_result}" | grep -Pq "${transaction_grep_check}"; then
+ log_info "[${cert_dest} - update] Transaction ${cert_input} -> ${cert_dest} started."
+ else
+ log_error "[${cert_dest} - update] Transaction ${cert_input} -> ${cert_dest} failed, please check if haproxy admin socket is running and ${cert_input} is not corrupted."
+ exit 1
+ fi
+
+ local cert_input_sha1=""
+ local cert_dest_sha1=""
+
+ cert_input_sha1=$(openssl x509 -noout -fingerprint -sha1 -inform pem -in ${cert_input} | awk -F '=' '{print $2}' | sed -e 's/://g')
+ cert_dest_sha1=$(echo "show ssl cert *${cert_dest}" | socat unix-connect:/var/lib/kolla/haproxy/haproxy.sock - | awk -F 'SHA1 FingerPrint: ' '{print $2}' | sed '/^$/d')
+ if [ "${cert_input_sha1}" = "${cert_dest_sha1}" ]; then
+ log_info "[${cert_dest} - update] Transaction ${cert_input} -> ${cert_dest} successful."
+ else
+ log_error "[${cert_dest} - update] Transaction ${cert_input} -> ${cert_dest} failed, SHA1 fingerprint of ${cert_input} is not the same as uploaded one."
+ exit 1
+ fi
+}
+
+function haproxy_upload_to_memory {
+ local cert_input=${1}
+ local cert_dest=${2}
+
+ local cert_upload_output=""
+
+ cert_upload_output=$(echo "commit ssl cert ${cert_dest}" | socat unix-connect:/var/lib/kolla/haproxy/haproxy.sock -)
+ if echo "${cert_upload_output}" | grep -q "Success!"; then
+ log_info "[${cert_dest} - update] Certificate ${cert_input} uploaded to haproxy memory."
+ else
+ log_error "[${cert_dest} - update] Certificate ${cert_input} upload to haproxy memory failed, please check if haproxy admin socket is running and ${cert_input} is not corrupted."
+ exit 1
+ fi
+}
+
+function haproxy_write_to_disk {
+ local cert_input=${1}
+ local cert_haproxy_path=${2}
+
+ local cert_backup_suffix=""
+ local cert_backup_path=""
+ local cert_backup_name=""
+
+ cert_backup_suffix="-$(date +%Y-%m-%d-%H-%M-%S).pem"
+ cert_backup_path=$(echo "${cert_input}" | awk -v suffix="$cert_backup_suffix" -F '.pem' '{print $1suffix}')
+ cert_backup_name=$(echo ${cert_backup_path} | awk -F '/' '{print $NF}')
+ mkdir -p /etc/letsencrypt/backups
+ log_info "[${cert_haproxy_path} - update] Backuping currently loaded ${cert_haproxy_path} -> /etc/letsencrypt/backups/${cert_backup_name}"
+ cp -a ${cert_haproxy_path} /etc/letsencrypt/backups/${cert_backup_name}
+ cp -a ${cert_input} ${cert_haproxy_path}
+ rm -f ${cert_input}
+}
+
+
+# Parser
+
+INTERNAL_SET="false"
+EXTERNAL_SET="false"
+
+VALID_ARGS=$(getopt -o ie --long internal,external -- "$@")
+if [[ $? -ne 0 ]]; then
+ exit 1;
+fi
+
+eval set -- "$VALID_ARGS"
+while [ : ]; do
+ case "$1" in
+ -i | --internal)
+ CERT_TYPE="internal"
+ INTERNAL_SET="true"
+ shift
+ ;;
+ -e | --external)
+ CERT_TYPE="external"
+ EXTERNAL_SET="true"
+ shift
+ ;;
+ --) shift;
+ break
+ ;;
+ esac
+done
+
+if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
+ if [ "${INTERNAL_SET}" = "${EXTERNAL_SET}" ]; then
+ echo "[e] Only --internal or --external parameter is allowed at a time"
+ exit 1
+ fi
+ if [ "${INTERNAL_SET}" = "true" ]; then
+ HAPROXY_CERT_INCOMING_PATH="/var/lib/haproxy/haproxy-internal.pem"
+ HAPROXY_CERT_PATH="/etc/haproxy/certificates/haproxy-internal.pem"
+ else
+ HAPROXY_CERT_INCOMING_PATH="/var/lib/haproxy/haproxy.pem"
+ HAPROXY_CERT_PATH="/etc/haproxy/certificates/haproxy.pem"
+ fi
+fi
+
+# Main
+
+haproxy_transaction_start ${HAPROXY_CERT_INCOMING_PATH} ${HAPROXY_CERT_PATH}
+haproxy_upload_to_memory ${HAPROXY_CERT_INCOMING_PATH} ${HAPROXY_CERT_PATH}
+haproxy_write_to_disk ${HAPROXY_CERT_INCOMING_PATH} ${HAPROXY_CERT_PATH}
diff --git a/kolla/docker/haproxy/haproxy/Dockerfile.j2 b/kolla/docker/haproxy/haproxy/Dockerfile.j2
new file mode 100644
index 000000000..36c7ca943
--- /dev/null
+++ b/kolla/docker/haproxy/haproxy/Dockerfile.j2
@@ -0,0 +1,26 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block haproxy_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='haproxy') }}
+
+{% set haproxy_packages = [
+ 'haproxy'
+] %}
+{{ macros.install_packages(haproxy_packages | customizable("packages")) }}
+
+COPY ensure_latest_config.sh /usr/local/bin/kolla_ensure_haproxy_latest_config
+
+RUN mkdir -p /var/lib/kolla/haproxy \
+ && chown -R haproxy: /var/lib /run \
+ && chmod 755 /usr/local/bin/kolla_ensure_haproxy_latest_config
+
+{{ macros.kolla_patch_sources() }}
+
+{% block haproxy_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/haproxy/haproxy/ensure_latest_config.sh b/kolla/docker/haproxy/haproxy/ensure_latest_config.sh
new file mode 100644
index 000000000..4b84f3aed
--- /dev/null
+++ b/kolla/docker/haproxy/haproxy/ensure_latest_config.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -o errexit
+
+CURRENT_CONFIG_HASH=$(sha1sum /etc/haproxy/haproxy.cfg | cut -f1 -d' ')
+NEW_CONFIG_HASH=$(sha1sum /var/lib/kolla/config_files/haproxy.cfg | cut -f1 -d' ')
+
+if [[ $CURRENT_CONFIG_HASH != $NEW_CONFIG_HASH ]]; then
+ changed=changed
+ python /usr/local/bin/kolla_set_configs
+ kill -USR2 $(pgrep -f /usr/sbin/haproxy-systemd-wrapper)
+fi
+
+echo $changed
diff --git a/kolla/docker/heat/heat-api-cfn/Dockerfile.j2 b/kolla/docker/heat/heat-api-cfn/Dockerfile.j2
new file mode 100644
index 000000000..db3030c8d
--- /dev/null
+++ b/kolla/docker/heat/heat-api-cfn/Dockerfile.j2
@@ -0,0 +1,13 @@
+FROM {{ namespace }}/{{ image_prefix }}heat-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block heat_api_cfn_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block heat_api_cfn_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/heat/heat-api/Dockerfile.j2 b/kolla/docker/heat/heat-api/Dockerfile.j2
new file mode 100644
index 000000000..af2471279
--- /dev/null
+++ b/kolla/docker/heat/heat-api/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}heat-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block heat_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_heat_extend_start
+RUN chmod 644 /usr/local/bin/kolla_heat_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block heat_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/heat/heat-api/extend_start.sh b/kolla/docker/heat/heat-api/extend_start.sh
new file mode 100644
index 000000000..ddf4f81f4
--- /dev/null
+++ b/kolla/docker/heat/heat-api/extend_start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ heat-manage db_sync
+
+ EXISTING_DOMAINS=$(openstack domain list -f value -c Name)
+
+ if ! echo "$EXISTING_DOMAINS" | grep '^heat_user_domain$' &>/dev/null; then
+ openstack domain create heat_user_domain
+ openstack user create --domain heat_user_domain heat_domain_admin --password ${HEAT_DOMAIN_ADMIN_PASSWORD}
+ openstack role add --domain heat_user_domain --user-domain heat_user_domain --user heat_domain_admin admin
+ fi
+
+ exit 0
+fi
diff --git a/kolla/docker/heat/heat-base/Dockerfile.j2 b/kolla/docker/heat/heat-base/Dockerfile.j2
new file mode 100644
index 000000000..a06ea6f0d
--- /dev/null
+++ b/kolla/docker/heat/heat-base/Dockerfile.j2
@@ -0,0 +1,41 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block heat_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='heat') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+{% endif %}
+
+{% set heat_base_packages = [
+] %}
+
+{{ macros.install_packages(heat_base_packages | customizable("packages")) }}
+
+ADD heat-base-archive /heat-base-source
+
+{% set heat_base_pip_packages = [
+ '/heat'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s heat-base-source/* heat \
+ && {{ macros.install_pip(heat_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/heat \
+ && cp -r /heat/etc/heat/* /etc/heat/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/heat_api_audit_map.conf /etc/heat/ \
+ && touch /usr/local/bin/kolla_heat_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_heat_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block heat_base_footer %}{% endblock %}
diff --git a/kolla/docker/heat/heat-base/extend_start.sh b/kolla/docker/heat/heat-base/extend_start.sh
new file mode 100644
index 000000000..1c1e86b8e
--- /dev/null
+++ b/kolla/docker/heat/heat-base/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/heat" ]]; then
+ mkdir -p /var/log/kolla/heat
+fi
+if [[ $(stat -c %U:%G /var/log/kolla/heat) != "heat:kolla" ]]; then
+ chown -R heat:kolla /var/log/kolla/heat
+fi
+if [[ $(stat -c %a /var/log/kolla/heat) != "755" ]]; then
+ chmod 755 /var/log/kolla/heat
+fi
+
+. /usr/local/bin/kolla_heat_extend_start
diff --git a/kolla/docker/heat/heat-engine/Dockerfile.j2 b/kolla/docker/heat/heat-engine/Dockerfile.j2
new file mode 100644
index 000000000..86c943582
--- /dev/null
+++ b/kolla/docker/heat/heat-engine/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}heat-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block heat_engine_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block heat_engine_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER heat
diff --git a/kolla/docker/horizon/Dockerfile.j2 b/kolla/docker/horizon/Dockerfile.j2
new file mode 100644
index 000000000..73dc65dd1
--- /dev/null
+++ b/kolla/docker/horizon/Dockerfile.j2
@@ -0,0 +1,74 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block horizon_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='horizon') }}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set horizon_packages = [
+ 'gettext'
+ ] %}
+
+{{ macros.install_packages(horizon_packages | customizable("packages")) }}
+{% elif base_package_type == 'deb' %}
+
+ {% set horizon_packages = [
+ 'gettext',
+ 'locales',
+ 'tzdata'
+ ] %}
+
+{{ macros.install_packages(horizon_packages | customizable("packages")) }}
+
+{% endif %}
+
+ADD horizon-archive /horizon-source
+ADD plugins-archive /
+
+{% set horizon_pip_packages = [
+ '/horizon',
+ 'django_redis'
+] %}
+
+{% set horizon_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+# NOTE(hrw): to install horizon from unpacked sources we cannot have it in upper-constraints.txt
+# NOTE(kevko): This dance with local settings python paths below is needed
+# because we are using different distros with different python version and we need to
+# know to which path symlink should point to.
+RUN ln -s horizon-source/* horizon \
+ && {{ macros.upper_constraints_remove("horizon") }} \
+ && {{ macros.install_pip(horizon_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/openstack-dashboard \
+ && cp -r /horizon/openstack_dashboard/conf/* /etc/openstack-dashboard/ \
+ && cp /horizon/openstack_dashboard/local/local_settings.py.example /etc/openstack-dashboard/local_settings.py \
+ && local_settings=$(python -c 'import os;import openstack_dashboard;settings=os.path.dirname(openstack_dashboard.__file__) + "/local/local_settings.py";print(settings);') \
+ && rm -f $local_settings \
+ && ln -s /etc/openstack-dashboard/local_settings.py $local_settings \
+ && mkdir -p /etc/openstack-dashboard/local_settings.d \
+ && local_settings_d=$(python -c 'import os;import openstack_dashboard;settings_d=os.path.dirname(openstack_dashboard.__file__) + "/local/local_settings.d";print(settings_d);') \
+ && rm -rf $local_settings_d \
+ && ln -s /etc/openstack-dashboard/local_settings.d $local_settings_d \
+ && cp /horizon/manage.py /var/lib/kolla/venv/bin/manage.py \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(horizon_plugins_pip_packages) }}; \
+ fi \
+ && for locale in /var/lib/kolla/venv/lib/python3/site-packages/*/locale; do \
+ (cd ${locale%/*} && /var/lib/kolla/venv/bin/django-admin compilemessages) \
+ done \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block horizon_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/horizon/extend_start.sh b/kolla/docker/horizon/extend_start.sh
new file mode 100644
index 000000000..d1198adb4
--- /dev/null
+++ b/kolla/docker/horizon/extend_start.sh
@@ -0,0 +1,248 @@
+#!/bin/bash
+
+set -o errexit
+
+FORCE_GENERATE="${FORCE_GENERATE:-no}"
+HASH_PATH=/var/lib/kolla/.settings.md5sum.txt
+
+SITE_PACKAGES="/var/lib/kolla/venv/lib/python3/site-packages"
+
+MANAGE_PY="/var/lib/kolla/venv/bin/python /var/lib/kolla/venv/bin/manage.py"
+
+if [[ ! -f ${SITE_PACKAGES}/openstack_dashboard/local/local_settings.py ]]; then
+ ln -s /etc/openstack-dashboard/local_settings \
+ ${SITE_PACKAGES}/openstack_dashboard/local/local_settings.py
+fi
+
+if [[ -f /etc/openstack-dashboard/custom_local_settings ]]; then
+ CUSTOM_SETTINGS_FILE="${SITE_PACKAGES}/openstack_dashboard/local/custom_local_settings.py"
+
+ if [[ ! -L ${CUSTOM_SETTINGS_FILE} ]]; then
+ ln -s /etc/openstack-dashboard/custom_local_settings ${CUSTOM_SETTINGS_FILE}
+ fi
+fi
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ $MANAGE_PY migrate --noinput
+ exit 0
+fi
+
+function config_dashboard {
+ ENABLE=$1
+ SRC=$2
+ DEST=$3
+ if [[ ! -f ${SRC} ]]; then
+ echo "WARNING: ${SRC} is required"
+ elif [[ "${ENABLE}" == "yes" ]] && [[ ! -f "${DEST}" ]]; then
+ cp -a "${SRC}" "${DEST}"
+ FORCE_GENERATE="yes"
+ elif [[ "${ENABLE}" != "yes" ]] && [[ -f "${DEST}" ]]; then
+ # remove pyc pyo files too
+ rm -f "${DEST}" "${DEST}c" "${DEST}o"
+ FORCE_GENERATE="yes"
+ fi
+}
+
+function config_blazar_dashboard {
+ for file in ${SITE_PACKAGES}/blazar_dashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_BLAZAR:-no}" \
+ "${SITE_PACKAGES}/blazar_dashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_cloudkitty_dashboard {
+ for file in ${SITE_PACKAGES}/cloudkittydashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_CLOUDKITTY:-no}" \
+ "${SITE_PACKAGES}/cloudkittydashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_designate_dashboard {
+ for file in ${SITE_PACKAGES}/designatedashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_DESIGNATE:-no}" \
+ "${SITE_PACKAGES}/designatedashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_fwaas_dashboard {
+ for file in ${SITE_PACKAGES}/neutron_fwaas_dashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_FWAAS:-no}" \
+ "${SITE_PACKAGES}/neutron_fwaas_dashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_heat_dashboard {
+ for file in ${SITE_PACKAGES}/heat_dashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_HEAT:-no}" \
+ "${SITE_PACKAGES}/heat_dashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+
+ config_dashboard "${ENABLE_HEAT:-no}" \
+ "${SITE_PACKAGES}/heat_dashboard/conf/heat_policy.yaml" \
+ "/etc/openstack-dashboard/heat_policy.yaml"
+ config_dashboard "${ENABLE_HEAT:-no}" \
+ "${SITE_PACKAGES}/heat_dashboard/conf/default_policies/heat.yaml" \
+ "/etc/openstack-dashboard/default_policies/heat.yaml"
+}
+
+function config_ironic_dashboard {
+ for file in ${SITE_PACKAGES}/ironic_ui/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_IRONIC:-no}" \
+ "${SITE_PACKAGES}/ironic_ui/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_magnum_dashboard {
+ for file in ${SITE_PACKAGES}/magnum_ui/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_MAGNUM:-no}" \
+ "${SITE_PACKAGES}/magnum_ui/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_manila_ui {
+ for file in ${SITE_PACKAGES}/manila_ui/local/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_MANILA:-no}" \
+ "${SITE_PACKAGES}/manila_ui/local/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+
+ config_dashboard "${ENABLE_MANILA:-no}" \
+ "${SITE_PACKAGES}/manila_ui/conf/manila_policy.yaml" \
+ "/etc/openstack-dashboard/manila_policy.yaml"
+ config_dashboard "${ENABLE_MANILA:-no}" \
+ "${SITE_PACKAGES}/manila_ui/conf/default_policies/manila.yaml" \
+ "/etc/openstack-dashboard/default_policies/manila.yaml"
+}
+
+function config_masakari_dashboard {
+ for file in ${SITE_PACKAGES}/masakaridashboard/local/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_MASAKARI:-no}" \
+ "${SITE_PACKAGES}/masakaridashboard/local/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+ config_dashboard "${ENABLE_MASAKARI:-no}"\
+ "${SITE_PACKAGES}/masakaridashboard/conf/masakari_policy.yaml" \
+ "/etc/openstack-dashboard/masakari_policy.yaml"
+ config_dashboard "${ENABLE_MASAKARI:-no}"\
+ "${SITE_PACKAGES}/masakaridashboard/local/local_settings.d/_50_masakari.py" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/local_settings.d/_50_masakari.py"
+}
+
+function config_mistral_dashboard {
+ config_dashboard "${ENABLE_MISTRAL:-no}" \
+ "${SITE_PACKAGES}/mistraldashboard/enabled/_50_mistral.py" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/_50_mistral.py"
+}
+
+function config_neutron_vpnaas_dashboard {
+ config_dashboard "${ENABLE_NEUTRON_VPNAAS:-no}" \
+ "${SITE_PACKAGES}/neutron_vpnaas_dashboard/enabled/_7100_project_vpn_panel.py" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/_7100_project_vpn_panel.py"
+}
+
+function config_octavia_dashboard {
+ config_dashboard "${ENABLE_OCTAVIA:-no}" \
+ "${SITE_PACKAGES}/octavia_dashboard/enabled/_1482_project_load_balancer_panel.py" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/_1482_project_load_balancer_panel.py"
+}
+
+
+function config_tacker_dashboard {
+ for file in ${SITE_PACKAGES}/tacker_horizon/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_TACKER:-no}" \
+ "${SITE_PACKAGES}/tacker_horizon/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_trove_dashboard {
+ for file in ${SITE_PACKAGES}/trove_dashboard/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_TROVE:-no}" \
+ "${SITE_PACKAGES}/trove_dashboard/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+}
+
+function config_watcher_dashboard {
+ for file in ${SITE_PACKAGES}/watcher_dashboard/local/enabled/_*[^__].py; do
+ config_dashboard "${ENABLE_WATCHER:-no}" \
+ "${SITE_PACKAGES}/watcher_dashboard/local/enabled/${file##*/}" \
+ "${SITE_PACKAGES}/openstack_dashboard/local/enabled/${file##*/}"
+ done
+
+ config_dashboard "${ENABLE_WATCHER:-no}" \
+ "${SITE_PACKAGES}/watcher_dashboard/conf/watcher_policy.json" \
+ "/etc/openstack-dashboard/watcher_policy.json"
+}
+
+# Regenerate the compressed javascript and css if any configuration files have
+# changed. Use a static modification date when generating the tarball
+# so that we only trigger on content changes.
+function settings_bundle {
+ # NOTE(yoctozepto): We ignore errors here (|| true) to make it work with
+ # `set -o pipefail` (files might be missing - no problem).
+ tar -cf- --mtime=1970-01-01 \
+ /etc/openstack-dashboard/local_settings \
+ /etc/openstack-dashboard/custom_local_settings \
+ /etc/openstack-dashboard/local_settings.d \
+ /etc/openstack-dashboard/themes 2> /dev/null || true
+}
+
+function settings_changed {
+ changed=1
+
+ if [[ ! -f $HASH_PATH ]] || ! settings_bundle | md5sum -c --status $HASH_PATH || [[ $FORCE_GENERATE == yes ]]; then
+ changed=0
+ fi
+
+ return ${changed}
+}
+
+config_blazar_dashboard
+config_cloudkitty_dashboard
+config_designate_dashboard
+config_fwaas_dashboard
+config_heat_dashboard
+config_ironic_dashboard
+config_magnum_dashboard
+config_manila_ui
+config_masakari_dashboard
+config_mistral_dashboard
+config_neutron_vpnaas_dashboard
+config_octavia_dashboard
+config_tacker_dashboard
+config_trove_dashboard
+config_watcher_dashboard
+
+if settings_changed; then
+ ${MANAGE_PY} collectstatic --noinput --clear
+ ${MANAGE_PY} compress --force
+ settings_bundle | md5sum > $HASH_PATH
+fi
+
+# NOTE(sbezverk) since Horizon is now storing logs in its own location, /var/log/horizon
+# needs to be created if it does not exist
+if [[ ! -d "/var/log/kolla/horizon" ]]; then
+ mkdir -p /var/log/kolla/horizon
+fi
+
+if [[ $(stat -c %U:%G /var/log/kolla/horizon) != "horizon:kolla" ]]; then
+ chown -R horizon:kolla /var/log/kolla/horizon
+fi
+
+if [[ $(stat -c %a /var/log/kolla/horizon) != "755" ]]; then
+ chmod 755 /var/log/kolla/horizon
+fi
+
+if [[ -f ${SITE_PACKAGES}/openstack_dashboard/local/.secret_key_store ]] && [[ $(stat -c %U ${SITE_PACKAGES}/openstack_dashboard/local/.secret_key_store) != "horizon" ]]; then
+ chown horizon ${SITE_PACKAGES}/openstack_dashboard/local/.secret_key_store
+fi
diff --git a/kolla/docker/httpd/Dockerfile.j2 b/kolla/docker/httpd/Dockerfile.j2
new file mode 100644
index 000000000..ec5637283
--- /dev/null
+++ b/kolla/docker/httpd/Dockerfile.j2
@@ -0,0 +1,42 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block apache_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+{{ macros.enable_extra_repos(['epel']) }}
+ {% set apache_packages = [
+ 'httpd',
+ 'mod_ssl',
+ 'mod_auth_mellon',
+ 'mod_auth_openidc',
+ 'mod_proxy_uwsgi'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set apache_packages = [
+ 'apache2',
+ 'ca-certificates',
+ 'libapache2-mod-auth-mellon',
+ 'libapache2-mod-auth-openidc'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(apache_packages | customizable("packages")) }}
+
+{% if base_package_type == 'rpm' %}
+RUN sed -i -r 's,^(Listen 80),#\1,' /etc/httpd/conf/httpd.conf \
+ && sed -i -r 's,^(Listen 443),#\1,' /etc/httpd/conf.d/ssl.conf
+{% elif base_package_type == 'deb' %}
+RUN echo > /etc/apache2/ports.conf
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY httpd_setup.sh /usr/local/bin/kolla_httpd_setup
+RUN chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_httpd_setup
+USER root
+
+{% block apache_footer %}{% endblock %}
diff --git a/kolla/docker/httpd/extend_start.sh b/kolla/docker/httpd/extend_start.sh
new file mode 100644
index 000000000..d47af64b9
--- /dev/null
+++ b/kolla/docker/httpd/extend_start.sh
@@ -0,0 +1 @@
+. /usr/local/bin/kolla_httpd_setup
diff --git a/kolla/docker/httpd/httpd_setup.sh b/kolla/docker/httpd/httpd_setup.sh
new file mode 100644
index 000000000..35f7b5b1e
--- /dev/null
+++ b/kolla/docker/httpd/httpd_setup.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# This script performs setup necessary to run the Apache httpd web server.
+# It should be sourced rather than executed as environment variables are set.
+
+# Assume the service runs on top of Apache httpd when user is root.
+if [[ "$(whoami)" == 'root' ]]; then
+ # NOTE(pbourke): httpd will not clean up after itself in some cases which
+ # results in the container not being able to restart. (bug #1489676, 1557036)
+ if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ # NOTE(yoctozepto): APACHE_CONFDIR has to be set to pass the next step
+ # under the `set -o nounset` regime
+ APACHE_CONFDIR=
+ # Loading Apache2 ENV variables
+ . /etc/apache2/envvars
+ install -d /var/run/apache2/
+ rm -rf /var/run/apache2/*
+ else
+ rm -rf /var/run/httpd/* /run/httpd/* /tmp/httpd*
+ # NOTE(mmalchuk): This added to make Rocky/Centos similar to Ubuntu/Debian
+ # to provide /server-status handler for local monitoring of the Apache.
+ # The module already loaded in the /etc/httpd/conf.modules.d/00-base.conf.
+ cat << EOF >/etc/httpd/conf.modules.d/99-server-status.conf
+<Location "/server-status">
+ SetHandler server-status
+ Require local
+</Location>
+EOF
+ fi
+
+ # CentOS/Rocky have an issue with mod_ssl which produces an invalid Apache
+ # configuration in /etc/httpd/conf.d/ssl.conf. This causes the following error
+ # on startup:
+ # SSLCertificateFile: file '/etc/pki/tls/certs/localhost.crt' does not exist or is empty
+ # Work around this by generating certificates manually.
+ # NOTE(mnasiadka): in EL9 upgrade jobs gencerts is failing on wrong permissions to dhparams.pem
+ if [[ "${KOLLA_BASE_DISTRO}" =~ centos|rocky ]] && [[ ! -e /etc/pki/tls/certs/localhost.crt ]]; then
+ rm -f /tmp/dhparams.pem
+ /usr/libexec/httpd-ssl-gencerts
+ fi
+fi
+
+# The default system locale with unicode support
+LANG=C.UTF-8
+
+# Override the default locale if configured
+if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ [ -f /etc/default/locale ] && . /etc/default/locale
+else
+ [ -f /etc/locale.conf ] && . /etc/locale.conf
+fi
diff --git a/kolla/docker/ironic/ironic-api/Dockerfile.j2 b/kolla/docker/ironic/ironic-api/Dockerfile.j2
new file mode 100644
index 000000000..1c10d0372
--- /dev/null
+++ b/kolla/docker/ironic/ironic-api/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}ironic-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set ironic_api_packages = [
+] %}
+{{ macros.install_packages(ironic_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_ironic_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ironic_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ironic/ironic-api/extend_start.sh b/kolla/docker/ironic/ironic-api/extend_start.sh
new file mode 100644
index 000000000..ce2f7fb3e
--- /dev/null
+++ b/kolla/docker/ironic/ironic-api/extend_start.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ ironic-dbsync upgrade
+ ironic-dbsync online_data_migrations
+ exit 0
+fi
+
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ ironic-dbsync upgrade
+ exit 0
+fi
+
+if [[ "${!KOLLA_OSM[@]}" ]]; then
+ ironic-dbsync online_data_migrations
+ exit 0
+fi
diff --git a/kolla/docker/ironic/ironic-base/Dockerfile.j2 b/kolla/docker/ironic/ironic-base/Dockerfile.j2
new file mode 100644
index 000000000..e2f7871f6
--- /dev/null
+++ b/kolla/docker/ironic/ironic-base/Dockerfile.j2
@@ -0,0 +1,32 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='ironic', shell='/bin/bash') }}
+
+ADD ironic-base-archive /ironic-base-source
+ADD ironic_sudoers /etc/sudoers.d/kolla_ironic_sudoers
+
+{% set ironic_base_pip_packages = [
+ '/ironic'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s ironic-base-source/* ironic \
+ && {{ macros.install_pip(ironic_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/ironic \
+ && cp /var/lib/kolla/venv/etc/pycadf/ironic_api_audit_map.conf /etc/ironic/ \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_ironic_sudoers \
+ && touch /usr/local/bin/kolla_ironic_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_ironic_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_base_footer %}{% endblock %}
diff --git a/kolla/docker/ironic/ironic-base/extend_start.sh b/kolla/docker/ironic/ironic-base/extend_start.sh
new file mode 100644
index 000000000..dc6b05caa
--- /dev/null
+++ b/kolla/docker/ironic/ironic-base/extend_start.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+LOG_PATH=/var/log/kolla/ironic
+METRICS_PATH=/var/lib/ironic-metrics
+
+if [[ ! -d "${LOG_PATH}" ]]; then
+ mkdir -p "${LOG_PATH}"
+fi
+if [[ ! -d "${METRICS_PATH}" ]]; then
+ sudo mkdir -p "${METRICS_PATH}"
+fi
+if [[ $(stat -c %a "${LOG_PATH}") != "755" ]]; then
+ chmod 755 "${LOG_PATH}"
+fi
+if [[ $(stat -c %U:%G "${METRICS_PATH}") != "ironic:ironic" ]]; then
+ sudo chown ironic:ironic "${METRICS_PATH}"
+fi
+if [[ $(stat -c %a "${METRICS_PATH}") != "2775" ]]; then
+ sudo chmod 2775 "${METRICS_PATH}"
+fi
+
+. /usr/local/bin/kolla_ironic_extend_start
diff --git a/kolla/docker/ironic/ironic-base/ironic_sudoers b/kolla/docker/ironic/ironic-base/ironic_sudoers
new file mode 100644
index 000000000..9b9deed4d
--- /dev/null
+++ b/kolla/docker/ironic/ironic-base/ironic_sudoers
@@ -0,0 +1,3 @@
+ironic ALL = (root) NOPASSWD: /bin/mkdir -p /var/lib/ironic-metrics, /usr/bin/mkdir -p /var/lib/ironic-metrics
+ironic ALL = (root) NOPASSWD: /bin/chown ironic\:ironic /var/lib/ironic-metrics, /usr/bin/chown ironic\:ironic /var/lib/ironic-metrics
+ironic ALL = (root) NOPASSWD: /bin/chmod 2755 /var/lib/ironic-metrics, /usr/bin/chmod 2775 /var/lib/ironic-metrics
diff --git a/kolla/docker/ironic/ironic-conductor/Dockerfile.j2 b/kolla/docker/ironic/ironic-conductor/Dockerfile.j2
new file mode 100644
index 000000000..bce981fb0
--- /dev/null
+++ b/kolla/docker/ironic/ironic-conductor/Dockerfile.j2
@@ -0,0 +1,76 @@
+FROM {{ namespace }}/{{ image_prefix }}ironic-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ironic_conductor_packages = [
+ 'dosfstools',
+ 'e2fsprogs',
+ 'fuse',
+ 'gdisk',
+ 'ipmitool',
+ 'mtools',
+ 'openssh-clients',
+ 'parted',
+ 'psmisc',
+ 'python3-systemd',
+ 'qemu-img',
+ 'systemd-udev',
+ 'util-linux',
+ 'xfsprogs',
+ 'xorriso'
+ ] %}
+ {% if base_arch in ['x86_64'] %}
+ {% set ironic_conductor_packages = ironic_conductor_packages + [
+ 'syslinux'
+ ] %}
+ {% endif %}
+{% elif base_package_type == 'deb' %}
+ {% set ironic_conductor_packages = [
+ 'bsdmainutils',
+ 'dosfstools',
+ 'e2fsprogs',
+ 'gdisk',
+ 'genisoimage',
+ 'ipmitool',
+ 'isolinux',
+ 'mtools',
+ 'openssh-client',
+ 'parted',
+ 'psmisc',
+ 'python3-systemd',
+ 'qemu-utils',
+ 'shellinabox',
+ 'udev',
+ 'xfsprogs',
+ ] %}
+{% endif %}
+
+{% set ironic_conductor_pip_packages = [
+ '-r /ironic/driver-requirements.txt',
+ 'ironic-staging-drivers',
+] %}
+
+{% set ironic_conductor_plugins_pip_packages = [
+'/plugins/*'
+] %}
+
+ADD plugins-archive /
+RUN {{ macros.install_pip(ironic_conductor_pip_packages | customizable("pip_packages")) }} \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(ironic_conductor_plugins_pip_packages) }}; \
+ fi
+
+{{ macros.install_packages(ironic_conductor_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER ironic
diff --git a/kolla/docker/ironic/ironic-prometheus-exporter/Dockerfile.j2 b/kolla/docker/ironic/ironic-prometheus-exporter/Dockerfile.j2
new file mode 100644
index 000000000..2ac448dfd
--- /dev/null
+++ b/kolla/docker/ironic/ironic-prometheus-exporter/Dockerfile.j2
@@ -0,0 +1,17 @@
+FROM {{ namespace }}/{{ image_prefix }}ironic-conductor:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+USER root
+
+{% block ironic_prometheus_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+ENV IRONIC_CONFIG /etc/ironic/ironic.conf
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_prometheus_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ironic/ironic-pxe-filter/Dockerfile.j2 b/kolla/docker/ironic/ironic-pxe-filter/Dockerfile.j2
new file mode 100644
index 000000000..5b593e9f0
--- /dev/null
+++ b/kolla/docker/ironic/ironic-pxe-filter/Dockerfile.j2
@@ -0,0 +1,24 @@
+FROM {{ namespace }}/{{ image_prefix }}ironic-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_pxe_filter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set ironic_pxe_filter_packages = [] %}
+
+{% set ironic_pxe_filter_pip_packages = [] %}
+
+RUN {{ macros.install_pip(ironic_pxe_filter_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.install_packages(ironic_pxe_filter_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+COPY extend_start.sh /usr/local/bin/kolla_ironic_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ironic_extend_start
+
+{% block ironic_pxe_filter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ironic/ironic-pxe-filter/extend_start.sh b/kolla/docker/ironic/ironic-pxe-filter/extend_start.sh
new file mode 100644
index 000000000..c7a807c8d
--- /dev/null
+++ b/kolla/docker/ironic/ironic-pxe-filter/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+IRONIC_DHCP_HOSTS_DIR=${IRONIC_DHCP_HOSTS_DIR:-/etc/dnsmasq/dhcp-hostsdir}
+DNSMASQ_PIDFILE=${DNSMASQ_PIDFILE:-/run/ironic/dnsmasq.pid}
+
+# NOTE(wszumski): This container must be in same process namespace as dnsmasq
+rm -f $IRONIC_DHCP_HOSTS_DIR/* && kill -HUP $(cat "$DNSMASQ_PIDFILE") || true
diff --git a/kolla/docker/ironic/ironic-pxe/Dockerfile.j2 b/kolla/docker/ironic/ironic-pxe/Dockerfile.j2
new file mode 100644
index 000000000..0475a5b05
--- /dev/null
+++ b/kolla/docker/ironic/ironic-pxe/Dockerfile.j2
@@ -0,0 +1,55 @@
+FROM {{ namespace }}/{{ image_prefix }}ironic-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_pxe_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ironic_pxe_packages = [
+ 'dosfstools',
+ 'grub2-tools',
+ 'grub2-efi-*64',
+ 'grub2-efi-aa64-modules',
+ 'ipxe-bootimgs',
+ 'ipxe-bootimgs-aarch64',
+ 'mtools',
+ 'shim-*64',
+ 'syslinux-tftpboot',
+ 'tftp-server',
+ ] %}
+
+{{ macros.install_packages(ironic_pxe_packages | customizable("packages")) }}
+{% elif base_package_type == 'deb' %}
+ {% set ironic_pxe_packages = [
+ 'dosfstools',
+ 'grub-efi-*64-signed',
+ 'ipxe',
+ 'mtools',
+ 'pxelinux',
+ 'shim-signed',
+ 'syslinux-common',
+ 'tftpd-hpa'
+ ] %}
+
+ {% if base_arch == 'x86_64' %}
+ {% set ironic_pxe_packages = ironic_pxe_packages + [
+ 'syslinux'
+ ] %}
+ {% endif %}
+
+{{ macros.install_packages(ironic_pxe_packages | customizable("packages")) }}
+{% endif %}
+
+ENV TFTPBOOT_PATH=/tftpboot \
+ HTTPBOOT_PATH=/httpboot
+COPY tftp-map-file-template /map-file-template
+COPY extend_start.sh /usr/local/bin/kolla_ironic_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ironic_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_pxe_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ironic/ironic-pxe/extend_start.sh b/kolla/docker/ironic/ironic-pxe/extend_start.sh
new file mode 100644
index 000000000..06a8312b4
--- /dev/null
+++ b/kolla/docker/ironic/ironic-pxe/extend_start.sh
@@ -0,0 +1,103 @@
+#!/bin/bash
+
+
+# For x86 legacy BIOS boot mode
+function prepare_pxe_pxelinux {
+ if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ cp /usr/lib/PXELINUX/pxelinux.0 \
+ /usr/lib/syslinux/modules/bios/{chain.c32,ldlinux.c32} \
+ ${TFTPBOOT_PATH}/
+ elif [[ "${KOLLA_BASE_DISTRO}" =~ centos|rocky ]]; then
+ if [[ "${TFTPBOOT_PATH}" != /tftpboot ]]; then
+ cp /tftpboot/{pxelinux.0,chain.c32,ldlinux.c32} \
+ ${TFTPBOOT_PATH}/
+ fi
+ fi
+}
+
+# For UEFI boot mode
+function prepare_pxe_grub {
+ if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ shim_src_file="/usr/lib/shim/shim*64.efi.signed"
+ grub_src_file="/usr/lib/grub/*-efi-signed/grubnet*64.efi.signed"
+ elif [[ "${KOLLA_BASE_DISTRO}" =~ centos|rocky ]]; then
+ shim_src_file="/boot/efi/EFI/${KOLLA_BASE_DISTRO}/shim*64.efi"
+ grub_src_file="/boot/efi/EFI/${KOLLA_BASE_DISTRO}/grub*64.efi"
+ fi
+
+ if [[ "${KOLLA_BASE_ARCH}" == "x86_64" ]]; then
+ shim_dst_file="bootx64.efi"
+ grub_dst_file="grubx64.efi"
+ elif [[ "${KOLLA_BASE_ARCH}" == "aarch64" ]]; then
+ shim_dst_file="bootaa64.efi"
+ grub_dst_file="grubaa64.efi"
+ fi
+
+ cp $shim_src_file ${TFTPBOOT_PATH}/$shim_dst_file
+ cp $grub_src_file ${TFTPBOOT_PATH}/$grub_dst_file
+}
+
+function prepare_ipxe {
+ # NOTE(mgoddard): Ironic uses snponly.efi as the default for
+ # uefi_ipxe_bootfile_name since Xena. In Wallaby and earlier releases it
+ # was ipxe.efi. Ensure that both exist, using symlinks where the files are
+ # named differently to allow the original names to be used in ironic.conf.
+ if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ # NOTE(m-anson): ipxe-arm64.efi is not symlinked from /boot to
+ # /usr/lib/ipxe by the Ubuntu ipxe package, so fix that here.
+ if [[ -e /boot/ipxe-arm64.efi ]]; then
+ ln -s /boot/ipxe-arm64.efi /usr/lib/ipxe/
+ fi
+ cp /usr/lib/ipxe/{undionly.kpxe,ipxe*.efi,snponly.efi} ${TFTPBOOT_PATH}/
+ elif [[ "${KOLLA_BASE_DISTRO}" =~ centos|rocky ]]; then
+ cp /usr/share/ipxe/{undionly.kpxe,ipxe-snponly-x86_64.efi} ${TFTPBOOT_PATH}/
+ cp /usr/share/ipxe/arm64-efi/snponly.efi ${TFTPBOOT_PATH}/ipxe-snponly-aarch64.efi
+ if [[ ! -e ${TFTPBOOT_PATH}/snponly.efi ]]; then
+ ln -s ${TFTPBOOT_PATH}/ipxe-snponly-${KOLLA_BASE_ARCH}.efi ${TFTPBOOT_PATH}/snponly.efi
+ fi
+ fi
+}
+
+function prepare_esp_image {
+ # NOTE(bbezak): based on https://docs.openstack.org/ironic/2024.2/install/configure-esp.html
+ # ESP image needs to be provided for UEFI boot with virtual media:
+ # https://docs.openstack.org/ironic/2024.2/admin/drivers/redfish.html#virtual-media-boot
+ if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ shim_src_file="/usr/lib/shim/shim*64.efi.signed"
+ grub_src_file="/usr/lib/grub/*-efi-signed/grubnet*64.efi.signed"
+ elif [[ "${KOLLA_BASE_DISTRO}" =~ centos|rocky ]]; then
+ shim_src_file="/boot/efi/EFI/${KOLLA_BASE_DISTRO}/shim*64.efi"
+ grub_src_file="/boot/efi/EFI/${KOLLA_BASE_DISTRO}/grub*64.efi"
+ fi
+
+ if [[ "${KOLLA_BASE_ARCH}" == "x86_64" ]]; then
+ shim_dst_file="bootx64.efi"
+ grub_dst_file="grubx64.efi"
+ elif [[ "${KOLLA_BASE_ARCH}" == "aarch64" ]]; then
+ shim_dst_file="bootaa64.efi"
+ grub_dst_file="grubaa64.efi"
+ fi
+
+ DEST=${HTTPBOOT_PATH}/esp.img
+ dd if=/dev/zero of=$DEST bs=4096 count=2048
+ mkfs.msdos -F 12 -n ESP_IMAGE $DEST
+ mmd -i $DEST EFI EFI/BOOT
+ mcopy -i $DEST -v $shim_src_file ::EFI/BOOT/$shim_dst_file
+ mcopy -i $DEST -v $grub_src_file ::EFI/BOOT/$grub_dst_file
+ mdir -i $DEST ::EFI/BOOT
+}
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ mkdir -p ${TFTPBOOT_PATH} ${HTTPBOOT_PATH}
+ chown ironic: ${TFTPBOOT_PATH} ${HTTPBOOT_PATH}
+ prepare_pxe_pxelinux
+ prepare_pxe_grub
+ prepare_ipxe
+ prepare_esp_image
+ exit 0
+fi
+
+# Template out a TFTP map file, using the TFTPBOOT_PATH variable.
+envsubst < /map-file-template > /map-file
diff --git a/kolla/docker/ironic/ironic-pxe/tftp-map-file-template b/kolla/docker/ironic/ironic-pxe/tftp-map-file-template
new file mode 100644
index 000000000..5d3243a7e
--- /dev/null
+++ b/kolla/docker/ironic/ironic-pxe/tftp-map-file-template
@@ -0,0 +1,4 @@
+re ^(${TFTPBOOT_PATH}/) ${TFTPBOOT_PATH}/\2
+re ^${TFTPBOOT_PATH}/ ${TFTPBOOT_PATH}/
+re ^(^/) ${TFTPBOOT_PATH}/\1
+re ^([^/]) ${TFTPBOOT_PATH}/\1
diff --git a/kolla/docker/iscsid/Dockerfile.j2 b/kolla/docker/iscsid/Dockerfile.j2
new file mode 100644
index 000000000..a1e4bba61
--- /dev/null
+++ b/kolla/docker/iscsid/Dockerfile.j2
@@ -0,0 +1,36 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block iscsid_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set iscsid_packages = [
+ 'iscsi-initiator-utils',
+ 'python3-rtslib',
+ 'targetcli'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set iscsid_packages = [
+ 'open-iscsi',
+ 'python3-rtslib-fb',
+ 'targetcli-fb'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(iscsid_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+# NOTE(hrw): CentOS Stream 9 lacks initiatorname.iscsi file
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && rm -f /etc/iscsi/initiatorname.iscsi
+
+{{ macros.kolla_patch_sources() }}
+
+{% block iscsid_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/iscsid/extend_start.sh b/kolla/docker/iscsid/extend_start.sh
new file mode 100644
index 000000000..78f2317d2
--- /dev/null
+++ b/kolla/docker/iscsid/extend_start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# check if unique iSCSI initiator name already exists
+if [[ ! -f /etc/iscsi/initiatorname.iscsi ]]; then
+ echo "Generating new iSCSI initiator name"
+ echo InitiatorName=$(/sbin/iscsi-iname) > /etc/iscsi/initiatorname.iscsi
+fi
diff --git a/kolla/docker/keepalived/Dockerfile.j2 b/kolla/docker/keepalived/Dockerfile.j2
new file mode 100644
index 000000000..2dcdeccd7
--- /dev/null
+++ b/kolla/docker/keepalived/Dockerfile.j2
@@ -0,0 +1,34 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block keepalived_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.enable_extra_repos(['mariadb']) }}
+
+{% if base_package_type == 'rpm' %}
+ {% set keepalived_packages = [
+ 'keepalived',
+ 'MariaDB-client'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set keepalived_packages = [
+ 'keepalived',
+ 'mariadb-client'
+ ] %}
+{% endif %}
+{{ macros.install_packages(keepalived_packages | customizable("packages")) }}
+
+COPY check_alive.sh /
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 755 /check_alive.sh
+
+{{ macros.kolla_patch_sources() }}
+
+{% block keepalived_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/keepalived/check_alive.sh b/kolla/docker/keepalived/check_alive.sh
new file mode 100644
index 000000000..857131123
--- /dev/null
+++ b/kolla/docker/keepalived/check_alive.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# This will return 0 when it successfully passes all checks for all daemons
+# Failures return 1
+
+declare -A check_results
+final_result=0
+
+if [ -d "/checks" ]; then
+ CHECKS=$(find /checks -type f)
+fi
+
+if [ "${CHECKS}" ]; then
+ # Store results
+ for check in ${CHECKS}; do
+ # Run check but do not print stderr
+ # as single check can be executed manually to see the result
+ ${check} 2>/dev/null
+ check_results[${check}]=$?
+ done
+
+ # Print results and save the final result
+ for i in "${!check_results[@]}"; do
+ if [ "${check_results[$i]}" == "0" ]; then
+ echo "Keepalived check script ${i} succeeded."
+ else
+ final_result=1
+ echo "Keepalived check script ${i} failed."
+ fi
+ done
+
+ exit ${final_result}
+fi
diff --git a/kolla/docker/keepalived/extend_start.sh b/kolla/docker/keepalived/extend_start.sh
new file mode 100644
index 000000000..889da64fd
--- /dev/null
+++ b/kolla/docker/keepalived/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Workaround for bug #1485079
+if [ -f /run/keepalived.pid ]; then
+ rm /run/keepalived.pid
+fi
diff --git a/kolla/docker/kerbside/kerbside-api/Dockerfile.j2 b/kolla/docker/kerbside/kerbside-api/Dockerfile.j2
new file mode 100644
index 000000000..6ea99eb7a
--- /dev/null
+++ b/kolla/docker/kerbside/kerbside-api/Dockerfile.j2
@@ -0,0 +1,9 @@
+FROM {{ namespace }}/{{ image_prefix }}kerbside-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+USER kerbside
diff --git a/kolla/docker/kerbside/kerbside-api/extend_start.sh b/kolla/docker/kerbside/kerbside-api/extend_start.sh
new file mode 100644
index 000000000..92ef80a22
--- /dev/null
+++ b/kolla/docker/kerbside/kerbside-api/extend_start.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# Make the log directory
+mkdir -p /var/log/kolla/kerbside
diff --git a/kolla/docker/kerbside/kerbside-base/Dockerfile.j2 b/kolla/docker/kerbside/kerbside-base/Dockerfile.j2
new file mode 100644
index 000000000..77f881399
--- /dev/null
+++ b/kolla/docker/kerbside/kerbside-base/Dockerfile.j2
@@ -0,0 +1,38 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block kerbside_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='kerbside', groups='kerbside') }}
+
+{% if base_package_type == 'rpm' %}
+ {% set kerbside_packages = [
+ 'mariadb-devel',
+ 'pkgconfig'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set kerbside_packages = [
+ 'libmariadb-dev-compat',
+ 'pkg-config'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(kerbside_packages | customizable("packages")) }}
+
+{% set kerbside_pip_packages = [
+ '/kerbside'
+] %}
+
+ADD kerbside-base-archive /kerbside-source
+RUN ln -s kerbside-source/* kerbside \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(kerbside_plugins_pip_packages) }}; \
+ fi \
+ && {{ macros.install_pip(kerbside_pip_packages | customizable("pip_packages"), false) }} \
+ && rm -f /etc/machine-id
+
+{% block kerbside_base_footer %}{% endblock %}
diff --git a/kolla/docker/kerbside/kerbside-proxy/Dockerfile.j2 b/kolla/docker/kerbside/kerbside-proxy/Dockerfile.j2
new file mode 100644
index 000000000..6ea99eb7a
--- /dev/null
+++ b/kolla/docker/kerbside/kerbside-proxy/Dockerfile.j2
@@ -0,0 +1,9 @@
+FROM {{ namespace }}/{{ image_prefix }}kerbside-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+USER kerbside
diff --git a/kolla/docker/kerbside/kerbside-proxy/extend_start.sh b/kolla/docker/kerbside/kerbside-proxy/extend_start.sh
new file mode 100644
index 000000000..92ef80a22
--- /dev/null
+++ b/kolla/docker/kerbside/kerbside-proxy/extend_start.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# Make the log directory
+mkdir -p /var/log/kolla/kerbside
diff --git a/kolla/docker/keystone/keystone-base/Dockerfile.j2 b/kolla/docker/keystone/keystone-base/Dockerfile.j2
new file mode 100644
index 000000000..faa424fa7
--- /dev/null
+++ b/kolla/docker/keystone/keystone-base/Dockerfile.j2
@@ -0,0 +1,45 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block keystone_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='keystone') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['epel']) }}
+
+ {% set keystone_base_packages = [
+ 'cyrus-sasl-devel',
+ 'openldap-devel',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set keystone_base_packages = [
+ 'libldap-common',
+ 'libldap2-dev',
+ 'libsasl2-dev',
+ ] %}
+{% endif %}
+{{ macros.install_packages(keystone_base_packages | customizable("packages")) }}
+
+{% block keystone_source_install %}
+ADD keystone-base-archive /keystone-base-source
+
+{% set keystone_base_pip_packages = [
+ '/keystone[ldap]',
+] %}
+
+RUN ln -s keystone-base-source/* keystone \
+ && {{ macros.install_pip(keystone_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/keystone \
+ && cp -r /keystone/etc/* /etc/keystone/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block keystone_base_footer %}{% endblock %}
diff --git a/kolla/docker/keystone/keystone-fernet/Dockerfile.j2 b/kolla/docker/keystone/keystone-fernet/Dockerfile.j2
new file mode 100644
index 000000000..da32ddb86
--- /dev/null
+++ b/kolla/docker/keystone/keystone-fernet/Dockerfile.j2
@@ -0,0 +1,34 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block keystone_fernet_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set keystone_fernet_packages = [
+ 'cronie',
+ 'openssh-clients',
+ 'rsync'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set keystone_fernet_packages = [
+ 'cron',
+ 'openssh-client',
+ 'rsync'
+ ] %}
+{% endif %}
+{{ macros.install_packages(keystone_fernet_packages | customizable("packages")) }}
+
+COPY fetch_fernet_tokens.py /usr/bin/
+COPY keystone_bootstrap.sh /usr/local/bin/kolla_keystone_bootstrap
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 755 /usr/local/bin/kolla_keystone_bootstrap /usr/bin/fetch_fernet_tokens.py
+
+{{ macros.kolla_patch_sources() }}
+
+{% block keystone_fernet_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/keystone/keystone-fernet/extend_start.sh b/kolla/docker/keystone/keystone-fernet/extend_start.sh
new file mode 100644
index 000000000..cf356949f
--- /dev/null
+++ b/kolla/docker/keystone/keystone-fernet/extend_start.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+FERNET_SYNC=/usr/bin/fernet-node-sync.sh
+FERNET_TOKEN_DIR="/etc/keystone/fernet-keys"
+
+if [[ -f "${FERNET_SYNC}" ]]; then
+ ${FERNET_SYNC}
+fi
+
+if [[ $(stat -c %U:%G ${FERNET_TOKEN_DIR}) != "keystone:keystone" ]]; then
+ chown keystone:keystone ${FERNET_TOKEN_DIR}
+fi
diff --git a/kolla/docker/keystone/keystone-fernet/fetch_fernet_tokens.py b/kolla/docker/keystone/keystone-fernet/fetch_fernet_tokens.py
new file mode 100644
index 000000000..c57943630
--- /dev/null
+++ b/kolla/docker/keystone/keystone-fernet/fetch_fernet_tokens.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Basically this module will fetch the fernet tokens and compare them to the
+# required time constrains to determine whether the host needs to resync with
+# other nodes in the cluster.
+import argparse
+from datetime import datetime
+from datetime import timedelta
+import json
+import os
+import sys
+
+# Adding nosec since this fails bandit B105, 'Possible hardcoded password'.
+TOKEN_PATH = '/etc/keystone/fernet-keys' # nosec
+
+
+def json_exit(msg=None, failed=False, changed=False):
+ if type(msg) is not dict:
+ msg = {'msg': str(msg)}
+ msg.update({'failed': failed, 'changed': changed})
+ print(json.dumps(msg))
+ sys.exit()
+
+
+def has_file(filename_path):
+ if not os.path.exists(filename_path):
+ return False
+ return True
+
+
+def num_tokens():
+ _, _, files = next(os.walk(TOKEN_PATH))
+ return len(files)
+
+
+def tokens_populated(expected):
+ return num_tokens() >= int(expected)
+
+
+def token_stale(seconds, filename='0'):
+ max_token_age = datetime.now() - timedelta(seconds=int(seconds))
+ filename_path = os.path.join(TOKEN_PATH, filename)
+
+ if not has_file(filename_path):
+ return True
+ modified_date = datetime.fromtimestamp(os.path.getmtime(filename_path))
+ return modified_date < max_token_age
+
+
+def main():
+ parser = argparse.ArgumentParser(description='''Checks to see if a fernet
+ token no older than a desired time.''')
+ parser.add_argument('-t', '--time',
+ help='Time in seconds for a token rotation',
+ required=True)
+ parser.add_argument('-f', '--filename',
+ help='Filename of token to check',
+ default='0')
+ parser.add_argument('-n', '--number',
+ help='Minimum number of tokens that should exist',
+ required=True)
+ args = parser.parse_args()
+
+ json_exit({
+ 'populated': tokens_populated(args.number),
+ 'update_required': token_stale(args.time, args.filename),
+ })
+
+
+if __name__ == '__main__':
+ main()
diff --git a/kolla/docker/keystone/keystone-fernet/keystone_bootstrap.sh b/kolla/docker/keystone/keystone-fernet/keystone_bootstrap.sh
new file mode 100644
index 000000000..d361767bb
--- /dev/null
+++ b/kolla/docker/keystone/keystone-fernet/keystone_bootstrap.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -x
+
+USERNAME=$1
+GROUP=$2
+
+function fail_json {
+ echo '{"failed": true, "msg": "'$1'", "changed": true}'
+ exit 1
+}
+
+function exit_json {
+ echo '{"failed": false, "changed": '"${changed}"'}'
+}
+
+changed="false"
+keystone_bootstrap=$(keystone-manage --config-file /etc/keystone/keystone.conf fernet_setup --keystone-user ${USERNAME} --keystone-group ${GROUP} 2>&1)
+if [[ $? != 0 ]]; then
+ fail_json "${keystone_bootstrap}"
+fi
+
+changed=$(echo "${keystone_bootstrap}" | awk '
+ /Key repository is already initialized/ {count++}
+ END {
+ if (count == 1) changed="true"; else changed="false"
+ print changed
+ }'
+)
+
+exit_json
diff --git a/kolla/docker/keystone/keystone-ssh/Dockerfile.j2 b/kolla/docker/keystone/keystone-ssh/Dockerfile.j2
new file mode 100644
index 000000000..af290bd5f
--- /dev/null
+++ b/kolla/docker/keystone/keystone-ssh/Dockerfile.j2
@@ -0,0 +1,38 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block keystone_ssh_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='keystone', shell='/bin/bash') }}
+
+{% if base_package_type == 'rpm' %}
+ {% set keystone_ssh_packages = [
+ 'openssh-server',
+ 'rsync'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set keystone_ssh_packages = [
+ 'openssh-server',
+ 'rsync'
+ ] %}
+
+RUN mkdir -p /var/run/sshd \
+ && chmod 0755 /var/run/sshd
+
+{% endif %}
+{{ macros.install_packages(keystone_ssh_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN sed -ri 's/session(\s+)required(\s+)pam_loginuid.so/session\1optional\2pam_loginuid.so/' /etc/pam.d/sshd \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block keystone_ssh_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/keystone/keystone-ssh/extend_start.sh b/kolla/docker/keystone/keystone-ssh/extend_start.sh
new file mode 100644
index 000000000..559cd65a9
--- /dev/null
+++ b/kolla/docker/keystone/keystone-ssh/extend_start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# NOTE(mnasiadka): CentOS 10 does not support dsa
+SSH_HOST_KEY_TYPES=( "rsa" "ecdsa" "ed25519" )
+
+for key_type in ${SSH_HOST_KEY_TYPES[@]}; do
+ KEY_PATH=/etc/ssh/ssh_host_${key_type}_key
+ if [[ ! -f "${KEY_PATH}" ]]; then
+ ssh-keygen -q -t ${key_type} -f ${KEY_PATH} -N ""
+ fi
+done
+
+mkdir -p /var/lib/keystone/.ssh
+
+if [[ $(stat -c %U:%G /var/lib/keystone/.ssh) != "keystone:keystone" ]]; then
+ sudo chown keystone: /var/lib/keystone/.ssh
+fi
diff --git a/kolla/docker/keystone/keystone/Dockerfile.j2 b/kolla/docker/keystone/keystone/Dockerfile.j2
new file mode 100644
index 000000000..742626c05
--- /dev/null
+++ b/kolla/docker/keystone/keystone/Dockerfile.j2
@@ -0,0 +1,45 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block keystone_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set keystone_packages = [
+ 'krb5-devel',
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set keystone_packages = [
+ 'libkrb5-dev',
+ ] %}
+{% endif %}
+{{ macros.install_packages(keystone_packages | customizable("packages")) }}
+
+{% set keystone_pip_packages = [
+ 'requests-kerberos',
+] %}
+
+RUN {{ macros.install_pip(keystone_pip_packages | customizable("pip_packages")) }}
+
+ADD plugins-archive /
+
+{% set keystone_plugins_pip_packages = [
+ '/plugins/*',
+] %}
+
+COPY keystone_bootstrap.sh /usr/local/bin/kolla_keystone_bootstrap
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 755 /usr/local/bin/kolla_keystone_bootstrap \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(keystone_plugins_pip_packages) }}; \
+ fi
+
+{{ macros.kolla_patch_sources() }}
+
+{% block keystone_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/keystone/keystone/extend_start.sh b/kolla/docker/keystone/keystone/extend_start.sh
new file mode 100644
index 000000000..b45c94d0d
--- /dev/null
+++ b/kolla/docker/keystone/keystone/extend_start.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+# Create log dir for Keystone logs
+KEYSTONE_LOG_DIR="/var/log/kolla/keystone"
+if [[ ! -d "${KEYSTONE_LOG_DIR}" ]]; then
+ mkdir -p ${KEYSTONE_LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${KEYSTONE_LOG_DIR}) != "keystone:kolla" ]]; then
+ chown keystone:kolla ${KEYSTONE_LOG_DIR}
+fi
+if [ ! -f "${KEYSTONE_LOG_DIR}/keystone.log" ]; then
+ touch ${KEYSTONE_LOG_DIR}/keystone.log
+fi
+if [[ $(stat -c %U:%G ${KEYSTONE_LOG_DIR}/keystone.log) != "keystone:keystone" ]]; then
+ chown keystone:keystone ${KEYSTONE_LOG_DIR}/keystone.log
+fi
+if [[ $(stat -c %a ${KEYSTONE_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${KEYSTONE_LOG_DIR}
+fi
+
+EXTRA_KEYSTONE_MANAGE_ARGS=${EXTRA_KEYSTONE_MANAGE_ARGS-}
+# Upgrade and exit if KOLLA_UPGRADE variable is set. This catches all cases
+# of the KOLLA_UPGRADE variable being set, including empty.
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ # TODO(duonghq): check doctor result here
+ # TODO: find reason why doctor failed in gate
+ # sudo -H -u keystone keystone-manage doctor
+ sudo -H -u keystone keystone-manage ${EXTRA_KEYSTONE_MANAGE_ARGS} db_sync --expand
+ sudo -H -u keystone keystone-manage ${EXTRA_KEYSTONE_MANAGE_ARGS} db_sync --migrate
+ exit 0
+fi
+
+# Contract database and exit if KOLLA_FINISH_UPGRADE variable is set.
+# This catches all cases of the KOLLA_FINISH_UPGRADE variable being set,
+# including empty.
+if [[ "${!KOLLA_FINISH_UPGRADE[@]}" ]]; then
+ sudo -H -u keystone keystone-manage ${EXTRA_KEYSTONE_MANAGE_ARGS} db_sync --contract
+ exit 0
+fi
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ sudo -H -u keystone keystone-manage ${EXTRA_KEYSTONE_MANAGE_ARGS} db_sync
+ exit 0
+fi
diff --git a/kolla/docker/keystone/keystone/keystone_bootstrap.sh b/kolla/docker/keystone/keystone/keystone_bootstrap.sh
new file mode 100644
index 000000000..1b04d99b6
--- /dev/null
+++ b/kolla/docker/keystone/keystone/keystone_bootstrap.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+set -o pipefail
+
+# NOTE(SamYaple): Kolla needs to wraps `keystone-manage bootstrap` to ensure
+# any change is reported correctly for idempotency. This script will exit with
+# valid json that can be parsed with information about if the task has failed
+# and if anything changed.
+
+USERNAME=$1
+PASSWORD=$2
+if [ -z "$PASSWORD" ]; then
+ # Avoid having the password always come in via CLI (which makes
+ # it show up in things like ara)
+ PASSWORD="$OS_BOOTSTRAP_PASSWORD"
+fi
+PROJECT=$3
+ROLE=$4
+if [ $# -ge 8 ]; then
+ # ignored ADMIN_URL compat mode
+ # ADMIN_URL=$5
+ INTERNAL_URL=$6
+ PUBLIC_URL=$7
+ REGION=$8
+else
+ # no ADMIN_URL modern mode
+ INTERNAL_URL=$5
+ PUBLIC_URL=$6
+ REGION=$7
+fi
+
+function fail_json {
+ echo '{"failed": true, "msg": "'$1'", "changed": true}'
+ exit 1
+}
+
+function exit_json {
+ echo '{"failed": false, "changed": '"${changed}"'}'
+}
+
+changed="false"
+# NOTE(mgoddard): pipe through cat -v to remove unprintable control characters
+# which prevent JSON decoding.
+# NOTE(yoctozepto): also apply sed to escape double quotation marks
+# and backslashes
+keystone_bootstrap=$(keystone-manage bootstrap --bootstrap-username "${USERNAME}" --bootstrap-password "${PASSWORD}" --bootstrap-project-name "${PROJECT}" --bootstrap-role-name "${ROLE}" --bootstrap-internal-url "${INTERNAL_URL}" --bootstrap-public-url "${PUBLIC_URL}" --bootstrap-service-name "keystone" --bootstrap-region-id "${REGION}" 2>&1 | cat -v | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
+if [[ $? != 0 ]]; then
+ fail_json "${keystone_bootstrap}"
+fi
+
+changed=$(echo "${keystone_bootstrap}" | awk '
+ /Domain default already exists, skipping creation./ ||
+ /Project '"${PROJECT}"' already exists, skipping creation./ ||
+ /User '"${USERNAME}"' already exists, skipping creation./ ||
+ /Role '"${ROLE}"' exists, skipping creation./ ||
+ /User '"${USERNAME}"' already has '"${ROLE}"' on '"${PROJECT}"'./ ||
+ /Region '"${REGION}"' exists, skipping creation./ ||
+ /Skipping internal endpoint as already created/ ||
+ /Skipping public endpoint as already created/ {count++}
+ END {
+ if (count == 9) changed="false"; else changed="true"
+ print changed
+ }'
+)
+
+exit_json
diff --git a/kolla/docker/kolla-toolbox/Dockerfile.j2 b/kolla/docker/kolla-toolbox/Dockerfile.j2
new file mode 100644
index 000000000..eac78905d
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/Dockerfile.j2
@@ -0,0 +1,129 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block kolla_toolbox_header %}{% endblock %}
+
+{% set venv_path='/opt/ansible' %}
+{% set os_client_config= venv_path + '/lib/python3/site-packages/os_client_config/defaults.json' %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='ansible') }}
+{{ macros.configure_user(name='rabbitmq') }}
+
+{{ macros.enable_extra_repos(['erlang', 'rabbitmq']) }}
+
+{% block rabbitmq_apt_preferences %}
+{% if base_package_type == 'deb' %}
+COPY apt_preferences_rabbitmq.{{ base_distro }} /etc/apt/preferences.d/rabbitmq
+{% endif %}
+{% endblock %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['crb', 'epel', 'openvswitch']) }}
+
+ {% set kolla_toolbox_packages = [
+ 'erlang-27.*',
+ 'gcc',
+ 'gdisk',
+ 'git',
+ 'jq',
+ 'libffi-devel',
+ 'libxml2-devel',
+ 'libxslt-devel',
+ 'make',
+ 'openssh-clients',
+ 'openssl-devel',
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'python3',
+ 'python3-devel',
+ 'rabbitmq-server-4.2.*'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set kolla_toolbox_packages = [
+ 'build-essential',
+ 'ca-certificates',
+ 'gdisk',
+ 'git',
+ 'jq',
+ 'libffi-dev',
+ 'libssl-dev',
+ 'libxslt1-dev',
+ 'openvswitch-switch',
+ 'python3-dev',
+ 'python3-venv',
+ 'rabbitmq-server',
+ ] %}
+{% endif %}
+{{ macros.install_packages(kolla_toolbox_packages | customizable("packages")) }}
+
+{% block kolla_toolbox_pip_conf %}
+ENV UPPER_CONSTRAINTS_FILE=/requirements/upper-constraints.txt
+{% endblock %}
+
+{% block kolla_toolbox_upper_constraints %}
+ADD kolla-toolbox-archive /kolla-toolbox-source
+
+RUN case "$UPPER_CONSTRAINTS_FILE" in \
+ http*) mkdir -p /requirements && curl -LO $UPPER_CONSTRAINTS_FILE /requirements/upper-constraints.txt ;; \
+ *) ln -s kolla-toolbox-source/* /requirements ;; \
+ esac \
+ && {{ macros.upper_constraints_remove("openstacksdk") }} \
+ && python3 -m venv --system-site-packages {{ venv_path }} \
+ && KOLLA_DISTRO_PYTHON_VERSION=$(/usr/bin/python3 -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))") \
+ && cd {{ venv_path }}/lib \
+ && ln -s python${KOLLA_DISTRO_PYTHON_VERSION} {{ venv_path }}/lib/python3
+{% endblock %}
+
+ENV PATH {{ venv_path }}/bin:$PATH
+
+{% set kolla_toolbox_pip_packages = [
+ 'ansible-core==2.20.*',
+ 'ansible-runner',
+ 'cmd2',
+ 'openstacksdk',
+ 'os-client-config',
+ 'pbr',
+ 'pymysql',
+ 'python-ironicclient',
+ 'python-openstackclient',
+ 'pytz',
+ 'pyudev',
+ ] %}
+
+RUN {{ macros.install_pip(['pip', 'wheel', 'setuptools']) }} \
+ && {{ macros.install_pip((kolla_toolbox_pip_packages | customizable("pip_packages"))) }} \
+ && mkdir -p /etc/ansible /usr/share/ansible \
+ && echo 'localhost ansible_connection=local ansible_python_interpreter={{ venv_path }}/bin/python' > /etc/ansible/hosts \
+ && sed -i 's| "identity_api_version": "2.0",| "identity_api_version": "3",|' {{ os_client_config }}
+
+{% block kolla_toolbox_collections_install %}
+COPY requirements.yml /var/lib/ansible/
+RUN fail=1; for i in $(seq 1 5); do if \
+ansible-galaxy collection install --timeout 120 -p /usr/share/ansible/collections -r /var/lib/ansible/requirements.yml \
+; then fail=0; break; fi; echo "Collection download failed, retrying"; sleep 5; done; \
+if [ "$fail" -eq 1 ]; then exit 1; fi
+
+ENV ANSIBLE_LIBRARY /usr/share/ansible:$ANSIBLE_LIBRARY
+{% endblock %}
+
+COPY ansible.cfg /etc/ansible/ansible.cfg
+COPY ansible_sudoers /etc/sudoers.d/kolla_ansible_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY kolla_toolbox.sh /usr/local/bin/kolla_toolbox
+
+RUN chmod 644 /etc/ansible/ansible.cfg \
+ /usr/local/bin/kolla_extend_start \
+ && chmod 755 /usr/local/bin/kolla_toolbox \
+ && chmod 440 /etc/sudoers.d/kolla_ansible_sudoers
+
+{{ macros.kolla_patch_sources() }}
+
+{% block kolla_toolbox_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER ansible
diff --git a/kolla/docker/kolla-toolbox/ansible.cfg b/kolla/docker/kolla-toolbox/ansible.cfg
new file mode 100644
index 000000000..ec505f4a4
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/ansible.cfg
@@ -0,0 +1,4 @@
+[defaults]
+inject_facts_as_vars = False
+log_path = /var/log/kolla/ansible.log
+remote_tmp = /tmp
diff --git a/kolla/docker/kolla-toolbox/ansible_sudoers b/kolla/docker/kolla-toolbox/ansible_sudoers
new file mode 100644
index 000000000..5a3f109b4
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/ansible_sudoers
@@ -0,0 +1,3 @@
+Defaults secure_path="/opt/ansible/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+
+ansible ALL=(root) NOPASSWD: /opt/ansible/bin/ansible localhost -m find_disks -a *, /usr/local/bin/ansible localhost -m find_disks -a *
diff --git a/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.debian b/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.debian
new file mode 120000
index 000000000..f1cc84d9b
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.debian
@@ -0,0 +1 @@
+../rabbitmq/apt_preferences_rabbitmq.debian \ No newline at end of file
diff --git a/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.ubuntu b/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.ubuntu
new file mode 120000
index 000000000..977ded074
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/apt_preferences_rabbitmq.ubuntu
@@ -0,0 +1 @@
+../rabbitmq/apt_preferences_rabbitmq.ubuntu \ No newline at end of file
diff --git a/kolla/docker/kolla-toolbox/extend_start.sh b/kolla/docker/kolla-toolbox/extend_start.sh
new file mode 100644
index 000000000..198587145
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -f "/var/log/kolla/ansible.log" ]]; then
+ touch /var/log/kolla/ansible.log
+fi
+
+if [[ $(stat -c %U:%G /var/log/kolla/ansible.log) != "ansible:kolla" ]]; then
+ chown -R ansible:kolla /var/log/kolla/ansible.log
+fi
+
+if [[ $(stat -c %a /var/log/kolla/ansible.log) != "664" ]]; then
+ chmod 664 /var/log/kolla/ansible.log
+fi
diff --git a/kolla/docker/kolla-toolbox/kolla_toolbox.sh b/kolla/docker/kolla-toolbox/kolla_toolbox.sh
new file mode 100644
index 000000000..5dfd65338
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/kolla_toolbox.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+catch_term() {
+ exit 0
+}
+
+catch_quit() {
+ exit 0
+}
+
+trap catch_term SIGTERM
+trap catch_quit SIGQUIT
+
+while :; do
+ sleep 1
+done
diff --git a/kolla/docker/kolla-toolbox/requirements.yml b/kolla/docker/kolla-toolbox/requirements.yml
new file mode 100644
index 000000000..83c795142
--- /dev/null
+++ b/kolla/docker/kolla-toolbox/requirements.yml
@@ -0,0 +1,14 @@
+---
+collections:
+ - name: ansible.posix
+ version: '<3'
+ - name: community.general
+ version: '<13'
+ - name: community.mysql
+ version: '<5'
+ - name: community.rabbitmq
+ version: '<2'
+ - name: openstack.cloud
+ version: '<3'
+ - name: openvswitch.openvswitch
+ version: '<3'
diff --git a/kolla/docker/letsencrypt/letsencrypt-base/Dockerfile.j2 b/kolla/docker/letsencrypt/letsencrypt-base/Dockerfile.j2
new file mode 100644
index 000000000..8e9e60db9
--- /dev/null
+++ b/kolla/docker/letsencrypt/letsencrypt-base/Dockerfile.j2
@@ -0,0 +1,14 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block letsencrypt_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block letsencrypt_base_footer %}{% endblock %}
diff --git a/kolla/docker/letsencrypt/letsencrypt-base/extend_start.sh b/kolla/docker/letsencrypt/letsencrypt-base/extend_start.sh
new file mode 100644
index 000000000..0300f0b44
--- /dev/null
+++ b/kolla/docker/letsencrypt/letsencrypt-base/extend_start.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# Logs
+
+if [[ ! -d "/var/log/kolla/letsencrypt" ]]; then
+ mkdir -p /var/log/kolla/letsencrypt
+fi
+if [[ $(stat -c %a /var/log/kolla/letsencrypt) != "755" ]]; then
+ chmod 755 /var/log/kolla/letsencrypt
+fi
+
+# Structure for lego and webserver
+
+FOLDERS_LEGO="/etc/letsencrypt /etc/letsencrypt/lego /etc/letsencrypt/lego/internal /etc/letsencrypt/lego/external"
+FOLDERS_WEBSERVER="/etc/letsencrypt/http-01 /etc/letsencrypt/http-01/.well-known /etc/letsencrypt/http-01/.well-known/acme-challenge"
+
+if [[ "${KOLLA_BASE_DISTRO}" =~ debian|ubuntu ]]; then
+ USER="www-data"
+ GROUP="www-data"
+ USERGROUP_WEBSERVER="${USER}:${GROUP}"
+ USERGROUP="haproxy:haproxy"
+else
+ USER="apache"
+ GROUP="apache"
+ USERGROUP_WEBSERVER="${USER}:${GROUP}"
+ USERGROUP="haproxy:haproxy"
+fi
+
+for folder in ${FOLDERS_LEGO}; do
+ if [[ ! -d "${folder}" ]]; then
+ mkdir -p ${folder}
+ fi
+
+ if [[ $(stat -c %U:%G ${folder}) != "${USERGROUP}" ]]; then
+ if getent passwd ${USER} 2 > /dev/null; then
+ chown ${USERGROUP} ${folder}
+ fi
+ fi
+
+ if [[ "${folder}" == "/etc/letsencrypt" ]]; then
+ if [[ $(stat -c %a ${folder}) != "751" ]]; then
+ chmod 751 ${folder}
+ fi
+ else
+ if [[ $(stat -c %a ${folder}) != "755" ]]; then
+ chmod 755 ${folder}
+ fi
+ fi
+done
+
+for folder in ${FOLDERS_WEBSERVER}; do
+ if [[ ! -d "${folder}" ]]; then
+ mkdir -p ${folder}
+ fi
+
+ if [[ $(stat -c %U:%G ${folder}) != "${USERGROUP_WEBSERVER}" ]]; then
+ if getent passwd ${USER} 2 > /dev/null; then
+ chown ${USERGROUP_WEBSERVER} ${folder}
+ fi
+ fi
+
+ if [[ $(stat -c %a ${folder}) != "755" ]]; then
+ chmod 755 ${folder}
+ fi
+done
+
+if [ -e /usr/local/bin/kolla_letsencrypt_extend_start ]; then
+ . /usr/local/bin/kolla_letsencrypt_extend_start
+fi
diff --git a/kolla/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2 b/kolla/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2
new file mode 100755
index 000000000..b7cda214e
--- /dev/null
+++ b/kolla/docker/letsencrypt/letsencrypt-lego/Dockerfile.j2
@@ -0,0 +1,41 @@
+FROM {{ namespace }}/{{ image_prefix }}letsencrypt-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block letsencrypt_lego_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='letsencrypt', shell='/bin/bash') }}
+
+{% if base_package_type == 'rpm' %}
+ {% set letsencrypt_lego_packages = [
+ 'openssh-clients',
+ 'cronie',
+ 'rsync'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set letsencrypt_lego_packages = [
+ 'openssh-client',
+ 'cron',
+ 'rsync'
+ ] %}
+{% endif %}
+{{ macros.install_packages(letsencrypt_lego_packages | customizable("packages")) }}
+
+{% block letsencrypt_lego_install %}
+ADD letsencrypt-lego-archive /lego-source
+
+RUN cp -r /lego-source/lego /opt/
+{% endblock %}
+
+COPY letsencrypt-certificates.sh /usr/bin/letsencrypt-certificates
+COPY sync-and-update-certificate.sh /usr/bin/sync-and-update-certificate
+
+RUN chmod +x /usr/bin/letsencrypt-certificates /usr/bin/sync-and-update-certificate
+
+{{ macros.kolla_patch_sources() }}
+
+{% block letsencrypt_lego_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh b/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
new file mode 100755
index 000000000..83bdede87
--- /dev/null
+++ b/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
@@ -0,0 +1,226 @@
+#!/bin/bash
+
+function log_info {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [INFO] ${message}"
+}
+
+function log_error {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [ERROR] ${message}"
+}
+
+function obtain_or_renew_certificate {
+ local certificate_fqdns="${1}"
+ local certificate_type="${2}"
+ local listen_port="${3}"
+ local valid_days="${4}"
+ local acme_url="${5}"
+ local mail="${6}"
+ local letsencrypt_ssh_port="${7}"
+ local eab="${8}"
+ local hmac="${9}"
+ local key_id="${10}"
+ local letsencrypt_key_type="${11:-}"
+
+ certificate_domain_opts=$(echo ${certificate_fqdns} | sed -r -e 's/^/,/g' -e 's/,/--domains=/g' -e 's/--/ --/g')
+ certificate_fqdn=$(echo ${certificate_fqdns} | awk -F ',' '{print $1}')
+ certificate_fqdns=$(echo ${certificate_fqdns} | sed -r 's/,/\ /g')
+
+ if [ -d "/etc/letsencrypt/lego/${certificate_type}/certificates" ]; then
+ garbage_count=$(find /etc/letsencrypt/lego/${certificate_type}/certificates/ -type f | grep -v "${certificate_fqdn}" | wc -l)
+ if [ ${garbage_count} -ne 0 ]; then
+ log_info "[${certificate_fqdn} - cron] Cleaning up garbage in certificates directory."
+ find /etc/letsencrypt/lego/${certificate_type}/certificates/ -type f | grep -v "${certificate_fqdn}" | xargs rm -f
+ fi
+ fi
+
+ if [ -e "/etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem" ]; then
+ certificate_current_fqdns=$(openssl x509 -text -in /etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem \
+ | grep DNS: \
+ | sed -r -e 's/\ *DNS://g' -e 's/^/,/g' -e 's/$/,/g')
+
+ local domains_add=""
+ for i in ${certificate_fqdns}; do
+ if ! echo "${certificate_current_fqdns}" | grep -q ",${i},"; then
+ domains_add="${domains_add} ${i}"
+ fi
+ done
+ domains_add=$(echo "${domains_add}" | sed -r -e 's/^\ //g' -e 's/\ /, /g')
+
+ if [ "${domains_add}" != "" ]; then
+ log_info "[${certificate_fqdn} - cron] Domains ${domains_add} will be added to certificate."
+ rm -f /etc/letsencrypt/lego/${certificate_type}/certificates/*
+ fi
+ fi
+
+ [ ! -e "/etc/letsencrypt/lego/${certificate_type}/certificates/${certificate_fqdn}.pem" ] && local lego_action="run" || local lego_action="renew"
+
+ if [ "${eab}" = "true" ]; then
+ if [ "${hmac}" != "NONE" ] && [ "${key_id}" != "NONE" ]; then
+ eab_opts="--eab --hmac ${hmac} --kid ${key_id}"
+ else
+ eab_opts=""
+ log_error "External Account Binding requires EAB key ID and EAB HMAC key."
+ exit 1
+ fi
+ fi
+
+ log_info "[${certificate_fqdn} - cron] Obtaining certificate for domains ${certificate_fqdns}."
+ mapfile -t cmd_output < <(/opt/lego --email="${mail}" \
+ $( [ -n "${letsencrypt_key_type}" ] && echo "--key-type ${letsencrypt_key_type}" ) \
+ ${certificate_domain_opts} \
+ --server "${acme_url}" \
+ --path "/etc/letsencrypt/lego/${certificate_type}/" \
+ --http.webroot "/etc/letsencrypt/http-01" \
+ --http.port ${listen_port} \
+ --cert.timeout ${valid_days} \
+ --accept-tos \
+ --http \
+ ${eab_opts} \
+ --pem ${lego_action} \
+ --${lego_action}-hook="/usr/bin/sync-and-update-certificate --${certificate_type} --fqdn ${certificate_fqdn} --haproxies-ssh ${letsencrypt_ssh_port}" 2>&1)
+
+ # Fix LOG formatting as some output has no same format
+ #
+ # Example:
+ #
+ # 2023/10/31 11:52:26 No key found for account michal.arbet@ultimum.io. Generating a P256 key.
+ # 2023/10/31 11:52:26 Saved key to /etc/letsencrypt/lego/external/accounts/acme-v02.api.letsencrypt.org/michal.arbet@ultimum.io/keys/michal.arbet@ultimum.io.key
+ # 2023/10/31 11:52:27 [INFO] acme: Registering account for michal.arbet@ultimum.io
+ # !!!! HEADS UP !!!!
+
+ for i in "${cmd_output[@]}"; do
+ if [ "${i}" == "" ]; then
+ continue
+ fi
+ if ! echo "${i}" | grep -q '\[INFO\]'; then
+ if [ "$(echo "${i}" | awk -F ' ' '{print $1}')" == "$(date +%Y/%m/%d)" ]; then
+ echo "${i}" | awk '{out = ""; for (i = 3; i <= NF; i++) {out = out " " $i}; print $1" "$2" [INFO]"out}'
+ else
+ dt=$(date '+%Y/%m/%d %H:%M:%S')
+ echo "${i}" | awk -v dt="$dt" '{print dt" [INFO] "$0}'
+ fi
+ else
+ echo "${i}"
+ fi
+ done
+}
+
+
+# Parser
+
+INTERNAL_SET="false"
+EXTERNAL_SET="false"
+EXTERNAL_ACCOUNT_BINDING="false"
+HMAC="NONE"
+KEY_ID="NONE"
+LOG_FILE="/var/log/kolla/letsencrypt/lesencrypt-lego.log"
+
+
+VALID_ARGS=$(getopt -o ief:p:d:m:a:v:h:k: --long internal,external,fqdns:,port:,days:,mail:,acme:,vips:,haproxies-ssh:,eab,kid:,hmac:,key-type: -- "$@")
+if [[ $? -ne 0 ]]; then
+ exit 1;
+fi
+
+eval set -- "$VALID_ARGS"
+while [ : ]; do
+ case "$1" in
+ -i | --internal)
+ CERT_TYPE="internal"
+ INTERNAL_SET="true"
+ shift
+ ;;
+ -e | --external)
+ CERT_TYPE="external"
+ EXTERNAL_SET="true"
+ shift
+ ;;
+ -f | --fqdns)
+ FQDNS="${2}"
+ shift 2
+ ;;
+ -p | --port)
+ PORT="${2}"
+ shift 2
+ ;;
+ -d | --days)
+ DAYS="${2}"
+ shift 2
+ ;;
+ -m | --mail)
+ MAIL="${2}"
+ shift 2
+ ;;
+ -a | --acme)
+ ACME="${2}"
+ shift 2
+ ;;
+ -v | --vips)
+ VIPS="${2}"
+ shift 2
+ ;;
+ -h | --haproxies-ssh)
+ LETSENCRYPT_SSH_PORT="${2}"
+ shift 2
+ ;;
+ --eab)
+ EXTERNAL_ACCOUNT_BINDING="true"
+ shift
+ ;;
+ --hmac)
+ HMAC="${2}"
+ shift 2
+ ;;
+ --kid)
+ KEY_ID="${2}"
+ shift 2
+ ;;
+ -k | --key-type)
+ LETSENCRYPT_KEY_TYPE="${2}"
+ shift 2
+ ;;
+ --) shift;
+ break
+ ;;
+ esac
+done
+
+FQDN=$(echo "${FQDNS}" | awk -F ',' '{print $1}')
+
+if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
+ if [ "${INTERNAL_SET}" = "${EXTERNAL_SET}" ]; then
+ log_error "[${FQDN} - cron] Only --internal or --external parameter is allowed at a time."
+ exit 1
+ fi
+
+ LETSENCRYPT_VIP_ADDRESSES="$(echo ${VIPS} | sed -e 's/,/|/g')"
+ if [ "${INTERNAL_SET}" = "true" ]; then
+ LETSENCRYPT_INTERNAL_FQDNS="${FQDNS}"
+ fi
+
+ if [ "${EXTERNAL_SET}" = "true" ]; then
+ LETSENCRYPT_EXTERNAL_FQDNS="${FQDNS}"
+ fi
+
+ if /usr/sbin/ip a | egrep -q "${LETSENCRYPT_VIP_ADDRESSES}"; then
+ log_info "[${FQDN} - cron] This Letsencrypt-lego host is active..."
+ if [ "${LETSENCRYPT_INTERNAL_FQDNS}" != "" ]; then
+ log_info "[${FQDN} - cron] Processing domains ${LETSENCRYPT_INTERNAL_FQDNS}"
+ obtain_or_renew_certificate ${LETSENCRYPT_INTERNAL_FQDNS} internal ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT} ${EXTERNAL_ACCOUNT_BINDING} ${HMAC} ${KEY_ID} ${LETSENCRYPT_KEY_TYPE}
+ fi
+
+ if [ "${LETSENCRYPT_EXTERNAL_FQDNS}" != "" ]; then
+ log_info "[${FQDN} - cron] Processing domains ${LETSENCRYPT_EXTERNAL_FQDNS}"
+ obtain_or_renew_certificate ${LETSENCRYPT_EXTERNAL_FQDNS} external ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT} ${EXTERNAL_ACCOUNT_BINDING} ${HMAC} ${KEY_ID} ${LETSENCRYPT_KEY_TYPE}
+ fi
+ else
+ log_info "[${FQDN} - cron] This Letsencrypt-lego host is passive, nothing to do..."
+ fi
+fi
+
+if [ -d "/etc/letsencrypt/lego" ]; then
+ chown -R haproxy:haproxy /etc/letsencrypt/lego
+fi
diff --git a/kolla/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh b/kolla/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh
new file mode 100644
index 000000000..e1e421c46
--- /dev/null
+++ b/kolla/docker/letsencrypt/letsencrypt-lego/sync-and-update-certificate.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+function log_info {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [INFO] ${message}"
+}
+
+function log_error {
+ local message="${1}"
+
+ echo "$(date '+%Y/%m/%d %H:%M:%S') [ERROR] ${message}"
+}
+
+
+# Parser
+
+INTERNAL_SET="false"
+EXTERNAL_SET="false"
+LOG_FILE="/var/log/kolla/letsencrypt/lesencrypt-lego.log"
+
+
+VALID_ARGS=$(getopt -o ief:l: --long internal,external,fqdn:,haproxies-ssh: -- "$@")
+if [[ $? -ne 0 ]]; then
+ exit 1;
+fi
+
+eval set -- "$VALID_ARGS"
+while [ : ]; do
+ case "$1" in
+ -i | --internal)
+ CERT_TYPE="internal"
+ INTERNAL_SET="true"
+ shift
+ ;;
+ -e | --external)
+ CERT_TYPE="external"
+ EXTERNAL_SET="true"
+ shift
+ ;;
+ -f | --fqdn)
+ FQDN="${2}"
+ shift 2
+ ;;
+ -l | --haproxies-ssh)
+ HAPROXIES_SSH_HOSTS_PORT="${2}"
+ shift 2
+ ;;
+ --) shift;
+ break
+ ;;
+ esac
+done
+
+if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
+ if [ "${INTERNAL_SET}" = "${EXTERNAL_SET}" ]; then
+ log_error "[${FQDN} - hook] Only --internal or --external parameter is allowed at a time"
+ exit 1
+ fi
+
+ HAPROXIES_SSH_HOSTS_PORT=$(echo ${HAPROXIES_SSH_HOSTS_PORT} | sed -e 's/,/ /g')
+
+ for i in ${HAPROXIES_SSH_HOSTS_PORT}; do
+
+ server=$(echo $i | awk -F ':' '{print $1}')
+ port=$(echo $i | awk -F ':' '{print $2}')
+
+ if ! /usr/sbin/ip a | grep -q "${server}"; then
+
+ log_info "[${FQDN} - hook] Rsync lego data /etc/letsencrypt/lego/ to server ${server} and port ${port}"
+ rsync -a -e "ssh -p ${port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" /etc/letsencrypt/lego/ haproxy@${server}:/etc/letsencrypt/lego/ --delete >/dev/null 2>&1
+ if [ "$?" -eq 0 ]; then
+ log_info "[${FQDN} - hook] Rsync Successful."
+ fi
+
+ else
+ log_info "[${FQDN} - hook] Rsync lego data /etc/letsencrypt/lego/ to server ${server} and port ${port} not needed."
+ fi
+
+ if [ "${INTERNAL_SET}" = "true" ]; then
+ internal_cert_path=$(find /etc/letsencrypt/lego/internal/ -name '*.pem')
+ sed -i '/^$/d' ${internal_cert_path}
+ rsync -av -e "ssh -p ${port} -o StrictHostKeyChecking=no -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" ${internal_cert_path} haproxy@${server}:/var/lib/haproxy/haproxy-internal.pem --delete >/dev/null 2>&1
+ ssh -p ${port} -i /var/lib/letsencrypt/.ssh/id_rsa -o StrictHostKeyChecking=no haproxy@${server} "/usr/bin/update-loadbalancer-cert --internal"
+ else
+ external_cert_path=$(find /etc/letsencrypt/lego/external/ -name '*.pem')
+ sed -i '/^$/d' ${external_cert_path}
+ rsync -av -e "ssh -p ${port} -o StrictHostKeyChecking=no -o IdentityFile=/var/lib/letsencrypt/.ssh/id_rsa" ${external_cert_path} haproxy@${server}:/var/lib/haproxy/haproxy.pem --delete >/dev/null 2>&1
+ ssh -p ${port} -i /var/lib/letsencrypt/.ssh/id_rsa -o StrictHostKeyChecking=no haproxy@${server} "/usr/bin/update-loadbalancer-cert --external"
+ fi
+
+ done
+fi
diff --git a/kolla/docker/macros.j2 b/kolla/docker/macros.j2
new file mode 100644
index 000000000..34d67335c
--- /dev/null
+++ b/kolla/docker/macros.j2
@@ -0,0 +1,97 @@
+{% macro install_packages(packages, chain=False, clean=clean_package_cache) -%}
+{% if packages is defined and packages|length > 0 -%}
+ {% if not chain -%} RUN {% endif -%}
+ {%- if base_package_type == 'rpm' -%}
+ {{ distro_package_manager }} -y install {{ packages | join(' ') }}
+ {%- if clean %} \
+ && {{ distro_package_manager }} clean all && rm -rf /var/cache/{{ distro_package_manager }}{% endif -%}
+ {%- elif base_package_type == 'deb' -%}
+ {#-
+ debian_package_install is a utility method to build up an appropriate
+ set of commands to install packages in a debian-based environment that
+ may include URL links to a .deb package
+ -#}
+ {{ debian_package_install(packages, clean) }}
+ {%- endif %}
+{%- endif %}
+{%- endmacro %}
+
+{# Be Extra vigilant about introducing any extra whitespace at the
+ end of the macro for chaining purposes -#}
+{% macro rpm_security_update(cleanup) -%}
+ {{ distro_package_manager }} -y distro-sync --security --sec-severity=Important --sec-severity=Critical
+ {%- if cleanup %} \
+ && {{ distro_package_manager }} clean all && rm -rf /var/cache/{{ distro_package_manager }}{% endif -%}
+{%- endmacro %}
+
+{% macro sed(file='', expressions=[]) -%}
+{% if file != '' and expressions|length >0 %}
+ RUN {% for expression in expressions %} sed -i "{{ file }}" -e "{{ expression }}" {% if not loop.last %} && {% endif %} {% endfor %}
+{% else %}
+ RUN echo "file and expressions must be set to use the sed macro"; /bin/false
+{% endif %}
+{%- endmacro %}
+
+{% macro install_pip(packages, constraints=true, python_version='3') %}
+{%- if packages is sequence and packages|length > 0 -%}
+ python{{ python_version }} -m pip --no-cache-dir install --upgrade{{ ' ' }}
+ {%- if constraints %}-c /requirements/upper-constraints.txt {% endif -%}
+ {{ packages | join(' ') }}
+{%- else -%}
+ true
+{%- endif -%}
+{% endmacro %}
+
+{% macro configure_user(name, groups=None, shell=None, homedir=None) %}
+{%- if name not in users %}
+{{ raise_error("Failed to find configuration for '" + name + "' user. Try specifying '" + name + "-user' config section.") }}
+{%- endif %}
+{% set user=users[name] %}
+{%- if not homedir %}
+{% set homedir='/var/lib/' + name %}
+{%- endif %}
+RUN usermod --append --home {{ homedir }} --groups kolla {{ name }} \
+ {%- if groups %}
+ && usermod --append --groups {{ groups }} {{ name }} \
+ {%- endif %}
+ {%- if shell %}
+ && chsh --shell {{ shell }} {{ name }} \
+ {%- endif %}
+ && mkdir -p {{ homedir }} \
+ && chown -R {{ user.uid }}:{{ user.gid }} {{ homedir }}
+{% endmacro %}
+
+{% macro install_fluent_plugins(plugins, chain=False) -%}
+{% if plugins is defined and plugins|length > 0 -%}
+ {% if not chain -%} RUN {% endif -%}
+ ulimit -n 1024 && fluent-gem install {{ plugins | join(' ') }}
+{%- endif %}
+{%- endmacro %}
+
+{% macro enable_extra_repos(repos) %}
+
+{{ handle_repos(repos, 'enable') }}
+
+{% endmacro %}
+
+{% macro disable_extra_repos(repos) %}
+
+{{ handle_repos(repos, 'disable') }}
+
+{% endmacro %}
+
+{% macro upper_constraints_remove(package_name) -%}
+sed -e "/^{{ package_name }}===/d" -i requirements/upper-constraints.txt
+{%- endmacro %}
+
+{# NOTE(hrw): we change to "==" to allow for "21.*" syntax #}
+{% macro upper_constraints_version_change(package_name, from, to) -%}
+sed -e "s/^{{package_name}}==={{ from }}/{{package_name}}=={{ to }}/g" -i requirements/upper-constraints.txt
+{%- endmacro %}
+
+{% macro kolla_patch_sources() -%}
+{% if patches_path %}
+ COPY patches /patches
+ RUN kolla_patch
+{% endif %}
+{%- endmacro %}
diff --git a/kolla/docker/magnum/magnum-api/Dockerfile.j2 b/kolla/docker/magnum/magnum-api/Dockerfile.j2
new file mode 100644
index 000000000..304b54e20
--- /dev/null
+++ b/kolla/docker/magnum/magnum-api/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}magnum-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block magnum_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_magnum_extend_start
+RUN chmod 644 /usr/local/bin/kolla_magnum_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block magnum_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER magnum
diff --git a/kolla/docker/magnum/magnum-api/extend_start.sh b/kolla/docker/magnum/magnum-api/extend_start.sh
new file mode 100644
index 000000000..0f92f6e7b
--- /dev/null
+++ b/kolla/docker/magnum/magnum-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ magnum-db-manage upgrade
+ exit 0
+fi
diff --git a/kolla/docker/magnum/magnum-base/Dockerfile.j2 b/kolla/docker/magnum/magnum-base/Dockerfile.j2
new file mode 100644
index 000000000..ac17b94b4
--- /dev/null
+++ b/kolla/docker/magnum/magnum-base/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block magnum_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='magnum') }}
+
+ADD magnum-base-archive /magnum-base-source
+
+{% set magnum_base_pip_packages = [
+ '/magnum[osprofiler]',
+ 'magnum-cluster-api'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s magnum-base-source/* magnum \
+ && {{ macros.install_pip(magnum_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/magnum \
+ && cp -r /magnum/etc/magnum/* /etc/magnum \
+ && mv /etc/magnum/keystone_auth_default_policy.sample /etc/magnum/keystone_auth_default_policy.json \
+ && touch /usr/local/bin/kolla_magnum_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_magnum_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block magnum_base_footer %}{% endblock %}
diff --git a/kolla/docker/magnum/magnum-base/extend_start.sh b/kolla/docker/magnum/magnum-base/extend_start.sh
new file mode 100644
index 000000000..2e58c9776
--- /dev/null
+++ b/kolla/docker/magnum/magnum-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/magnum" ]]; then
+ mkdir -p /var/log/kolla/magnum
+fi
+if [[ $(stat -c %a /var/log/kolla/magnum) != "755" ]]; then
+ chmod 755 /var/log/kolla/magnum
+fi
+
+. /usr/local/bin/kolla_magnum_extend_start
diff --git a/kolla/docker/magnum/magnum-conductor/Dockerfile.j2 b/kolla/docker/magnum/magnum-conductor/Dockerfile.j2
new file mode 100644
index 000000000..6e2df8fed
--- /dev/null
+++ b/kolla/docker/magnum/magnum-conductor/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}magnum-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block magnum_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+ADD plugins-archive /
+RUN cp /plugins/linux-{{ debian_arch }}/helm /usr/local/bin/helm
+
+{{ macros.kolla_patch_sources() }}
+
+{% block magnum_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER magnum
diff --git a/kolla/docker/manila/manila-api/Dockerfile.j2 b/kolla/docker/manila/manila-api/Dockerfile.j2
new file mode 100644
index 000000000..d7b75d388
--- /dev/null
+++ b/kolla/docker/manila/manila-api/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}manila-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block manila_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set manila_api_packages = [
+] %}
+{{ macros.install_packages(manila_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_manila_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_manila_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block manila_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER manila
diff --git a/kolla/docker/manila/manila-api/extend_start.sh b/kolla/docker/manila/manila-api/extend_start.sh
new file mode 100644
index 000000000..bea77dbba
--- /dev/null
+++ b/kolla/docker/manila/manila-api/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -o errexit
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ manila-manage db sync
+ exit 0
+fi
diff --git a/kolla/docker/manila/manila-base/Dockerfile.j2 b/kolla/docker/manila/manila-base/Dockerfile.j2
new file mode 100644
index 000000000..a236a20df
--- /dev/null
+++ b/kolla/docker/manila/manila-base/Dockerfile.j2
@@ -0,0 +1,48 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block manila_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='manila') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+ {% set manila_base_packages = ['openvswitch${KOLLA_RPM_OVS_VERSION}'] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set manila_base_packages = ['openvswitch-switch'] %}
+
+{% endif %}
+
+{{ macros.install_packages(manila_base_packages | customizable("packages")) }}
+
+ADD manila-base-archive /manila-base-source
+
+{% set manila_base_pip_packages = [
+ '/manila'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY manila_sudoers /etc/sudoers.d/kolla_manila_sudoers
+
+RUN ln -s manila-base-source/* manila \
+ && {{ macros.install_pip(manila_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/manila /var/cache/manila \
+ && cp -r /manila/etc/manila/* /etc/manila/ \
+ && chown -R manila: /var/cache/manila \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/manila/rootwrap.conf \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_manila_sudoers \
+ && touch /usr/local/bin/kolla_manila_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_manila_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block manila_base_footer %}{% endblock %}
diff --git a/kolla/docker/manila/manila-base/extend_start.sh b/kolla/docker/manila/manila-base/extend_start.sh
new file mode 100644
index 000000000..c018416a3
--- /dev/null
+++ b/kolla/docker/manila/manila-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/manila" ]]; then
+ mkdir -p /var/log/kolla/manila
+fi
+if [[ $(stat -c %a /var/log/kolla/manila) != "755" ]]; then
+ chmod 755 /var/log/kolla/manila
+fi
+
+. /usr/local/bin/kolla_manila_extend_start
diff --git a/kolla/docker/manila/manila-base/manila_sudoers b/kolla/docker/manila/manila-base/manila_sudoers
new file mode 100644
index 000000000..623e6789b
--- /dev/null
+++ b/kolla/docker/manila/manila-base/manila_sudoers
@@ -0,0 +1 @@
+manila ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/manila-rootwrap /etc/manila/rootwrap.conf *
diff --git a/kolla/docker/manila/manila-data/Dockerfile.j2 b/kolla/docker/manila/manila-data/Dockerfile.j2
new file mode 100644
index 000000000..06e1d9dc9
--- /dev/null
+++ b/kolla/docker/manila/manila-data/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}manila-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block manila_data_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set manila_data_packages = [
+ 'nfs-utils'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set manila_data_packages = [
+ 'nfs-common'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(manila_data_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block manila_data_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER manila
diff --git a/kolla/docker/manila/manila-scheduler/Dockerfile.j2 b/kolla/docker/manila/manila-scheduler/Dockerfile.j2
new file mode 100644
index 000000000..12f7860f4
--- /dev/null
+++ b/kolla/docker/manila/manila-scheduler/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}manila-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block manila_scheduler_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block manila_scheduler_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER manila
diff --git a/kolla/docker/manila/manila-share/Dockerfile.j2 b/kolla/docker/manila/manila-share/Dockerfile.j2
new file mode 100644
index 000000000..161b6865a
--- /dev/null
+++ b/kolla/docker/manila/manila-share/Dockerfile.j2
@@ -0,0 +1,34 @@
+FROM {{ namespace }}/{{ image_prefix }}manila-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block manila_share_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set manila_share_packages = [
+ 'ceph-common',
+ 'sqlite',
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set manila_share_packages = [
+ 'ceph-common',
+ 'glusterfs-client',
+ 'python3-cephfs',
+ 'python3-rados',
+ 'python3-rbd',
+ 'sqlite3'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(manila_share_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block manila_share_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER manila
diff --git a/kolla/docker/mariadb-server/Dockerfile.j2 b/kolla/docker/mariadb-server/Dockerfile.j2
new file mode 100644
index 000000000..612b37330
--- /dev/null
+++ b/kolla/docker/mariadb-server/Dockerfile.j2
@@ -0,0 +1,96 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mariadb_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='mysql') }}
+
+{% if base_package_type == 'rpm' %}
+
+{# NOTE(mgoddard): EPEL required for pv package #}
+{{ macros.enable_extra_repos(['epel', 'mariadb']) }}
+
+ {% set mariadb_packages = [
+ 'expect',
+ 'mariadb',
+ 'mariadb-backup',
+ 'mariadb-server',
+ 'pv',
+ 'rsync',
+ 'tar'
+ ] %}
+
+{# NOTE(yoctozepto): this is to ensure Kolla Ansible can configure MariaDB to find it #}
+RUN ln -s /usr/lib64/galera-4 /usr/lib64/galera
+
+{% elif base_package_type == 'deb' %}
+{{ macros.enable_extra_repos(['mariadb']) }}
+ {% set mariadb_packages = [
+ 'expect',
+ 'mariadb-backup',
+ 'mariadb-client',
+ 'mariadb-server',
+ 'pv',
+ 'rsync',
+ 'tar'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(mariadb_packages | customizable("packages")) }}
+
+{% block mariadb_healthcheck %}
+ENV MARIADB_VERSION=11.4
+ADD plugins-archive /
+RUN install -m 755 /plugins/mariadb-server-plugin-mariadb-docker-archive*/$MARIADB_VERSION/healthcheck.sh /usr/bin/healthcheck.sh
+{% endblock %}
+
+COPY mariadb_sudoers /etc/sudoers.d/kolla_mariadb_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY security_reset.expect /usr/local/bin/kolla_security_reset
+COPY backup.sh /usr/local/bin/kolla_mariadb_backup.sh
+COPY backup_replica.sh /usr/local/bin/kolla_mariadb_backup_replica.sh
+
+RUN chmod 644 /usr/local/bin/kolla_extend_start \
+ && chmod 755 /usr/local/bin/kolla_security_reset \
+ /usr/local/bin/kolla_mariadb_backup.sh \
+ /usr/local/bin/kolla_mariadb_backup_replica.sh \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_mariadb_sudoers \
+ && rm -rf /var/lib/mysql/*
+
+{% if base_package_type == 'deb' %}
+RUN mkdir -p /var/run/mysqld && chown mysql /var/run/mysqld && chmod 755 /var/run/mysqld
+{% endif %}
+
+{% if docker_healthchecks %}
+{% block healthcheck_installation %}
+
+COPY healthcheck_mariadb /usr/local/bin/healthcheck_mariadb
+RUN chmod 755 /usr/local/bin/healthcheck_mariadb
+
+{% endblock %}
+{% endif %}
+
+{% if use_dumb_init %}
+{% block mariadb_entrypoint %}
+# NOTE(mgoddard): Override the dumb-init arguments to avoid passing
+# --single-child. This does not play well with mysqld_safe, which ignores
+# SIGTERM, meaning Docker needs to forcibly kill the container to stop it.
+# Without --single-child, the TERM signal is sent to all subprocesses,
+# including mysqld.
+
+ENTRYPOINT ["dumb-init", "--"]
+CMD ["kolla_start"]
+{% endblock %}
+{% endif %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mariadb_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER mysql
diff --git a/kolla/docker/mariadb-server/backup.sh b/kolla/docker/mariadb-server/backup.sh
new file mode 100644
index 000000000..be63cd938
--- /dev/null
+++ b/kolla/docker/mariadb-server/backup.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+# Execute a full backup
+backup_full() {
+ echo "Taking a full backup"
+ LAST_FULL_DATE=$(date +%d-%m-%Y-%s)
+ BACKUP_FILE="mysqlbackup-${LAST_FULL_DATE}.qp.xbc.xbs.gz"
+ BACKUP_PATH="$BACKUP_DIR/full-${LAST_FULL_DATE}"
+ mkdir -p "$BACKUP_PATH"
+
+ mariabackup \
+ --defaults-file=/etc/mysql/my.cnf \
+ --backup \
+ --stream=xbstream \
+ --history=$LAST_FULL_DATE | gzip > \
+ "$BACKUP_PATH/$BACKUP_FILE"
+
+ echo "$BACKUP_PATH/$BACKUP_FILE" > "$BACKUP_DIR/last_full_file"
+}
+
+# Execute an incremental backup
+backup_incremental() {
+ echo "Taking an incremental backup"
+
+ if [ ! -r "$BACKUP_DIR/last_full_file" ]; then
+ echo "Error: No full backup file found."
+ exit 1
+ fi
+
+ FULL_BACKUP_FILE=$(cat "$BACKUP_DIR/last_full_file")
+ LAST_FULL_DATE=$(basename "$(dirname "$FULL_BACKUP_FILE")" | sed 's/^full-//')
+ NOW=$(date +%H-%M-%S-%d-%m-%Y)
+ INCR_DIR="$BACKUP_DIR/incr-${NOW}-since-${LAST_FULL_DATE}"
+ mkdir -p "$INCR_DIR"
+
+ TMP_BASEDIR=$(mktemp -d)
+ echo "Decompressing full backup to temp dir: $TMP_BASEDIR"
+ gunzip -c "$FULL_BACKUP_FILE" | mbstream -x -C "$TMP_BASEDIR"
+
+ mariabackup \
+ --defaults-file=/etc/mysql/my.cnf \
+ --backup \
+ --stream=xbstream \
+ --incremental-basedir="$TMP_BASEDIR" \
+ --history="incr-${NOW}" | gzip > \
+ "$INCR_DIR/incremental-${NOW}-mysqlbackup-${LAST_FULL_DATE}.qp.xbc.xbs.gz"
+
+ rm -rf "$TMP_BASEDIR"
+}
+
+BACKUP_DIR=/backup/
+cd "$BACKUP_DIR"
+
+if [ -n "${BACKUP_TYPE:-}" ]; then
+ case "$BACKUP_TYPE" in
+ "full")
+ backup_full
+ ;;
+ "incremental")
+ backup_incremental
+ ;;
+ *)
+ echo "Only full or incremental options are supported."
+ exit 1
+ ;;
+ esac
+else
+ echo "You need to specify either full or incremental backup options."
+ exit 1
+fi
diff --git a/kolla/docker/mariadb-server/backup_replica.sh b/kolla/docker/mariadb-server/backup_replica.sh
new file mode 100644
index 000000000..fe10bc61b
--- /dev/null
+++ b/kolla/docker/mariadb-server/backup_replica.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+BACKUP_DIR=/backup/
+DEFAULT_MY_CNF="/etc/mysql/my.cnf"
+REPLICA_MY_CNF="$(mktemp)"
+RETRY_INTERVAL=5 # Interval between retries (in seconds)
+MAX_RETRIES=12 # Max retries (12 retries * 5 seconds = 60 seconds)
+VERIFY_DB_SERVER_CERT="${VERIFY_DB_SERVER_CERT:=FALSE}"
+
+# Cleanup function to remove the REPLICA_MY_CNF file
+cleanup() {
+ rm -f "${REPLICA_MY_CNF}"
+}
+
+# Set trap to ensure cleanup occurs on exit or error
+trap cleanup EXIT
+
+cd "${BACKUP_DIR}"
+
+# Execute a full backup
+backup_full() {
+ echo "Taking a full backup"
+ LAST_FULL_DATE=$(date +%d-%m-%Y-%s)
+ BACKUP_FILE="backup-full-${LAST_FULL_DATE}.mbs.gz"
+ BACKUP_PATH="${BACKUP_DIR}/full-${LAST_FULL_DATE}"
+ mkdir -p "${BACKUP_PATH}"
+
+ mariabackup \
+ --defaults-file="${REPLICA_MY_CNF}" \
+ --backup \
+ --stream=mbstream \
+ --history="${LAST_FULL_DATE}" \
+ | gzip > "${BACKUP_PATH}/${BACKUP_FILE}"
+
+ echo "${BACKUP_PATH}/${BACKUP_FILE}" > "${BACKUP_DIR}/last_full_file"
+}
+
+# Execute an incremental backup
+backup_incremental() {
+ if [ ! -r "${BACKUP_DIR}/last_full_file" ]; then
+ echo "Error: No full backup file found."
+ exit 1
+ fi
+
+ FULL_BACKUP_FILE=$(cat "${BACKUP_DIR}/last_full_file")
+ LAST_FULL_DATE=$(basename "$(dirname "${FULL_BACKUP_FILE}")" | sed 's/^full-//')
+ NOW=$(date +%H-%M-%S-%d-%m-%Y)
+ INCR_DIR="${BACKUP_DIR}/incr-${NOW}-since-${LAST_FULL_DATE}"
+ mkdir -p "${INCR_DIR}"
+
+ # Temp dir for full base restore
+ TMP_BASEDIR=$(mktemp -d)
+
+ echo "Decompressing full backup to temp dir: ${TMP_BASEDIR}"
+ gunzip -c "${FULL_BACKUP_FILE}" | mbstream -x -C "${TMP_BASEDIR}"
+
+ mariabackup \
+ --defaults-file="${REPLICA_MY_CNF}" \
+ --backup \
+ --stream=mbstream \
+ --incremental-basedir="${TMP_BASEDIR}" \
+ --history="incr-${NOW}" \
+ | gzip > "${INCR_DIR}/backup-incremental-${NOW}.mbs.gz"
+
+ rm -rf "${TMP_BASEDIR}"
+}
+
+# Retry logic for database queries
+retry_mysql_query() {
+ local query="$1"
+ local result=""
+ local attempt=1
+
+ while [ ${attempt} -le ${MAX_RETRIES} ]; do
+ result=$(mariadb --ssl-verify-server-cert="${VERIFY_DB_SERVER_CERT}" -h "${HOST}" -u "${USER}" -p"${PASS}" -s -N -e "${query}" 2>/dev/null || true)
+ if [ -n "${result}" ]; then
+ echo "${result}"
+ return 0
+ fi
+ echo "Attempt ${attempt}/${MAX_RETRIES} failed. Retrying in ${RETRY_INTERVAL} seconds..."
+ sleep "${RETRY_INTERVAL}"
+ attempt=$((attempt + 1))
+ done
+
+ echo "Error: Failed to execute the query after ${MAX_RETRIES} attempts."
+ return 1
+}
+
+get_and_set_replica_server() {
+ HOST="$(grep '^host' "${DEFAULT_MY_CNF}" | awk -F '=' '{print $2}' | xargs)"
+ USER="$(grep '^user' "${DEFAULT_MY_CNF}" | awk -F '=' '{print $2}' | xargs)"
+ PASS="$(grep '^password' "${DEFAULT_MY_CNF}" | awk -F '=' '{print $2}' | xargs)"
+
+ ALL_HOSTS_SELECT="SELECT REGEXP_REPLACE(VARIABLE_VALUE, ':[0-9]*','') FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_incoming_addresses';"
+ ALL_HOSTS=$(retry_mysql_query "${ALL_HOSTS_SELECT}")
+ if [ -z "${ALL_HOSTS}" ]; then
+ echo "Backup failed due to inability to fetch a list of servers."
+ exit 1
+ fi
+
+ ACTIVE_HOST_SELECT='SELECT @@hostname;'
+ ACTIVE_HOST=$(retry_mysql_query "${ACTIVE_HOST_SELECT}" | xargs getent hosts | awk '{print $1}')
+ if [ -z "${ACTIVE_HOST}" ]; then
+ echo "Backup failed due to inability to fetch active host."
+ exit 1
+ fi
+
+ # Multinode
+ if echo "${ALL_HOSTS}" | grep -q ','; then
+ for server in $(echo "${ALL_HOSTS}" | tr ',' '\n'); do
+ if [[ "${server}" != "${ACTIVE_HOST}" ]]; then
+ REPLICA_HOST="${server}"
+ break
+ fi
+ done
+ # Single node
+ else
+ REPLICA_HOST="${ALL_HOSTS}"
+ fi
+ if [ -z "${REPLICA_HOST}" ]; then
+ echo "Backup failed due to inability to determine replica host."
+ exit 1
+ fi
+
+ cp "${DEFAULT_MY_CNF}" "${REPLICA_MY_CNF}"
+ sed -i "s/${HOST}/${REPLICA_HOST}/g" "${REPLICA_MY_CNF}"
+}
+
+if [ -n "${BACKUP_TYPE}" ]; then
+ get_and_set_replica_server
+ case "${BACKUP_TYPE}" in
+ "full")
+ backup_full
+ ;;
+ "incremental")
+ backup_incremental
+ ;;
+ *)
+ echo "Only full or incremental options are supported."
+ exit 1
+ ;;
+ esac
+else
+ echo "You need to specify either full or incremental backup options."
+ exit 1
+fi
diff --git a/kolla/docker/mariadb-server/extend_start.sh b/kolla/docker/mariadb-server/extend_start.sh
new file mode 100644
index 000000000..f14c85949
--- /dev/null
+++ b/kolla/docker/mariadb-server/extend_start.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+: ${MARIADB_LOG_DIR:=/var/log/kolla/mariadb}
+
+function bootstrap_db {
+ mariadbd-safe --wsrep-new-cluster --skip-networking --wsrep-on=OFF --pid-file=/var/lib/mysql/mariadb.pid &
+ # Wait for the mariadb server to be "Ready" before starting the security reset with a max timeout
+ # NOTE(huikang): the location of mysql's socket file varies depending on the OS distributions.
+ # Querying the cluster status has to be executed after the existence of mysql.sock and mariadb.pid.
+ TIMEOUT=${DB_MAX_TIMEOUT:-60}
+ while [[ ! -S /var/lib/mysql/mysql.sock ]] && \
+ [[ ! -S /var/run/mysqld/mysqld.sock ]] || \
+ [[ ! -f /var/lib/mysql/mariadb.pid ]]; do
+ if [[ ${TIMEOUT} -gt 0 ]]; then
+ let TIMEOUT-=1
+ sleep 1
+ else
+ exit 1
+ fi
+ done
+
+ sudo -E kolla_security_reset
+
+ set +o xtrace
+ mariadb -u root --password="${DB_ROOT_PASSWORD}" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '${DB_ROOT_PASSWORD}' WITH GRANT OPTION;"
+ mariadb -u root --password="${DB_ROOT_PASSWORD}" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '${DB_ROOT_PASSWORD}' WITH GRANT OPTION;"
+ mariadb-admin -uroot -p"${DB_ROOT_PASSWORD}" shutdown
+ set -o xtrace
+}
+
+# Create log directory, with appropriate permissions
+if [[ ! -d "${MARIADB_LOG_DIR}" ]]; then
+ mkdir -p ${MARIADB_LOG_DIR}
+fi
+if [[ $(stat -c %a ${MARIADB_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${MARIADB_LOG_DIR}
+fi
+
+# This catches all cases of the BOOTSTRAP variable being set, including empty
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ mariadb-install-db 2>&1 | tee -a ${MARIADB_LOG_DIR}/mariadb-bootstrap.log
+ bootstrap_db 2>&1 | tee -a ${MARIADB_LOG_DIR}/mariadb-bootstrap.log
+ exit 0
+fi
+
+# This catches all cases of the KOLLA_UPGRADE variable being set, including empty
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ # The mariadb-upgrade command treats any directories under /var/lib/mysql as
+ # databases. Somehow we can end up with a .pki directory, which causes the
+ # command to fail with this error:
+ # Incorrect database name '#mysql50#.pki' when selecting the database
+ # There doesn't seem to be anything in the directory, so remove it.
+ rm -rf /var/lib/mysql/.pki
+
+ mariadb-upgrade --host=${DB_HOST} --port=${DB_PORT} --user=root --password="${DB_ROOT_PASSWORD}" 2>&1 | tee -a ${MARIADB_LOG_DIR}/mariadb-upgrade.log
+ exit 0
+fi
+
+if [[ "${!BOOTSTRAP_ARGS[@]}" ]]; then
+ ARGS="${BOOTSTRAP_ARGS}"
+fi
diff --git a/kolla/docker/mariadb-server/healthcheck_mariadb b/kolla/docker/mariadb-server/healthcheck_mariadb
new file mode 100755
index 000000000..0d9f59a95
--- /dev/null
+++ b/kolla/docker/mariadb-server/healthcheck_mariadb
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e
+
+MYSQL_USERNAME="${MYSQL_USERNAME:=-haproxy}"
+MYSQL_TIMEOUT=10
+
+MYSQL_CMDLINE="mysql -sN --connect-timeout=${MYSQL_TIMEOUT} -u ${MYSQL_USERNAME}"
+
+WSREP_STATUS=$($MYSQL_CMDLINE -e "SHOW STATUS LIKE 'wsrep_local_state_comment';" | awk '{print $2}')
+
+if [[ "${WSREP_STATUS}" == "Synced" ]]
+then
+ echo "MariaDB Galera Cluster Node is synced."
+ exit 0
+else
+ echo "MariaDB Galera Cluster Node is NOT synced"
+ exit 1
+fi
diff --git a/kolla/docker/mariadb-server/mariadb_sudoers b/kolla/docker/mariadb-server/mariadb_sudoers
new file mode 100644
index 000000000..150534e16
--- /dev/null
+++ b/kolla/docker/mariadb-server/mariadb_sudoers
@@ -0,0 +1 @@
+%kolla ALL=(root) NOPASSWD: /usr/local/bin/kolla_security_reset
diff --git a/kolla/docker/mariadb-server/security_reset.expect b/kolla/docker/mariadb-server/security_reset.expect
new file mode 100644
index 000000000..06f1d5c8e
--- /dev/null
+++ b/kolla/docker/mariadb-server/security_reset.expect
@@ -0,0 +1,67 @@
+#!/usr/bin/expect -f
+
+if [catch {set timeout $env(DB_MAX_TIMEOUT)}] {set timeout 10}
+spawn mariadb-secure-installation
+expect {
+ timeout { send_user "\nFailed to get 'Enter current password for root (enter for none):' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Enter current password for root (enter for none):' prompt\n"; exit 1 }
+ "Enter current password for root (enter for none):"
+}
+send "\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Switch to unix_socket authentication [Y/n] ' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Switch to unix_socket authentication [Y/n]' prompt\n"; exit 1 }
+ "Switch to unix_socket authentication \\\[Y/n\\\] "
+}
+send "n\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Change the root password? [Y/n]' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Change the root password? [Y/n]' prompt\n"; exit 1 }
+ "Change the root password? \\\[Y/n\\\] "
+}
+send "y\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'New password:' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'New password:' prompt\n"; exit 1 }
+ "New password:"
+}
+send "$env(DB_ROOT_PASSWORD)\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Re-enter new password:' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Re-enter new password:' prompt\n"; exit 1 }
+ "Re-enter new password:"
+}
+send "$env(DB_ROOT_PASSWORD)\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Remove anonymous users?' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Remove anonymous users?' prompt\n"; exit 1 }
+ "Remove anonymous users?"
+}
+send "y\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Disallow root login remotely?' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Disallow root login remotely?' prompt\n"; exit 1 }
+ "Disallow root login remotely?"
+}
+send "n\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Remove test database and access to it?' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Remove test database and access to it?' prompt\n"; exit 1 }
+ "Remove test database and access to it?"
+}
+send "y\r"
+
+expect {
+ timeout { send_user "\nFailed to get 'Reload privilege tables now?' prompt\n"; exit 1 }
+ eof { send_user "\nFailed to get 'Reload privilege tables now?' prompt\n"; exit 1 }
+ "Reload privilege tables now?"
+}
+send "y\r"
+expect eof
diff --git a/kolla/docker/masakari/masakari-api/Dockerfile.j2 b/kolla/docker/masakari/masakari-api/Dockerfile.j2
new file mode 100644
index 000000000..5285c6a36
--- /dev/null
+++ b/kolla/docker/masakari/masakari-api/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}masakari-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block masakari_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_masakari_extend_start
+RUN chmod 644 /usr/local/bin/kolla_masakari_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block masakari_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/masakari/masakari-api/extend_start.sh b/kolla/docker/masakari/masakari-api/extend_start.sh
new file mode 100755
index 000000000..549759f49
--- /dev/null
+++ b/kolla/docker/masakari/masakari-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ masakari-manage db sync
+ exit 0
+fi
diff --git a/kolla/docker/masakari/masakari-base/Dockerfile.j2 b/kolla/docker/masakari/masakari-base/Dockerfile.j2
new file mode 100644
index 000000000..97e31247e
--- /dev/null
+++ b/kolla/docker/masakari/masakari-base/Dockerfile.j2
@@ -0,0 +1,43 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block masakari_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='masakari') }}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set masakari_base_packages = [
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set masakari_base_packages = [
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(masakari_base_packages | customizable("packages")) }}
+
+ADD masakari-base-archive /masakari-base-source
+
+{% set masakari_base_pip_packages = [
+ '/masakari'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s masakari-base-source/* masakari \
+ && {{ macros.install_pip(masakari_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/masakari \
+ && cp -r /masakari/etc/masakari/* /etc/masakari/ \
+ && touch /usr/local/bin/kolla_masakari_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_masakari_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block masakari_base_footer %}{% endblock %}
diff --git a/kolla/docker/masakari/masakari-base/extend_start.sh b/kolla/docker/masakari/masakari-base/extend_start.sh
new file mode 100755
index 000000000..4f13d3e8b
--- /dev/null
+++ b/kolla/docker/masakari/masakari-base/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/masakari" ]]; then
+ mkdir -p /var/log/kolla/masakari
+fi
+if [[ $(stat -c %U:%G /var/log/kolla/masakari) != "masakari:kolla" ]]; then
+ chown -R masakari:kolla /var/log/kolla/masakari
+fi
+if [[ $(stat -c %a /var/log/kolla/masakari) != "755" ]]; then
+ chmod 755 /var/log/kolla/masakari
+fi
+
+. /usr/local/bin/kolla_masakari_extend_start
diff --git a/kolla/docker/masakari/masakari-engine/Dockerfile.j2 b/kolla/docker/masakari/masakari-engine/Dockerfile.j2
new file mode 100644
index 000000000..8dfb3175c
--- /dev/null
+++ b/kolla/docker/masakari/masakari-engine/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}masakari-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block masakari_engine_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block masakari_engine_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER masakari
diff --git a/kolla/docker/masakari/masakari-monitors/Dockerfile.j2 b/kolla/docker/masakari/masakari-monitors/Dockerfile.j2
new file mode 100644
index 000000000..92b8c3947
--- /dev/null
+++ b/kolla/docker/masakari/masakari-monitors/Dockerfile.j2
@@ -0,0 +1,55 @@
+FROM {{ namespace }}/{{ image_prefix }}masakari-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block masakari_monitors_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['hacluster']) }}
+
+ {% set masakari_monitors_packages = [
+ 'cyrus-sasl-md5',
+ 'cyrus-sasl-scram',
+ 'python3-libvirt',
+ 'pacemaker-cli',
+ 'tcpdump',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set masakari_monitors_packages = [
+ 'libsasl2-modules-gssapi-mit',
+ 'python3-libvirt',
+ 'pacemaker-cli-utils',
+ 'sasl2-bin',
+ 'tcpdump',
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(masakari_monitors_packages | customizable("packages")) }}
+
+ADD masakari-monitors-archive /masakari-monitors-source
+
+{% set masakari_monitors_pip_packages = [
+ '/masakari-monitors'
+] %}
+
+COPY masakari_monitors_sudoers /etc/sudoers.d/kolla_masakari_monitors_sudoers
+
+RUN ln -s masakari-monitors-source/* masakari-monitors \
+ && {{ macros.install_pip(masakari_monitors_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/masakari-monitors \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 640 /etc/sudoers.d/kolla_masakari_monitors_sudoers
+
+{{ macros.kolla_patch_sources() }}
+
+{% block masakari_monitors_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER masakari
diff --git a/kolla/docker/masakari/masakari-monitors/masakari_monitors_sudoers b/kolla/docker/masakari/masakari-monitors/masakari_monitors_sudoers
new file mode 100644
index 000000000..be3867b5c
--- /dev/null
+++ b/kolla/docker/masakari/masakari-monitors/masakari_monitors_sudoers
@@ -0,0 +1,5 @@
+masakari ALL=(root) NOPASSWD: /var/lib/kolla/venv/bin/privsep-helper
+masakari ALL=(root) NOPASSWD: /usr/sbin/tcpdump *
+masakari ALL=(root) NOPASSWD: /usr/sbin/crm_mon -X
+masakari ALL=(root) NOPASSWD: /usr/sbin/crmadmin *
+masakari ALL=(root) NOPASSWD: /usr/sbin/cibadmin --query
diff --git a/kolla/docker/memcached/Dockerfile.j2 b/kolla/docker/memcached/Dockerfile.j2
new file mode 100644
index 000000000..dfe90b18e
--- /dev/null
+++ b/kolla/docker/memcached/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block memcached_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='memcached', homedir='/run/memcache') }}
+
+{% set memcached_packages = ['memcached'] %}
+{{ macros.install_packages(memcached_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block memcached_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER memcached
diff --git a/kolla/docker/mistral/mistral-api/Dockerfile.j2 b/kolla/docker/mistral/mistral-api/Dockerfile.j2
new file mode 100644
index 000000000..be3e431e1
--- /dev/null
+++ b/kolla/docker/mistral/mistral-api/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}mistral-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mistral_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set mistral_api_packages = [
+] %}
+{{ macros.install_packages(mistral_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_mistral_extend_start
+RUN chmod 644 /usr/local/bin/kolla_mistral_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mistral_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/mistral/mistral-api/extend_start.sh b/kolla/docker/mistral/mistral-api/extend_start.sh
new file mode 100644
index 000000000..113adb8a2
--- /dev/null
+++ b/kolla/docker/mistral/mistral-api/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ mistral-db-manage --config-file /etc/mistral/mistral.conf upgrade head
+ mistral-db-manage --config-file /etc/mistral/mistral.conf populate
+ exit 0
+fi
diff --git a/kolla/docker/mistral/mistral-base/Dockerfile.j2 b/kolla/docker/mistral/mistral-base/Dockerfile.j2
new file mode 100644
index 000000000..1d72c1cac
--- /dev/null
+++ b/kolla/docker/mistral/mistral-base/Dockerfile.j2
@@ -0,0 +1,38 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mistral_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='mistral') }}
+
+ADD mistral-base-archive /mistral-base-source
+ADD plugins-archive /
+
+{% set mistral_base_pip_packages = [
+ '/mistral',
+ 'redis'
+] %}
+
+{% set mistral_base_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s mistral-base-source/* mistral \
+ && {{ macros.install_pip(mistral_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/mistral \
+ && cp -r /mistral/etc/* /etc/mistral/ \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(mistral_base_plugins_pip_packages) }}; \
+ fi \
+ && touch /usr/local/bin/kolla_mistral_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_mistral_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mistral_base_footer %}{% endblock %}
diff --git a/kolla/docker/mistral/mistral-base/extend_start.sh b/kolla/docker/mistral/mistral-base/extend_start.sh
new file mode 100644
index 000000000..ac2980d6c
--- /dev/null
+++ b/kolla/docker/mistral/mistral-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/mistral" ]]; then
+ mkdir -p /var/log/kolla/mistral
+fi
+if [[ $(stat -c %a /var/log/kolla/mistral) != "755" ]]; then
+ chmod 755 /var/log/kolla/mistral
+fi
+
+. /usr/local/bin/kolla_mistral_extend_start
diff --git a/kolla/docker/mistral/mistral-engine/Dockerfile.j2 b/kolla/docker/mistral/mistral-engine/Dockerfile.j2
new file mode 100644
index 000000000..c5fd5eb0d
--- /dev/null
+++ b/kolla/docker/mistral/mistral-engine/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}mistral-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mistral_engine_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mistral_engine_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER mistral
diff --git a/kolla/docker/mistral/mistral-event-engine/Dockerfile.j2 b/kolla/docker/mistral/mistral-event-engine/Dockerfile.j2
new file mode 100644
index 000000000..2a05ecf76
--- /dev/null
+++ b/kolla/docker/mistral/mistral-event-engine/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}mistral-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mistral_event_engine_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mistral_event_engine_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER mistral
diff --git a/kolla/docker/mistral/mistral-executor/Dockerfile.j2 b/kolla/docker/mistral/mistral-executor/Dockerfile.j2
new file mode 100644
index 000000000..aeae9ed06
--- /dev/null
+++ b/kolla/docker/mistral/mistral-executor/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}mistral-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mistral_executor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mistral_executor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER mistral
diff --git a/kolla/docker/multipathd/Dockerfile.j2 b/kolla/docker/multipathd/Dockerfile.j2
new file mode 100644
index 000000000..1341c62a7
--- /dev/null
+++ b/kolla/docker/multipathd/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block multipathd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set multipathd_packages = ['device-mapper-multipath'] %}
+{% elif base_package_type == 'deb' %}
+ {% set multipathd_packages = ['multipath-tools'] %}
+{% endif %}
+
+{{ macros.install_packages(multipathd_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block multipathd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/networking-baremetal/ironic-neutron-agent/Dockerfile.j2 b/kolla/docker/networking-baremetal/ironic-neutron-agent/Dockerfile.j2
new file mode 100644
index 000000000..a7ae6ccb4
--- /dev/null
+++ b/kolla/docker/networking-baremetal/ironic-neutron-agent/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ironic_neutron_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ironic_neutron_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-base/Dockerfile.j2 b/kolla/docker/neutron/neutron-base/Dockerfile.j2
new file mode 100644
index 000000000..e91c1e6e6
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/Dockerfile.j2
@@ -0,0 +1,104 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.enable_extra_repos(['docker-ce']) }}
+{{ macros.configure_user(name='neutron') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+ {% set neutron_base_packages = [
+ 'conntrack-tools',
+ 'dnsmasq',
+ 'dnsmasq-utils',
+ 'docker-ce-cli',
+ 'haproxy',
+ 'iproute-tc',
+ 'ipset',
+ 'iptables',
+ 'iputils',
+ 'keepalived',
+ 'net-tools',
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'python3-openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'radvd',
+ 'uuid'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set neutron_base_packages = [
+ 'conntrack',
+ 'dnsmasq',
+ 'dnsmasq-utils',
+ 'docker-ce-cli',
+ 'haproxy',
+ 'iproute2',
+ 'ipset',
+ 'iptables',
+ 'iputils-arping',
+ 'iputils-ping',
+ 'keepalived',
+ 'net-tools',
+ 'openvswitch-switch',
+ 'python3-openvswitch',
+ 'radvd',
+ 'uuid-runtime'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(neutron_base_packages | customizable("packages")) }}
+
+ADD neutron-base-archive /neutron-base-source
+ADD plugins-archive /
+
+{% set neutron_base_pip_packages = [
+ '/neutron',
+ 'oslo-vmware',
+ 'tap-as-a-service'
+] %}
+
+{% set neutron_base_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+COPY neutron_sudoers /etc/sudoers.d/kolla_neutron_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN mkdir -p /usr/local/lib/neutron-wrappers /usr/local/etc/neutron-wrappers
+COPY dnsmasq haproxy keepalived neutron-keepalived-state-change radvd copy-wrappers delete-wrappers /usr/local/lib/neutron-wrappers/
+RUN chmod +x /usr/local/lib/neutron-wrappers/copy-wrappers \
+ && chmod +x /usr/local/lib/neutron-wrappers/delete-wrappers
+
+RUN ln -s neutron-base-source/* neutron \
+ && {{ macros.upper_constraints_remove('networking-generic-switch') }} \
+ && {{ macros.install_pip(neutron_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/neutron \
+ && cp -r /neutron/etc/* /etc/neutron/ \
+ && cp -r /neutron/etc/neutron/* /etc/neutron/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/neutron_api_audit_map.conf /etc/neutron/ \
+ && mv /etc/neutron/neutron/ /etc/neutron/plugins/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/neutron/rootwrap.conf \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(neutron_base_plugins_pip_packages) }} \
+ && for neutron_plugins in $(ls /plugins); do \
+ if [ -d "/plugins/$neutron_plugins/etc/neutron/rootwrap.d" ]; then \
+ cp /plugins/$neutron_plugins/etc/neutron/rootwrap.d/* /etc/neutron/rootwrap.d; \
+ fi; \
+ done; \
+ fi \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_neutron_sudoers \
+ && touch /usr/local/bin/kolla_neutron_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_neutron_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_base_footer %}{% endblock %}
diff --git a/kolla/docker/neutron/neutron-base/copy-wrappers b/kolla/docker/neutron/neutron-base/copy-wrappers
new file mode 100644
index 000000000..e03bdfdf7
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/copy-wrappers
@@ -0,0 +1,9 @@
+#!/bin/bash
+for binary in dnsmasq haproxy keepalived neutron-keepalived-state-change radvd; do
+ cp /usr/local/lib/neutron-wrappers/${binary} /usr/local/bin/${binary}
+ chmod +x /usr/local/bin/${binary}
+done
+
+echo "KOLLA_IMAGE=${KOLLA_IMAGE}" > /usr/local/etc/neutron-wrappers/config
+echo "KOLLA_NAME=${KOLLA_NAME}" >> /usr/local/etc/neutron-wrappers/config
+echo "KOLLA_SERVICE_NAME=${KOLLA_SERVICE_NAME}" >> /usr/local/etc/neutron-wrappers/config
diff --git a/kolla/docker/neutron/neutron-base/delete-wrappers b/kolla/docker/neutron/neutron-base/delete-wrappers
new file mode 100644
index 000000000..239d977f2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/delete-wrappers
@@ -0,0 +1,6 @@
+#!/bin/bash
+for binary in dnsmasq haproxy keepalived neutron-keepalived-state-change radvd; do
+ rm -f /usr/local/bin/${binary}
+done
+
+rm -f /usr/local/etc/neutron-wrappers/config
diff --git a/kolla/docker/neutron/neutron-base/dnsmasq b/kolla/docker/neutron/neutron-base/dnsmasq
new file mode 100644
index 000000000..44b73f0b2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/dnsmasq
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+source /usr/local/etc/neutron-wrappers/config
+
+ARGS="$@"
+
+# Extract the network namespace UUID from the command line args provided by
+# neutron. Typically of the form (with dnsmasq as an example):
+#
+# dnsmasq --no-hosts --no-resolv --except-interface=lo \
+# --pid-file=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/pid \
+# --dhcp-hostsfile=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/host ...
+NETNS=$(ip netns identify)
+NAME=${KOLLA_NAME}_dnsmasq_${NETNS}
+
+if [[ -S "/var/run/docker.sock" ]]; then
+ CLI="docker"
+ CMD="ip netns exec ${NETNS} /usr/sbin/dnsmasq -k"
+elif [[ -S "/run/podman/podman.sock" ]]; then
+ CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t1 podman"
+ CMD="/usr/sbin/dnsmasq -k"
+else
+ echo "Could not detect a supported container runtime, exiting."
+ exit 1
+fi
+
+LIST=$($CLI ps -a --filter name=${SERVICE}_dnsmasq_ --format '{{.ID}}:{{.Names}}:{{.Status}}' | awk '{print $1}')
+
+# If the NAME is already taken by a container, give it an unique name
+printf "%s\n" "${LIST}" | grep -q "${NAME}$" && NAME="${NAME}_$(date +%Y-%m-%d-%H%M%S-%N)"
+
+echo "Starting a new child container ${NAME} using image ${KOLLA_IMAGE}"
+$CLI run --rm --detach \
+ -v /etc/kolla/${KOLLA_SERVICE_NAME}:/etc/neutron:ro \
+ -v /run/netns:/run/netns:shared \
+ -v neutron_metadata_socket:/var/lib/neutron/kolla/ \
+ -v kolla_logs:/var/log/kolla \
+ --net host \
+ --pid host \
+ --cgroupns host \
+ --privileged \
+ -u root \
+ --name $NAME \
+ ${KOLLA_IMAGE} \
+ $CMD $ARGS
diff --git a/kolla/docker/neutron/neutron-base/extend_start.sh b/kolla/docker/neutron/neutron-base/extend_start.sh
new file mode 100644
index 000000000..e2a2dfca4
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/extend_start.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/neutron" ]]; then
+ mkdir -p /var/log/kolla/neutron
+fi
+if [[ $(stat -c %a /var/log/kolla/neutron) != "755" ]]; then
+ chmod 755 /var/log/kolla/neutron
+fi
+
+# NOTE(hrw): from RHEL 9 release notes:
+# "Iptables-nft and ipset are now deprecated, which included the utilities,
+# iptables, ip6tables, ebtables, and arptables. These are all replaced by the
+# nftables framework."
+# so no need to even use u-a on RHEL 9 family as there is one provider
+# (and there is no u-a for ip6tables so script fails)
+
+if [[ ! ${KOLLA_BASE_DISTRO} =~ centos|rocky ]]; then
+ if /usr/bin/update-alternatives --display iptables; then
+ # NOTE(yoctozepto): Kolla-Ansible does not always set KOLLA_LEGACY_IPTABLES;
+ # the workaround below ensures it gets set to `false` in such cases to fix
+ # this code under `set -o nounset`.
+ KOLLA_LEGACY_IPTABLES=${KOLLA_LEGACY_IPTABLES-false}
+
+ if [[ $KOLLA_LEGACY_IPTABLES == "true" ]]; then
+ sudo /usr/bin/update-alternatives --set iptables /usr/sbin/iptables-legacy
+ sudo /usr/bin/update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
+ else
+ sudo /usr/bin/update-alternatives --auto iptables
+ sudo /usr/bin/update-alternatives --auto ip6tables
+ fi
+ fi
+fi
+
+. /usr/local/bin/kolla_neutron_extend_start
diff --git a/kolla/docker/neutron/neutron-base/haproxy b/kolla/docker/neutron/neutron-base/haproxy
new file mode 100644
index 000000000..43b9ef9d2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/haproxy
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+source /usr/local/etc/neutron-wrappers/config
+
+ARGS="$@"
+
+# Extract the network namespace UUID from the command line args provided by
+# neutron. Typically of the form (with dnsmasq as an example):
+#
+# dnsmasq --no-hosts --no-resolv --except-interface=lo \
+# --pid-file=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/pid \
+# --dhcp-hostsfile=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/host ...
+NETNS=$(ip netns identify)
+NAME=${KOLLA_NAME}_haproxy_${NETNS}
+HAPROXY_CMD='$(if [ -f /usr/sbin/haproxy-systemd-wrapper ]; then echo "/usr/sbin/haproxy -Ds"; else echo "/usr/sbin/haproxy -Ws"; fi)'
+if [[ -S "/var/run/docker.sock" ]]; then
+ CLI="docker"
+ CMD="ip netns exec ${NETNS} "'$HAPROXY'
+elif [[ -S "/run/podman/podman.sock" ]]; then
+ CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t 1 podman"
+ CMD='$HAPROXY'
+else
+ echo "Could not detect a supported container runtime, exiting."
+ exit 1
+fi
+
+LIST=$($CLI ps -a --filter name=${KOLLA_NAME}_haproxy_ --format '{{.ID}}:{{.Names}}:{{.Status}}' | awk '{print $1}')
+
+# If the NAME is already taken by a container, give it an unique name
+printf "%s\n" "${LIST}" | grep -q "${NAME}$" && NAME="${NAME}_$(date +%Y-%m-%d-%H%M%S-%N)"
+
+echo "Starting a new child container ${NAME} using image ${KOLLA_IMAGE}"
+$CLI run --rm --detach \
+ -v /etc/kolla/${KOLLA_SERVICE_NAME}:/etc/neutron:ro \
+ -v /run/netns:/run/netns:shared \
+ -v neutron_metadata_socket:/var/lib/neutron/kolla \
+ -v kolla_logs:/var/log/kolla \
+ --net host \
+ --pid host \
+ --cgroupns host \
+ --privileged \
+ -u root \
+ --name $NAME \
+ ${KOLLA_IMAGE} \
+ /bin/bash -c "HAPROXY=\"$HAPROXY_CMD\"; exec $CMD $ARGS"
diff --git a/kolla/docker/neutron/neutron-base/keepalived b/kolla/docker/neutron/neutron-base/keepalived
new file mode 100644
index 000000000..6bb2b4d3d
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/keepalived
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+source /usr/local/etc/neutron-wrappers/config
+
+ARGS="$@"
+
+# Extract the network namespace UUID from the command line args provided by
+# neutron. Typically of the form (with dnsmasq as an example):
+#
+# dnsmasq --no-hosts --no-resolv --except-interface=lo \
+# --pid-file=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/pid \
+# --dhcp-hostsfile=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/host ...
+NETNS=$(ip netns identify)
+NAME=${KOLLA_NAME}_keepalived_${NETNS}
+
+if [[ -S "/var/run/docker.sock" ]]; then
+ CLI="docker"
+ CMD="ip netns exec ${NETNS} /usr/sbin/keepalived -n -l -D"
+elif [[ -S "/run/podman/podman.sock" ]]; then
+ CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t 1 podman"
+ CMD='/usr/sbin/keepalived -n -l -D'
+else
+ echo "Could not detect a supported container runtime, exiting."
+ exit 1
+fi
+
+LIST=$($CLI ps -a --filter name=${KOLLA_SERVICE}_keepalived_ --format '{{.ID}}:{{.Names}}:{{.Status}}' | awk '{print $1}')
+
+# If the NAME is already taken by a container, give it an unique name
+printf "%s\n" "${LIST}" | grep -q "${NAME}$" && NAME="${NAME}_$(date +%Y-%m-%d-%H%M%S-%N)"
+
+echo "Starting a new child container ${NAME} using image ${KOLLA_IMAGE}"
+$CLI run --rm --detach \
+ -v /etc/kolla/${KOLLA_SERVICE_NAME}:/etc/neutron:ro \
+ -v /lib/modules:/lib/modules:ro \
+ -v /sbin/modprobe:/sbin/modprobe:ro \
+ -v /run/netns:/run/netns:shared \
+ -v neutron_metadata_socket:/var/lib/neutron/kolla/ \
+ -v kolla_logs:/var/log/kolla \
+ --net host \
+ --pid host \
+ --cgroupns host \
+ --privileged \
+ -u root \
+ --name $NAME \
+ ${KOLLA_IMAGE} \
+ $CMD $ARGS
diff --git a/kolla/docker/neutron/neutron-base/neutron-keepalived-state-change b/kolla/docker/neutron/neutron-base/neutron-keepalived-state-change
new file mode 100644
index 000000000..0b30d88cf
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/neutron-keepalived-state-change
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+source /usr/local/etc/neutron-wrappers/config
+
+ARGS="$@"
+
+# Extract the network namespace UUID from the command line args provided by
+# neutron. Typically of the form (with dnsmasq as an example):
+#
+# dnsmasq --no-hosts --no-resolv --except-interface=lo \
+# --pid-file=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/pid \
+# --dhcp-hostsfile=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/host ...
+NETNS=$(ip netns identify)
+NAME=${KOLLA_NAME}_keepalived_${NETNS}
+if [[ -S "/var/run/docker.sock" ]]; then
+ CLI="docker exec --detach"
+ CMD="ip netns exec ${NETNS} /usr/bin/neutron-keepalived-state-change"
+elif [[ -S "/run/podman/podman.sock" ]]; then
+ CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t 1 podman exec"
+ CMD="/usr/bin/neutron-keepalived-state-change"
+else
+ echo "Could not detect a supported container runtime, exiting."
+ exit 1
+fi
+
+# The state change daemon only runs as a daemon for the moment so we need to
+# run it within an existing container with a sensibly matching lifetime. The
+# related keepalived container seems an obvious choice.
+container_id=$($CLI ps --filter name=$NAME --format "{{.ID}}")
+
+if [[ -z $container_id ]];
+then
+ echo "WARNING: keepalived container is not running."
+ exit 0
+fi
+
+$CLI -u root \
+ --privileged \
+ $NAME \
+ $CMD $ARGS
diff --git a/kolla/docker/neutron/neutron-base/neutron_sudoers b/kolla/docker/neutron/neutron-base/neutron_sudoers
new file mode 100644
index 000000000..d0de94f0e
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/neutron_sudoers
@@ -0,0 +1,11 @@
+neutron ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/neutron-rootwrap /etc/neutron/rootwrap.conf *
+neutron ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/neutron-rootwrap-daemon /etc/neutron/rootwrap.conf
+neutron ALL = (root) NOPASSWD: /usr/bin/update-alternatives --set iptables /usr/sbin/iptables-legacy
+neutron ALL = (root) NOPASSWD: /usr/bin/update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
+neutron ALL = (root) NOPASSWD: /usr/bin/update-alternatives --auto iptables
+neutron ALL = (root) NOPASSWD: /usr/bin/update-alternatives --auto ip6tables
+neutron ALL = (root) NOPASSWD: /usr/sbin/update-alternatives --set iptables /usr/sbin/iptables-legacy
+neutron ALL = (root) NOPASSWD: /usr/sbin/update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
+neutron ALL = (root) NOPASSWD: /usr/sbin/update-alternatives --auto iptables
+neutron ALL = (root) NOPASSWD: /usr/sbin/update-alternatives --auto ip6tables
+neutron ALL = (root) NOPASSWD: /usr/local/lib/neutron-wrappers/copy-wrappers, /usr/local/lib/neutron-wrappers/delete-wrappers
diff --git a/kolla/docker/neutron/neutron-base/radvd b/kolla/docker/neutron/neutron-base/radvd
new file mode 100644
index 000000000..3f5dd1cfa
--- /dev/null
+++ b/kolla/docker/neutron/neutron-base/radvd
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+source /usr/local/etc/neutron-wrappers/config
+
+ARGS="$@"
+# Extract the network namespace UUID from the command line args provided by
+# neutron. Typically of the form (with dnsmasq as an example):
+#
+# dnsmasq --no-hosts --no-resolv --except-interface=lo \
+# --pid-file=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/pid \
+# --dhcp-hostsfile=/var/lib/neutron/dhcp/317716b8-919a-4a6f-8db1-78128ec3b100/host ...
+NETWORK_ID=$(echo $ARGS| awk '{if (match($0, /(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/,m)) print m[0] }')
+NAME=${KOLLA_NAME}_radvd_${NETWORK_ID}
+if [[ -S "/var/run/docker.sock" ]]; then
+ CLI="docker"
+ CMD="ip netns exec qrouter-${NETWORK_ID} /usr/sbin/radvd -n"
+elif [[ -S "/run/podman/podman.sock" ]]; then
+ CLI="nsenter --net=/run/netns/${NETNS} --preserve-credentials -m -t 1 podman"
+ CMD="/usr/sbin/radvd -n"
+else
+ echo "Could not detect a supported container runtime, exiting."
+ exit 1
+fi
+
+LIST=$($CLI ps -a --filter name=${KOLLA_NAME}_radvd_ --format '{{.ID}}:{{.Names}}:{{.Status}}' | awk '{print $1}')
+# If the NAME is already taken by a container, give it an unique name
+printf "%s\n" "${LIST}" | grep -q "${NAME}$" && NAME="${NAME}_$(date +%Y-%m-%d-%H%M%S-%N)"
+
+echo "Starting a new child container ${NAME} using image ${KOLLA_IMAGE}"
+$CLI run --rm --detach \
+ -v /etc/kolla/${KOLLA_SERVICE_NAME}:/etc/neutron:ro \
+ -v /run/netns:/run/netns:shared \
+ -v neutron_metadata_socket:/var/lib/neutron/kolla/ \
+ -v kolla_logs:/var/log/kolla \
+ --net host \
+ --pid host \
+ --cgroupns host \
+ --privileged \
+ -u root \
+ --name $NAME \
+ ${KOLLA_IMAGE} \
+ $CMD $ARGS
diff --git a/kolla/docker/neutron/neutron-bgp-dragent/Dockerfile.j2 b/kolla/docker/neutron/neutron-bgp-dragent/Dockerfile.j2
new file mode 100644
index 000000000..3ad2ab596
--- /dev/null
+++ b/kolla/docker/neutron/neutron-bgp-dragent/Dockerfile.j2
@@ -0,0 +1,24 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_bgp_dragent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+ADD neutron-bgp-dragent-archive /neutron-bgp-dragent-source
+
+ {% set neutron_bgp_dragent_pip_packages = [
+ '/neutron_dynamic_routing'
+ ] %}
+
+RUN ln -s neutron-bgp-dragent-source/* neutron_dynamic_routing \
+ && {{ macros.install_pip(neutron_bgp_dragent_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_bgp_dragent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-dhcp-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-dhcp-agent/Dockerfile.j2
new file mode 100644
index 000000000..035433bf2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-dhcp-agent/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_dhcp_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set neutron_dhcp_agent_packages = [
+] %}
+
+{{ macros.install_packages(neutron_dhcp_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{% block neutron_dhcp_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-dhcp-agent/extend_start.sh b/kolla/docker/neutron/neutron-dhcp-agent/extend_start.sh
new file mode 100644
index 000000000..edaad2a3a
--- /dev/null
+++ b/kolla/docker/neutron/neutron-dhcp-agent/extend_start.sh
@@ -0,0 +1,17 @@
+if [[ ! -d "/var/log/kolla/neutron" ]]; then
+ mkdir -p /var/log/kolla/neutron
+fi
+if [[ ! -f "/var/log/kolla/neutron/dnsmasq.log" ]]; then
+ touch /var/log/kolla/neutron/dnsmasq.log
+ chown neutron:kolla /var/log/kolla/neutron/dnsmasq.log
+fi
+
+if [[ "${KOLLA_NEUTRON_WRAPPERS:-false}" == "true" ]]; then
+ echo "Copying neutron agent wrappers to /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/copy-wrappers
+else
+ echo "Removing neutron agent wrappers from /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/delete-wrappers
+fi
+
+. /usr/local/bin/kolla_neutron_extend_start
diff --git a/kolla/docker/neutron/neutron-l3-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-l3-agent/Dockerfile.j2
new file mode 100644
index 000000000..ae0457c18
--- /dev/null
+++ b/kolla/docker/neutron/neutron-l3-agent/Dockerfile.j2
@@ -0,0 +1,32 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_l3_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set neutron_l3_agent_packages = [
+ 'libreswan',
+ 'radvd'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set neutron_l3_agent_packages = [
+ 'strongswan',
+ 'radvd',
+ 'libnetfilter-log1'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(neutron_l3_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{% block neutron_l3_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-l3-agent/extend_start.sh b/kolla/docker/neutron/neutron-l3-agent/extend_start.sh
new file mode 100644
index 000000000..f95c1eda2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-l3-agent/extend_start.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+if [[ ! -d "/var/log/kolla/neutron" ]]; then
+ mkdir -p /var/log/kolla/neutron
+fi
+if [[ $(stat -c %a /var/log/kolla/neutron) != "755" ]]; then
+ chmod 755 /var/log/kolla/neutron
+fi
+
+# NOTE(hrw): from RHEL 9 release notes:
+# "Iptables-nft and ipset are now deprecated, which included the utilities,
+# iptables, ip6tables, ebtables, and arptables. These are all replaced by the
+# nftables framework."
+# so no need to even use u-a on RHEL 9 family as there is one provider
+# (and there is no u-a for ip6tables so script fails)
+if [[ ! ${KOLLA_BASE_DISTRO} =~ centos|rocky ]]; then
+ if /usr/bin/update-alternatives --display iptables; then
+ # NOTE(yoctozepto): Kolla-Ansible does not always set KOLLA_LEGACY_IPTABLES;
+ # the workaround below ensures it gets set to `false` in such cases to fix
+ # this code under `set -o nounset`.
+ KOLLA_LEGACY_IPTABLES=${KOLLA_LEGACY_IPTABLES-false}
+ if [[ $KOLLA_LEGACY_IPTABLES == "true" ]]; then
+ sudo /usr/bin/update-alternatives --set iptables /usr/sbin/iptables-legacy
+ sudo /usr/bin/update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
+ else
+ sudo /usr/bin/update-alternatives --auto iptables
+ sudo /usr/bin/update-alternatives --auto ip6tables
+ fi
+ fi
+fi
+
+if [[ "${KOLLA_NEUTRON_WRAPPERS:-false}" == "true" ]]; then
+ echo "Copying neutron agent wrappers to /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/copy-wrappers
+else
+ echo "Removing neutron agent wrappers from /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/delete-wrappers
+fi
+
+. /usr/local/bin/kolla_neutron_extend_start
diff --git a/kolla/docker/neutron/neutron-metadata-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-metadata-agent/Dockerfile.j2
new file mode 100644
index 000000000..3736f70c8
--- /dev/null
+++ b/kolla/docker/neutron/neutron-metadata-agent/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_metadata_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set neutron_metadata_agent_packages = [
+] %}
+
+{{ macros.install_packages(neutron_metadata_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+{% block neutron_metadata_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-metadata-agent/extend_start.sh b/kolla/docker/neutron/neutron-metadata-agent/extend_start.sh
new file mode 100644
index 000000000..001898f68
--- /dev/null
+++ b/kolla/docker/neutron/neutron-metadata-agent/extend_start.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/neutron" ]]; then
+ mkdir -p /var/log/kolla/neutron
+fi
+if [[ $(stat -c %a /var/log/kolla/neutron) != "755" ]]; then
+ chmod 755 /var/log/kolla/neutron
+fi
+
+if [[ "${KOLLA_NEUTRON_WRAPPERS:-false}" == "true" ]]; then
+ echo "Copying neutron agent wrappers to /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/copy-wrappers
+else
+ echo "Removing neutron agent wrappers from /usr/local/bin"
+ sudo -E /usr/local/lib/neutron-wrappers/delete-wrappers
+fi
+
+. /usr/local/bin/kolla_neutron_extend_start
diff --git a/kolla/docker/neutron/neutron-metering-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-metering-agent/Dockerfile.j2
new file mode 100644
index 000000000..e9d7f74e0
--- /dev/null
+++ b/kolla/docker/neutron/neutron-metering-agent/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_metering_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_metering_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-mlnx-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-mlnx-agent/Dockerfile.j2
new file mode 100644
index 000000000..4ee89c19a
--- /dev/null
+++ b/kolla/docker/neutron/neutron-mlnx-agent/Dockerfile.j2
@@ -0,0 +1,27 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_mlnx_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set neutron_mlnx_agent_packages = [
+ 'python3-libvirt',
+] %}
+
+{{ macros.install_packages(neutron_mlnx_agent_packages | customizable("packages")) }}
+
+{% set neutron_mlnx_agent_pip_packages = [
+ 'networking-mlnx',
+] %}
+
+RUN {{ macros.install_pip(neutron_mlnx_agent_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_mlnx_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-openvswitch-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-openvswitch-agent/Dockerfile.j2
new file mode 100644
index 000000000..b9499e6f7
--- /dev/null
+++ b/kolla/docker/neutron/neutron-openvswitch-agent/Dockerfile.j2
@@ -0,0 +1,17 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_openvswitch_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.install_packages(neutron_openvswitch_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_openvswitch_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-ovn-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-ovn-agent/Dockerfile.j2
new file mode 100644
index 000000000..fe4e352ac
--- /dev/null
+++ b/kolla/docker/neutron/neutron-ovn-agent/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_ovn_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set neutron_ovn_agent_packages = [
+] %}
+
+{{ macros.install_packages(neutron_ovn_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_ovn_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-ovn-vpn-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-ovn-vpn-agent/Dockerfile.j2
new file mode 100644
index 000000000..4fbaaa78f
--- /dev/null
+++ b/kolla/docker/neutron/neutron-ovn-vpn-agent/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_ovn_vpn_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+# Note: VPN is currently not working on RHEL 10 due to a known issue:
+# https://bugs.launchpad.net/neutron/+bug/2146308
+{% if base_package_type == 'rpm' %}
+ {% set neutron_ovn_vpn_agent_packages = [
+ 'libreswan'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set neutron_ovn_vpn_agent_packages = [
+ 'strongswan',
+ 'strongswan-starter',
+ 'strongswan-charon'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(neutron_ovn_vpn_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_ovn_vpn_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-server/Dockerfile.j2 b/kolla/docker/neutron/neutron-server/Dockerfile.j2
new file mode 100644
index 000000000..bda0d3dcf
--- /dev/null
+++ b/kolla/docker/neutron/neutron-server/Dockerfile.j2
@@ -0,0 +1,33 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set neutron_server_pip_packages = [
+ 'networking-hyperv'
+] %}
+
+{% set neutron_server_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+ADD plugins-archive /
+
+COPY extend_start.sh /usr/local/bin/kolla_neutron_extend_start
+
+RUN {{ macros.install_pip(neutron_server_pip_packages | customizable("pip_packages")) }} \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(neutron_server_plugins_pip_packages) }}; \
+ fi \
+ && chmod 644 /usr/local/bin/kolla_neutron_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/neutron/neutron-server/extend_start.sh b/kolla/docker/neutron/neutron-server/extend_start.sh
new file mode 100644
index 000000000..630eb6e1f
--- /dev/null
+++ b/kolla/docker/neutron/neutron-server/extend_start.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ neutron-db-manage --subproject neutron upgrade head
+ if [[ "${!NEUTRON_BOOTSTRAP_SERVICES[@]}" ]]; then
+ for service in ${NEUTRON_BOOTSTRAP_SERVICES}; do
+ neutron-db-manage --subproject $service upgrade head
+ done
+ fi
+ exit 0
+fi
+
+# Migrate database and exit if KOLLA_UPGRADE variable is set. This catches all cases
+# of the KOLLA_UPGRADE variable being set, including empty.
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ if [[ "${!NEUTRON_DB_EXPAND[@]}" ]]; then
+ DB_ACTION="--expand"
+ echo "Expanding database"
+ fi
+ if [[ "${!NEUTRON_DB_CONTRACT[@]}" ]]; then
+ DB_ACTION="--contract"
+ echo "Contracting database"
+ fi
+
+ neutron-db-manage --subproject neutron upgrade $DB_ACTION
+ if [[ "${!NEUTRON_ROLLING_UPGRADE_SERVICES[@]}" ]]; then
+ for service in ${NEUTRON_ROLLING_UPGRADE_SERVICES}; do
+ neutron-db-manage --subproject $service upgrade $DB_ACTION
+ done
+ fi
+ exit 0
+fi
diff --git a/kolla/docker/neutron/neutron-sriov-agent/Dockerfile.j2 b/kolla/docker/neutron/neutron-sriov-agent/Dockerfile.j2
new file mode 100644
index 000000000..c5d74fbb2
--- /dev/null
+++ b/kolla/docker/neutron/neutron-sriov-agent/Dockerfile.j2
@@ -0,0 +1,17 @@
+FROM {{ namespace }}/{{ image_prefix }}neutron-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block neutron_sriov_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.install_packages(neutron_sriov_agent_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block neutron_sriov_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER neutron
diff --git a/kolla/docker/nova/nova-api/Dockerfile.j2 b/kolla/docker/nova/nova-api/Dockerfile.j2
new file mode 100644
index 000000000..57846af79
--- /dev/null
+++ b/kolla/docker/nova/nova-api/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set nova_api_packages = [
+] %}
+{{ macros.install_packages(nova_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_nova_extend_start
+RUN chmod 644 /usr/local/bin/kolla_nova_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/nova/nova-api/extend_start.sh b/kolla/docker/nova/nova-api/extend_start.sh
new file mode 100644
index 000000000..ead6c252b
--- /dev/null
+++ b/kolla/docker/nova/nova-api/extend_start.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# TODO(dszumski): When Nova Conductor in Kolla Ansible supports triggering DB
+# operations, we should review this script and remove any duplicate
+# operations. This is probably anything that isn't to do with the API.
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ nova-manage api_db sync
+ nova-manage db sync --local_cell
+ nova-manage db online_data_migrations
+ exit 0
+fi
+
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ nova-manage api_db sync
+ nova-manage db sync --local_cell
+ exit 0
+fi
+
+if [[ "${!KOLLA_OSM[@]}" ]]; then
+ nova-manage db online_data_migrations
+ exit 0
+fi
+
+if [[ "${!KOLLA_UPGRADE_CHECK[@]}" ]]; then
+ nova-status upgrade check
+ exit $?
+fi
diff --git a/kolla/docker/nova/nova-base/Dockerfile.j2 b/kolla/docker/nova/nova-base/Dockerfile.j2
new file mode 100644
index 000000000..423a9760a
--- /dev/null
+++ b/kolla/docker/nova/nova-base/Dockerfile.j2
@@ -0,0 +1,86 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='nova', groups='qemu') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+ {% set nova_base_packages = [
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'python3-openvswitch${KOLLA_RPM_OVS_VERSION}'
+ ] %}
+ {% if base_arch == 'x86_64' %}
+ {% set nova_base_packages = nova_base_packages + [
+ 'edk2-ovmf'
+ ] %}
+ {% elif base_arch == 'aarch64' %}
+ {% set nova_base_packages = nova_base_packages + [
+ 'edk2-aarch64'
+ ] %}
+ {% endif %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set nova_base_packages = [
+ 'dnsmasq',
+ 'ebtables',
+ 'iproute2',
+ 'iptables',
+ 'openssh-client',
+ 'openvswitch-switch',
+ 'python3-libvirt',
+ 'python3-openvswitch'
+ ] %}
+ {% if base_arch == 'x86_64' %}
+ {% set nova_base_packages = nova_base_packages + [
+ 'ovmf'
+ ] %}
+ {% elif base_arch == 'aarch64' %}
+ {% set nova_base_packages = nova_base_packages + [
+ 'qemu-efi-aarch64',
+ ] %}
+ {% endif %}
+
+{% endif %}
+
+{{ macros.install_packages(nova_base_packages | customizable("packages")) }}
+
+ADD nova-base-archive /nova-base-source
+ADD plugins-archive /
+
+{% set nova_base_pip_packages = [
+ '/nova'
+] %}
+
+{% set nova_base_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+COPY nova_sudoers /etc/sudoers.d/kolla_nova_sudoers
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s nova-base-source/* nova \
+ && {{ macros.install_pip(nova_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/nova/ \
+ && cp -r /nova/etc/nova/* /etc/nova/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/nova_api_audit_map.conf /etc/nova/ \
+ && sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/nova/rootwrap.conf \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(nova_base_plugins_pip_packages) }}; \
+ fi \
+ && chmod 750 /etc/sudoers.d \
+ && chmod 440 /etc/sudoers.d/kolla_nova_sudoers \
+ && touch /usr/local/bin/kolla_nova_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_nova_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_base_footer %}{% endblock %}
diff --git a/kolla/docker/nova/nova-base/extend_start.sh b/kolla/docker/nova/nova-base/extend_start.sh
new file mode 100644
index 000000000..eee0f16a1
--- /dev/null
+++ b/kolla/docker/nova/nova-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/nova" ]]; then
+ mkdir -p /var/log/kolla/nova
+fi
+if [[ $(stat -c %a /var/log/kolla/nova) != "755" ]]; then
+ chmod 755 /var/log/kolla/nova
+fi
+
+. /usr/local/bin/kolla_nova_extend_start
diff --git a/kolla/docker/nova/nova-base/nova_sudoers b/kolla/docker/nova/nova-base/nova_sudoers
new file mode 100644
index 000000000..6d73da6d3
--- /dev/null
+++ b/kolla/docker/nova/nova-base/nova_sudoers
@@ -0,0 +1 @@
+nova ALL = (root) NOPASSWD: /var/lib/kolla/venv/bin/nova-rootwrap /etc/nova/rootwrap.conf *
diff --git a/kolla/docker/nova/nova-compute-ironic/Dockerfile.j2 b/kolla/docker/nova/nova-compute-ironic/Dockerfile.j2
new file mode 100644
index 000000000..3c455e2ec
--- /dev/null
+++ b/kolla/docker/nova/nova-compute-ironic/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_compute_ironic_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set nova_compute_ironic_packages = [
+ 'genisoimage',
+ 'nvme-cli',
+ 'xorriso'
+] %}
+
+{{ macros.install_packages(nova_compute_ironic_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_compute_ironic_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-compute/Dockerfile.j2 b/kolla/docker/nova/nova-compute/Dockerfile.j2
new file mode 100644
index 000000000..8b3c1bcfe
--- /dev/null
+++ b/kolla/docker/nova/nova-compute/Dockerfile.j2
@@ -0,0 +1,112 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_compute_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['ceph', 'epel']) }}
+
+ {% set nova_compute_packages = [
+ 'ceph-common',
+ 'cyrus-sasl-md5',
+ 'cyrus-sasl-scram',
+ 'device-mapper-multipath',
+ 'dosfstools',
+ 'e2fsprogs',
+ 'genisoimage',
+ 'iscsi-initiator-utils',
+ 'libosinfo',
+ 'ndctl',
+ 'nfs-utils',
+ 'nvme-cli',
+ 'parted',
+ 'python3-libguestfs',
+ 'python3-libvirt',
+ 'python3-rtslib',
+ 'qemu-img',
+ 'qemu-kvm-block-rbd',
+ 'rsync',
+ 'sysfsutils',
+ 'targetcli',
+ 'xfsprogs'
+ ] %}
+
+ # NOTE(hrw): daxio is part of pmdk which is x86-64 only in CentOS 8
+ {% if base_arch == 'x86_64' %}
+ {% set nova_compute_packages = nova_compute_packages + [
+ 'daxio'
+ ] %}
+ {% endif %}
+
+{{ macros.install_packages(nova_compute_packages | customizable("packages")) }}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set nova_compute_packages = [
+ 'ceph-common',
+ 'dosfstools',
+ 'e2fsprogs',
+ 'genisoimage',
+ 'libosinfo-bin',
+ 'libsasl2-modules-gssapi-mit',
+ 'multipath-tools',
+ 'ndctl',
+ 'nfs-common',
+ 'nvme-cli',
+ 'open-iscsi',
+ 'parted',
+ 'pmdk-tools',
+ 'python3-cephfs',
+ 'python3-guestfs',
+ 'python3-libvirt',
+ 'python3-rados',
+ 'python3-rbd',
+ 'python3-rtslib-fb',
+ 'qemu-block-extra',
+ 'qemu-utils',
+ 'rsync',
+ 'sasl2-bin',
+ 'swtpm',
+ 'swtpm-tools',
+ 'sysfsutils',
+ 'targetcli-fb',
+ 'tpm2-tools',
+ 'xfsprogs'
+ ] %}
+
+RUN mkdir -p /etc/ceph \
+ && {{ macros.install_packages(nova_compute_packages | customizable("packages"), chain=True) }}
+
+{% endif %}
+
+{% set nova_compute_pip_packages = [
+ 'oslo-vmware'
+ ]
+%}
+
+{% set nova_compute_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+ADD plugins-archive /
+
+COPY extend_start.sh /usr/local/bin/kolla_nova_extend_start
+
+RUN {{ macros.install_pip(nova_compute_pip_packages | customizable("pip_packages")) }} \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(nova_compute_plugins_pip_packages) }}; \
+ fi \
+ && chmod 644 /usr/local/bin/kolla_nova_extend_start \
+ && rm -f /etc/machine-id
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_compute_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-compute/extend_start.sh b/kolla/docker/nova/nova-compute/extend_start.sh
new file mode 100644
index 000000000..a458625ec
--- /dev/null
+++ b/kolla/docker/nova/nova-compute/extend_start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+if [[ ! -d /var/lib/nova/instances ]]; then
+ mkdir -p /var/lib/nova/instances
+fi
diff --git a/kolla/docker/nova/nova-conductor/Dockerfile.j2 b/kolla/docker/nova/nova-conductor/Dockerfile.j2
new file mode 100644
index 000000000..b14000400
--- /dev/null
+++ b/kolla/docker/nova/nova-conductor/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_nova_extend_start
+RUN chmod 644 /usr/local/bin/kolla_nova_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-conductor/extend_start.sh b/kolla/docker/nova/nova-conductor/extend_start.sh
new file mode 100644
index 000000000..72c9493f5
--- /dev/null
+++ b/kolla/docker/nova/nova-conductor/extend_start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ nova-manage db sync --local_cell
+ nova-manage db online_data_migrations
+ exit 0
+fi
+
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ nova-manage db sync --local_cell
+ exit 0
+fi
+
+if [[ "${!KOLLA_OSM[@]}" ]]; then
+ nova-manage db online_data_migrations
+ exit 0
+fi
diff --git a/kolla/docker/nova/nova-libvirt-spice/Dockerfile.j2 b/kolla/docker/nova/nova-libvirt-spice/Dockerfile.j2
new file mode 100644
index 000000000..4c7ded894
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt-spice/Dockerfile.j2
@@ -0,0 +1,120 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build_date }}"
+{% endblock %}
+
+{% block nova_libvirt_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='nova', groups='qemu') }}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set nova_libvirt_extra_repos = ['ceph', 'crb', 'epel', 'openvswitch'] %}
+ {% if base_distro == 'rocky' %}
+ {#
+ The stock Rocky qemu-kvm has SPICE compiled out. To enable
+ Kerbside and the nova ``spice-direct`` console we therefore
+ layer the ligenix COPR in so that qemu-kvm and spice-server
+ are available with SPICE re-enabled. The repo definition
+ lives in kolla/template/repos.yaml so deployers can override
+ it like any other extra repo.
+ #}
+ {% set nova_libvirt_extra_repos = nova_libvirt_extra_repos + ['ligenix-qemu-spice'] %}
+ {% endif %}
+
+{{ macros.enable_extra_repos(nova_libvirt_extra_repos) }}
+
+ {% set nova_libvirt_packages = [
+ 'ceph-common',
+ 'cyrus-sasl-md5',
+ 'cyrus-sasl-scram',
+ 'libguestfs',
+ 'libvirt-client',
+ 'libvirt-daemon',
+ 'libvirt-daemon-log',
+ 'libvirt-daemon-config-nwfilter',
+ 'libvirt-daemon-driver-nwfilter',
+ 'libvirt-daemon-driver-nodedev',
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'qemu-img',
+ 'qemu-kvm',
+ 'swtpm',
+ ] %}
+ {% if base_arch == 'x86_64' %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'edk2-ovmf'
+ ] %}
+ {% elif base_arch == 'aarch64' %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'edk2-aarch64'
+ ] %}
+ {% endif %}
+
+ {% if use_ligenix_qemu | default(false) and base_distro == 'rocky' %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'qemu-kvm-ui-spice',
+ 'spice-server',
+ ] %}
+ {% endif %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set nova_libvirt_packages = [
+ 'ceph-common',
+ 'ebtables',
+ 'libsasl2-modules-gssapi-mit',
+ 'libvirt-clients',
+ 'libvirt-daemon-system',
+ 'mdevctl',
+ 'openvswitch-switch',
+ 'pm-utils',
+ 'python3-cephfs',
+ 'python3-rados',
+ 'python3-rbd',
+ 'qemu-block-extra',
+ 'qemu-system',
+ 'qemu-system-modules-spice',
+ 'qemu-utils',
+ 'sasl2-bin',
+ 'swtpm',
+ 'swtpm-tools',
+ 'tpm2-tools',
+ 'trousers'
+ ] %}
+
+ {% if base_arch == "aarch64" %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'qemu-efi-aarch64'
+ ] %}
+ {% elif base_arch == "x86_64" %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'ovmf',
+ ] %}
+ {% endif %}
+
+ {% if base_distro in ['debian'] %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'libvirt-daemon-log',
+ 'usermode'
+ ] %}
+ {% endif %}
+{% endif %}
+
+{{ macros.install_packages(nova_libvirt_packages | customizable("packages")) }}
+
+{% if base_package_type == 'deb' %}
+
+RUN rm -f /etc/libvirt/qemu/networks/default.xml /etc/libvirt/qemu/networks/autostart/default.xml
+
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY kolla_nova_libvirt_start.sh /usr/local/bin/kolla_nova_libvirt_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start && chmod 744 /usr/local/bin/kolla_nova_libvirt_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_libvirt_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/nova/nova-libvirt-spice/extend_start.sh b/kolla/docker/nova/nova-libvirt-spice/extend_start.sh
new file mode 100644
index 000000000..cc41b480b
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt-spice/extend_start.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [[ ! -e "/var/log/kolla/libvirt/libvirtd.log" ]]; then
+ mkdir -p /var/log/kolla/libvirt
+ touch /var/log/kolla/libvirt/libvirtd.log
+ chmod 644 /var/log/kolla/libvirt/libvirtd.log
+fi
+if [[ $(stat -c %a /var/log/kolla/libvirt) != "755" ]]; then
+ chmod 755 /var/log/kolla/libvirt
+ chmod 644 /var/log/kolla/libvirt/libvirtd.log
+fi
diff --git a/kolla/docker/nova/nova-libvirt-spice/kolla_nova_libvirt_start.sh b/kolla/docker/nova/nova-libvirt-spice/kolla_nova_libvirt_start.sh
new file mode 100644
index 000000000..f06a63d9e
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt-spice/kolla_nova_libvirt_start.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -euo pipefail
+
+# NOTE(mikal): Start virtlogd as a sidecar. We need this for
+# nova instance console logs to rotate. Otherwise they will
+# consume unbounded amounts of disk.
+#
+# No exec here: libvirtd will be the "main" process.
+/usr/sbin/virtlogd &
+
+# Now hand over PID 1's direct child role to libvirtd.
+exec /usr/sbin/libvirtd --listen
diff --git a/kolla/docker/nova/nova-libvirt/Dockerfile.j2 b/kolla/docker/nova/nova-libvirt/Dockerfile.j2
new file mode 100644
index 000000000..fb82401b3
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt/Dockerfile.j2
@@ -0,0 +1,100 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_libvirt_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='nova', groups='qemu') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['ceph', 'crb', 'epel', 'openvswitch']) }}
+
+ {% set nova_libvirt_packages = [
+ 'ceph-common',
+ 'cyrus-sasl-md5',
+ 'cyrus-sasl-scram',
+ 'libguestfs',
+ 'libvirt-client',
+ 'libvirt-daemon',
+ 'libvirt-daemon-log',
+ 'libvirt-daemon-config-nwfilter',
+ 'libvirt-daemon-driver-nwfilter',
+ 'libvirt-daemon-driver-nodedev',
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'qemu-img',
+ 'qemu-kvm',
+ 'swtpm',
+ ] %}
+ {% if base_arch == 'x86_64' %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'edk2-ovmf'
+ ] %}
+ {% elif base_arch == 'aarch64' %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'edk2-aarch64'
+ ] %}
+ {% endif %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set nova_libvirt_packages = [
+ 'ceph-common',
+ 'ebtables',
+ 'libsasl2-modules-gssapi-mit',
+ 'libvirt-clients',
+ 'libvirt-daemon-system',
+ 'mdevctl',
+ 'openvswitch-switch',
+ 'pm-utils',
+ 'python3-cephfs',
+ 'python3-rados',
+ 'python3-rbd',
+ 'qemu-block-extra',
+ 'qemu-system',
+ 'qemu-system-modules-spice',
+ 'qemu-utils',
+ 'sasl2-bin',
+ 'swtpm',
+ 'swtpm-tools',
+ 'tpm2-tools',
+ 'trousers'
+ ] %}
+
+ {% if base_arch == "aarch64" %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'qemu-efi-aarch64'
+ ] %}
+ {% elif base_arch == "x86_64" %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'ovmf',
+ ] %}
+ {% endif %}
+
+ {% if base_distro in ['debian'] %}
+ {% set nova_libvirt_packages = nova_libvirt_packages + [
+ 'libvirt-daemon-log',
+ 'usermode'
+ ] %}
+ {% endif %}
+{% endif %}
+
+{{ macros.install_packages(nova_libvirt_packages | customizable("packages")) }}
+
+{% if base_package_type == 'deb' %}
+
+RUN rm -f /etc/libvirt/qemu/networks/default.xml /etc/libvirt/qemu/networks/autostart/default.xml
+
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY kolla_nova_libvirt_start.sh /usr/local/bin/kolla_nova_libvirt_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start && chmod 744 /usr/local/bin/kolla_nova_libvirt_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_libvirt_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/nova/nova-libvirt/extend_start.sh b/kolla/docker/nova/nova-libvirt/extend_start.sh
new file mode 100644
index 000000000..cc41b480b
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt/extend_start.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [[ ! -e "/var/log/kolla/libvirt/libvirtd.log" ]]; then
+ mkdir -p /var/log/kolla/libvirt
+ touch /var/log/kolla/libvirt/libvirtd.log
+ chmod 644 /var/log/kolla/libvirt/libvirtd.log
+fi
+if [[ $(stat -c %a /var/log/kolla/libvirt) != "755" ]]; then
+ chmod 755 /var/log/kolla/libvirt
+ chmod 644 /var/log/kolla/libvirt/libvirtd.log
+fi
diff --git a/kolla/docker/nova/nova-libvirt/kolla_nova_libvirt_start.sh b/kolla/docker/nova/nova-libvirt/kolla_nova_libvirt_start.sh
new file mode 100644
index 000000000..f06a63d9e
--- /dev/null
+++ b/kolla/docker/nova/nova-libvirt/kolla_nova_libvirt_start.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -euo pipefail
+
+# NOTE(mikal): Start virtlogd as a sidecar. We need this for
+# nova instance console logs to rotate. Otherwise they will
+# consume unbounded amounts of disk.
+#
+# No exec here: libvirtd will be the "main" process.
+/usr/sbin/virtlogd &
+
+# Now hand over PID 1's direct child role to libvirtd.
+exec /usr/sbin/libvirtd --listen
diff --git a/kolla/docker/nova/nova-novncproxy/Dockerfile.j2 b/kolla/docker/nova/nova-novncproxy/Dockerfile.j2
new file mode 100644
index 000000000..4952ed734
--- /dev/null
+++ b/kolla/docker/nova/nova-novncproxy/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_novncproxy_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set nova_novncproxy_packages = [
+ 'novnc'
+] %}
+
+{{ macros.install_packages(nova_novncproxy_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_novncproxy_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-scheduler/Dockerfile.j2 b/kolla/docker/nova/nova-scheduler/Dockerfile.j2
new file mode 100644
index 000000000..5b0b0b98e
--- /dev/null
+++ b/kolla/docker/nova/nova-scheduler/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_scheduler_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_scheduler_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-serialproxy/Dockerfile.j2 b/kolla/docker/nova/nova-serialproxy/Dockerfile.j2
new file mode 100644
index 000000000..c63e925d5
--- /dev/null
+++ b/kolla/docker/nova/nova-serialproxy/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_serialproxy_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_serialproxy_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-spicehtml5proxy/Dockerfile.j2 b/kolla/docker/nova/nova-spicehtml5proxy/Dockerfile.j2
new file mode 100644
index 000000000..f3a90b0c5
--- /dev/null
+++ b/kolla/docker/nova/nova-spicehtml5proxy/Dockerfile.j2
@@ -0,0 +1,33 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_spicehtml5proxy_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set nova_spicehtml5proxy_packages = [
+ 'python3-numpy',
+ 'spice-html5'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set nova_spicehtml5proxy_packages = [
+ 'spice-html5',
+ 'websockify'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(nova_spicehtml5proxy_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_spicehtml5proxy_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER nova
diff --git a/kolla/docker/nova/nova-ssh/Dockerfile.j2 b/kolla/docker/nova/nova-ssh/Dockerfile.j2
new file mode 100644
index 000000000..0d9e36b65
--- /dev/null
+++ b/kolla/docker/nova/nova-ssh/Dockerfile.j2
@@ -0,0 +1,34 @@
+FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block nova_ssh_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='nova', shell='/bin/bash') }}
+
+{% set nova_ssh_packages = [
+ 'openssh-server',
+ 'rsync'
+] %}
+
+{% if base_package_type == 'deb' %}
+
+RUN mkdir -p /var/run/sshd \
+ && chmod 0755 /var/run/sshd
+
+{% endif %}
+
+{{ macros.install_packages(nova_ssh_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN sed -ri 's/session(\s+)required(\s+)pam_loginuid.so/session\1optional\2pam_loginuid.so/' /etc/pam.d/sshd \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block nova_ssh_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/nova/nova-ssh/extend_start.sh b/kolla/docker/nova/nova-ssh/extend_start.sh
new file mode 100644
index 000000000..b80114c0a
--- /dev/null
+++ b/kolla/docker/nova/nova-ssh/extend_start.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+SSH_HOST_KEY_TYPES=( "rsa" "ecdsa" "ed25519" )
+
+for key_type in ${SSH_HOST_KEY_TYPES[@]}; do
+ KEY_PATH=/etc/ssh/ssh_host_${key_type}_key
+ if [[ ! -f "${KEY_PATH}" ]]; then
+ ssh-keygen -q -t ${key_type} -f ${KEY_PATH} -N ""
+ fi
+done
+
+mkdir -p /var/lib/nova/.ssh
+
+if [[ $(stat -c %U:%G /var/lib/nova/.ssh) != "nova:nova" ]]; then
+ chown nova: /var/lib/nova/.ssh
+fi
diff --git a/kolla/docker/octavia/octavia-api/Dockerfile.j2 b/kolla/docker/octavia/octavia-api/Dockerfile.j2
new file mode 100644
index 000000000..ccb7bad11
--- /dev/null
+++ b/kolla/docker/octavia/octavia-api/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}octavia-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set octavia_api_packages = [
+] %}
+
+{% set octavia_api_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+ADD plugins-archive /
+
+COPY extend_start.sh /usr/local/bin/kolla_octavia_extend_start
+
+RUN if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(octavia_api_plugins_pip_packages) }} ; \
+ fi \
+ && chmod 644 /usr/local/bin/kolla_octavia_extend_start
+
+{{ macros.install_packages(octavia_api_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/octavia/octavia-api/extend_start.sh b/kolla/docker/octavia/octavia-api/extend_start.sh
new file mode 100644
index 000000000..b05d8abf1
--- /dev/null
+++ b/kolla/docker/octavia/octavia-api/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ octavia-db-manage upgrade head
+ octavia-db-manage upgrade_persistence
+ exit 0
+fi
diff --git a/kolla/docker/octavia/octavia-base/Dockerfile.j2 b/kolla/docker/octavia/octavia-base/Dockerfile.j2
new file mode 100644
index 000000000..81a589a09
--- /dev/null
+++ b/kolla/docker/octavia/octavia-base/Dockerfile.j2
@@ -0,0 +1,48 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='octavia') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+ {% set octavia_base_packages = [
+ 'python3-openvswitch${KOLLA_RPM_OVS_VERSION}'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set octavia_base_packages = [
+ 'python3-openvswitch'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(octavia_base_packages | customizable("packages")) }}
+
+ADD octavia-base-archive /octavia-base-source
+
+{% set octavia_base_pip_packages = [
+ '/octavia'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s /octavia-base-source/* octavia \
+ && {{ macros.install_pip(octavia_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/octavia \
+ && cp -r /octavia/etc/* /etc/octavia/ \
+ && cp /octavia/etc/audit/octavia_api_audit_map.conf.sample /etc/octavia/octavia_api_audit_map.conf \
+ && touch /usr/local/bin/kolla_octavia_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_octavia_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_base_footer %}{% endblock %}
diff --git a/kolla/docker/octavia/octavia-base/extend_start.sh b/kolla/docker/octavia/octavia-base/extend_start.sh
new file mode 100644
index 000000000..b27a88a31
--- /dev/null
+++ b/kolla/docker/octavia/octavia-base/extend_start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -o errexit
+
+OCTAVIA_LOG_DIR=/var/log/kolla/octavia
+
+if [[ ! -d "${OCTAVIA_LOG_DIR}" ]]; then
+ mkdir -p ${OCTAVIA_LOG_DIR}
+fi
+if [[ $(stat -c %U:%G ${OCTAVIA_LOG_DIR}) != "octavia:kolla" ]]; then
+ chown -R octavia:kolla /var/log/kolla/octavia
+fi
+if [[ $(stat -c %a ${OCTAVIA_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${OCTAVIA_LOG_DIR}
+fi
+
+. /usr/local/bin/kolla_octavia_extend_start
diff --git a/kolla/docker/octavia/octavia-driver-agent/Dockerfile.j2 b/kolla/docker/octavia/octavia-driver-agent/Dockerfile.j2
new file mode 100644
index 000000000..b3519922a
--- /dev/null
+++ b/kolla/docker/octavia/octavia-driver-agent/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}octavia-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_driver_agent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set octavia_driver_agent_packages = [
+] %}
+
+{% set octavia_driver_agent_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+{{ macros.install_packages(octavia_driver_agent_packages | customizable("packages")) }}
+
+ADD plugins-archive /
+RUN if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(octavia_driver_agent_plugins_pip_packages) }} ; \
+ fi
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_driver_agent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER octavia
diff --git a/kolla/docker/octavia/octavia-health-manager/Dockerfile.j2 b/kolla/docker/octavia/octavia-health-manager/Dockerfile.j2
new file mode 100644
index 000000000..1ffbe2050
--- /dev/null
+++ b/kolla/docker/octavia/octavia-health-manager/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}octavia-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_health_manager_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_health_manager_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER octavia
diff --git a/kolla/docker/octavia/octavia-housekeeping/Dockerfile.j2 b/kolla/docker/octavia/octavia-housekeeping/Dockerfile.j2
new file mode 100644
index 000000000..775797cca
--- /dev/null
+++ b/kolla/docker/octavia/octavia-housekeeping/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}octavia-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_housekeeping_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_housekeeping_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER octavia
diff --git a/kolla/docker/octavia/octavia-worker/Dockerfile.j2 b/kolla/docker/octavia/octavia-worker/Dockerfile.j2
new file mode 100644
index 000000000..85d4727b7
--- /dev/null
+++ b/kolla/docker/octavia/octavia-worker/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}octavia-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block octavia_worker_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block octavia_worker_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER octavia
diff --git a/kolla/docker/opensearch/opensearch-dashboards/Dockerfile.j2 b/kolla/docker/opensearch/opensearch-dashboards/Dockerfile.j2
new file mode 100644
index 000000000..3249b62d2
--- /dev/null
+++ b/kolla/docker/opensearch/opensearch-dashboards/Dockerfile.j2
@@ -0,0 +1,26 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block opensearch_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='opensearch-dashboards', shell='/bin/bash', homedir='/usr/share/opensearch-dashboards') }}
+{{ macros.enable_extra_repos(['opensearch-dashboards']) }}
+
+{% set opensearch_dashboards_packages = ['opensearch-dashboards'] %}
+
+{{ macros.install_packages(opensearch_dashboards_packages | customizable("packages")) }} \
+ && chown -R opensearch-dashboards:opensearch-dashboards /usr/share/opensearch-dashboards
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 755 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block opensearch_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER opensearch-dashboards
diff --git a/kolla/docker/opensearch/opensearch-dashboards/extend_start.sh b/kolla/docker/opensearch/opensearch-dashboards/extend_start.sh
new file mode 100644
index 000000000..608bb34bf
--- /dev/null
+++ b/kolla/docker/opensearch/opensearch-dashboards/extend_start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/opensearch-dashboards" ]]; then
+ mkdir -p /var/log/kolla/opensearch-dashboards
+fi
+if [[ $(stat -c %a /var/log/kolla/opensearch-dashboards) != "755" ]]; then
+ chmod 755 /var/log/kolla/opensearch-dashboards
+fi
+
+export DASHBOARDS_HOME=/usr/share/opensearch-dashboards
+
+PLUGIN_LIST=$($DASHBOARDS_HOME/bin/opensearch-dashboards-plugin list)
+if [[ "${OPENSEARCH_DASHBOARDS_SECURITY_PLUGIN:-True}" == "False" ]]; then
+ if [[ ${PLUGIN_LIST} =~ "securityDashboards" ]]; then
+ $DASHBOARDS_HOME/bin/opensearch-dashboards-plugin remove securityDashboards
+ fi
+fi
diff --git a/kolla/docker/opensearch/opensearch/Dockerfile.j2 b/kolla/docker/opensearch/opensearch/Dockerfile.j2
new file mode 100644
index 000000000..6ad8fb29a
--- /dev/null
+++ b/kolla/docker/opensearch/opensearch/Dockerfile.j2
@@ -0,0 +1,32 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block opensearch_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='opensearch', shell='/bin/bash', homedir='/usr/share/opensearch') }}
+{{ macros.enable_extra_repos(['opensearch']) }}
+
+ {% set opensearch_packages = [
+ 'opensearch'
+ ] %}
+
+ENV OPENSEARCH_INITIAL_ADMIN_PASSWORD="Kolla24OpenSearch"
+
+{{ macros.install_packages(opensearch_packages | customizable("packages")) }} \
+ && chown -R opensearch:opensearch /usr/share/opensearch \
+ && rm -f /etc/opensearch/*.pem \
+ && rm -f /etc/opensearch/securityadmin_demo.sh
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 755 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block opensearch_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER opensearch
diff --git a/kolla/docker/opensearch/opensearch/extend_start.sh b/kolla/docker/opensearch/opensearch/extend_start.sh
new file mode 100644
index 000000000..5855b1be2
--- /dev/null
+++ b/kolla/docker/opensearch/opensearch/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/opensearch" ]]; then
+ mkdir -p /var/log/kolla/opensearch
+fi
+if [[ $(stat -c %a /var/log/kolla/opensearch) != "755" ]]; then
+ chmod 755 /var/log/kolla/opensearch
+fi
diff --git a/kolla/docker/openstack-base/Dockerfile.j2 b/kolla/docker/openstack-base/Dockerfile.j2
new file mode 100644
index 000000000..b8ed2e465
--- /dev/null
+++ b/kolla/docker/openstack-base/Dockerfile.j2
@@ -0,0 +1,213 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block openstack_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['crb', 'epel']) }}
+
+ {% set openstack_base_packages = [
+ 'gcc',
+ 'gcc-c++',
+ 'git',
+ 'httpd',
+ 'iproute',
+ 'libffi-devel',
+ 'libxml2-devel',
+ 'libxslt-devel',
+ 'libyaml-devel',
+ 'make',
+ 'mod_ssl',
+ 'openssl',
+ 'openssl-devel',
+ 'pcre2-devel',
+ 'postgresql',
+ 'postgresql-devel',
+ 'postgresql-server-devel',
+ 'python3-devel',
+ 'sqlite-devel',
+ 'uwsgi-plugin-python3',
+ 'zip'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set openstack_base_packages = [
+ 'build-essential',
+ 'ca-certificates',
+ 'git',
+ 'libffi-dev',
+ 'libpcre2-dev',
+ 'libpq-dev',
+ 'libssl-dev',
+ 'libxml2-dev',
+ 'libxslt1-dev',
+ 'libyaml-dev',
+ 'libz-dev',
+ 'pkg-config',
+ 'python3-dev',
+ 'python3-venv',
+ 'uwsgi-plugin-python3',
+ 'zip'
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(openstack_base_packages | customizable("packages")) }}
+
+{% set openstack_base_pip_packages = [
+ 'Mako',
+ 'MarkupSafe',
+ 'Paste',
+ 'PasteDeploy',
+ 'PyNaCl',
+ 'PyYAML',
+ 'Routes',
+ 'SQLAlchemy',
+ 'Tempita',
+ 'WSME',
+ 'WebOb',
+ 'alembic',
+ 'amqp',
+ 'aodhclient',
+ 'appdirs',
+ 'automaton',
+ 'bcrypt',
+ 'cachetools',
+ 'castellan',
+ 'click',
+ 'cliff',
+ 'cmd2',
+ 'contextlib2',
+ 'cryptography',
+ 'debtcollector',
+ 'decorator',
+ 'eventlet',
+ 'fasteners',
+ 'fluent-logger',
+ 'funcsigs',
+ 'futurist',
+ 'gnocchiclient',
+ 'greenlet',
+ 'httplib2',
+ 'iso8601',
+ 'jinja2',
+ 'jsonpatch',
+ 'jsonpointer',
+ 'jsonschema',
+ 'keystoneauth1',
+ 'keystonemiddleware',
+ 'kombu',
+ 'logutils',
+ 'monotonic',
+ 'netaddr',
+ 'netifaces',
+ 'os-brick',
+ 'os-client-config',
+ 'os-traits',
+ 'os-win',
+ 'oslo.concurrency',
+ 'oslo.config',
+ 'oslo.context',
+ 'oslo.db',
+ 'oslo.i18n',
+ 'oslo.log',
+ 'oslo.messaging',
+ 'oslo.middleware',
+ 'oslo.policy',
+ 'oslo.privsep',
+ 'oslo.reports',
+ 'oslo.rootwrap',
+ 'oslo.serialization',
+ 'oslo.service',
+ 'oslo.upgradecheck',
+ 'oslo.utils',
+ 'oslo.versionedobjects',
+ 'oslo.vmware',
+ 'osprofiler',
+ 'paramiko',
+ 'pbr',
+ 'pecan',
+ 'pika',
+ 'prettytable',
+ 'psutil',
+ 'pycadf',
+ 'pymysql',
+ 'pyngus',
+ 'pyparsing',
+ 'pyroute2',
+ 'python-barbicanclient',
+ 'python-binary-memcached',
+ 'python-cinderclient',
+ 'python-cloudkittyclient',
+ 'python-dateutil',
+ 'python-designateclient',
+ 'python-editor',
+ 'python-glanceclient',
+ 'python-heatclient',
+ 'python-ironicclient',
+ 'python-keystoneclient',
+ 'python-magnumclient',
+ 'python-manilaclient',
+ 'python-memcached',
+ 'python-mistralclient',
+ 'python-neutronclient',
+ 'python-novaclient',
+ 'python-openstackclient',
+ 'python-qpid-proton',
+ 'python-swiftclient',
+ 'python-troveclient',
+ 'pytz',
+ 'repoze.lru',
+ 'requests',
+ 'requestsexceptions',
+ 'retrying',
+ 'setproctitle',
+ 'simplegeneric',
+ 'simplejson',
+ 'six',
+ 'sqlparse',
+ 'stevedore',
+ 'tooz[consul,etcd,etcd3gw,redis,postgresql,mysql,zookeeper,memcached,ipc]',
+ 'unicodecsv',
+ 'uwsgi',
+ 'warlock',
+ 'wrapt'
+ ]
+%}
+
+ADD openstack-base-archive /openstack-base-source
+ADD plugins-archive /
+
+RUN ln -s openstack-base-source/* /requirements \
+
+{# NOTE(mnasiadka): Remove ovs from upper-constraints.txt because python3-openvswitch
+ is usually newer than UC entry and older version would get installed
+ in venv (see https://launchpad.net/bugs/1961874).
+#}
+ && {{ macros.upper_constraints_remove("ovs") }} \
+ && mkdir -p /var/lib/kolla \
+ && python3 -m venv --system-site-packages /var/lib/kolla/venv \
+ && KOLLA_DISTRO_PYTHON_VERSION=$(/usr/bin/python3 -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))") \
+ && ln -s python${KOLLA_DISTRO_PYTHON_VERSION} /var/lib/kolla/venv/lib/python3
+
+{# Block used for overriding global version constraints.
+
+ Overriding the package version using upper_constraints_remove(package_name) or
+ upper_constraints_version_change(package_name, from, to) at this location will
+ apply to all services.
+#}
+{% block openstack_base_override_upper_constraints %}{% endblock %}
+
+ENV PATH /var/lib/kolla/venv/bin:$PATH
+
+RUN {{ macros.install_pip(['pip', 'wheel', 'setuptools']) }} \
+ && {{ macros.install_pip(openstack_base_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block openstack_base_footer %}{% endblock %}
diff --git a/kolla/docker/openvswitch/openvswitch-base/Dockerfile.j2 b/kolla/docker/openvswitch/openvswitch-base/Dockerfile.j2
new file mode 100644
index 000000000..44d8df41c
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-base/Dockerfile.j2
@@ -0,0 +1,45 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block openvswitch_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block ovs_install %}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+ {% set openvswitch_base_packages = [
+ 'libibverbs',
+ 'openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'python3-openvswitch${KOLLA_RPM_OVS_VERSION}',
+ 'tcpdump'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set openvswitch_base_packages = [
+ 'openvswitch-switch',
+ 'python3-openvswitch',
+ 'tcpdump'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(openvswitch_base_packages | customizable("packages")) }}
+
+{% endblock %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+COPY ovs_wrapper.sh /usr/local/bin/ovs_wrapper
+
+# NOTE(hrw): Upstream moved tcpdump to /usr/bin/ in 4.99 version (used in Debian)
+RUN if [ -e /usr/sbin/tcpdump ]; then mv /usr/sbin/tcpdump /usr/bin/tcpdump; fi \
+ && touch /usr/local/bin/kolla_openvswitch_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_openvswitch_extend_start \
+ && chmod 755 /usr/local/bin/ovs_wrapper
+
+{{ macros.kolla_patch_sources() }}
+
+{% block openvswitch_base_footer %}{% endblock %}
diff --git a/kolla/docker/openvswitch/openvswitch-base/extend_start.sh b/kolla/docker/openvswitch/openvswitch-base/extend_start.sh
new file mode 100644
index 000000000..1b0ed1300
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-base/extend_start.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/openvswitch" ]]; then
+ mkdir -p /var/log/kolla/openvswitch
+fi
+if [[ $(stat -c %a /var/log/kolla/openvswitch) != "755" ]]; then
+ chmod 755 /var/log/kolla/openvswitch
+fi
+
+chown -R openvswitch:openvswitch /var/log/kolla/openvswitch
+
+. /usr/local/bin/kolla_openvswitch_extend_start
diff --git a/kolla/docker/openvswitch/openvswitch-base/ovs_wrapper.sh b/kolla/docker/openvswitch/openvswitch-base/ovs_wrapper.sh
new file mode 100644
index 000000000..552c496ac
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-base/ovs_wrapper.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+catch_term() {
+ ovs-appctl -T1 -t /run/openvswitch/$1.$child.ctl exit
+ exit 0
+}
+
+catch_quit() {
+ ovs-appctl -T1 -t /run/openvswitch/$1.$child.ctl exit
+ exit 0
+}
+
+trap catch_term SIGTERM
+trap catch_quit SIGQUIT
+
+"$@" &
+
+child=$!
+wait "$child"
diff --git a/kolla/docker/openvswitch/openvswitch-db-server/Dockerfile.j2 b/kolla/docker/openvswitch/openvswitch-db-server/Dockerfile.j2
new file mode 100644
index 000000000..869dbabb6
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-db-server/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}openvswitch-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block openvswitch_db_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_openvswitch_extend_start
+RUN chmod 755 /usr/local/bin/kolla_openvswitch_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block openvswitch_db_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/openvswitch/openvswitch-db-server/extend_start.sh b/kolla/docker/openvswitch/openvswitch-db-server/extend_start.sh
new file mode 100644
index 000000000..d25038bdd
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-db-server/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+mkdir -p "/run/openvswitch"
+if [[ ! -e "/var/lib/openvswitch/conf.db" ]]; then
+ ovsdb-tool create "/var/lib/openvswitch/conf.db"
+fi
+
+if [[ $(ovsdb-tool needs-conversion /var/lib/openvswitch/conf.db) == yes ]]; then
+ ovsdb-tool convert "/var/lib/openvswitch/conf.db"
+fi
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovsdb-server.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovsdb-server.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovsdb-server.log
+fi
diff --git a/kolla/docker/openvswitch/openvswitch-db-server/releasenotes/notes/drop-ovs-ensure-configured-7973d36969e157fa.yaml b/kolla/docker/openvswitch/openvswitch-db-server/releasenotes/notes/drop-ovs-ensure-configured-7973d36969e157fa.yaml
new file mode 100644
index 000000000..cc8daf1f4
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-db-server/releasenotes/notes/drop-ovs-ensure-configured-7973d36969e157fa.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ ``openvswitch`` script ``ovs_ensure_configured.sh`` has been dropped after
+ being replaced with Ansible modules in ``kolla-ansible``.
diff --git a/kolla/docker/openvswitch/openvswitch-vswitchd/Dockerfile.j2 b/kolla/docker/openvswitch/openvswitch-vswitchd/Dockerfile.j2
new file mode 100644
index 000000000..fe6dd4347
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-vswitchd/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}openvswitch-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block openvswitch_vswitchd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_openvswitch_extend_start
+RUN chmod 644 /usr/local/bin/kolla_openvswitch_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block openvswitch_vswitchd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/openvswitch/openvswitch-vswitchd/extend_start.sh b/kolla/docker/openvswitch/openvswitch-vswitchd/extend_start.sh
new file mode 100644
index 000000000..fde542130
--- /dev/null
+++ b/kolla/docker/openvswitch/openvswitch-vswitchd/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovs-vswitchd.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovs-vswitchd.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovs-vswitchd.log
+fi
+
+modprobe openvswitch
diff --git a/kolla/docker/ovn/ovn-base/Dockerfile.j2 b/kolla/docker/ovn/ovn-base/Dockerfile.j2
new file mode 100644
index 000000000..3efd001a3
--- /dev/null
+++ b/kolla/docker/ovn/ovn-base/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}openvswitch-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ovn_base_packages = [
+ 'ovn${KOLLA_RPM_OVN_VERSION}',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set ovn_base_packages = [
+ 'ovn-common',
+ ] %}
+
+{% endif %}
+{{ macros.install_packages(ovn_base_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovn_base_footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-base/extend_start.sh b/kolla/docker/ovn/ovn-base/extend_start.sh
new file mode 100644
index 000000000..eae4ab9e1
--- /dev/null
+++ b/kolla/docker/ovn/ovn-base/extend_start.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+if [[ ! -d "/run/ovn" ]]; then
+ mkdir -p /run/ovn
+fi
+if [[ ! -d "/var/log/kolla/openvswitch" ]]; then
+ mkdir -p /var/log/kolla/openvswitch
+fi
+if [[ $(stat -c %a /var/log/kolla/openvswitch) != "755" ]]; then
+ chmod 755 /var/log/kolla/openvswitch
+fi
+
+. /usr/local/bin/kolla_ovn_extend_start
diff --git a/kolla/docker/ovn/ovn-controller/Dockerfile.j2 b/kolla/docker/ovn/ovn-controller/Dockerfile.j2
new file mode 100644
index 000000000..f95e61fee
--- /dev/null
+++ b/kolla/docker/ovn/ovn-controller/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}ovn-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_controller_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ovn_controller_packages = [
+ 'ovn${KOLLA_RPM_OVN_VERSION}-host'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set ovn_controller_packages = [
+ 'ovn-host'
+ ] %}
+
+{% endif %}
+{{ macros.install_packages(ovn_controller_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_ovn_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovn_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovn_controller_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-controller/extend_start.sh b/kolla/docker/ovn/ovn-controller/extend_start.sh
new file mode 100644
index 000000000..8080ea7c8
--- /dev/null
+++ b/kolla/docker/ovn/ovn-controller/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovn-controller.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovn-controller.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovn-controller.log
+fi
diff --git a/kolla/docker/ovn/ovn-nb-db-server/Dockerfile.j2 b/kolla/docker/ovn/ovn-nb-db-server/Dockerfile.j2
new file mode 100644
index 000000000..ea6a53249
--- /dev/null
+++ b/kolla/docker/ovn/ovn-nb-db-server/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}ovn-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_nb_db_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ovn_nb_db_server_packages = [
+ 'ovn${KOLLA_RPM_OVN_VERSION}-central',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set ovn_nb_db_server_packages = [
+ 'ovn-central',
+ ] %}
+
+{% endif %}
+{{ macros.install_packages(ovn_nb_db_server_packages | customizable("packages")) }}
+
+COPY start_nb_db_server.sh /usr/local/bin/start-nb-db-server
+RUN chmod 755 /usr/local/bin/start-nb-db-server
+COPY extend_start.sh /usr/local/bin/kolla_ovn_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovn_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovn_nb_db_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-nb-db-server/extend_start.sh b/kolla/docker/ovn/ovn-nb-db-server/extend_start.sh
new file mode 100644
index 000000000..4c0e742f5
--- /dev/null
+++ b/kolla/docker/ovn/ovn-nb-db-server/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovn-nb-db.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovn-nb-db.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovn-nb-db.log
+fi
diff --git a/kolla/docker/ovn/ovn-nb-db-server/start_nb_db_server.sh b/kolla/docker/ovn/ovn-nb-db-server/start_nb_db_server.sh
new file mode 100755
index 000000000..d08dd2890
--- /dev/null
+++ b/kolla/docker/ovn/ovn-nb-db-server/start_nb_db_server.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# All the option passed to this script will be
+# passed to the ovn-ctl script. Please see the options
+# supported by ovn-ctl script -
+# https://github.com/ovn-org/ovn/blob/master/utilities/ovn-ctl
+args=$@
+
+# Use ovn-ctl script to start ovn NB db server as it
+# takes care of creating the db file from the schema
+# file if the db file is not present. It also takes care
+# of updating the db file if the schema file is updated.
+
+# Check for the presence of ovn-ctl script in two locations.
+# If latest OVN is used (post split from openvswitch),
+# then the new location for the ovn-ctl script is
+# is - /usr/share/ovn/scripts/ovn-ctl. Otherwise it is
+# /usr/share/openvswitch/scripts/ovn-ctl.
+
+if [[ -f "/usr/share/openvswitch/scripts/ovn-ctl" ]]; then
+ set /usr/share/openvswitch/scripts/ovn-ctl --no-monitor
+elif [[ -f "/usr/share/ovn/scripts/ovn-ctl" ]]; then
+ set /usr/share/ovn/scripts/ovn-ctl --no-monitor
+else
+ exit 1
+fi
+
+$@ $args run_nb_ovsdb
diff --git a/kolla/docker/ovn/ovn-northd/Dockerfile.j2 b/kolla/docker/ovn/ovn-northd/Dockerfile.j2
new file mode 100644
index 000000000..4a15e10eb
--- /dev/null
+++ b/kolla/docker/ovn/ovn-northd/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}ovn-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_northd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ovn_northd_packages = [
+ 'ovn${KOLLA_RPM_OVN_VERSION}-central',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set ovn_northd_packages = [
+ 'ovn-central',
+ ] %}
+
+{% endif %}
+{{ macros.install_packages(ovn_northd_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_ovn_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovn_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovn_northd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-northd/extend_start.sh b/kolla/docker/ovn/ovn-northd/extend_start.sh
new file mode 100644
index 000000000..c832c5383
--- /dev/null
+++ b/kolla/docker/ovn/ovn-northd/extend_start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+if [[ ! -r "/var/log/kolla/openvswitch/ovn-northd.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovn-northd.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovn-northd.log
+fi
diff --git a/kolla/docker/ovn/ovn-sb-db-relay/Dockerfile.j2 b/kolla/docker/ovn/ovn-sb-db-relay/Dockerfile.j2
new file mode 100644
index 000000000..427b6f7f1
--- /dev/null
+++ b/kolla/docker/ovn/ovn-sb-db-relay/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}ovn-sb-db-server:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_sb_db_server_header %}{% endblock %}
+
+{% block ovn_sb_db_relay_ovn_ctl %}
+{# TODO(mnasiadka): Switch to 25.03 branch when available, ideally rpm/deb packages will be
+ available. #}
+RUN curl -o /usr/share/ovn/scripts/ovn-ctl https://raw.githubusercontent.com/ovn-org/ovn/refs/heads/main/utilities/ovn-ctl
+{% endblock %}
+
+COPY extend_start.sh /usr/local/bin/kolla_ovn_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovn_extend_start
+
+{% block ovn_sb_db_relay_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-sb-db-relay/extend_start.sh b/kolla/docker/ovn/ovn-sb-db-relay/extend_start.sh
new file mode 100644
index 000000000..04b6c5357
--- /dev/null
+++ b/kolla/docker/ovn/ovn-sb-db-relay/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovn-sb-relay-${RELAY_ID}.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovn-sb-relay-${RELAY_ID}.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovn-sb-relay-${RELAY_ID}.log
+fi
diff --git a/kolla/docker/ovn/ovn-sb-db-server/Dockerfile.j2 b/kolla/docker/ovn/ovn-sb-db-server/Dockerfile.j2
new file mode 100644
index 000000000..afceb8173
--- /dev/null
+++ b/kolla/docker/ovn/ovn-sb-db-server/Dockerfile.j2
@@ -0,0 +1,31 @@
+FROM {{ namespace }}/{{ image_prefix }}ovn-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovn_sb_db_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set ovn_sb_db_server_packages = [
+ 'ovn${KOLLA_RPM_OVN_VERSION}-central',
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set ovn_sb_db_server_packages = [
+ 'ovn-central',
+ ] %}
+
+{% endif %}
+{{ macros.install_packages(ovn_sb_db_server_packages | customizable("packages")) }}
+
+COPY start_sb_db_server.sh /usr/local/bin/start-sb-db-server
+RUN chmod 755 /usr/local/bin/start-sb-db-server
+COPY extend_start.sh /usr/local/bin/kolla_ovn_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovn_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovn_sb_db_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovn/ovn-sb-db-server/extend_start.sh b/kolla/docker/ovn/ovn-sb-db-server/extend_start.sh
new file mode 100644
index 000000000..07bcd1b45
--- /dev/null
+++ b/kolla/docker/ovn/ovn-sb-db-server/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+if [[ ! -r "/var/log/kolla/openvswitch/ovn-sb-db.log" ]]; then
+ touch /var/log/kolla/openvswitch/ovn-sb-db.log
+ chown openvswitch:openvswitch /var/log/kolla/openvswitch/ovn-sb-db.log
+fi
diff --git a/kolla/docker/ovn/ovn-sb-db-server/start_sb_db_server.sh b/kolla/docker/ovn/ovn-sb-db-server/start_sb_db_server.sh
new file mode 100755
index 000000000..ef39b8d25
--- /dev/null
+++ b/kolla/docker/ovn/ovn-sb-db-server/start_sb_db_server.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# All the option passed to this script will be
+# passed to the ovn-ctl script. Please see the options
+# supported by ovn-ctl script -
+# https://github.com/ovn-org/ovn/blob/master/utilities/ovn-ctl
+args=$@
+
+# Use ovn-ctl script to start ovn SB db server as it
+# takes care of creating the db file from the schema
+# file if the db file is not present. It also takes care
+# of updating the db file if the schema file is updated.
+
+# Check for the presence of ovn-ctl script in two locations.
+# If latest OVN is used (post split from openvswitch),
+# then the new location for the ovn-ctl script is
+# is - /usr/share/ovn/scripts/ovn-ctl. Otherwise it is
+# /usr/share/openvswitch/scripts/ovn-ctl.
+
+
+if [[ -f "/usr/share/openvswitch/scripts/ovn-ctl" ]]; then
+ set /usr/share/openvswitch/scripts/ovn-ctl --no-monitor
+elif [[ -f "/usr/share/ovn/scripts/ovn-ctl" ]]; then
+ set /usr/share/ovn/scripts/ovn-ctl --no-monitor
+else
+ exit 1
+fi
+
+$@ $args run_sb_ovsdb
diff --git a/kolla/docker/ovsdpdk/ovsdpdk-db/Dockerfile.j2 b/kolla/docker/ovsdpdk/ovsdpdk-db/Dockerfile.j2
new file mode 100644
index 000000000..2527fa0e6
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk-db/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}ovsdpdk:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovsdpdk_db_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_ovsdpdk_extend_start
+COPY start_ovsdb_server.sh /usr/local/bin/start-ovsdb-server
+RUN chmod 644 /usr/local/bin/kolla_ovsdpdk_extend_start && \
+ chmod 755 /usr/local/bin/start-ovsdb-server
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovsdpdk_db_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovsdpdk/ovsdpdk-db/extend_start.sh b/kolla/docker/ovsdpdk/ovsdpdk-db/extend_start.sh
new file mode 100644
index 000000000..3883b50ed
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk-db/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+mkdir -p "/run/openvswitch"
+if [[ ! -e "/var/lib/openvswitch/conf.db" ]]; then
+ ovsdb-tool create "/var/lib/openvswitch/conf.db"
+fi
diff --git a/kolla/docker/ovsdpdk/ovsdpdk-db/start_ovsdb_server.sh b/kolla/docker/ovsdpdk/ovsdpdk-db/start_ovsdb_server.sh
new file mode 100755
index 000000000..5c92fba78
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk-db/start_ovsdb_server.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# NOTE: (sbezverk) ovs_bridge and ovs_ext_intf variables get initialized only when
+# this script is executed for kubernetes deployment. With Ansible deployment, only
+# ovsdb-server gets launched and then the following workflow step will create
+# an external bridge and plug an external interface. With Kubernetes we want to
+# leverage its dynamic nature of automatic scaling up and down. It means all
+# activities related to creating initial bridge, plugging external interface
+# must be done by DaemonSet launched container.
+
+ovsdb_ip=$1
+ovs_bridge=$2
+ovs_ext_intf=$3
+
+# NOTE: (sbezverk) The reason for introducing this script is to be able
+# to launch ovsdb-server and to create the initial external bridge in one step.
+# It is required in order to be able to use DaemonSet.
+
+if [ ! -e $ovs_bridge ] && [ ! -e $ovs_ext_intf ]; then
+# NOTE: (sbezverk) This part is executed only by kubernetes deployment.
+# Creating external bridge
+ /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --run="ovs-vsctl --no-wait --db=unix:/var/run/openvswitch/db.sock add-br $ovs_bridge"
+# Plug the external interface into the external bridge.
+ /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --run="ovs-vsctl --no-wait --db=unix:/var/run/openvswitch/db.sock add-port $ovs_bridge $ovs_ext_intf"
+# Run ovsdb server process
+ /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --remote=ptcp:6640 --log-file=/var/log/kolla/openvswitch/ovsdb-server.log
+else
+# NOTE: (sbezverk) This part is executed only by kolla-ansible deployment.
+ /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/run/openvswitch/db.sock --remote=ptcp:6640:$ovsdb_ip --log-file=/var/log/kolla/openvswitch/ovsdb-server.log
+fi
diff --git a/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/Dockerfile.j2 b/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/Dockerfile.j2
new file mode 100644
index 000000000..059efd46b
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}ovsdpdk:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block ovsdpdk_vswitchd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_ovsdpdk_extend_start
+RUN chmod 644 /usr/local/bin/kolla_ovsdpdk_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovsdpdk_vswitchd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/extend_start.sh b/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/extend_start.sh
new file mode 100644
index 000000000..cf9d733e8
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk-vswitchd/extend_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# using userspace netdev datapath so do not loading ovs kernel module
+# chmod openvsitch run directory so libvirt can create vhost-user sockets.
+chmod 777 /var/run/openvswitch
+
diff --git a/kolla/docker/ovsdpdk/ovsdpdk/Dockerfile.j2 b/kolla/docker/ovsdpdk/ovsdpdk/Dockerfile.j2
new file mode 100644
index 000000000..eab4f5474
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk/Dockerfile.j2
@@ -0,0 +1,40 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+{% block ovsdpdk_header %}{% endblock %}
+
+{% block ovsdpdk_install %}
+
+{% if base_package_type == 'rpm' %}
+
+#TODO(sean-k-mooney) create repo via open suse build system to package
+# for centos distro.
+
+RUN echo 'image not yet available for {{ base_distro }}' \
+ && /bin/false
+
+{% elif base_package_type == 'deb' %}
+
+{% set ovs_dpdk_packages = [
+ 'crudini',
+ 'libdpdk-dev',
+ 'openvswitch-switch-dpdk'
+] %}
+
+{{ macros.install_packages(ovs_dpdk_packages | customizable("packages")) }}
+
+RUN update-alternatives --set ovs-vswitchd /usr/lib/openvswitch-switch-dpdk/ovs-vswitchd-dpdk
+
+{% endif %}
+
+{% endblock %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block ovsdpdk_footer %}{% endblock %}
diff --git a/kolla/docker/ovsdpdk/ovsdpdk/extend_start.sh b/kolla/docker/ovsdpdk/ovsdpdk/extend_start.sh
new file mode 100644
index 000000000..60ab0c088
--- /dev/null
+++ b/kolla/docker/ovsdpdk/ovsdpdk/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/openvswitch" ]]; then
+ mkdir -p /var/log/kolla/openvswitch
+fi
+if [[ $(stat -c %a /var/log/kolla/openvswitch) != "755" ]]; then
+ chmod 755 /var/log/kolla/openvswitch
+fi
+
+. /usr/local/bin/kolla_ovsdpdk_extend_start
diff --git a/kolla/docker/placement/placement-api/Dockerfile.j2 b/kolla/docker/placement/placement-api/Dockerfile.j2
new file mode 100644
index 000000000..a13f65bdf
--- /dev/null
+++ b/kolla/docker/placement/placement-api/Dockerfile.j2
@@ -0,0 +1,19 @@
+FROM {{ namespace }}/{{ image_prefix }}placement-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block placement_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set placement_api_packages = [
+] %}
+{{ macros.install_packages(placement_api_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_placement_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block placement_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/placement/placement-api/extend_start.sh b/kolla/docker/placement/placement-api/extend_start.sh
new file mode 100644
index 000000000..e468cbcaf
--- /dev/null
+++ b/kolla/docker/placement/placement-api/extend_start.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ placement-manage db sync
+ placement-manage db online_data_migrations
+ exit 0
+fi
+
+# DB synchronisation. To be executed prior to upgrading services.
+if [[ "${!KOLLA_UPGRADE[@]}" ]]; then
+ placement-manage db sync
+ exit 0
+fi
+
+# Online data migrations. To be executed after upgrading services.
+if [[ "${!KOLLA_OSM[@]}" ]]; then
+ placement-manage db online_data_migrations
+ exit 0
+fi
diff --git a/kolla/docker/placement/placement-base/Dockerfile.j2 b/kolla/docker/placement/placement-base/Dockerfile.j2
new file mode 100644
index 000000000..6454da748
--- /dev/null
+++ b/kolla/docker/placement/placement-base/Dockerfile.j2
@@ -0,0 +1,44 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block placement_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='placement') }}
+
+{% if base_package_type == 'rpm' %}
+
+ {% set placement_base_packages = [
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+
+ {% set placement_base_packages = [
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(placement_base_packages | customizable("packages")) }}
+
+ADD placement-base-archive /placement-base-source
+ADD plugins-archive /
+
+{% set placement_base_pip_packages = [
+ '/placement'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s placement-base-source/* placement \
+ && {{ macros.install_pip(placement_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/placement/ \
+ && cp -r /placement/etc/placement/* /etc/placement/ \
+ && touch /usr/local/bin/kolla_placement_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_placement_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block placement_base_footer %}{% endblock %}
diff --git a/kolla/docker/placement/placement-base/extend_start.sh b/kolla/docker/placement/placement-base/extend_start.sh
new file mode 100644
index 000000000..900ea8447
--- /dev/null
+++ b/kolla/docker/placement/placement-base/extend_start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/placement" ]]; then
+ mkdir -p /var/log/kolla/placement
+ touch /var/log/kolla/placement/placement-api.log
+fi
+if [[ $(stat -c %U:%G /var/log/kolla/placement) != "placement:kolla" ]]; then
+ chown -R placement:kolla /var/log/kolla/placement
+fi
+if [[ $(stat -c %a /var/log/kolla/placement) != "755" ]]; then
+ chmod 755 /var/log/kolla/placement
+ chmod 644 /var/log/kolla/placement/placement-api.log
+fi
+
+. /usr/local/bin/kolla_placement_extend_start
diff --git a/kolla/docker/prometheus/prometheus-alertmanager/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-alertmanager/Dockerfile.j2
new file mode 100644
index 000000000..8f2df0492
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-alertmanager/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_alertmanager_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_alertmanager_install %}
+ADD prometheus-alertmanager-archive /prometheus-alertmanager-source
+RUN cp -r /prometheus-alertmanager-source/* /opt/ \
+ && ln -s /opt/alertmanager* /opt/prometheus_alertmanager
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_alertmanager_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-base/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-base/Dockerfile.j2
new file mode 100644
index 000000000..123bd4ba3
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-base/Dockerfile.j2
@@ -0,0 +1,17 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='prometheus') }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_base_footer %}{% endblock %}
diff --git a/kolla/docker/prometheus/prometheus-base/extend_start.sh b/kolla/docker/prometheus/prometheus-base/extend_start.sh
new file mode 100644
index 000000000..8613330c6
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-base/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Create log directory, with appropriate permissions
+if [[ ! -d "/var/log/kolla/prometheus" ]]; then
+ mkdir -p /var/log/kolla/prometheus
+fi
+if [[ $(stat -c %a /var/log/kolla/prometheus) != "755" ]]; then
+ chmod 755 /var/log/kolla/prometheus
+fi
diff --git a/kolla/docker/prometheus/prometheus-blackbox-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-blackbox-exporter/Dockerfile.j2
new file mode 100644
index 000000000..95018facf
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-blackbox-exporter/Dockerfile.j2
@@ -0,0 +1,36 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_blackbox_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+ {% set blackbox_packages = [
+ 'libcap'
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set blackbox_packages = [
+ 'libcap2-bin'
+ ] %}
+{% endif %}
+
+{{ macros.install_packages(blackbox_packages | customizable("packages")) }}
+
+{% block prometheus_blackbox_exporter_install %}
+ADD prometheus-blackbox-exporter-archive /prometheus-blackbox-exporter-source
+
+RUN ln -s /prometheus-blackbox-exporter-source/* prometheus-blackbox-exporter \
+ && mkdir -p /opt/blackbox_exporter/ \
+ && cp /prometheus-blackbox-exporter/blackbox_exporter /opt/blackbox_exporter/ \
+ && setcap cap_net_raw+ep /opt/blackbox_exporter/blackbox_exporter
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_blackbox_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-cadvisor/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-cadvisor/Dockerfile.j2
new file mode 100644
index 000000000..884f4acd2
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-cadvisor/Dockerfile.j2
@@ -0,0 +1,20 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_cadvisor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_cadvisor_install %}
+ADD prometheus-cadvisor-archive /prometheus-cadvisor-source
+
+RUN cp /prometheus-cadvisor-source /opt/cadvisor \
+ && chmod 0755 /opt/cadvisor
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_cadvisor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/prometheus/prometheus-elasticsearch-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-elasticsearch-exporter/Dockerfile.j2
new file mode 100644
index 000000000..63d09409c
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-elasticsearch-exporter/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_elasticsearch_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_elasticsearch_exporter_install %}
+ADD prometheus-elasticsearch-exporter-archive /prometheus-elasticsearch-exporter-source
+
+RUN ln -s /prometheus-elasticsearch-exporter-source/* prometheus-elasticsearch-exporter \
+ && mkdir -p /opt/elasticsearch_exporter \
+ && cp /prometheus-elasticsearch-exporter/elasticsearch_exporter /opt/elasticsearch_exporter/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_elasticsearch_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-libvirt-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-libvirt-exporter/Dockerfile.j2
new file mode 100644
index 000000000..c32b2f8c7
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-libvirt-exporter/Dockerfile.j2
@@ -0,0 +1,25 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_libvirt_exporter_header %}{% endblock %}
+
+{{ macros.install_packages(prometheus_libvirt_exporter_packages | customizable("packages")) }}
+
+{% block prometheus_libvirt_exporter_install %}
+ADD prometheus-libvirt-exporter-archive /prometheus-libvirt-exporter-source
+
+RUN ln -s /prometheus-libvirt-exporter-source/* prometheus-libvirt-exporter \
+ && cp /prometheus-libvirt-exporter/prometheus-libvirt-exporter /opt/libvirt-exporter
+
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_libvirt_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER root
diff --git a/kolla/docker/prometheus/prometheus-memcached-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-memcached-exporter/Dockerfile.j2
new file mode 100644
index 000000000..cb50c2095
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-memcached-exporter/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_memcached_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_memcached_exporter_install %}
+ADD prometheus-memcached-exporter-archive /prometheus-memcached-exporter-source
+
+RUN ln -s /prometheus-memcached-exporter-source/* prometheus-memcached-exporter \
+ && mkdir -p /opt/memcached_exporter/ \
+ && cp /prometheus-memcached-exporter/memcached_exporter /opt/memcached_exporter/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_memcached_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-mtail/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-mtail/Dockerfile.j2
new file mode 100644
index 000000000..a562bfd71
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-mtail/Dockerfile.j2
@@ -0,0 +1,25 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_mtail_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_mtail_install %}
+ADD prometheus-mtail-archive /prometheus-mtail-source
+
+RUN cp /prometheus-mtail-source/* /opt/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_mtail_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+# NOTE(dszumski): We run as root so that mtail can scrape root owned logs
+# such as /var/log/secure which are mounted into the container as read only
+# for auditing purposes. Longer term we should use a more secure mechanism
+# for tailing these logs.
+USER root
diff --git a/kolla/docker/prometheus/prometheus-mysqld-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-mysqld-exporter/Dockerfile.j2
new file mode 100644
index 000000000..f8d2abaa6
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-mysqld-exporter/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block mysqld_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block mysqld_exporter_install %}
+ADD prometheus-mysqld-exporter-archive /prometheus-mysqld-exporter-source
+
+RUN ln -s /prometheus-mysqld-exporter-source/* prometheus-mysqld-exporter \
+ && mkdir -p /opt/mysqld_exporter \
+ && cp /prometheus-mysqld-exporter/mysqld_exporter /opt/mysqld_exporter/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block mysqld_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-node-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-node-exporter/Dockerfile.j2
new file mode 100644
index 000000000..3250d3d7a
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-node-exporter/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block node_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block node_exporter_install %}
+ADD prometheus-node-exporter-archive /prometheus-node-exporter-source
+
+RUN ln -s /prometheus-node-exporter-source/* prometheus-node-exporter \
+ && mkdir -p /opt/node_exporter/ \
+ && cp /prometheus-node-exporter/node_exporter /opt/node_exporter/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block node_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-openstack-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-openstack-exporter/Dockerfile.j2
new file mode 100644
index 000000000..437f3a6cd
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-openstack-exporter/Dockerfile.j2
@@ -0,0 +1,22 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_openstack_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_openstack_exporter_install %}
+ADD prometheus-openstack-exporter-archive /prometheus-openstack-exporter-source
+
+RUN mkdir /opt/openstack-exporter \
+ && cp /prometheus-openstack-exporter-source/openstack-exporter /opt/openstack-exporter/
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_openstack_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-openstack-network-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-openstack-network-exporter/Dockerfile.j2
new file mode 100644
index 000000000..3d4d9197b
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-openstack-network-exporter/Dockerfile.j2
@@ -0,0 +1,24 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_openstack_network_exporter_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_openstack_network_exporter_install %}
+ADD prometheus-openstack-network-exporter-archive /prometheus-openstack-network-exporter-source
+
+RUN mkdir /opt/openstack-network-exporter \
+ && cp /prometheus-openstack-network-exporter-source /opt/openstack-network-exporter/openstack-network-exporter \
+ && chmod 0755 /opt/openstack-network-exporter/openstack-network-exporter
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_openstack_network_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+# NOTE: OVN/OVS are running as root. Since we need to read sockets owned by
+# these services we don't switch to the Prometheus user here.
diff --git a/kolla/docker/prometheus/prometheus-server/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-server/Dockerfile.j2
new file mode 100644
index 000000000..a3b227d7e
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-server/Dockerfile.j2
@@ -0,0 +1,24 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block prometheus_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_server_install %}
+ADD prometheus-server-archive /prometheus-server-source
+
+RUN ln -s /prometheus-server-source/* prometheus-server \
+ && mkdir -p /opt/prometheus \
+ && cp -r /prometheus-server/* /opt/prometheus/ \
+ && mkdir -p /etc/prometheus /data
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/prometheus/prometheus-valkey-exporter/Dockerfile.j2 b/kolla/docker/prometheus/prometheus-valkey-exporter/Dockerfile.j2
new file mode 100644
index 000000000..c6cf79ac3
--- /dev/null
+++ b/kolla/docker/prometheus/prometheus-valkey-exporter/Dockerfile.j2
@@ -0,0 +1,25 @@
+FROM {{ namespace }}/{{ image_prefix }}prometheus-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% block prometheus_valkey_exporter_header %}{% endblock %}
+
+{{ macros.install_packages(prometheus_valkey_exporter_packages | customizable("packages")) }}
+
+{% block prometheus_valkey_exporter_install %}
+ADD prometheus-valkey-exporter-archive /prometheus-valkey-exporter-source
+
+RUN ln -s /prometheus-valkey-exporter-source/* prometheus-valkey-exporter \
+ && cp /prometheus-valkey-exporter/redis_exporter /opt/valkey-exporter
+
+{% endblock %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block prometheus_valkey_exporter_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER prometheus
diff --git a/kolla/docker/proxysql/Dockerfile.j2 b/kolla/docker/proxysql/Dockerfile.j2
new file mode 100644
index 000000000..a76dbcda8
--- /dev/null
+++ b/kolla/docker/proxysql/Dockerfile.j2
@@ -0,0 +1,43 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block proxysql_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.enable_extra_repos(['proxysql']) }}
+
+{% set proxysql_packages = [
+ 'proxysql',
+] %}
+
+{% if base_package_type == 'rpm' %}
+ {% set proxysql_packages = proxysql_packages + [
+ 'python3-PyYAML',
+ ] %}
+{% elif base_package_type == 'deb' %}
+ {% set proxysql_packages = proxysql_packages + [
+ 'python3-yaml',
+ ] %}
+{% endif %}
+
+{{ macros.configure_user(name='proxysql') }}
+{{ macros.install_packages(proxysql_packages | customizable("packages")) }}
+
+COPY kolla_proxysql_config_sync kolla_extend_start /usr/local/bin/
+RUN chmod 755 /usr/local/bin/kolla_proxysql_config_sync \
+ && chmod 644 /usr/local/bin/kolla_extend_start
+
+RUN install -g proxysql -o proxysql -m 770 -d /var/lib/kolla/proxysql \
+ && touch /etc/proxysql.cnf \
+ && chown proxysql:proxysql /etc/proxysql.cnf \
+ && chmod 600 /etc/proxysql.cnf
+
+{{ macros.kolla_patch_sources() }}
+
+{% block proxysql_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER proxysql
diff --git a/kolla/docker/proxysql/kolla_extend_start b/kolla/docker/proxysql/kolla_extend_start
new file mode 100644
index 000000000..229e8ada2
--- /dev/null
+++ b/kolla/docker/proxysql/kolla_extend_start
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+PROXYSQL_LIB_DIR="/var/lib/proxysql"
+PROXYSQL_LOG_DIR="/var/log/kolla/proxysql"
+
+if [[ ! -d "${PROXYSQL_LOG_DIR}" ]]; then
+ mkdir -p "${PROXYSQL_LOG_DIR}"
+fi
+chown -R proxysql:kolla "${PROXYSQL_LOG_DIR}"
+
+# Remove old pid
+rm -f ${PROXYSQL_LIB_DIR}/proxysql.pid
+
+# As proxysql supports only one
+# configuration file, there is a
+# script which reads global and services
+# configs (similar to haproxy) and
+# generate single configuration file
+# from which is internal DB created
+# every time.
+kolla_proxysql_config_sync
diff --git a/kolla/docker/proxysql/kolla_proxysql_config_sync b/kolla/docker/proxysql/kolla_proxysql_config_sync
new file mode 100644
index 000000000..094642c8a
--- /dev/null
+++ b/kolla/docker/proxysql/kolla_proxysql_config_sync
@@ -0,0 +1,162 @@
+#!/usr/bin/python3
+
+# Copyright 2021 Michal Arbet
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import yaml
+
+# Default paths
+PROXYSQL_CONFIG_DIR = "/etc/proxysql"
+PROXYSQL_CONFIG = "/etc/proxysql.cnf"
+
+# Logging
+log_format = '%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s'
+logging.basicConfig(format=log_format,
+ datefmt='%H:%M:%S',
+ level=logging.DEBUG)
+LOG = logging.getLogger("proxysql_config_sync")
+
+
+class ProxySQLConfig:
+
+ def __init__(self, conf_dir, conf_file):
+ self.configs = dict()
+ self.config = dict()
+ self.configs['global'] = "{}/proxysql.yaml".format(conf_dir)
+ self.configs['users'] = "{}/users".format(conf_dir)
+ self.configs['rules'] = "{}/rules".format(conf_dir)
+ self.conf_file = conf_file
+ self._load_config()
+
+ def _load_config(self):
+ users = dict()
+ rules = dict()
+
+ for cfg, path in self.configs.items():
+ if not os.path.isdir(path):
+ with open(path) as config_file:
+ self.config.update(
+ yaml.safe_load(config_file))
+ else:
+ users['mysql_users'] = list()
+ rules['mysql_query_rules'] = list()
+ user_paths = [os.path.join(self.configs['users'], f)
+ for f in os.listdir(self.configs['users'])]
+ rule_paths = [os.path.join(self.configs['rules'], f)
+ for f in os.listdir(self.configs['rules'])]
+
+ for user_conf in user_paths:
+ with open(user_conf) as config_file:
+ tmp_users = yaml.safe_load(config_file)
+ for i in tmp_users['mysql_users']:
+ users['mysql_users'].append(i)
+ self.config.update(users)
+ for rule_conf in rule_paths:
+ with open(rule_conf) as config_file:
+ tmp_rules = yaml.safe_load(config_file)
+ for i in tmp_rules['mysql_query_rules']:
+ rules['mysql_query_rules'].append(i)
+ self.config.update(rules)
+ self._sanity()
+
+ def _sanity(self):
+ self._users_sanity()
+ self._rules_sanity()
+
+ def _users_sanity(self):
+ users_added = list()
+ users = list()
+ for user in self.config['mysql_users']:
+ if user['username'] not in users_added:
+ users_added.append(user['username'])
+ users.append(user)
+ else:
+ LOG.warning("User {} already exist, ignoring."
+ .format(user['username']))
+ self.config['mysql_users'] = users
+
+ def _rules_sanity(self):
+ rules_added = list()
+ rules = list()
+ rule_id = 1
+ for rule in self.config['mysql_query_rules']:
+ if 'schemaname' in rule:
+ key = f"schema:{rule['schemaname']}"
+ elif 'username' in rule:
+ key = f"user:{rule['username']}"
+ else:
+ LOG.warning(
+ f"Rule without schemaname or username found, skipping: {rule}"
+ )
+ continue
+
+ if key not in rules_added:
+ rules_added.append(key)
+ rule['rule_id'] = rule_id
+ rules.append(rule)
+ rule_id += 1
+ else:
+ LOG.warning("Duplicate rule for {}, ignoring.".format(key))
+
+ self.config['mysql_query_rules'] = rules
+
+ def _write_dict(self, key, value):
+ if not isinstance(value, list):
+ value = [value]
+ with open(self.conf_file, "a+") as f:
+ if key:
+ f.write("{} =\n".format(key))
+ for i in range(len(value)):
+ f.write(" {\n")
+ for k, v in value[i].items():
+ if isinstance(v, str):
+ v = '"{}"'.format(v)
+ f.write(" {} = {}\n".format(k, v))
+ if i == len(value)-1:
+ f.write(" }\n")
+ else:
+ f.write(" },\n")
+
+ def _write_list(self, key, values):
+ with open(self.conf_file, "a+") as f:
+ f.write("{} =\n".format(key))
+ f.write("(\n")
+ self._write_dict(key=None, value=values)
+ with open(self.conf_file, "a+") as f:
+ f.write(")\n")
+
+ def _write(self, key, value):
+ with open(self.conf_file, "a+") as f:
+ if isinstance(value, str):
+ value = '"{}"'.format(value)
+ f.write("{} = {}\n".format(key, value))
+
+ def write_config(self):
+ LOG.info("Writing config to {}".format(self.conf_file))
+ with open(self.conf_file, 'wb') as f:
+ pass
+ for k, v in self.config.items():
+ if isinstance(v, dict):
+ self._write_dict(k, v)
+ elif isinstance(v, list):
+ self._write_list(k, v)
+ else:
+ self._write(k, v)
+
+
+if __name__ == "__main__":
+ config = ProxySQLConfig(PROXYSQL_CONFIG_DIR, PROXYSQL_CONFIG)
+ config.write_config()
diff --git a/kolla/docker/rabbitmq/Dockerfile.j2 b/kolla/docker/rabbitmq/Dockerfile.j2
new file mode 100644
index 000000000..a167356a7
--- /dev/null
+++ b/kolla/docker/rabbitmq/Dockerfile.j2
@@ -0,0 +1,69 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block rabbitmq_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='rabbitmq') }}
+
+{{ macros.enable_extra_repos(['erlang', 'rabbitmq']) }}
+
+{% block rabbitmq_apt_preferences %}
+{% if base_package_type == 'deb' %}
+COPY apt_preferences_rabbitmq.{{ base_distro }} /etc/apt/preferences.d/rabbitmq
+{% endif %}
+{% endblock %}
+
+{% block rabbitmq_install %}
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['crb']) }}
+
+ {% set rabbitmq_packages = [
+ 'erlang-27.*',
+ 'rabbitmq-server-4.2.*'
+ ] %}
+
+{% elif base_package_type == 'deb' %}
+ {% set rabbitmq_packages = [
+ 'logrotate',
+ 'rabbitmq-server',
+ ] %}
+
+{% endif %}
+
+{{ macros.install_packages(rabbitmq_packages | customizable("packages")) }}
+
+{% endblock %}
+
+{% block rabbitmq_install_plugins %}
+
+RUN rm -rf /var/lib/rabbitmq/* \
+ && mkdir -p /usr/lib/rabbitmq/plugins \
+ && /usr/lib/rabbitmq/bin/rabbitmq-plugins enable --offline \
+ rabbitmq_management \
+ rabbitmq_prometheus
+
+{% endblock %}
+
+{% if docker_healthchecks %}
+{% block healthcheck_installation %}
+
+COPY healthcheck_rabbitmq /usr/local/bin/healthcheck_rabbitmq
+RUN chmod 755 /usr/local/bin/healthcheck_rabbitmq
+
+{% endblock %}
+{% endif %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block rabbitmq_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER rabbitmq
diff --git a/kolla/docker/rabbitmq/apt_preferences_rabbitmq.debian b/kolla/docker/rabbitmq/apt_preferences_rabbitmq.debian
new file mode 100644
index 000000000..cc6404b3e
--- /dev/null
+++ b/kolla/docker/rabbitmq/apt_preferences_rabbitmq.debian
@@ -0,0 +1,7 @@
+Package: rabbitmq-server
+Pin: version 4.2.*
+Pin-Priority: 1000
+
+Package: erlang*
+Pin: version 1:27.*
+Pin-Priority: 1000
diff --git a/kolla/docker/rabbitmq/apt_preferences_rabbitmq.ubuntu b/kolla/docker/rabbitmq/apt_preferences_rabbitmq.ubuntu
new file mode 100644
index 000000000..cc6404b3e
--- /dev/null
+++ b/kolla/docker/rabbitmq/apt_preferences_rabbitmq.ubuntu
@@ -0,0 +1,7 @@
+Package: rabbitmq-server
+Pin: version 4.2.*
+Pin-Priority: 1000
+
+Package: erlang*
+Pin: version 1:27.*
+Pin-Priority: 1000
diff --git a/kolla/docker/rabbitmq/extend_start.sh b/kolla/docker/rabbitmq/extend_start.sh
new file mode 100644
index 000000000..ef28d2b5f
--- /dev/null
+++ b/kolla/docker/rabbitmq/extend_start.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+: ${RABBITMQ_LOG_DIR:=/var/log/kolla/rabbitmq}
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+
+# NOTE(sbezverk): In kubernetes environment, if this file exists from previous
+# bootstrap, the system does not allow to overwrite it (it bootstrap files with
+# permission denied error) but it allows to delete it and then recreate it.
+ if [[ -e "/var/lib/rabbitmq/.erlang.cookie" ]]; then
+ rm -f /var/lib/rabbitmq/.erlang.cookie
+ fi
+ echo "${RABBITMQ_CLUSTER_COOKIE}" > /var/lib/rabbitmq/.erlang.cookie
+ chmod 400 /var/lib/rabbitmq/.erlang.cookie
+ exit 0
+fi
+
+if [[ ! -d "${RABBITMQ_LOG_DIR}" ]]; then
+ mkdir -p ${RABBITMQ_LOG_DIR}
+fi
+
+if [[ $(stat -c %a ${RABBITMQ_LOG_DIR}) != "755" ]]; then
+ chmod 755 ${RABBITMQ_LOG_DIR}
+fi
diff --git a/kolla/docker/rabbitmq/healthcheck_rabbitmq b/kolla/docker/rabbitmq/healthcheck_rabbitmq
new file mode 100644
index 000000000..277ab098c
--- /dev/null
+++ b/kolla/docker/rabbitmq/healthcheck_rabbitmq
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -e
+
+for check in ping status check_running check_local_alarms check_port_connectivity check_virtual_hosts; do
+ echo "Running rabbitmq-diagnostics $check"
+ rabbitmq-diagnostics -t 5 -q $check || (echo "ERROR: $check failed" && exit 1)
+done
diff --git a/kolla/docker/skyline/skyline-apiserver/Dockerfile.j2 b/kolla/docker/skyline/skyline-apiserver/Dockerfile.j2
new file mode 100644
index 000000000..85b40d51c
--- /dev/null
+++ b/kolla/docker/skyline/skyline-apiserver/Dockerfile.j2
@@ -0,0 +1,27 @@
+FROM {{ namespace }}/{{ image_prefix }}skyline-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block skyline_apiserver_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+ADD skyline-apiserver-archive /skyline-apiserver-source
+
+{% set skyline_apiserver_pip_packages = [
+ '/skyline-apiserver'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_skyline_extend_start
+
+RUN ln -s skyline-apiserver-source/* skyline-apiserver \
+ && {{ macros.install_pip(skyline_apiserver_pip_packages | customizable("pip_packages")) }} \
+ && chmod 644 /usr/local/bin/kolla_skyline_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block skyline_apiserver_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER skyline
diff --git a/kolla/docker/skyline/skyline-apiserver/extend_start.sh b/kolla/docker/skyline/skyline-apiserver/extend_start.sh
new file mode 100644
index 000000000..e1802937b
--- /dev/null
+++ b/kolla/docker/skyline/skyline-apiserver/extend_start.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ SITE_PACKAGES="/var/lib/kolla/venv/lib/python3/site-packages"
+ pushd ${SITE_PACKAGES}/skyline_apiserver/db/alembic
+ alembic upgrade head
+ popd
+ exit 0
+fi
diff --git a/kolla/docker/skyline/skyline-base/Dockerfile.j2 b/kolla/docker/skyline/skyline-base/Dockerfile.j2
new file mode 100644
index 000000000..815fa504f
--- /dev/null
+++ b/kolla/docker/skyline/skyline-base/Dockerfile.j2
@@ -0,0 +1,19 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block skyline_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='skyline') }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN touch /usr/local/bin/kolla_skyline_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_skyline_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block skyline_base_footer %}{% endblock %}
diff --git a/kolla/docker/skyline/skyline-base/extend_start.sh b/kolla/docker/skyline/skyline-base/extend_start.sh
new file mode 100644
index 000000000..34362ad04
--- /dev/null
+++ b/kolla/docker/skyline/skyline-base/extend_start.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/skyline" ]]; then
+ mkdir -p /var/log/kolla/skyline
+fi
+if [[ $(stat -c %U:%G /var/log/kolla/skyline) != "skyline:kolla" ]]; then
+ chown skyline:kolla /var/log/kolla/skyline
+fi
+if [[ $(stat -c %a /var/log/kolla/skyline) != "755" ]]; then
+ chmod 755 /var/log/kolla/skyline
+fi
+
+if [[ ! -d "/var/lib/skyline" ]]; then
+ mkdir -p /var/lib/skyline
+fi
+if [[ $(stat -c %U:%G /var/lib/skyline) != "skyline:kolla" ]]; then
+ chown skyline:kolla /var/lib/skyline
+fi
+if [[ $(stat -c %a /var/lib/skyline) != "755" ]]; then
+ chmod 755 /var/lib/skyline
+fi
+
+. /usr/local/bin/kolla_skyline_extend_start
diff --git a/kolla/docker/skyline/skyline-console/Dockerfile.j2 b/kolla/docker/skyline/skyline-console/Dockerfile.j2
new file mode 100644
index 000000000..ba62f5041
--- /dev/null
+++ b/kolla/docker/skyline/skyline-console/Dockerfile.j2
@@ -0,0 +1,30 @@
+FROM {{ namespace }}/{{ image_prefix }}skyline-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block skyline_console_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% set skyline_console_packages = [
+ 'nginx'
+] %}
+
+{{ macros.install_packages(skyline_console_packages | customizable("packages")) }}
+
+ADD skyline-console-archive /skyline-console-source
+
+{% set skyline_console_pip_packages = [
+ '/skyline-console'
+] %}
+
+RUN ln -s skyline-console-source/* skyline-console \
+ && {{ macros.install_pip(skyline_console_pip_packages | customizable("pip_packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block skyline_console_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER root
diff --git a/kolla/docker/tacker/tacker-base/Dockerfile.j2 b/kolla/docker/tacker/tacker-base/Dockerfile.j2
new file mode 100644
index 000000000..c24c8d87c
--- /dev/null
+++ b/kolla/docker/tacker/tacker-base/Dockerfile.j2
@@ -0,0 +1,43 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block tacker_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='tacker') }}
+
+{% if base_package_type == 'rpm' %}
+
+{{ macros.enable_extra_repos(['openvswitch']) }}
+
+{% endif %}
+
+ADD tacker-base-archive /tacker-base-source
+ADD plugins-archive /
+
+{% set tacker_base_pip_packages = [
+ '/tacker'
+] %}
+
+{% set tacker_base_plugins_pip_packages = [
+ '/plugins/*'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s tacker-base-source/* tacker \
+ && {{ macros.install_pip(tacker_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/tacker \
+ && cp -r /tacker/etc/tacker/* /etc/tacker \
+ && if [ "$(ls /plugins)" ]; then \
+ {{ macros.install_pip(tacker_base_plugins_pip_packages) }}; \
+ fi \
+ && touch /usr/local/bin/kolla_tacker_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_tacker_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block tacker_base_footer %}{% endblock %}
diff --git a/kolla/docker/tacker/tacker-base/extend_start.sh b/kolla/docker/tacker/tacker-base/extend_start.sh
new file mode 100644
index 000000000..a3b7db8d3
--- /dev/null
+++ b/kolla/docker/tacker/tacker-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/tacker" ]]; then
+ mkdir -p /var/log/kolla/tacker
+fi
+if [[ $(stat -c %a /var/log/kolla/tacker) != "755" ]]; then
+ chmod 755 /var/log/kolla/tacker
+fi
+
+. /usr/local/bin/kolla_tacker_extend_start
diff --git a/kolla/docker/tacker/tacker-conductor/Dockerfile.j2 b/kolla/docker/tacker/tacker-conductor/Dockerfile.j2
new file mode 100644
index 000000000..676c90106
--- /dev/null
+++ b/kolla/docker/tacker/tacker-conductor/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}tacker-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block tacker_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block tacker_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER tacker
diff --git a/kolla/docker/tacker/tacker-server/Dockerfile.j2 b/kolla/docker/tacker/tacker-server/Dockerfile.j2
new file mode 100644
index 000000000..659b70bea
--- /dev/null
+++ b/kolla/docker/tacker/tacker-server/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}tacker-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block tacker_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_tacker_extend_start
+RUN chmod 644 /usr/local/bin/kolla_tacker_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block tacker_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER tacker
diff --git a/kolla/docker/tacker/tacker-server/extend_start.sh b/kolla/docker/tacker/tacker-server/extend_start.sh
new file mode 100644
index 000000000..311d21f71
--- /dev/null
+++ b/kolla/docker/tacker/tacker-server/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ tacker-db-manage --config-file /etc/tacker/tacker.conf upgrade head
+ exit 0
+fi
diff --git a/kolla/docker/tgtd/Dockerfile.j2 b/kolla/docker/tgtd/Dockerfile.j2
new file mode 100644
index 000000000..b8cbbaaf5
--- /dev/null
+++ b/kolla/docker/tgtd/Dockerfile.j2
@@ -0,0 +1,26 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block tgtd_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+
+RUN echo 'Not available for {{ base_distro }}' \
+ && /bin/false
+
+{% elif base_package_type == 'deb' %}
+ {% set tgtd_packages = ['tgt'] %}
+
+{{ macros.install_packages(tgtd_packages | customizable("packages")) }}
+RUN sed -i '1 i include /var/lib/cinder/volumes/*' /etc/tgt/targets.conf
+
+{% endif %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block tgtd_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/trove/trove-api/Dockerfile.j2 b/kolla/docker/trove/trove-api/Dockerfile.j2
new file mode 100644
index 000000000..8d2885b86
--- /dev/null
+++ b/kolla/docker/trove/trove-api/Dockerfile.j2
@@ -0,0 +1,16 @@
+FROM {{ namespace }}/{{ image_prefix }}trove-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block trove_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_trove_extend_start
+RUN chmod 644 /usr/local/bin/kolla_trove_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block trove_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
diff --git a/kolla/docker/trove/trove-api/extend_start.sh b/kolla/docker/trove/trove-api/extend_start.sh
new file mode 100644
index 000000000..76839e2d4
--- /dev/null
+++ b/kolla/docker/trove/trove-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ trove-manage db_sync
+ exit 0
+fi
diff --git a/kolla/docker/trove/trove-base/Dockerfile.j2 b/kolla/docker/trove/trove-base/Dockerfile.j2
new file mode 100644
index 000000000..a675b2b72
--- /dev/null
+++ b/kolla/docker/trove/trove-base/Dockerfile.j2
@@ -0,0 +1,30 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block trove_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='trove') }}
+
+ADD trove-base-archive /trove-base-source
+
+{% set trove_base_pip_packages = [
+ '/trove'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s trove-base-source/* trove \
+ && {{ macros.install_pip(trove_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/trove \
+ && cp -r /trove/etc/trove/* /etc/trove/ \
+ && cp /var/lib/kolla/venv/etc/pycadf/trove_api_audit_map.conf /etc/trove/ \
+ && touch /usr/local/bin/kolla_trove_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_trove_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block trove_base_footer %}{% endblock %}
diff --git a/kolla/docker/trove/trove-base/extend_start.sh b/kolla/docker/trove/trove-base/extend_start.sh
new file mode 100644
index 000000000..581a83d7b
--- /dev/null
+++ b/kolla/docker/trove/trove-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/trove" ]]; then
+ mkdir -p /var/log/kolla/trove
+fi
+if [[ $(stat -c %a /var/log/kolla/trove) != "755" ]]; then
+ chmod 755 /var/log/kolla/trove
+fi
+
+. /usr/local/bin/kolla_trove_extend_start
diff --git a/kolla/docker/trove/trove-conductor/Dockerfile.j2 b/kolla/docker/trove/trove-conductor/Dockerfile.j2
new file mode 100644
index 000000000..4a038c7f9
--- /dev/null
+++ b/kolla/docker/trove/trove-conductor/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}trove-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block trove_conductor_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block trove_conductor_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER trove
diff --git a/kolla/docker/trove/trove-guestagent/Dockerfile.j2 b/kolla/docker/trove/trove-guestagent/Dockerfile.j2
new file mode 100644
index 000000000..178260cae
--- /dev/null
+++ b/kolla/docker/trove/trove-guestagent/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}trove-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block trove_guestagent_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block trove_guestagent_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER trove
diff --git a/kolla/docker/trove/trove-taskmanager/Dockerfile.j2 b/kolla/docker/trove/trove-taskmanager/Dockerfile.j2
new file mode 100644
index 000000000..cef0a0ce9
--- /dev/null
+++ b/kolla/docker/trove/trove-taskmanager/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}trove-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block trove_taskmanager_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block trove_taskmanager_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER trove
diff --git a/kolla/docker/valkey/valkey-base/Dockerfile.j2 b/kolla/docker/valkey/valkey-base/Dockerfile.j2
new file mode 100644
index 000000000..bc17c0cbb
--- /dev/null
+++ b/kolla/docker/valkey/valkey-base/Dockerfile.j2
@@ -0,0 +1,17 @@
+FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block valkey_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='valkey', homedir='/run/valkey') }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block valkey_base_footer %}{% endblock %}
diff --git a/kolla/docker/valkey/valkey-base/extend_start.sh b/kolla/docker/valkey/valkey-base/extend_start.sh
new file mode 100644
index 000000000..3a3a321d4
--- /dev/null
+++ b/kolla/docker/valkey/valkey-base/extend_start.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/valkey" ]]; then
+ mkdir -p /var/log/kolla/valkey
+fi
+
+if [[ $(stat -c %a /var/log/kolla/valkey) != "755" ]]; then
+ chmod 755 /var/log/kolla/valkey
+fi
diff --git a/kolla/docker/valkey/valkey-sentinel/Dockerfile.j2 b/kolla/docker/valkey/valkey-sentinel/Dockerfile.j2
new file mode 100644
index 000000000..94828a38f
--- /dev/null
+++ b/kolla/docker/valkey/valkey-sentinel/Dockerfile.j2
@@ -0,0 +1,26 @@
+FROM {{ namespace }}/{{ image_prefix }}valkey-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block valkey_sentinel_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+{{ macros.enable_extra_repos(['epel']) }}
+ {% set valkey_sentinel_packages = ['valkey'] %}
+{% elif base_package_type == 'deb' %}
+ {% set valkey_sentinel_packages = ['valkey-sentinel'] %}
+{% endif %}
+{{ macros.install_packages(valkey_sentinel_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 644 /usr/local/bin/kolla_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block valkey_sentinel_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER valkey
diff --git a/kolla/docker/valkey/valkey-sentinel/extend_start.sh b/kolla/docker/valkey/valkey-sentinel/extend_start.sh
new file mode 100644
index 000000000..e6af42ef6
--- /dev/null
+++ b/kolla/docker/valkey/valkey-sentinel/extend_start.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/valkey" ]]; then
+ mkdir -p /var/log/kolla/valkey
+fi
+
+if [[ $(stat -c %a /var/log/kolla/valkey) != "755" ]]; then
+ chmod 755 /var/log/kolla/valkey
+fi
+
+# The CONFIG REWRITE command rewrites the valkey.conf
+# file the server was started with, applying the minimal
+# changes needed to make it reflect the configuration
+# currently used by the server, which may be different
+# compared to the original one because of the use of
+# the CONFIG SET command.
+#
+# https://valkey.io/commands/config-rewrite/
+#
+# Because of above behaviour it's needed to
+# hack kolla's CMD.
+#
+# Without this hack
+# /usr/local/bin/kolla_set_configs --check
+# is always reporting changed.
+#
+# Therefore valkey-sentinel is always restarted
+# even if configuration is not changed from
+# kolla-ansible side.
+if [ ! -z "${VALKEY_CONF}" ] && [ ! -z ${VALKEY_GEN_CONF} ]; then
+ cp ${VALKEY_CONF} ${VALKEY_GEN_CONF}
+fi
diff --git a/kolla/docker/valkey/valkey-server/Dockerfile.j2 b/kolla/docker/valkey/valkey-server/Dockerfile.j2
new file mode 100644
index 000000000..be157d89e
--- /dev/null
+++ b/kolla/docker/valkey/valkey-server/Dockerfile.j2
@@ -0,0 +1,23 @@
+FROM {{ namespace }}/{{ image_prefix }}valkey-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block valkey_server_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{% if base_package_type == 'rpm' %}
+{{ macros.enable_extra_repos(['epel']) }}
+{% endif %}
+
+{% set valkey_server_packages = ['valkey'] %}
+
+{{ macros.install_packages(valkey_server_packages | customizable("packages")) }}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block valkey_server_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER valkey
diff --git a/kolla/docker/watcher/watcher-api/Dockerfile.j2 b/kolla/docker/watcher/watcher-api/Dockerfile.j2
new file mode 100644
index 000000000..f31b53ecf
--- /dev/null
+++ b/kolla/docker/watcher/watcher-api/Dockerfile.j2
@@ -0,0 +1,18 @@
+FROM {{ namespace }}/{{ image_prefix }}watcher-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block watcher_api_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+COPY extend_start.sh /usr/local/bin/kolla_watcher_extend_start
+RUN chmod 644 /usr/local/bin/kolla_watcher_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block watcher_api_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER watcher
diff --git a/kolla/docker/watcher/watcher-api/extend_start.sh b/kolla/docker/watcher/watcher-api/extend_start.sh
new file mode 100644
index 000000000..629ea6355
--- /dev/null
+++ b/kolla/docker/watcher/watcher-api/extend_start.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Bootstrap and exit if KOLLA_BOOTSTRAP variable is set. This catches all cases
+# of the KOLLA_BOOTSTRAP variable being set, including empty.
+if [[ "${!KOLLA_BOOTSTRAP[@]}" ]]; then
+ watcher-db-manage --config-file /etc/watcher/watcher.conf upgrade
+ exit 0
+fi
diff --git a/kolla/docker/watcher/watcher-applier/Dockerfile.j2 b/kolla/docker/watcher/watcher-applier/Dockerfile.j2
new file mode 100644
index 000000000..db28d81d9
--- /dev/null
+++ b/kolla/docker/watcher/watcher-applier/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}watcher-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block watcher_applier_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block watcher_applier_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER watcher
diff --git a/kolla/docker/watcher/watcher-base/Dockerfile.j2 b/kolla/docker/watcher/watcher-base/Dockerfile.j2
new file mode 100644
index 000000000..8de6ceb9d
--- /dev/null
+++ b/kolla/docker/watcher/watcher-base/Dockerfile.j2
@@ -0,0 +1,29 @@
+FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block watcher_base_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.configure_user(name='watcher') }}
+
+ADD watcher-base-archive /watcher-base-source
+
+{% set watcher_base_pip_packages = [
+ '/watcher'
+] %}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+
+RUN ln -s watcher-base-source/* watcher \
+ && {{ macros.install_pip(watcher_base_pip_packages | customizable("pip_packages")) }} \
+ && mkdir -p /etc/watcher \
+ && cp -r /watcher/etc/watcher/* /etc/watcher/ \
+ && touch /usr/local/bin/kolla_watcher_extend_start \
+ && chmod 644 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_watcher_extend_start
+
+{{ macros.kolla_patch_sources() }}
+
+{% block watcher_base_footer %}{% endblock %}
diff --git a/kolla/docker/watcher/watcher-base/extend_start.sh b/kolla/docker/watcher/watcher-base/extend_start.sh
new file mode 100644
index 000000000..9e27a9cf0
--- /dev/null
+++ b/kolla/docker/watcher/watcher-base/extend_start.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [[ ! -d "/var/log/kolla/watcher" ]]; then
+ mkdir -p /var/log/kolla/watcher
+fi
+if [[ $(stat -c %a /var/log/kolla/watcher) != "755" ]]; then
+ chmod 755 /var/log/kolla/watcher
+fi
+
+. /usr/local/bin/kolla_watcher_extend_start
diff --git a/kolla/docker/watcher/watcher-engine/Dockerfile.j2 b/kolla/docker/watcher/watcher-engine/Dockerfile.j2
new file mode 100644
index 000000000..2518a92b6
--- /dev/null
+++ b/kolla/docker/watcher/watcher-engine/Dockerfile.j2
@@ -0,0 +1,15 @@
+FROM {{ namespace }}/{{ image_prefix }}watcher-base:{{ tag }}
+{% block labels %}
+LABEL maintainer="{{ maintainer }}" name="{{ image_name }}"
+{% endblock %}
+
+{% block watcher_engine_header %}{% endblock %}
+
+{% import "macros.j2" as macros with context %}
+
+{{ macros.kolla_patch_sources() }}
+
+{% block watcher_engine_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+
+USER watcher