# Copyright 2017 AT&T Intellectual Property.  All other rights reserved.
#
# 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 os

import testtools
import yaml

from armada.tests.unit import base
from armada.utils import validate

template_chart = """
schema: armada/Chart/v1
metadata:
  schema: metadata/Document/v1
  name: example-chart
data:
  chart_name: keystone
  release: keystone
  namespace: undercloud
  timeout: 100
  install:
    no_hooks: false
  upgrade:
    no_hooks: false
  values: {}
  source:
    type: git
    location: git://github.com/example/example
    subpath: example-chart
    reference: master
  dependencies: []
"""

template_chart_group = """
schema: armada/ChartGroup/v1
metadata:
    schema: metadata/Document/v1
    name: example-manifest
data:
    description: this is sample
    chart_group:
        - example-chart
"""

template_manifest = """
schema: armada/Manifest/v1
metadata:
    schema: metadata/Document/v1
    name: example-manifest
data:
    release_prefix: example
    chart_groups:
        - example-chart
"""


class BaseValidateTest(base.ArmadaTestCase):
    def setUp(self):
        super(BaseValidateTest, self).setUp()
        self.basepath = os.path.join(os.path.dirname(__file__), os.pardir)

    def _build_error_message(self, document, name, message):
        return "Invalid document [{}] {}: {}.".format(document, name, message)


class ValidateOwnExamplesTestCase(BaseValidateTest):
    """Validates that each of the relevant example documents under
    ``armada/examples`` passes internal Armada schema validation.

    """
    def test_own_document_examples(self):
        examples_path = os.path.join(os.getcwd(), 'examples')
        example_files = [
            os.path.join(examples_path, f) for f in os.listdir(examples_path)
            if os.path.isfile(os.path.join(examples_path, f))
        ]
        validated_manifests = []

        for example_file in example_files:
            with open(example_file) as f:
                documents = yaml.safe_load_all(f.read())

            # If the example file doesn't have a document with
            # armada/Manifest/v1 then skip validating it as the example could
            # merely be an override.
            has_manifest = any(
                x['schema'] == 'armada/Manifest/v1' for x in documents)
            if not has_manifest:
                continue

            validated_manifests.append(example_file)
            valid, _ = validate.validate_armada_documents(list(documents))
            self.assertTrue(valid)

        self.assertTrue(validated_manifests)


class ValidateTestCase(BaseValidateTest):
    def test_validate_armada_yaml_passes(self):
        template = '{}/resources/valid_armada_document.yaml'.format(
            self.basepath)

        with open(template) as f:
            documents = yaml.safe_load_all(f.read())
        valid, details = validate.validate_armada_documents(list(documents))

        self.assertTrue(valid)

    def test_validate_manifest_passes(self):
        manifest = yaml.safe_load(template_manifest)
        is_valid, error = validate.validate_armada_document(manifest)

        self.assertTrue(is_valid)

    def test_validate_chart_group_with_values(self):
        test_chart_group = """
---
schema: armada/ChartGroup/v1
metadata:
    name: kubernetes-proxy
    schema: metadata/Document/v1
data:
    description: Kubernetes proxy
    name: kubernetes-proxy
    sequenced: true
    chart_group:
      - proxy
---
schema: armada/Chart/v1
metadata:
    name: proxy
    schema: metadata/Document/v1
data:
    chart_name: proxy
    timeout: 600
    release: kubernetes-proxy
    source:
      subpath: proxy
      type: local
      location: "/etc/genesis/armada/assets/charts"
    namespace: kube-system
    upgrade:
      no_hooks: true
    values:
      images:
        tags:
          proxy: gcr.io/google_containers/hyperkube-amd64:v1.8.6
      network:
        kubernetes_netloc: 127.0.0.1:6553
    dependencies:
      - helm-toolkit
---
schema: armada/Chart/v1
metadata:
    name: helm-toolkit
    schema: metadata/Document/v1
data:
    chart_name: helm-toolkit
    wait:
      timeout: 600
    release: helm-toolkit
    source:
      reference: master
      subpath: helm-toolkit
      location: https://git.openstack.org/openstack/openstack-helm-infra
      type: git
    namespace: helm-toolkit
    upgrade:
      no_hooks: true
    values: {}
    dependencies: []
"""

        chart_group = yaml.safe_load_all(test_chart_group)
        is_valid, error = validate.validate_armada_documents(list(chart_group))

        self.assertTrue(is_valid)

    def test_validate_group_passes(self):
        chart_group = yaml.safe_load(template_chart_group)
        is_valid, error = validate.validate_armada_document(chart_group)

        self.assertTrue(is_valid)

    def test_validate_chart_passes(self):
        chart = yaml.safe_load(template_chart)
        is_valid, error = validate.validate_armada_document(chart)

        self.assertTrue(is_valid)

    @testtools.skipUnless(
        base.is_connected(),
        'validate_manifest_url requires network connectivity.')
    def test_validate_manifest_url(self):
        value = 'url'
        self.assertFalse(validate.validate_manifest_url(value))
        value = 'https://opendev.org/airship/armada/' \
                'raw/branch/master/examples/simple.yaml'
        self.assertTrue(validate.validate_manifest_url(value))


