package org.openslx.dozmod.gui.wizard.page; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.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.ContainerDefinition; import org.openslx.dozmod.state.UploadWizardState; import org.openslx.dozmod.thrift.*; import org.openslx.dozmod.thrift.cache.MetaDataCache; import org.openslx.dozmod.util.ContainerUtils; import org.openslx.virtualization.configuration.VirtualizationConfigurationDocker; 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.io.File; import java.io.IOException; import java.nio.ByteBuffer; 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 = LogManager.getLogger(ContainerUploadPage.class); private final UploadWizardState state; private final ImageDetailsRead existingImage; private 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(); } // This constructor is used in case the user wants do upload a new version. public ContainerUploadPage(Wizard wizard, UploadWizardState uploadWizardState, ImageDetailsRead imageDetailsRead) { super(wizard); state = uploadWizardState; existingImage = imageDetailsRead; containerDefinition = ContainerUtils.getContainerDefinition( Session.getSatelliteToken(), imageDetailsRead.getImageName(), imageDetailsRead.getLatestVersionId()); lblImageName.setEnabled(existingImage == null); txtImageName.setEnabled(existingImage == null); txtImageName.setText(existingImage.getImageName()); 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(); } public ContainerUploadPage(Wizard wizard, UploadWizardState state, ContainerDefinition containerDefinition) { this(wizard, state); this.containerDefinition = containerDefinition; } /** * register for each user input control the proper action/method * TODO ugly ... */ 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(); } }); txtImageRepo.getDocument().addDocumentListener(new TextChangeListener() { @Override public void changed() { reactOnUserInput(); } }); this.btnBrowseImageTar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseImageTarFile(); } }); this.txtImageTar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseImageTarFile(); } }); btnBrowseForImage.requestFocus(); } private void browseImageTarFile() { 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 || !ContainerUtils.isValidTar(file)) { txtImageTar.setText(""); return; } txtImageTar.setText(file.getAbsolutePath()); Config.setUploadPath(file.getParent()); reactOnUserInput(); 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; 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() { switch (getCurrentContext()) { case IMAGE_REPOSITORY: if (txtImageRepo.getText() == null || txtImageRepo.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoImageRepo")); return false; } break; case DOCKERFILE: if (txtImageFile.getText() == null || txtImageFile.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoReceipt")); return false; } if (!ContainerDefinition.isValidDockerfile(txtImageFile.getText())) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoValidDockerfiler")); return false; } break; case GIT_REPOSITORY: if (txtGitRepo.getText() == null || txtGitRepo.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoGitRepository")); return false; } break; case DOCKER_ARCHIVE: if (txtImageTar.getText() == null || txtImageTar.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoDockerArchive")); return false; } break; default: // The case is not provided return false; } if (txtImageName.getText() == null || txtImageName.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoProperName")); return false; } return true; } private VirtualizationConfigurationDocker createVirtualizationConfig() { containerDefinition.getContainerMeta().setContainerImageContext(getCurrentContext().ordinal()); containerDefinition.getContainerMeta().setImageName(txtImageName.getText()); switch (containerDefinition.getContainerImageContext()) { case DOCKERFILE: containerDefinition.setContainerRecipe(state.descriptionFile); state.diskFile = getDummyFile(); break; case IMAGE_REPOSITORY: containerDefinition.getContainerMeta().setImageRepo(txtImageRepo.getText()); state.diskFile = getDummyFile(); state.descriptionFile = getDummyFile(); break; case GIT_REPOSITORY: containerDefinition.getContainerMeta().setBuildContextUrl(txtGitRepo.getText()); state.diskFile = getDummyFile(); state.descriptionFile = getDummyFile(); break; case DOCKER_ARCHIVE: state.diskFile = new File(txtImageTar.getText()); state.descriptionFile = getDummyFile(); break; } return containerDefinition.createVirtualizationConfig(); } /** * 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() { state.virtualizationConfig = createVirtualizationConfig(); // -- create image to get uuid -- if (existingImage == null) { if (state.uuid == null) { state.name = txtImageName.getText(); 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(); state.name = existingImage.getImageName(); } // TODO copy/paste from ImageCreationWizard.wantFinish() // Do this only if the user wants to upload a new image version! if (existingImage != null) { // 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, ByteBuffer.wrap(state.virtualizationConfig.getConfigurationAsByteArray())); } 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()); return (f.isFile() && m.matches()) || f.isDirectory(); } @Override public String getDescription() { return "Dockerfile"; } } private static class ContainerImageFileFiler extends FileFilter { @Override public boolean accept(File f) { return (f.isFile() && f.toString().endsWith(".tar")) || f.isDirectory(); } @Override public String getDescription() { return "Container Image (.tar)"; } } }