mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 04:08:15 +00:00 
			
		
		
		
	Solved None key bugs and implemented tests to prevent it in the future
This commit is contained in:
		@@ -8,7 +8,6 @@ features:
 | 
			
		||||
  css:              true
 | 
			
		||||
  portfolio_iframe: true
 | 
			
		||||
  central_database: true
 | 
			
		||||
credentials:
 | 
			
		||||
domains:
 | 
			
		||||
  canonical:
 | 
			
		||||
    - "accounting.{{ primary_domain }}"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
image:              
 | 
			
		||||
  web:              "attendize_web:latest"
 | 
			
		||||
  worker:           "attendize_worker:latest"
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  matomo:           true
 | 
			
		||||
  css:              true
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
enable_greenlight:    "true"
 | 
			
		||||
setup:                false
 | 
			
		||||
credentials:
 | 
			
		||||
database:
 | 
			
		||||
  name:               "multiple_databases"
 | 
			
		||||
  username:           "postgres2"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ images:
 | 
			
		||||
  pds:              "ghcr.io/bluesky-social/pds:latest"
 | 
			
		||||
pds:
 | 
			
		||||
  version:          "latest"
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  matomo:           true
 | 
			
		||||
  css:              true
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
network:    "discourse_default"     # Name of the docker network
 | 
			
		||||
container:  "discourse_application" # Name of the container application
 | 
			
		||||
repository: "discourse_repository"  # Name of the repository folder                 
 | 
			
		||||
credentials:                    
 | 
			
		||||
features:
 | 
			
		||||
  matomo:             true
 | 
			
		||||
  css:                true
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ oauth2_proxy:
 | 
			
		||||
  application:        "application"
 | 
			
		||||
  port:               "80"
 | 
			
		||||
addons:
 | 
			
		||||
  keycloakpassword:
 | 
			
		||||
  ldapauth:
 | 
			
		||||
  keycloakpassword: {}
 | 
			
		||||
  ldapauth: {}
 | 
			
		||||
docker:
 | 
			
		||||
  services:
 | 
			
		||||
    database:
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@ features:
 | 
			
		||||
  ldap:               true
 | 
			
		||||
  central_database:   true
 | 
			
		||||
  oauth2:             false # Doesn't make sense to activate it atm, because login is possible on homepage
 | 
			
		||||
credentials:
 | 
			
		||||
domains:
 | 
			
		||||
  canonical:
 | 
			
		||||
    - "audio.{{ primary_domain }}"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
images: 
 | 
			
		||||
  keycloak:           "quay.io/keycloak/keycloak:latest"
 | 
			
		||||
import_realm:         True                                # If True realm will be imported. If false skip.
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  matomo:             true
 | 
			
		||||
  css:                false
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ images:
 | 
			
		||||
oauth2_proxy:
 | 
			
		||||
  application:                  application
 | 
			
		||||
  port:                         80
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  matomo:                       true
 | 
			
		||||
  css:                          true
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,5 @@ network:
 | 
			
		||||
  public:         False                               # Set to true in inventory file if you want to expose the LDAP port to the internet
 | 
			
		||||
hostname:         "ldap"                              # Hostname of the LDAP Server in the central_ldap network
 | 
			
		||||
webinterface:     "lam"                               # The webinterface which should be used. Possible: lam and phpldapadmin
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  ldap:           true
 | 
			
		||||
@@ -3,7 +3,6 @@ oidc:
 | 
			
		||||
  email_by_username:      true                              # If true, then the mail is set by the username. If wrong then the OIDC user email is used
 | 
			
		||||
  enable_user_creation:   true                              # Users will be created if not existing
 | 
			
		||||
domain:                   "{{primary_domain}}"              # The main domain from which mails will be send \ email suffix behind @                              
 | 
			
		||||
credentials:                                     
 | 
			
		||||
features:
 | 
			
		||||
  matomo:                 true
 | 
			
		||||
  css:                    false
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ images:
 | 
			
		||||
  streaming:          "ghcr.io/mastodon/mastodon-streaming:latest"
 | 
			
		||||
single_user_mode:     false                                           # Set true for initial setup
 | 
			
		||||
setup:                false                                           # Set true in inventory file to execute the setup and initializing procedures                         
 | 
			
		||||
credentials:                            
 | 
			
		||||
