Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deprecation): Entity Deprecation Backend #4073

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import com.linkedin.datahub.graphql.resolvers.browse.BrowsePathsResolver;
import com.linkedin.datahub.graphql.resolvers.browse.BrowseResolver;
import com.linkedin.datahub.graphql.resolvers.config.AppConfigResolver;
import com.linkedin.datahub.graphql.resolvers.deprecation.UpdateDeprecationResolver;
import com.linkedin.datahub.graphql.resolvers.domain.CreateDomainResolver;
import com.linkedin.datahub.graphql.resolvers.domain.DomainEntitiesResolver;
import com.linkedin.datahub.graphql.resolvers.domain.ListDomainsResolver;
Expand Down Expand Up @@ -613,6 +614,7 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
.dataFetcher("updateUserStatus", new UpdateUserStatusResolver(this.entityClient))
.dataFetcher("createDomain", new CreateDomainResolver(this.entityClient))
.dataFetcher("setDomain", new SetDomainResolver(this.entityClient, this.entityService))
.dataFetcher("updateDeprecation", new UpdateDeprecationResolver(this.entityClient, this.entityService))
.dataFetcher("unsetDomain", new UnsetDomainResolver(this.entityClient, this.entityService))
.dataFetcher("createSecret", new CreateSecretResolver(this.entityClient, this.secretService))
.dataFetcher("deleteSecret", new DeleteSecretResolver(this.entityClient))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.linkedin.datahub.graphql.resolvers;

import com.google.common.collect.ImmutableList;
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
import com.linkedin.metadata.authorization.PoliciesConfig;
import java.util.List;
import java.util.Optional;
import com.datahub.authorization.AuthorizationRequest;
Expand All @@ -8,6 +11,10 @@

