Skip to content

Commit

Permalink
Fixes #37900 - Allow syncing templates through HTTP proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
adamlazik1 committed Oct 23, 2024
1 parent f909477 commit ce1a701
Show file tree
Hide file tree
Showing 19 changed files with 252 additions and 25 deletions.
4 changes: 3 additions & 1 deletion app/controllers/api/v2/template_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class TemplateController < ::Api::V2::BaseController
param :repo, String, :required => false, :desc => N_("Override the default repo from settings.")
param :filter, String, :required => false, :desc => N_("Export templates with names matching this regex (case-insensitive; snippets are not filtered).")
param :negate, :bool, :required => false, :desc => N_("Negate the prefix (for purging).")
param :dirname, String, :required => false, :desc => N_("The directory within Git repo containing the templates")
param :dirname, String, :required => false, :desc => N_("Directory within Git repo containing the templates.")
param :http_proxy_policy, ForemanTemplates.http_proxy_policy_types.keys, :required => false, :desc => N_("HTTP proxy policy for template sync.")
param :http_proxy_id, :number, :required => false, :desc => N_("ID of an HTTP proxy to use for template sync.")
end

api :POST, "/templates/import/", N_("Initiate Import")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module TemplateParams

class_methods do
def filter_params_list
%i(verbose repo branch dirname filter negate metadata_export_mode)
%i(verbose repo branch dirname filter negate metadata_export_mode http_proxy_policy http_proxy_id)
end

def extra_import_params
Expand Down
25 changes: 24 additions & 1 deletion app/controllers/ui_template_syncs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class UITemplateSyncsController < ApplicationController
def sync_settings
import_settings = setting_definitions(ForemanTemplates::IMPORT_SETTING_NAMES)
export_settings = setting_definitions(ForemanTemplates::EXPORT_SETTING_NAMES)
@results = OpenStruct.new(:import => import_settings, :export => export_settings)
@results = OpenStruct.new(:import => import_settings, :export => export_settings, :proxy => http_proxy_settings)
end

def import
Expand Down Expand Up @@ -51,4 +51,27 @@ def render_errors(messages, severity = 'danger')
def setting_definitions(short_names)
short_names.map { |name| Foreman.settings.find("template_sync_#{name}") }
end

def http_proxy_settings
settings = [ Foreman.settings.find('template_sync_http_proxy_policy') ]
proxy_id = http_proxy_id_setting
settings << proxy_id if proxy_id
settings
end

def http_proxy_id_setting
proxy_list = HttpProxy.authorized(:view_http_proxies).with_taxonomy_scope.each_with_object({}) { |proxy, hash| hash[proxy.id] = proxy.name }
if proxy_list.empty?
return nil
end
default_proxy_id = proxy_list.keys.first
OpenStruct.new(id: 'template_sync_http_proxy_id',
name: 'template_sync_http_proxy_id',
description: N_('Select an HTTP proxy to use for template sync'),
settings_type: :string,
value: default_proxy_id,
default: default_proxy_id,
full_name: N_('HTTP proxy'),
select_values: proxy_list)
end
end
38 changes: 37 additions & 1 deletion app/services/foreman_templates/action.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'securerandom'

module ForemanTemplates
class Action
delegate :logger, :to => :Rails
Expand All @@ -15,7 +17,7 @@ def self.repo_start_with
end

def self.setting_overrides
%i(verbose prefix dirname filter repo negate branch)
%i(verbose prefix dirname filter repo negate branch http_proxy_policy)
end

def method_missing(method, *args, &block)
Expand Down Expand Up @@ -53,9 +55,43 @@ def verify_path!(path)
private

def assign_attributes(args = {})
@http_proxy_id = args[:http_proxy_id]
self.class.setting_overrides.each do |attribute|
instance_variable_set("@#{attribute}", args[attribute.to_sym] || Setting["template_sync_#{attribute}".to_sym])
end
end

protected

def init_git_repo
git_repo = Git.init(@dir)

case @http_proxy_policy
when 'global'
http_proxy_url = Setting[:http_proxy]
when 'selected'
http_proxy = HttpProxy.authorized(:view_http_proxies).with_taxonomy_scope.find(@http_proxy_id)
http_proxy_url = http_proxy.full_url

if http_proxy_url.include?('https') && http_proxy.cacert.present?
@proxy_cert = "/tmp/foreman_templates_proxy_cert_#{SecureRandom.hex(8)}.crt"

File.open(@proxy_cert, 'w') do |file|
file.write(http_proxy.cacert)
end

git_repo.config('http.proxySSLCAInfo', @proxy_cert)
end
end

if http_proxy_url.present?
git_repo.config('http.proxy', http_proxy_url)
end

