marketplace/deployer_util/set_ownership.py (169 lines of code) (raw):

#!/usr/bin/env python3 # # Copyright 2018 Google LLC # # 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 copy import os import sys import yaml import log_util as log from argparse import ArgumentParser from resources import find_application_resource from resources import set_app_resource_ownership from resources import set_namespace_resource_ownership from resources import set_service_account_resource_ownership from yaml_util import load_resources_yaml from yaml_util import parse_resources_yaml _PROG_HELP = """ Scans the manifest folder kubernetes resources and set the Application to own the ones defined in its list of components kinds. """ # From `kubectl api-resources --namespaced=false` with kubectl client and # server version of 1.13 _CLUSTER_SCOPED_KINDS = [ "ComponentStatus", "Namespace", "Node", "PersistentVolume", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration", "CustomResourceDefinition", "APIService", "TokenReview", "SelfSubjectAccessReview", "SelfSubjectRulesReview", "SubjectAccessReview", "CertificateSigningRequest", "PodSecurityPolicy", "NodeMetrics", "PodSecurityPolicy", "ClusterRoleBinding", "ClusterRole", "PriorityClass", "StorageClass", "VolumeAttachment", ] _DEPLOYER_OWNED_KINDS = ["Role", "RoleBinding"] def main(): parser = ArgumentParser(description=_PROG_HELP) parser.add_argument( "--app_name", help="The name of the application instance", required=True) parser.add_argument( "--app_uid", help="The uid of the application instance", required=True) parser.add_argument( "--app_api_version", help="The apiVersion of the Application CRD", required=True) parser.add_argument( "--deployer_name", help="The name of the deployer service account instance. " "If deployer_uid is also set, the deployer service account is set " "as the owner of namespaced deployer components.") parser.add_argument( "--deployer_uid", help="The uid of the deployer service account instance. " "If deployer_name is also set, the deployer service account is set " "as the owner of namespaced deployer components.") parser.add_argument( "--namespace", help="The namespace containing the application. " "If namespace_uid is also set, the namespace is set as the owner" "of cluster-scoped resources (otherwise unowned).") parser.add_argument( "--namespace_uid", help="The namespace containing the application. " "If namespace is also set, the namespace is set as the owner" "of cluster-scoped resources (otherwise unowned).") parser.add_argument( "--manifests", help="The folder containing the manifest templates, " "or - to read from stdin", required=True) parser.add_argument( "--dest", help="The output file for the resulting manifest, " "or - to write to stdout", required=True) parser.add_argument( "--noapp", action="store_true", help="Do not look for Application resource to determine " "what kinds to include. I.e. set owner references for " "all of the (namespaced) resources in the manifests") args = parser.parse_args() resources = [] if args.manifests == "-": resources = parse_resources_yaml(sys.stdin.read()) elif os.path.isfile(args.manifests): resources = load_resources_yaml(args.manifests) else: resources = [] for filename in os.listdir(args.manifests): resources += load_resources_yaml(os.path.join(args.manifests, filename)) if not args.noapp: app = find_application_resource(resources) kinds = set([x["kind"] for x in app["spec"].get("componentKinds", [])]) excluded_kinds = ["PersistentVolumeClaim", "Application"] included_kinds = [kind for kind in kinds if kind not in excluded_kinds] else: included_kinds = None if args.dest == "-": dump( sys.stdout, resources, included_kinds, namespace=args.namespace, namespace_uid=args.namespace_uid, app_name=args.app_name, app_uid=args.app_uid, app_api_version=args.app_api_version, deployer_name=args.deployer_name, deployer_uid=args.deployer_uid) sys.stdout.flush() else: with open(args.dest, "w", encoding='utf-8') as outfile: dump( outfile, resources, included_kinds, namespace=args.namespace, namespace_uid=args.namespace_uid, app_name=args.app_name, app_uid=args.app_uid, app_api_version=args.app_api_version, deployer_name=args.deployer_name, deployer_uid=args.deployer_uid) def dump(outfile, resources, included_kinds, namespace, namespace_uid, app_name, app_uid, app_api_version, deployer_name, deployer_uid): def maybe_assign_ownership(resource): if resource["kind"] in _CLUSTER_SCOPED_KINDS: # Cluster-scoped resources cannot be owned by a namespaced resource: # https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents # Set the namespace as owner if provided, otherwise leave unowned. if namespace and namespace_uid: log.info("Namespace '{:s}' owns '{:s}/{:s}'", namespace, resource["kind"], resource["metadata"]["name"]) set_namespace_resource_ownership( namespace_uid=namespace_uid, namespace_name=namespace, resource=resource) else: log.info("Application '{:s}' does not own cluster-scoped '{:s}/{:s}'", app_name, resource["kind"], resource["metadata"]["name"]) # Deployer-owned resources should not be owned by the Application, as # they should be deleted with the deployer service account (not the app). elif deployer_name and deployer_uid and should_be_deployer_owned(resource): log.info("ServiceAccount '{:s}' owns '{:s}/{:s}'", deployer_name, resource["kind"], resource["metadata"]["name"]) resource = copy.deepcopy(resource) set_service_account_resource_ownership( account_uid=deployer_uid, account_name=deployer_name, resource=resource) elif included_kinds is None or resource["kind"] in included_kinds: log.info("Application '{:s}' owns '{:s}/{:s}'", app_name, resource["kind"], resource["metadata"]["name"]) resource = copy.deepcopy(resource) set_app_resource_ownership( app_uid=app_uid, app_name=app_name, app_api_version=app_api_version, resource=resource) return resource to_be_dumped = [maybe_assign_ownership(resource) for resource in resources] yaml.safe_dump_all(to_be_dumped, outfile, default_flow_style=False, indent=2) def should_be_deployer_owned(resource): if not resource["kind"] in _DEPLOYER_OWNED_KINDS: return False if resource.get("metadata", {}).get("labels", {}).get( "app.kubernetes.io/component") != "deployer.marketplace.cloud.google.com": return False return True if __name__ == "__main__": main()