package org.openslx.dozmod.gui.wizard.page; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.openslx.bwlp.thrift.iface.ImageDetailsRead; import org.openslx.dozmod.Config; import org.openslx.dozmod.gui.Gui; import org.openslx.dozmod.gui.helper.I18n; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.gui.helper.QFileChooser; import org.openslx.dozmod.gui.helper.TextChangeListener; import org.openslx.dozmod.gui.wizard.Wizard; import org.openslx.dozmod.gui.wizard.layout.ContainerUploadPageLayout; import org.openslx.dozmod.model.ContainerBuildContextMethod; import org.openslx.dozmod.model.ContainerDefinition; import org.openslx.dozmod.model.ContainerMeta; import org.openslx.dozmod.state.UploadWizardState; import org.openslx.dozmod.thrift.ThriftActions; import org.openslx.dozmod.thrift.ThriftError; import org.openslx.dozmod.thrift.UploadInitiator; import org.openslx.dozmod.thrift.WrappedException; import org.openslx.dozmod.thrift.cache.MetaDataCache; import org.openslx.virtualization.configuration.container.DockerMetaDataDummy; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileFilter; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ContainerUploadPage extends ContainerUploadPageLayout { // TODO: Add a Instruction for the new Container-Feature in bwLehrpool. // TODO: Add link to instructions for Docker-Intetragtion at https://www.bwlehrpool.de/doku.php /** * Version for serialization. */ private static final long serialVersionUID = 2564301984375080698L; private final Logger LOGGER = Logger.getLogger(ContainerUploadPage.class); private final UploadWizardState state; private final ImageDetailsRead existingImage; private final ContainerDefinition containerDefinition; /** * Page for uploading an Container Image * * @param wizard The wizard dialog in which this page is active. */ public ContainerUploadPage(Wizard wizard, final UploadWizardState state) { super(wizard); this.containerDefinition = new ContainerDefinition(); this.state = state; canComeBack = false; existingImage = null; // HACK set dummy os state.selectedOs = MetaDataCache.getOsById(18); init(); } // TODO this constructor is currently used in case if user wants do upload a new version. // This makes no currently no sens in context of docker container and this is used. public ContainerUploadPage(Wizard wizard, UploadWizardState uploadWizardState, ImageDetailsRead imageDetailsRead) { super(wizard); state = uploadWizardState; existingImage = imageDetailsRead; // TODO fix this! containerDefinition = null; lblImageName.setEnabled(existingImage == null); txtImageName.setEnabled(existingImage == null); txtInfoText.setVisible(existingImage == null); state.name = imageDetailsRead.imageName; state.defaultPermissions = imageDetailsRead.getDefaultPermissions(); state.description = imageDetailsRead.getDescription(); state.detectedOs = MetaDataCache.getOsById(imageDetailsRead.getOsId()); state.selectedOs = MetaDataCache.getOsById(imageDetailsRead.getOsId()); state.tags = imageDetailsRead.getTags(); state.defaultPermissions = imageDetailsRead.getUserPermissions(); state.uuid = imageDetailsRead.getImageBaseId(); init(); } private void init() { this.txtImageFile.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseFile(); } }); this.btnBrowseForImage.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseFile(); } }); txtImageName.getDocument().addDocumentListener(new TextChangeListener() { @Override public void changed() { reactOnUserInput(); } }); tpInput.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { reactOnUserInput(); } }); txtGitRepo.getDocument().addDocumentListener(new TextChangeListener() { @Override public void changed() { reactOnUserInput(); } }); txtContainerImageFile.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() >= 2) browseContainerImageFile(); } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } }); btnBrowseForImage.requestFocus(); } private void browseContainerImageFile() { QFileChooser fc = new QFileChooser(Config.getUploadPath(), false); fc.setAcceptAllFileFilterUsed(false); fc.addChoosableFileFilter(new ContainerImageFileFiler()); int action = fc.showOpenDialog(getDialog()); File file = fc.getSelectedFile(); if (action != JFileChooser.APPROVE_OPTION || file == null) { txtContainerImageFile.setText(""); return; } txtContainerImageFile.setText(file.getAbsolutePath()); LOGGER.info("Tar File selected"); } private void browseFile() { QFileChooser fc = new QFileChooser(Config.getUploadPath(), false); fc.setAcceptAllFileFilterUsed(false); fc.addChoosableFileFilter(new DockerfileFilter()); int action = fc.showOpenDialog(getDialog()); File file = fc.getSelectedFile(); if (action != JFileChooser.APPROVE_OPTION || file == null) return; txtImageFile.setText(file.getAbsolutePath()); if (existingImage == null) txtImageName.setText(file.getParentFile().getName()); else txtImageName.setText(existingImage.getImageName()); state.descriptionFile = file; // TESTING: Upload also a prematurely created image (tar) String imageName = file.getParentFile().getName(); File imageTarFile = new File(file.getParentFile(), imageName.concat(".tar")); if (imageTarFile.exists()) { txtContainerImageFile.setText(imageTarFile.getAbsolutePath()); LOGGER.info("Prebuild Container Image found"); } Config.setUploadPath(file.getParent()); reactOnUserInput(); } private File getDummyFile() { String configDir = Config.getPath(); File zeroFile = new File(configDir, "ZERO-FILE"); // create a temp file with dummy content, // because vmchooser relies on an existing image after choosing a lecture. // TODO change behavior in vmchooser to allow start lectures without images. try { if (!zeroFile.exists()) { zeroFile.createNewFile(); FileUtils.writeStringToFile(zeroFile, "ZERO\n", "UTF-8"); } } catch (IOException e) { e.printStackTrace(); LOGGER.error("Could not create a dummy file.", e); } LOGGER.info("Use dummy file"); return zeroFile; } private void reactOnUserInput() { boolean completed = checkUserInput(); if (completed) setDescription(I18n.PAGE.getString("ContainerUploadPage.Description.ContainerDefFinished")); setPageComplete(completed); } private boolean checkUserInput() { ContainerBuildContextMethod method = getBuildContextMethod(); switch (method) { case FILE: if (txtImageFile.getText() == null || txtImageFile.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoReceipt")); return false; } break; case GIT_REPOSITORY: if (txtGitRepo.getText() == null || txtGitRepo.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoGitRepository")); return false; } break; } if (txtImageName.getText() == null || txtImageName.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoProperName")); return false; } return true; } private DockerMetaDataDummy createVmMeta() { ContainerMeta containerMeta = containerDefinition.getContainerMeta(); containerMeta.setBuildContextMethod(getBuildContextMethod().ordinal()); containerMeta.setImageName(txtImageName.getText()); File containerImageFile = new File(txtContainerImageFile.getText()); if (containerImageFile.exists()) state.diskFile = containerImageFile; else state.diskFile = getDummyFile(); switch (containerDefinition.getBuildContextMethod()) { case FILE: containerDefinition.setContainerRecipe(state.descriptionFile); break; case GIT_REPOSITORY: containerMeta.setBuildContextUrl(txtGitRepo.getText()); state.descriptionFile = getDummyFile(); break; } return containerDefinition.createVmMeta(); } /** * This function starts the image creation process. It is triggered by the * "Next" button. *

* Depending on the state, it will first try to get a UUID for the new image by * calling createImage() of the thrift API. If a UUID is received, it will * request an upload with requestImageVersionUpload(). If granted, it will * finally start a thread for the UploadTask. *

* Then a callback to the Gui is executed where we can process the upload state * and give the user feedback about it. */ @Override protected boolean wantNextOrFinish() { // are we creating a new image? then either: // get the image name either auto filled by VmwareMetaData or by user // get the image name from the image we are uploading a new version of state.name = existingImage != null ? existingImage.getImageName() : txtImageName.getText(); state.meta = createVmMeta(); // -- create image to get uuid -- if (existingImage == null) { if (state.uuid == null) { state.uuid = ThriftActions.createImage(JOptionPane.getFrameForComponent(this), state.name); if (state.uuid == null) return false; txtImageName.setEnabled(false); btnBrowseForImage.setEnabled(false); txtImageFile.setEnabled(false); } } else { state.uuid = existingImage.getImageBaseId(); } // Create upload initiator that will manage requesting a token, hashing the // file, connecting for upload... if (state.upload == null) { try { state.upload = new UploadInitiator(state.uuid, state.diskFile, state.meta.getFilteredDefinition()); } catch (WrappedException e) { ThriftError.showMessage(this, LOGGER, e.exception, e.displayMessage); return false; } catch (IOException e) { Gui.showMessageBox(this, I18n.PAGE.getString("ImageUpload.Message.error.uploadInitiatorFailed"), MessageType.ERROR, LOGGER, e); return false; } } // Start the hash check now state.upload.startHashing(); return true; } private static class DockerfileFilter extends FileFilter { @Override public boolean accept(File f) { Pattern p = Pattern.compile("[Dd]ockerfile"); Matcher m = p.matcher(f.getName()); boolean accept = false; if ((f.isFile() && m.matches()) || f.isDirectory()) accept = true; return accept; } @Override public String getDescription() { return "Dockerfile"; } } private static class ContainerImageFileFiler extends FileFilter { @Override public boolean accept(File f) { boolean accept = false; if ((f.isFile() && f.toString().endsWith(".tar")) || f.isDirectory()) accept = true; return accept; } @Override public String getDescription() { return "Container Image (.tar)"; } } }