git_repo.add_remote('origin', @repo)
git_repo.fetch
logger.debug "cloned '#{@repo}' to '#{@dir}'"
git_repo
end
end
end
4 changes: 2 additions & 2 deletions app/services/foreman_templates/template_exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ def export_to_git
@dir = Dir.mktmpdir
return if branch_missing?

git_repo = Git.clone(@repo, @dir)
logger.debug "cloned '#{@repo}' to '#{@dir}'"
git_repo = init_git_repo

setup_git_branch git_repo
dump_files!
Expand All @@ -57,6 +56,7 @@ def export_to_git
e.message
end
ensure
FileUtils.remove_entry_secure(@proxy_cert) if @proxy_cert.present? && File.exist?(@proxy_cert)
FileUtils.remove_entry_secure(@dir) if File.exist?(@dir)
end

Expand Down
6 changes: 3 additions & 3 deletions app/services/foreman_templates/template_importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ def import_from_git
@dir = Dir.mktmpdir

begin
logger.debug "cloned '#{@repo}' to '#{@dir}'"
gitrepo = Git.clone(@repo, @dir)
if @branch
gitrepo = init_git_repo
if @branch.present?
logger.debug "checking out branch '#{@branch}'"
gitrepo.checkout(@branch)
end

parse_files!
ensure
FileUtils.remove_entry_secure(@proxy_cert) if @proxy_cert.present? && File.exist?(@proxy_cert)
FileUtils.remove_entry_secure(@dir) if File.exist?(@dir)
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/views/ui_template_syncs/sync_settings.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ child @results => :results do
child :export => :export do
extends "template_sync_settings/show"
end

child :proxy => :proxy do
extends "template_sync_settings/show"
end
end
4 changes: 4 additions & 0 deletions lib/foreman_templates.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ def self.lock_types
def self.metadata_export_mode_types
{ 'refresh' => _('Refresh'), 'keep' => _('Keep'), 'remove' => _('Remove') }
end

def self.http_proxy_policy_types
{ 'global' => _('Global default HTTP proxy'), 'none' => _('No HTTP proxy'), 'selected' => _('Use selected HTTP proxy') }
end
end
6 changes: 6 additions & 0 deletions lib/foreman_templates/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class Engine < ::Rails::Engine
description: N_('Custom commit message for templates export'),
default: 'Templates export made by a Foreman user',
full_name: N_('Commit message'))
setting('template_sync_http_proxy_policy',
type: :string,
description: N_('HTTP proxy policy for template sync'),
default: 'global',
full_name: N_('HTTP proxy policy'),
collection: -> { ForemanTemplates.http_proxy_policy_types })
end
end

Expand Down
2 changes: 2 additions & 0 deletions webpack/components/NewTemplateSync/NewTemplateSyncReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const initialState = Immutable({
loadingSettings: false,
importSettings: [],
exportSettings: [],
proxySettings: [],
error: '',
});

Expand All @@ -23,6 +24,7 @@ const syncSettings = (state = initialState, action) => {
loadingSettings: false,
importSettings: payload.results.import,
exportSettings: payload.results.export,
proxySettings: payload.results.proxy,
});
case SYNC_SETTINGS_FAILURE:
return state.merge({ error: payload.error, loadingSettings: false });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const newSyncState = state => state.foremanTemplates.syncSettings;

