Skip to content

Commit

Permalink
SALTO-5737 space deployment filter to handle permission changes (#5795)
Browse files Browse the repository at this point in the history
  • Loading branch information
omrifarkash authored Apr 21, 2024
1 parent 4cdad50 commit 5927782
Show file tree
Hide file tree
Showing 12 changed files with 1,057 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ export {
TransformFunction,
AdjustFunction,
ContextParams,
GeneratedItem,
} from './shared'
export * from './utils'
3 changes: 3 additions & 0 deletions packages/confluence-adapter/src/adapter_creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { REFERENCES } from './definitions/references'
import { customConvertError } from './error_utils'
import transformTemplateBodyToTemplateExpressionFilterCreator from './filters/transform_template_body_to_template_expression'
import customPathsFilterCreator from './filters/custom_paths'
import deploySpaceAndPermissionsFilterCreator from './filters/deploy_space_and_permissions'

const { DEFAULT_RETRY_OPTS, RATE_LIMIT_UNLIMITED_MAX_CONCURRENT_REQUESTS } = client
const { defaultCredentialsFromConfig } = credentials
Expand All @@ -48,6 +49,8 @@ export const adapter = createAdapter<Credentials, Options, UserConfig>({
connectionCreatorFromConfig: () => createConnection,
credentialsFromConfig: defaultCredentialsFromConfig,
customizeFilterCreators: args => ({
// deploySpaceAndPermissionsFilterCreator should run before default deploy filter
deploySpaceAndPermissionsFilterCreator: deploySpaceAndPermissionsFilterCreator(args),
...filters.createCommonFilters<Options, UserConfig>(args),
// transform template body must run after references are created (fieldReferencesFilter)
transformTemplateBodyToTemplateExpressionFilterCreator,
Expand Down
97 changes: 86 additions & 11 deletions packages/confluence-adapter/src/definitions/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,25 @@
*/
import _ from 'lodash'
import { definitions, deployment } from '@salto-io/adapter-components'
import { isModificationChange } from '@salto-io/adapter-api'
import { AdditionalAction, ClientOptions } from '../types'
import { increasePagesVersion } from '../transformation_utils'
import {
BLOG_POST_TYPE_NAME,
GLOBAL_TEMPLATE_TYPE_NAME,
PAGE_TYPE_NAME,
PERMISSION_TYPE_NAME,
SPACE_TYPE_NAME,
TEMPLATE_TYPE_NAME,
} from '../../constants'

type InstanceDeployApiDefinitions = definitions.deploy.InstanceDeployApiDefinitions<AdditionalAction, ClientOptions>

const isSpaceChange = ({ change }: definitions.deploy.ChangeAndContext): boolean => {
if (!isModificationChange(change)) {
return false
}
return !_.isEqual(_.omit(change.data.before.value, 'permissions'), _.omit(change.data.after.value, 'permissions'))
}

const createCustomizations = (): Record<string, InstanceDeployApiDefinitions> => {
const standardRequestDefinitions = deployment.helpers.createStandardDeployDefinitions<
AdditionalAction,
ClientOptions
>({
[BLOG_POST_TYPE_NAME]: { bulkPath: 'wiki/api/v2/blogposts', idField: 'id' },
[GLOBAL_TEMPLATE_TYPE_NAME]: { bulkPath: 'wiki/rest/api/template', idField: 'templateId' },
[BLOG_POST_TYPE_NAME]: { bulkPath: '/wiki/api/v2/blogposts', idField: 'id' },
})

const customDefinitions: Record<string, Partial<InstanceDeployApiDefinitions>> = {
Expand Down Expand Up @@ -94,6 +86,9 @@ const createCustomizations = (): Record<string, InstanceDeployApiDefinitions> =>
},
},
[SPACE_TYPE_NAME]: {
referenceResolution: {
when: 'early',
},
requestsByAction: {
customizations: {
add: [
Expand All @@ -112,7 +107,9 @@ const createCustomizations = (): Record<string, InstanceDeployApiDefinitions> =>
modify: [
{
condition: {
custom: () => isSpaceChange,
transformForCheck: {
omit: ['permissions'],
},
},
request: {
endpoint: {
Expand Down Expand Up @@ -157,6 +154,7 @@ const createCustomizations = (): Record<string, InstanceDeployApiDefinitions> =>
},
},
},
// If updating the template deploy definitions, check if need to update global template as well
[TEMPLATE_TYPE_NAME]: {
requestsByAction: {
customizations: {
Expand Down Expand Up @@ -198,6 +196,83 @@ const createCustomizations = (): Record<string, InstanceDeployApiDefinitions> =>
},
},
},
// If updating the global template deploy definitions, check if need to update template as well
// This differs from the template definitions in the context queryArgs (no spaceKey)
[GLOBAL_TEMPLATE_TYPE_NAME]: {
requestsByAction: {
customizations: {
add: [
{
request: {
endpoint: {
path: '/wiki/rest/api/template',
method: 'post',
},
},
},
],
modify: [
{
request: {
endpoint: {
path: '/wiki/rest/api/template',
method: 'put',
},
},
},
],
remove: [
{
request: {
endpoint: {
path: '/wiki/rest/api/template/{templateId}',
method: 'delete',
},
},
},
],
},
},
},
// Permission is not a top-level type, we use it to deploy permissions changes on space instances
[PERMISSION_TYPE_NAME]: {
requestsByAction: {
default: {
request: {
context: {
spaceKey: '{_parent.0.value.key}',
},
},
},
customizations: {
add: [
{
request: {
endpoint: {
path: '/wiki/rest/api/space/{spaceKey}/permission',
method: 'post',
},
},
copyFromResponse: {
additional: {
pick: ['id'],
},
},
},
],
remove: [
{
request: {
endpoint: {
path: '/wiki/rest/api/space/{spaceKey}/permission/{id}',
method: 'delete',
},
},
},
],
},
},
},
}
return _.merge(standardRequestDefinitions, customDefinitions)
}
Expand Down
9 changes: 8 additions & 1 deletion packages/confluence-adapter/src/definitions/fetch/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
SPACE_TYPE_NAME,
TEMPLATE_TYPE_NAME,
} from '../../constants'
import { spaceMergeAndTransformAdjust } from '../transformation_utils/space'

const DEFAULT_FIELDS_TO_HIDE: Record<string, definitions.fetch.ElementFieldCustomization> = {
created_at: {
Expand Down Expand Up @@ -104,6 +105,9 @@ const createCustomizations = (): Record<string, definitions.fetch.InstanceFetchA
],
resource: {
directFetch: true,
mergeAndTransform: {
adjust: spaceMergeAndTransformAdjust,
},
recurseInto: {
permissions: {
typeName: PERMISSION_TYPE_NAME,
Expand Down Expand Up @@ -142,6 +146,9 @@ const createCustomizations = (): Record<string, definitions.fetch.InstanceFetchA
isTopLevel: true,
},
fieldCustomizations: {
permissionInternalIdMap: {
hide: true,
},
id: {
hide: true,
},
Expand All @@ -150,7 +157,7 @@ const createCustomizations = (): Record<string, definitions.fetch.InstanceFetchA
typeName: TEMPLATE_TYPE_NAME,
addParentAnnotation: true,
referenceFromParent: false,
nestPathUnderParent: false,
nestPathUnderParent: true,
},
},
settings: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2024 Salto Labs Ltd.
*
* 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 { definitions } from '@salto-io/adapter-components'
import _ from 'lodash'
import { logger } from '@salto-io/logging'
import { Value } from '@salto-io/adapter-api'
import { values } from '@salto-io/lowerdash'
import { assertValue } from './generic'

const log = logger(module)

export type PermissionObject = {
type: string
principalId: string
key: string
targetType: string
}

export const createPermissionUniqueKey = ({ type, principalId, key, targetType }: PermissionObject): string =>
`${type}_${principalId}_${key}_${targetType}`

export const isPermissionObject = (value: unknown): value is PermissionObject =>
_.isString(_.get(value, 'type')) &&
_.isString(_.get(value, 'principalId')) &&
_.isString(_.get(value, 'key')) &&
_.isString(_.get(value, 'targetType'))

/**
* Restructures a single raw permission object from the service and updates permissionInternalIdMap with the relevant service id.
* @param permission - raw permission from the service.
* @param permissionInternalIdMap - serviceIds map to update.
* @param onFetch - is raw permission came upon fetch or deploy (service returns different structures).
*/
export const transformPermissionAndUpdateIdMap = (
permission: Value,
permissionInternalIdMap: Record<string, string>,
onFetch?: boolean,
): PermissionObject | undefined => {
const type = onFetch ? _.get(permission, 'principal.type') : _.get(permission, 'subject.type')
const principalId = onFetch ? _.get(permission, 'principal.id') : _.get(permission, 'subject.identifier')
const key = _.get(permission, 'operation.key')
const targetType = onFetch ? _.get(permission, 'operation.targetType') : _.get(permission, 'operation.target')
const internalId = _.get(permission, 'id')
if ([type, principalId, key, targetType].some(x => !_.isString(x)) || internalId === undefined) {
log.warn('permission is not in expected format: %o, skipping', permission)
return undefined
}
permissionInternalIdMap[createPermissionUniqueKey({ type, principalId, key, targetType })] = String(internalId)
return { type, principalId, key, targetType }
}

/**
* Restructures permissions array on space instance value and creates an internal ID map.
* To be used on deploy. We need this as we cannot hide fields inside arrays
* @param value - value containing raw permissions array from the service.
*/
export const restructurePermissionsAndCreateInternalIdMap = (value: Record<string, Value>): void => {
const permissions = _.get(value, 'permissions')
if (!Array.isArray(permissions)) {
log.warn('permissions is not an array: %o, skipping space adjust function', permissions)
return
}
const permissionInternalIdMap: Record<string, string> = {}
const transformedPermissions = permissions
.map(per => transformPermissionAndUpdateIdMap(per, permissionInternalIdMap, true))
.filter(values.isDefined)
value.permissions = transformedPermissions
value.permissionInternalIdMap = { ...permissionInternalIdMap }
}

/**
* Adjust function for transforming space instances upon fetch.
* We reconstruct the permissions so we use this function on resource and not on request.
* @param item - The item to adjust.
*/
export const spaceMergeAndTransformAdjust: definitions.AdjustFunction<{
fragments: definitions.GeneratedItem[]
}> = item => {
const value = assertValue(item.value)
restructurePermissionsAndCreateInternalIdMap(value)
return { value }
}
Loading

0 comments on commit 5927782

Please sign in to comment.