#
# Copyright:: Copyright (c) 2016 GitLab Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require_relative 'nginx.rb'

module Registry
  class << self
    def parse_variables
      # first registry_external_url
      parse_registry_external_url
      # Enable the registry automatically if appropriate
      auto_enable
      # before this gitlab_rails[registry_path] needs to be parsed
      parse_registry
      # parsing the registry notifications
      parse_registry_notifications
      parse_database_configuration
    end

    ##
    # If registry_external_url is not set, we're using Let's Encrypt integration,
    # and the registry isn't explicitly disabled, we automatically enable the registry
    def auto_enable
      # If this is not nil, then it has been explicitly set and we should honor that
      return unless Gitlab['registry']['enable'].nil?

      # If registry_external_url is not nil, it has been explicitly set, and we should honor that
      return unless Gitlab['registry_external_url'].nil?

      # If Let's Encrypt is not enabled, we don't want to run
      return unless Gitlab['letsencrypt']['enable']

      uri = URI(Gitlab['external_url'].to_s)

      # Don't auto enable for relative urls
      return unless Gitlab['gitlab_rails']['gitlab_relative_url'].nil?

      Gitlab['registry']['enable'] = true
      Gitlab['registry_nginx']['listen_port'] ||= '5050'
      Gitlab['registry_nginx']['redirect_http_to_https'] = false
      Gitlab['registry_nginx']['fqdn'] = uri.host
      Gitlab['gitlab_rails']['registry_port'] ||= Gitlab['registry_nginx']['listen_port']
      set_ssl(uri.to_s)
      parse_defaults(uri)
    end

    def parse_secrets
      Gitlab['registry']['http_secret'] ||= SecretsHelper.generate_hex(64)
      gitlab_registry_crt, gitlab_registry_key = Registry.generate_registry_keypair
      Gitlab['registry']['internal_certificate'] ||= gitlab_registry_crt
      Gitlab['registry']['internal_key'] ||= gitlab_registry_key
    end

    def parse_registry_external_url
      return unless Gitlab['registry_external_url']

      uri = URI(Gitlab['registry_external_url'].to_s)

      raise "GitLab Container Registry external URL must include a schema and FQDN, e.g. https://registry.example.com/" unless uri.host

      Gitlab['registry']['enable'] = true if Gitlab['registry']['enable'].nil?
      parse_defaults(uri)

      set_ssl(uri.to_s)
      LetsEncryptHelper.add_service_alt_name("registry")
    end

    def parse_defaults(uri)
      Gitlab['gitlab_rails']['registry_enabled'] = true if Gitlab['registry']['enable']
      Gitlab['gitlab_rails']['registry_host'] = uri.host
      Gitlab['registry_nginx']['listen_port'] ||= uri.port
      Gitlab['registry']['registry_http_addr'] ||= "127.0.0.1:5000"
      Gitlab['registry']['registry_http_addr'].gsub(/^https?\:\/\/(www.)?/, '')
      Gitlab['registry']['token_realm'] ||= Gitlab['external_url']
      Gitlab['gitlab_rails']['registry_api_url'] ||= "http://#{Gitlab['registry']['registry_http_addr']}"
    end

    def set_ssl(url)
      uri = URI(url)

      case uri.scheme
      when "http"
        Gitlab['registry_nginx']['https'] ||= false
        Nginx.parse_proxy_headers('registry_nginx', false)
      when "https"
        Gitlab['registry_nginx']['https'] ||= true
        Gitlab['registry_nginx']['ssl_certificate'] ||= "/etc/gitlab/ssl/#{uri.host}.crt"
        Gitlab['registry_nginx']['ssl_certificate_key'] ||= "/etc/gitlab/ssl/#{uri.host}.key"

        Nginx.parse_proxy_headers('registry_nginx', true)
      else
        raise "Unsupported GitLab Registry external URL scheme: #{uri.scheme}"
      end

      raise "Unsupported GitLab Registry external URL path: #{uri.path}" unless ["", "/"].include?(uri.path)

      # Docker versions before 1.13 will fail to authenticate/push with the
      # registry if Registry URL contained :80 or :443. So, we don't set the
      # port in gitlab.yml.
      Gitlab['gitlab_rails']['registry_port'] = uri.port unless [80, 443].include?(uri.port)
    end

    def parse_registry
      return unless Gitlab['registry']['enable']

      Gitlab['gitlab_rails']['registry_path'] = "#{Gitlab['gitlab_rails']['shared_path']}/registry" if Gitlab['gitlab_rails']['registry_path'].nil?
      Gitlab['registry']['storage_delete_enabled'] = true if Gitlab['registry']['storage_delete_enabled'].nil?
      Gitlab['registry']['health_storagedriver_enabled'] = true if Gitlab['registry']['health_storagedriver_enabled'].nil?

      Gitlab['registry']['storage'] ||= {
        'filesystem' => { 'rootdirectory' => Gitlab['gitlab_rails']['registry_path'] }
      }

      Gitlab['registry']['storage']['cache'] ||= { 'blobdescriptor' => 'inmemory' }
      Gitlab['registry']['storage']['delete'] ||= { 'enabled' => Gitlab['registry']['storage_delete_enabled'] }
    end

    def parse_registry_notifications
      return unless Gitlab['registry']['notifications']

      user_configuration = Gitlab['registry']
      gitlab_configuration = Gitlab['node']['registry']

      # Use the registry defaults configured by the user but use the defaults from gitlab if they were not set
      user_configuration['default_notifications_timeout'] ||= gitlab_configuration['default_notifications_timeout']
      user_configuration['default_notifications_threshold'] ||= gitlab_configuration['default_notifications_threshold']
      user_configuration['default_notifications_maxretries'] ||= gitlab_configuration['default_notifications_maxretries']
      user_configuration['default_notifications_backoff'] ||=  gitlab_configuration['default_notifications_backoff']
      user_configuration['default_notifications_headers'] ||=  gitlab_configuration['default_notifications_headers']

      Gitlab['registry']['notifications'].each do |endpoint|
        # Get the values from default if they are not set
        endpoint['timeout'] ||= user_configuration['default_notifications_timeout']
        endpoint['threshold'] ||= user_configuration['default_notifications_threshold']
        endpoint['maxretries'] ||= user_configuration['default_notifications_maxretries']
        endpoint['backoff'] ||= user_configuration['default_notifications_backoff']

        # And merge the default headers with the ones specific to this endpoint
        endpoint['headers'] = user_configuration['default_notifications_headers'].merge(endpoint['headers'] || {})

        # As we set notification secret in gitlab.rb for registry we don't have to make user
        # fill it in separately for GitLab application
        Gitlab['gitlab_rails']['registry_notification_secret'] ||= endpoint['headers']['Authorization'].last if endpoint['name'] == 'geo_event'
      end
    end

    def parse_database_configuration
      # Set database defaults based on user configuration or calculated values
      database_config = Gitlab['registry']['database'] || {}

      # Host is taken preferrably from top to bottom:
      database_config['host'] ||=
        Gitlab['postgresql']['listen_address'] ||
        Gitlab['node']['postgresql']['listen_address'] ||
        Gitlab['postgresql']['dir'] ||
        Gitlab['node']['postgresql']['dir']
      # If multiple address are set, we take the first.
      database_config['host'] = take_first_address(database_config['host'])

      Gitlab['registry']['database'] = database_config
    end

    def take_first_address(address_list)
      return address_list unless address_list&.include?(',')

      warning = [
        "Received multiple postgresql address values.",
        "First address from '#{address_list}' will be used for registry database."
      ].join("\n  ")

      warn(warning)

      address_list.split(',')[0]
    end

    def generate_registry_keypair
      key, cert = SecretsHelper.generate_keypair(
        bits: 4096,
        subject: "/C=USA/O=GitLab/OU=Container/CN=Registry",
        validity: 365 * 10 # ten years from now
      )

      [cert.to_pem, key.to_pem]
    end
  end
end