export const selectImportSettings = state => newSyncState(state).importSettings;
export const selectExportSettings = state => newSyncState(state).exportSettings;
export const selectProxySettings = state => newSyncState(state).proxySettings;
export const selectLoadingSettings = state =>
newSyncState(state).loadingSettings;
export const selectError = state => newSyncState(state).error;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Object {
"exportSettings": Array [],
"importSettings": Array [],
"loadingSettings": false,
"proxySettings": Array [],
}
`;

Expand All @@ -15,6 +16,7 @@ Object {
"exportSettings": Array [],
"importSettings": Array [],
"loadingSettings": true,
"proxySettings": Array [],
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import SyncSettingsFields from '../SyncSettingFields';
import SyncTypeRadios from '../SyncTypeRadios';
import { redirectToResult, syncFormSchema } from './NewTemplateSyncFormHelpers';

import ProxySettingsFields from '../ProxySettingFields';

const NewTemplateSyncForm = ({
error,
submitForm,
importSettings,
exportSettings,
proxySettings,
history,
validationData,
importUrl,
Expand Down Expand Up @@ -109,13 +112,19 @@ const NewTemplateSyncForm = ({
syncType={syncType}
resetField={resetToDefault}
/>
<ProxySettingsFields
proxySettings={proxySettings}
syncType={syncType}
resetField={resetToDefault}
/>
</ForemanForm>
);
};

NewTemplateSyncForm.propTypes = {
importSettings: PropTypes.array,
exportSettings: PropTypes.array,
proxySettings: PropTypes.array,
userPermissions: PropTypes.object.isRequired,
error: PropTypes.object,
history: PropTypes.object,
Expand All @@ -129,6 +138,7 @@ NewTemplateSyncForm.propTypes = {
NewTemplateSyncForm.defaultProps = {
importSettings: [],
exportSettings: [],
proxySettings: [],
validationData: {},
error: undefined,
history: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as Yup from 'yup';

import React from 'react';
import { translate as __ } from 'foremanReact/common/I18n';

export const redirectToResult = history => () =>
history.push({ pathname: '/template_syncs/result' });

Expand Down Expand Up @@ -41,3 +44,13 @@ export const syncFormSchema = (syncType, settingsObj, validationData) => {
[syncType]: Yup.object().shape(schema),
});
};

export const tooltipContent = setting => (
<div
dangerouslySetInnerHTML={{
__html: __(setting.description),
}}
/>
);

export const label = setting => `${__(setting.fullName)}`;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createSelector } from 'reselect';
import {
selectImportSettings,
selectExportSettings,
selectProxySettings,
} from '../../NewTemplateSyncSelectors';

export const transformInitialValues = settings =>
Expand All @@ -14,8 +15,18 @@ export const transformInitialValues = settings =>
export const selectInitialFormValues = createSelector(
selectImportSettings,
selectExportSettings,
(importSettings, exportSettings) => ({
import: transformInitialValues(importSettings),
export: transformInitialValues(exportSettings),
})
selectProxySettings,
(importSettings, exportSettings, proxySettings) => {
const transformedProxySettings = transformInitialValues(proxySettings);
return {
import: {
...transformInitialValues(importSettings),
...transformedProxySettings,
},
export: {
...transformInitialValues(exportSettings),
...transformedProxySettings,
},
};
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import NewTemplateSyncForm from './NewTemplateSyncForm';
import {
selectImportSettings,
selectExportSettings,
selectProxySettings,
} from '../../NewTemplateSyncSelectors';

import { selectInitialFormValues } from './NewTemplateSyncFormSelectors';
Expand All @@ -16,12 +17,15 @@ const mapStateToProps = (state, ownProps) => {

const exportSettings = selectExportSettings(state);

const proxySettings = selectProxySettings(state);

const initialFormValues = selectInitialFormValues(state);

return {
initialValues: { ...initialFormValues },
importSettings,
exportSettings,
proxySettings,
};
};

Expand Down
41 changes: 41 additions & 0 deletions webpack/components/NewTemplateSync/components/ProxySettingField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';

import { FieldLevelHelp } from 'patternfly-react';
import RenderField from './TextButtonField/RenderField';
import ButtonTooltip from './ButtonTooltip';

import { tooltipContent, label, } from './NewTemplateSyncForm/NewTemplateSyncFormHelpers';

Check failure on line 9 in webpack/components/NewTemplateSync/components/ProxySettingField.js

View workflow job for this annotation

GitHub Actions / test_js (12)

Expected consistent spacing

Check failure on line 9 in webpack/components/NewTemplateSync/components/ProxySettingField.js

View workflow job for this annotation

GitHub Actions / test_js (12)

Replace `·tooltipContent,·label,·` with `⏎··tooltipContent,⏎··label,⏎`

const ProxySettingField = ({ setting, resetField, field, form, fieldName, }) => (

Check failure on line 11 in webpack/components/NewTemplateSync/components/ProxySettingField.js

View workflow job for this annotation

GitHub Actions / test_js (12)

Delete `,`
<RenderField
label={label(setting)}
fieldSelector={_ => 'select'}
tooltipHelp={<FieldLevelHelp content={tooltipContent(setting)} />}
buttonAttrs={{
buttonText: <ButtonTooltip tooltipId={fieldName} />,
buttonAction: () =>
resetField(fieldName, setting.value)(form.setFieldValue),
}}
blank={{}}
item={setting}
disabled={false}
fieldRequired={true}

Check failure on line 24 in webpack/components/NewTemplateSync/components/ProxySettingField.js

View workflow job for this annotation

GitHub Actions / test_js (12)

Value must be omitted for boolean attribute `fieldRequired`
meta={{
touched: get(form.touched, fieldName),
error: get(form.errors, fieldName),
}}
input={field}
/>
);

ProxySettingField.propTypes = {
setting: PropTypes.object.isRequired,
resetField: PropTypes.func.isRequired,
field: PropTypes.object.isRequired,
form: PropTypes.object.isRequired,
fieldName: PropTypes.string.isRequired,
};

export default ProxySettingField;
Loading

0 comments on commit ce1a701

Please sign in to comment.