# encoding: UTF-8 # # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved. # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. module GoodData module LCM class << self def ensure_users(domain, migration_spec, filter_on_segment = []) messages = [] # Ensure technical user is in all projects if migration_spec.key?(:technical_user) clients = domain.clients clients.peach do |c| segment = c.segment next if !filter_on_segment.empty? && !(filter_on_segment.include?(segment.id)) p = client.project begin p.create_users(migration_spec[:technical_user].map { |u| { login: u, role: 'admin' } }) rescue RestClient::Exception => e messages << { type: :technical_user_addition, status: 'ERROR', message: e.message } end end end messages end def transfer_everything(client, domain, migration_spec, opts = {}) filter_on_segment = migration_spec[:segments] || migration_spec['segments'] || [] GoodData.logger.info('Ensuring Users - warning: works across whole domain not just provided segment(s)') ensure_users(domain, migration_spec, filter_on_segment) GoodData.logger.info('Migrating Blueprints') bp_opts = { update_preference: opts[:update_preference] || opts['update_preference'], maql_replacements: opts[:maql_replacements] || opts['maql_replacements'] } ######################################################### # New Architecture of Transfer Everything Functionality # ######################################################### # # modes = { # release: [ # self.synchronize_ldm, # self.synchronize_label_types, # self.synchronize_meta, # Tag specified? If yes, transfer only tagged stuff. If not transfer all meta. # self.synchronize_etl, # Processes, Schedules, Additional Params # ], # provisioning: [ # # self.ensure_titles # Handled by Provisioning Brick? # self.ensure_users, # self.delete_clients, # self.provision_clients, # LCM API # self.synchronize_label_types, # self.synchronize_etl # Processes, Schedules, Additional Params # ], # rollout: [ # Works on segments only, not using collect_clients # self.ensure_users, # self.synchronize_ldm, # self.synchronize_label_types, # self.synchronize_etl, # Processes, Schedules, Additional Params # self.synchronize_clients # ] # } # # mode_name = param['mode'] # mode_actions = modes[mode_name] || fail("Invalid mode specified: '#{mode_name}'") # messages = mode_actions.map do |action| # action(params) # end domain.segments.peach do |segment| next if !filter_on_segment.empty? && !(filter_on_segment.include?(segment.id)) bp = segment.master_project.blueprint segment.clients.each do |c| p = c.project p.update_from_blueprint(bp, bp_opts) end # target_projects = segment.clients.map(&:project) # Transfer metadata objects # GoodData::LCM.transfer_meta(segment.master_project, target_projects) end GoodData.logger.info('Migrating Processes and Schedules') deployment_client = migration_spec.key?(:user_for_deployment) ? GoodData.connect(migration_spec[:user_for_deployment]) : client domain.clients.peach do |c| segment = c.segment next if !filter_on_segment.empty? && !(filter_on_segment.include?(segment.id)) segment_master = segment.master_project project = c.project # set metadata # TODO: Review this and remove if not required or duplicate # FIXME: TMA-210 project.set_metadata('GOODOT_CUSTOM_PROJECT_ID', c.id) # copy processes deployment_client_segment_master = deployment_client.projects(segment_master.pid) deployment_client_project = deployment_client.projects(project.pid) GoodData::Project.transfer_processes(deployment_client_segment_master, deployment_client_project) GoodData::Project.transfer_schedules(segment_master, project) # Set up unique parameters deployment_client_project.schedules.peach do |s| # TODO: Review this and remove if not required or duplicate (GOODOT_CUSTOM_PROJECT_ID vs CLIENT_ID) s.update_params('GOODOT_CUSTOM_PROJECT_ID' => c.id) s.update_params('CLIENT_ID' => c.id) s.update_params('SEGMENT_ID' => segment.id) s.update_params(migration_spec[:additional_params] || {}) s.update_hidden_params(migration_spec[:additional_hidden_params] || {}) s.save end # Transfer label types begin GoodData::LCM.transfer_label_types(segment_master, project) rescue => e GoodData.logger.error("Unable to transfer label_types, reason: #{e.message}") end # Transfer tagged objects # FIXME: Make sure it is not duplicate functionality mentioned in TMA-171 tag = migration_spec[:production_tag] GoodData::Project.transfer_tagged_stuff(segment_master, project, tag) if tag end do_not_synchronize_clients = migration_spec[:do_not_synchronize_clients] if do_not_synchronize_clients.nil? || !do_not_synchronize_clients GoodData.logger.info('Migrating Dashboards') if filter_on_segment.empty? domain.synchronize_clients else filter_on_segment.map do |s| domain.segments(s).synchronize_clients end end end # User groups must be migrated after dashboards GoodData.logger.info('Migrating User Groups') domain.clients.peach do |c| segment = c.segment segment_master = segment.master_project project = c.project GoodData::Project.transfer_user_groups(segment_master, project) end end def transfer_label_types(source_project, targets) semaphore = Mutex.new # Convert to array targets = [targets] unless targets.is_a?(Array) client = source_project.client # Get attributes from source project attributes = GoodData::Attribute[:all, client: client, project: source_project] # Get display forms display_forms = attributes.map do |attribute| attribute.content['displayForms'] end # Flatten result display_forms.flatten!(2) # Select only display forms with content type display_forms.select! { |display_form| display_form['content']['type'] } # Generate transfer table transfer = {} display_forms.each { |display_form| transfer[display_form['meta']['identifier']] = display_form['content']['type'] } GoodData.logger.info 'Transferring label types' GoodData.logger.info JSON.pretty_generate(transfer) # Transfer to target projects targets.peach do |target| transfer.peach do |identifier, type| uri = GoodData::MdObject.identifier_to_uri({ project: target, client: target.client }, identifier) next unless uri obj = GoodData::MdObject[uri, { project: target, client: target.client }] if obj if obj.content['type'] != type semaphore.synchronize do GoodData.logger.info "Updating #{identifier} -> #{type} in '#{target.title}'" end obj.content['type'] = type obj.save end else semaphore.synchronize do GoodData.logger.warn "Unable to find #{identifier} in '#{target.title}'" end end nil end nil end end # Synchronizes the dashboards tagged with the +$PRODUCTION_TAG+ from development project to master projects # Params: # +source_workspace+:: workspace with the tagged dashboards that are going to be synchronized to the target workspaces # +target_workspaces+:: array of target workspaces where the tagged dashboards are going be synchronized to def transfer_meta(source_workspace, target_workspaces, tag = nil) objects = get_dashboards(source_workspace, tag) begin token = source_workspace.objects_export(objects) GoodData.logger.info "Export token: '#{token}'" rescue => e GoodData.logger.error "Export failed, reason: #{e.message}" end target_workspaces.each do |target_workspace| begin target_workspace.objects_import(token) rescue => e GoodData.logger.error "Import failed, reason: #{e.message}" end end end # Retrieves all dashboards tagged with the +$PRODUCTION_TAG+ from the +workspace+ # Params: # +workspace+:: workspace with the tagged dashboards # Returns enumeration of the tagged dashboards URIs def get_dashboards(workspace, tag = nil) if tag GoodData::Dashboard.find_by_tag(tag, project: workspace, client: workspace.client).map(&:uri) else GoodData::Dashboard.all(project: workspace, client: workspace.client).map(&:uri) end end def transfer_attribute_drillpaths(source_project, targets) semaphore = Mutex.new # Convert to array targets = [targets] unless targets.is_a?(Array) # Get attributes from source project attributes = source_project.attributes # Generate transfer table drill_paths = attributes.pmap do |attribute| drill_label_uri = attribute.content['drillDownStepAttributeDF'] if drill_label_uri drill_label = source_project.labels(drill_label_uri) [attribute.uri, attribute.meta['identifier'], drill_label_uri, drill_label.identifier] else [] end end drill_paths.reject!(&:empty?) # Transfer to target projects targets.peach do |target| drill_paths.peach do |drill_path_info| src_attr_uri, attr_identifier, drill_label_uri, drill_path_identifier = drill_path_info attr_uri = GoodData::MdObject.identifier_to_uri({ project: target, client: target.client }, attr_identifier) next unless attr_uri attribute = GoodData::MdObject[attr_uri, project: target, client: target.client] if attribute drill_path = GoodData::MdObject.identifier_to_uri({ project: target, client: target.client }, drill_path_identifier) next unless drill_path if !attribute.content['drillDownStepAttributeDF'] || attribute.content['drillDownStepAttributeDF'] != drill_path semaphore.synchronize do GoodData.logger.debug <<-DEBUG Transfer from: { attr_uri: #{src_attr_uri}, attr_identifier: #{attr_identifier}, drill_label_uri: #{drill_label_uri}, drill_label_identifier: #{drill_path_identifier} } To: { attr_uri: #{attr_uri}, attr_identifier: #{attr_identifier}, drill_label_uri: #{drill_path}, drill_label_identifier: #{drill_path_identifier} } DEBUG GoodData.logger.info "Updating drill path of #{attr_identifier} -> #{drill_path} in '#{target.title}'" end attribute.content['drillDownStepAttributeDF'] = drill_path attribute.save end else semaphore.synchronize do GoodData.logger.warn "Unable to find #{attr_identifier} in '#{target.title}'" end end nil end nil end end end end end