class ValidateNegativeTestCase(BaseValidateTest):
    def test_validate_no_dictionary_expect_type_error(self):
        expected_error = 'The provided input "invalid" must be a dictionary.'
        self.assertRaisesRegex(
            TypeError, expected_error, validate.validate_armada_documents,
            ['invalid'])

    def test_validate_invalid_chart_armada_manifest(self):
        template = '{}/resources/valid_armada_document.yaml'.format(
            self.basepath)

        with open(template) as f:
            documents = list(yaml.safe_load_all(f.read()))

        mariadb_document = [
            d for d in documents if d['metadata']['name'] == 'mariadb'
        ][0]
        del mariadb_document['data']['release']

        _, error_messages = validate.validate_armada_documents(documents)
        expected_error = self._build_error_message(
            'armada/Chart/v1', 'mariadb', "'release' is a required property")

        self.assertEqual(1, len(error_messages))
        self.assertEqual(expected_error, error_messages[0]['message'])

    def test_validate_validate_group_without_required_chart_group(self):
        template_manifest = """
        schema: armada/ChartGroup/v1
        metadata:
            schema: metadata/Document/v1
            name: example-manifest
        data:
            description: this is sample
        """
        document = yaml.safe_load(template_manifest)
        is_valid, error = validate.validate_armada_document(document)

        expected_error = self._build_error_message(
            'armada/ChartGroup/v1', 'example-manifest',
            "'chart_group' is a required property")

        self.assertFalse(is_valid)
        self.assertEqual(error[0]['message'], expected_error)

    def test_validate_manifest_without_required_release_prefix(self):
        template_manifest = """
        schema: armada/Manifest/v1
        metadata:
            schema: metadata/Document/v1
            name: example-manifest
        data:
            chart_groups:
                - example-group
        """
        document = yaml.safe_load(template_manifest)
        is_valid, error = validate.validate_armada_document(document)
        expected_error = self._build_error_message(
            'armada/Manifest/v1', 'example-manifest',
            "'release_prefix' is a required property")

        self.assertFalse(is_valid)
        self.assertEqual(error[0]['message'], expected_error)

    def test_validate_chart_without_required_release_property(self):
        template_manifest = """
        schema: armada/Chart/v1
        metadata:
          schema: metadata/Document/v1
          name: example-chart
        data:
          chart_name: keystone
          namespace: undercloud
          timeout: 100
          install:
            no_hooks: false
          upgrade:
            no_hooks: false
          values: {}
          source:
            type: git
            location: git://github.com/example/example
            subpath: example-chart
            reference: master
          dependencies:
            - dep-chart
        """
        document = yaml.safe_load(template_manifest)
        is_valid, error = validate.validate_armada_document(document)
        expected_error = self._build_error_message(
            'armada/Chart/v1', 'example-chart',
            "'release' is a required property")

        self.assertFalse(is_valid)
        self.assertEqual(error[0]['message'], expected_error)