public class AuthUtils {

public static final ConjunctivePrivilegeGroup ALL_PRIVILEGES_GROUP = new ConjunctivePrivilegeGroup(ImmutableList.of(
PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType()
));

public static boolean isAuthorized(
String principal,
List<String> privilegeGroup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ private EntityType mapResourceTypeToEntityType(final String resourceType) {
return EntityType.DATA_JOB;
} else if (com.linkedin.metadata.authorization.PoliciesConfig.TAG_PRIVILEGES.getResourceType().equals(resourceType)) {
return EntityType.TAG;
} else if (com.linkedin.metadata.authorization.PoliciesConfig.GLOSSARY_TERM_PRIVILEGES.getResourceType().equals(resourceType)) {
return EntityType.GLOSSARY_TERM;
} else if (com.linkedin.metadata.authorization.PoliciesConfig.DOMAIN_PRIVILEGES.getResourceType().equals(resourceType)) {
return EntityType.DOMAIN;
} else if (com.linkedin.metadata.authorization.PoliciesConfig.CONTAINER_PRIVILEGES.getResourceType().equals(resourceType)) {
return EntityType.CONTAINER;
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.linkedin.datahub.graphql.resolvers.deprecation;

import com.google.common.collect.ImmutableList;
import com.linkedin.common.Deprecation;
import com.linkedin.common.urn.Urn;
import com.linkedin.data.template.SetMode;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.UpdateDeprecationInput;
import com.linkedin.datahub.graphql.resolvers.AuthUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.events.metadata.ChangeType;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.authorization.PoliciesConfig;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.utils.GenericAspectUtils;
import com.linkedin.mxe.MetadataChangeProposal;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.*;


/**
* Resolver used for updating the Domain associated with a Metadata Asset. Requires the EDIT_DOMAINS privilege for a particular asset.
*/
@Slf4j
@RequiredArgsConstructor
public class UpdateDeprecationResolver implements DataFetcher<CompletableFuture<Boolean>> {

private static final String EMPTY_STRING = "";
private final EntityClient _entityClient;
private final EntityService _entityService; // TODO: Remove this when 'exists' added to EntityClient

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {

final QueryContext context = environment.getContext();
final UpdateDeprecationInput input = bindArgument(environment.getArgument("input"), UpdateDeprecationInput.class);
final Urn entityUrn = Urn.createFromString(input.getUrn());

return CompletableFuture.supplyAsync(() -> {

if (!isAuthorizedToUpdateDeprecationForEntity(environment.getContext(), entityUrn)) {
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
}
validateUpdateDeprecationInput(
entityUrn,
_entityService
);
try {
Deprecation deprecation = (Deprecation) getAspectFromEntity(
entityUrn.toString(),
Constants.DEPRECATION_ASPECT_NAME,
_entityService,
new Deprecation());
updateDeprecation(deprecation, input, context);

// Create the Deprecation aspect
final MetadataChangeProposal proposal = new MetadataChangeProposal();
proposal.setEntityUrn(entityUrn);
proposal.setEntityType(entityUrn.getEntityType());
proposal.setAspectName(Constants.DEPRECATION_ASPECT_NAME);
proposal.setAspect(GenericAspectUtils.serializeAspect(deprecation));
proposal.setChangeType(ChangeType.UPSERT);
_entityClient.ingestProposal(proposal, context.getAuthentication());
return true;
} catch (Exception e) {
log.error("Failed to update Deprecation for resource with entity urn {}: {}", entityUrn, e.getMessage());
throw new RuntimeException(String.format("Failed to update Deprecation for resource with entity urn %s", entityUrn), e);
}
});
}

private boolean isAuthorizedToUpdateDeprecationForEntity(final QueryContext context, final Urn entityUrn) {
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of(
AuthUtils.ALL_PRIVILEGES_GROUP,
new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DEPRECATION_PRIVILEGE.getType()))
));

return AuthorizationUtils.isAuthorized(
context.getAuthorizer(),
context.getActorUrn(),
entityUrn.getEntityType(),
entityUrn.toString(),
orPrivilegeGroups);
}

public static Boolean validateUpdateDeprecationInput(
Urn entityUrn,
EntityService entityService
) {

if (!entityService.exists(entityUrn)) {
throw new IllegalArgumentException(
String.format("Failed to update deprecation for Entity %s. Entity does not exist.", entityUrn));
}

return true;
}

private static void updateDeprecation(Deprecation deprecation, UpdateDeprecationInput input, QueryContext context) {
deprecation.setDeprecated(input.getDeprecated());
deprecation.setDecommissionTime(input.getDecommissionTime(), SetMode.REMOVE_IF_NULL);
if (input.getNote() != null) {
deprecation.setNote(input.getNote());
} else {
// Note is required field in GMS. Set to empty string if not provided.
deprecation.setNote(EMPTY_STRING);
}
try {
deprecation.setActor(Urn.createFromString(context.getActorUrn()));
} catch (URISyntaxException e) {
// Should never happen.
throw new RuntimeException(
String.format("Failed to convert authorized actor into an Urn. actor urn: %s",
context.getActorUrn()),
e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public class ChartType implements SearchableEntityType<Chart>, BrowsableEntityTy
GLOSSARY_TERMS_ASPECT_NAME,
STATUS_ASPECT_NAME,
CONTAINER_ASPECT_NAME,
DOMAINS_ASPECT_NAME
DOMAINS_ASPECT_NAME,
DEPRECATION_ASPECT_NAME
);
private static final Set<String> FACET_FIELDS = ImmutableSet.of("access", "queryType", "tool", "type");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linkedin.datahub.graphql.types.chart.mappers;

import com.linkedin.chart.EditableChartProperties;
import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -21,6 +22,7 @@
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.types.common.mappers.AuditStampMapper;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StatusMapper;
Expand Down Expand Up @@ -104,6 +106,8 @@ public Chart apply(@Nonnull final EntityResponse entityResponse) {
.setType(EntityType.DOMAIN)
.setUrn(domains.getDomains().get(0).toString()).build());
}
} else if (DEPRECATION_ASPECT_NAME.equals(name)) {
result.setDeprecation(DeprecationMapper.map(new Deprecation(data)));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public class ContainerType implements com.linkedin.datahub.graphql.types.EntityT
Constants.GLOBAL_TAGS_ASPECT_NAME,
Constants.GLOSSARY_TERMS_ASPECT_NAME,
Constants.CONTAINER_ASPECT_NAME,
Constants.DOMAINS_ASPECT_NAME
Constants.DOMAINS_ASPECT_NAME,
Constants.DEPRECATION_ASPECT_NAME
);
private final EntityClient _entityClient;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linkedin.datahub.graphql.types.container.mappers;

import com.linkedin.common.DataPlatformInstance;
import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -13,6 +14,7 @@
import com.linkedin.datahub.graphql.generated.DataPlatform;
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StringMapMapper;
Expand Down Expand Up @@ -102,6 +104,11 @@ public static Container map(final EntityResponse entityResponse) {
}
}

final EnvelopedAspect envelopedDeprecation = aspects.get(Constants.DEPRECATION_ASPECT_NAME);
if (envelopedDeprecation != null) {
result.setDeprecation(DeprecationMapper.map(new Deprecation(envelopedDeprecation.getValue().data())));
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public class DashboardType implements SearchableEntityType<Dashboard>, Browsable
GLOSSARY_TERMS_ASPECT_NAME,
STATUS_ASPECT_NAME,
CONTAINER_ASPECT_NAME,
DOMAINS_ASPECT_NAME
DOMAINS_ASPECT_NAME,
DEPRECATION_ASPECT_NAME
);
private static final Set<String> FACET_FIELDS = ImmutableSet.of("access", "tool");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.datahub.graphql.types.dashboard.mappers;