features:
 | 
			
		||||
  matomo:             true
 | 
			
		||||
  css:                true
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@ oidc:
 | 
			
		||||
  # @see https://apps.nextcloud.com/apps/oidc_login
 | 
			
		||||
  # @see https://apps.nextcloud.com/apps/sociallogin
 | 
			
		||||
  flavor:                     "oidc_login"                                      # Keeping on sociallogin because the other option is not implemented yet                                             
 | 
			
		||||
credentials:
 | 
			
		||||
features:
 | 
			
		||||
  matomo:                       true
 | 
			
		||||
  css:                          false
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ features:
 | 
			
		||||
  central_database: false   # Enable Central Database Network
 | 
			
		||||
  recaptcha:        false   # Enable ReCaptcha
 | 
			
		||||
  oauth2:           false   # Enable the OAuth2-Proy
 | 
			
		||||
csp:                        
 | 
			
		||||
csp: {}      
 | 
			
		||||
domains:
 | 
			
		||||
  canonical:
 | 
			
		||||
    - "icons.{{ primary_domain }}"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
version:              "latest"
 | 
			
		||||
oidc:
 | 
			
		||||
oidc: {}
 | 
			
		||||
    # Taiga doesn't have a functioning oidc support at the moment
 | 
			
		||||
    # See
 | 
			
		||||
    # - https://community.taiga.io/t/taiga-and-oidc-plugin/4866
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								tests/integration/test_configuration_non_empty.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/integration/test_configuration_non_empty.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
import os
 | 
			
		||||
import glob
 | 
			
		||||
import yaml
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_none_values(data, prefix=None):
 | 
			
		||||
    """
 | 
			
		||||
    Recursively find keys with None values in a nested dict or list.
 | 
			
		||||
    Returns a list of (path, value) tuples where value is None.
 | 
			
		||||
    """
 | 
			
		||||
    errors = []
 | 
			
		||||
    if prefix is None:
 | 
			
		||||
        prefix = []
 | 
			
		||||
 | 
			
		||||
    if isinstance(data, dict):
 | 
			
		||||
        for key, value in data.items():
 | 
			
		||||
            path = prefix + [str(key)]
 | 
			
		||||
            if value is None:
 | 
			
		||||
                errors.append((".".join(path), value))
 | 
			
		||||
            elif isinstance(value, (dict, list)):
 | 
			
		||||
                errors.extend(find_none_values(value, path))
 | 
			
		||||
    elif isinstance(data, list):
 | 
			
		||||
        for idx, item in enumerate(data):
 | 
			
		||||
            path = prefix + [f"[{idx}]"]
 | 
			
		||||
            if item is None:
 | 
			
		||||
                errors.append((".".join(path), item))
 | 
			
		||||
            elif isinstance(item, (dict, list)):
 | 
			
		||||
                errors.extend(find_none_values(item, path))
 | 
			
		||||
 | 
			
		||||
    return errors
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestConfigurationNoNone(unittest.TestCase):
 | 
			
		||||
    def test_configuration_files_have_no_none_values(self):
 | 
			
		||||
        # Find all configuration.yml files under roles/*/vars
 | 
			
		||||
        pattern = os.path.join(
 | 
			
		||||
            os.path.dirname(__file__),
 | 
			
		||||
            os.pardir, os.pardir,
 | 
			
		||||
            'roles', '*', 'vars', 'configuration.yml'
 | 
			
		||||
        )
 | 
			
		||||
        files = glob.glob(pattern)
 | 
			
		||||
        self.assertTrue(files, f"No configuration.yml files found with pattern: {pattern}")
 | 
			
		||||
 | 
			
		||||
        all_errors = []
 | 
			
		||||
        for filepath in files:
 | 
			
		||||
            with open(filepath, 'r') as f:
 | 
			
		||||
                try:
 | 
			
		||||
                    data = yaml.safe_load(f)
 | 
			
		||||
                except yaml.YAMLError as e:
 | 
			
		||||
                    self.fail(f"Failed to parse YAML in {filepath}: {e}")
 | 
			
		||||
            errors = find_none_values(data)
 | 
			
		||||
            for path, value in errors:
 | 
			
		||||
                all_errors.append(f"{filepath}: Key '{path}' is None")
 | 
			
		||||
 | 
			
		||||
        if all_errors:
 | 
			
		||||
            self.fail("None values found in configuration files:\n" + "\n".join(all_errors))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user