use of io.cdap.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class DefaultArtifactInspector method inspectArtifact.
/**
* Inspect the given artifact to determine the classes contained in the artifact.
*
* @param artifactId the id of the artifact to inspect
* @param artifactFile the artifact file
* @param parentDescriptor {@link ArtifactDescriptor} of parent and grandparent (if any) artifacts.
* @param additionalPlugins Additional plugin classes
* @return metadata about the classes contained in the artifact
* @throws IOException if there was an exception opening the jar file
* @throws InvalidArtifactException if the artifact is invalid. For example, if the application main class is not
* actually an Application.
*/
@Override
public ArtifactClassesWithMetadata inspectArtifact(Id.Artifact artifactId, File artifactFile, List<ArtifactDescriptor> parentDescriptor, Set<PluginClass> additionalPlugins) throws IOException, InvalidArtifactException {
Path tmpDir = Paths.get(cConf.get(Constants.CFG_LOCAL_DATA_DIR), cConf.get(Constants.AppFabric.TEMP_DIR)).toAbsolutePath();
Files.createDirectories(tmpDir);
Location artifactLocation = Locations.toLocation(artifactFile);
EntityImpersonator entityImpersonator = new EntityImpersonator(artifactId.toEntityId(), impersonator);
Path stageDir = Files.createTempDirectory(tmpDir, artifactFile.getName());
try (ClassLoaderFolder clFolder = BundleJarUtil.prepareClassLoaderFolder(artifactLocation, () -> Files.createTempDirectory(stageDir, "unpacked-").toFile());
CloseableClassLoader parentClassLoader = createParentClassLoader(parentDescriptor, entityImpersonator);
CloseableClassLoader artifactClassLoader = artifactClassLoaderFactory.createClassLoader(clFolder.getDir());
PluginInstantiator pluginInstantiator = new PluginInstantiator(cConf, parentClassLoader == null ? artifactClassLoader : parentClassLoader, Files.createTempDirectory(stageDir, "plugins-").toFile(), false)) {
pluginInstantiator.addArtifact(artifactLocation, artifactId.toArtifactId());
ArtifactClasses.Builder builder = inspectApplications(artifactId, ArtifactClasses.builder(), artifactLocation, artifactClassLoader);
List<MetadataMutation> mutations = new ArrayList<>();
inspectPlugins(builder, artifactFile, artifactId.toEntityId(), pluginInstantiator, additionalPlugins, mutations);
return new ArtifactClassesWithMetadata(builder.build(), mutations);
} catch (EOFException | ZipException e) {
throw new InvalidArtifactException("Artifact " + artifactId + " is not a valid zip file.", e);
} finally {
try {
DirUtils.deleteDirectoryContents(stageDir.toFile());
} catch (IOException e) {
LOG.warn("Exception raised while deleting directory {}", stageDir, e);
}
}
}
use of io.cdap.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class DefaultArtifactInspector method inspectApplications.
private ArtifactClasses.Builder inspectApplications(Id.Artifact artifactId, ArtifactClasses.Builder builder, Location artifactLocation, ClassLoader artifactClassLoader) throws IOException, InvalidArtifactException {
// right now we force users to include the application main class as an attribute in their manifest,
// which forces them to have a single application class.
// in the future, we may want to let users do this or maybe specify a list of classes or
// a package that will be searched for applications, to allow multiple applications in a single artifact.
String mainClassName;
try {
Manifest manifest = BundleJarUtil.getManifest(artifactLocation);
if (manifest == null) {
return builder;
}
Attributes manifestAttributes = manifest.getMainAttributes();
if (manifestAttributes == null) {
return builder;
}
mainClassName = manifestAttributes.getValue(ManifestFields.MAIN_CLASS);
} catch (ZipException e) {
throw new InvalidArtifactException(String.format("Couldn't unzip artifact %s, please check it is a valid jar file.", artifactId), e);
}
if (mainClassName == null) {
return builder;
}
try {
Class<?> mainClass = artifactClassLoader.loadClass(mainClassName);
if (!(Application.class.isAssignableFrom(mainClass))) {
// possible for 3rd party plugin artifacts to have the main class set
return builder;
}
Application app = (Application) mainClass.newInstance();
java.lang.reflect.Type configType;
// we can deserialize the config into that object. Otherwise it'll just be a Config
try {
configType = Artifacts.getConfigType(app.getClass());
} catch (Exception e) {
throw new InvalidArtifactException(String.format("Could not resolve config type for Application class %s in artifact %s. " + "The type must extend Config and cannot be parameterized.", mainClassName, artifactId));
}
Schema configSchema = configType == Config.class ? null : schemaGenerator.generate(configType);
builder.addApp(new ApplicationClass(mainClassName, "", configSchema, getArtifactRequirements(app.getClass())));
} catch (ClassNotFoundException e) {
throw new InvalidArtifactException(String.format("Could not find Application main class %s in artifact %s.", mainClassName, artifactId));
} catch (UnsupportedTypeException e) {
throw new InvalidArtifactException(String.format("Config for Application %s in artifact %s has an unsupported schema. " + "The type must extend Config and cannot be parameterized.", mainClassName, artifactId));
} catch (InstantiationException | IllegalAccessException e) {
throw new InvalidArtifactException(String.format("Could not instantiate Application class %s in artifact %s.", mainClassName, artifactId), e);
}
return builder;
}
use of io.cdap.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ArtifactRepositoryTest method testCyclicDependenciesAreInvalid.
@Test
public void testCyclicDependenciesAreInvalid() throws Exception {
// create a1 artifact that extends app and a2
io.cdap.cdap.proto.id.ArtifactId a1Id = NamespaceId.DEFAULT.artifact("a1", "1.0.0");
io.cdap.cdap.proto.id.ArtifactId a2Id = NamespaceId.DEFAULT.artifact("a2", "1.0.0");
Manifest manifest = createManifest(ManifestFields.EXPORT_PACKAGE, Plugin1.class.getPackage().getName());
File jarFile = createPluginJar(Plugin1.class, new File(tmpDir, "a1-1.0.0.jar"), manifest);
Set<ArtifactRange> parents = ImmutableSet.of(new ArtifactRange(APP_ARTIFACT_ID.getNamespace().getId(), APP_ARTIFACT_ID.getName(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")), new ArtifactRange(a2Id.getNamespace(), a2Id.getArtifact(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
artifactRepository.addArtifact(Id.Artifact.fromEntityId(a1Id), jarFile, parents, null);
// create a2 artifact that extends a1, which should be invalid
manifest = createManifest(ManifestFields.EXPORT_PACKAGE, Plugin2.class.getPackage().getName());
jarFile = createPluginJar(Plugin2.class, new File(tmpDir, "a2-1.0.0.jar"), manifest);
parents = ImmutableSet.of(new ArtifactRange(a1Id.getNamespace(), a1Id.getArtifact(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
try {
artifactRepository.addArtifact(Id.Artifact.fromEntityId(a2Id), jarFile, parents, null);
Assert.fail("Artifact repository did not catch a cyclic dependency.");
} catch (InvalidArtifactException e) {
// expected
}
}
use of io.cdap.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ArtifactRepositoryTest method testGreatGrandparentsAreInvalid.
@Test
public void testGreatGrandparentsAreInvalid() throws Exception {
// create child artifact
io.cdap.cdap.proto.id.ArtifactId childId = NamespaceId.DEFAULT.artifact("child", "1.0.0");
Manifest manifest = createManifest(ManifestFields.EXPORT_PACKAGE, Plugin1.class.getPackage().getName());
File jarFile = createPluginJar(Plugin1.class, new File(tmpDir, "child-1.0.0.jar"), manifest);
// add the artifact
Set<ArtifactRange> parents = ImmutableSet.of(new ArtifactRange(APP_ARTIFACT_ID.getNamespace().getId(), APP_ARTIFACT_ID.getName(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
artifactRepository.addArtifact(Id.Artifact.fromEntityId(childId), jarFile, parents, null);
// create grandchild
io.cdap.cdap.proto.id.ArtifactId grandchildId = NamespaceId.DEFAULT.artifact("grandchild", "1.0.0");
manifest = createManifest(ManifestFields.EXPORT_PACKAGE, Plugin2.class.getPackage().getName());
jarFile = createPluginJar(Plugin2.class, new File(tmpDir, "grandchild-1.0.0.jar"), manifest);
parents = ImmutableSet.of(new ArtifactRange(childId.getNamespace(), childId.getArtifact(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
artifactRepository.addArtifact(Id.Artifact.fromEntityId(grandchildId), jarFile, parents, null);
// try and create great grandchild, should fail
io.cdap.cdap.proto.id.ArtifactId greatGrandchildId = NamespaceId.DEFAULT.artifact("greatgrandchild", "1.0.0");
manifest = createManifest(ManifestFields.EXPORT_PACKAGE, Plugin2.class.getPackage().getName());
jarFile = createPluginJar(Plugin2.class, new File(tmpDir, "greatgrandchild-1.0.0.jar"), manifest);
parents = ImmutableSet.of(new ArtifactRange(grandchildId.getNamespace(), grandchildId.getArtifact(), new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
try {
artifactRepository.addArtifact(Id.Artifact.fromEntityId(greatGrandchildId), jarFile, parents, null);
Assert.fail("Artifact repository is not supposed to allow great grandparents.");
} catch (InvalidArtifactException e) {
// expected
}
}
use of io.cdap.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ApplicationLifecycleService method updateApp.
/**
* Update an existing application. An application's configuration and artifact version can be updated.
*
* @param appId the id of the application to update
* @param appRequest the request to update the application, including new config and artifact
* @param programTerminator a program terminator that will stop programs that are removed when updating an app.
* For example, if an update removes a flow, the terminator defines how to stop that flow.
* @return information about the deployed application
* @throws ApplicationNotFoundException if the specified application does not exist
* @throws ArtifactNotFoundException if the requested artifact does not exist
* @throws InvalidArtifactException if the specified artifact is invalid. For example, if the artifact name changed,
* if the version is an invalid version, or the artifact contains no app classes
* @throws Exception if there was an exception during the deployment pipeline. This exception will often wrap
* the actual exception
*/
public ApplicationWithPrograms updateApp(ApplicationId appId, AppRequest appRequest, ProgramTerminator programTerminator) throws Exception {
// Check if the current user has admin privileges on it before updating.
accessEnforcer.enforce(appId, authenticationContext.getPrincipal(), StandardPermission.UPDATE);
// check that app exists
ApplicationSpecification currentSpec = store.getApplication(appId);
if (currentSpec == null) {
throw new ApplicationNotFoundException(appId);
}
ArtifactId currentArtifact = currentSpec.getArtifactId();
// if no artifact is given, use the current one.
ArtifactId newArtifactId = currentArtifact;
// otherwise, check requested artifact is valid and use it
ArtifactSummary requestedArtifact = appRequest.getArtifact();
if (requestedArtifact != null) {
// cannot change artifact name, only artifact version.
if (!currentArtifact.getName().equals(requestedArtifact.getName())) {
throw new InvalidArtifactException(String.format(" Only artifact version updates are allowed. Cannot change from artifact '%s' to '%s'.", currentArtifact.getName(), requestedArtifact.getName()));
}
if (!currentArtifact.getScope().equals(requestedArtifact.getScope())) {
throw new InvalidArtifactException("Only artifact version updates are allowed. " + "Cannot change from a non-system artifact to a system artifact or vice versa.");
}
// check requested artifact version is valid
ArtifactVersion requestedVersion = new ArtifactVersion(requestedArtifact.getVersion());
if (requestedVersion.getVersion() == null) {
throw new InvalidArtifactException(String.format("Requested artifact version '%s' is invalid", requestedArtifact.getVersion()));
}
newArtifactId = new ArtifactId(currentArtifact.getName(), requestedVersion, currentArtifact.getScope());
}
// ownerAdmin.getImpersonationPrincipal will give the owner which will be impersonated for the application
// irrespective of the version
SecurityUtil.verifyOwnerPrincipal(appId, appRequest.getOwnerPrincipal(), ownerAdmin);
Object requestedConfigObj = appRequest.getConfig();
// if config is null, use the previous config. Shouldn't use a static GSON since the request Config object can
// be a user class, otherwise there will be ClassLoader leakage.
String requestedConfigStr = requestedConfigObj == null ? currentSpec.getConfiguration() : new Gson().toJson(requestedConfigObj);
Id.Artifact artifactId = Id.Artifact.fromEntityId(Artifacts.toProtoArtifactId(appId.getParent(), newArtifactId));
return deployApp(appId.getParent(), appId.getApplication(), null, artifactId, requestedConfigStr, programTerminator, ownerAdmin.getOwner(appId), appRequest.canUpdateSchedules());
}
Aggregations