import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -18,6 +19,7 @@
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.types.common.mappers.AuditStampMapper;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StatusMapper;
Expand Down Expand Up @@ -97,6 +99,8 @@ public Dashboard apply(@Nonnull final EntityResponse entityResponse) {
.setType(EntityType.DOMAIN)
.setUrn(domains.getDomains().get(0).toString()).build());
}
} else if (DEPRECATION_ASPECT_NAME.equals(name)) {
result.setDeprecation(DeprecationMapper.map(new Deprecation(data)));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public class DataFlowType implements SearchableEntityType<DataFlow>, BrowsableEn
GLOBAL_TAGS_ASPECT_NAME,
GLOSSARY_TERMS_ASPECT_NAME,
STATUS_ASPECT_NAME,
DOMAINS_ASPECT_NAME
DOMAINS_ASPECT_NAME,
DEPRECATION_ASPECT_NAME
);
private static final Set<String> FACET_FIELDS = ImmutableSet.of("orchestrator", "cluster");
private final EntityClient _entityClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.datahub.graphql.types.dataflow.mappers;

import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -13,6 +14,7 @@
import com.linkedin.datahub.graphql.generated.DataPlatform;
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StatusMapper;
Expand Down Expand Up @@ -86,6 +88,8 @@ public DataFlow apply(@Nonnull final EntityResponse entityResponse) {
.setType(EntityType.DOMAIN)
.setUrn(domains.getDomains().get(0).toString()).build());
}
} else if (DEPRECATION_ASPECT_NAME.equals(name)) {
result.setDeprecation(DeprecationMapper.map(new Deprecation(data)));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public class DataJobType implements SearchableEntityType<DataJob>, BrowsableEnti
GLOBAL_TAGS_ASPECT_NAME,
GLOSSARY_TERMS_ASPECT_NAME,
STATUS_ASPECT_NAME,
DOMAINS_ASPECT_NAME
DOMAINS_ASPECT_NAME,
DEPRECATION_ASPECT_NAME
);
private static final Set<String> FACET_FIELDS = ImmutableSet.of("flow");
private final EntityClient _entityClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linkedin.datahub.graphql.types.datajob.mappers;

import com.google.common.collect.ImmutableList;
import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -16,6 +17,7 @@
import com.linkedin.datahub.graphql.generated.Dataset;
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StatusMapper;
Expand Down Expand Up @@ -85,6 +87,8 @@ public DataJob apply(@Nonnull final EntityResponse entityResponse) {
.setType(EntityType.DOMAIN)
.setUrn(domains.getDomains().get(0).toString()).build());
}
} else if (DEPRECATION_ASPECT_NAME.equals(name)) {
result.setDeprecation(DeprecationMapper.map(new Deprecation(data)));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public class DatasetType implements SearchableEntityType<Dataset>, BrowsableEnti
DATASET_KEY_ASPECT_NAME,
DATASET_PROPERTIES_ASPECT_NAME,
EDITABLE_DATASET_PROPERTIES_ASPECT_NAME,
DATASET_DEPRECATION_ASPECT_NAME,
DATASET_DEPRECATION_ASPECT_NAME, // This aspect is deprecated.
DEPRECATION_ASPECT_NAME,
DATASET_UPSTREAM_LINEAGE_ASPECT_NAME,
UPSTREAM_LINEAGE_ASPECT_NAME,
EDITABLE_SCHEMA_METADATA_ASPECT_NAME,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkedin.datahub.graphql.types.dataset.mappers;

import com.linkedin.common.Deprecation;
import com.linkedin.common.GlobalTags;
import com.linkedin.common.GlossaryTerms;
import com.linkedin.common.InstitutionalMemory;
Expand All @@ -13,6 +14,7 @@
import com.linkedin.datahub.graphql.generated.Domain;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.generated.FabricType;
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
import com.linkedin.datahub.graphql.types.common.mappers.StatusMapper;
Expand Down Expand Up @@ -126,6 +128,13 @@ public Dataset apply(@Nonnull final EntityResponse entityResponse) {
.setType(EntityType.DOMAIN)
.setUrn(domains.getDomains().get(0).toString()).build());
}
} else if (DEPRECATION_ASPECT_NAME.equals(name)) {
if (result.getDeprecation() == null) {
// If deprecation has not already been populated by the legacy
// 'datasetDeprecation' aspect, set it. If it's already been set,
// use the new Deprecation aspect.
result.setDeprecation(DeprecationMapper.map(new Deprecation(data)));
}
}
});
return result;
Expand Down
Loading