From bfd1a084d9ff3a3c2c63824797d6ffe01d947d71 Mon Sep 17 00:00:00 2001 From: ralph isenmann Date: Thu, 14 Oct 2021 14:22:59 +0200 Subject: [client] update input methods for container images - remove input method for dockerfiles and git repositories; requires building mechanism in the backend - add input method for docker-archives; tar-files from docker save ... --- .../wizard/layout/ContainerUploadPageLayout.java | 123 +++++++++++++-------- .../gui/wizard/page/ContainerUploadPage.java | 98 +++++++--------- .../org/openslx/dozmod/util/ContainerUtils.java | 83 ++++++++++++++ 3 files changed, 201 insertions(+), 103 deletions(-) diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java index b0201e47..509ba11e 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java @@ -29,7 +29,8 @@ public class ContainerUploadPageLayout extends WizardPage { protected final JTabbedPane tpInput; protected final JTextField txtGitRepo; - protected final JTextField txtContainerImageFile; + protected final JTextField txtImageTar; + protected final JButton btnBrowseImageTar; /** * Constructor to define the Layout @@ -39,75 +40,97 @@ public class ContainerUploadPageLayout extends WizardPage { super(wizard, I18n.PAGE_LAYOUT.getString("ContainerUploadPage.title")); setDescription(I18n.PAGE_LAYOUT.getString("ContainerUploadPage.description")); GridManager grid = new GridManager(this, 3, false); + GridManager tmpGrid; - JPanel imageRepoPanel = new JPanel(); - imageRepoPanel.setVisible(true); - GridManager tmpGrid = new GridManager(imageRepoPanel, 2, true, new Insets(0, 5, 0, 5)); - QLabel lblImageRepo = new QLabel(I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepo.label")); - lblImageRepo.setToolTipText( - I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText")); - txtImageRepo = new JTextField(); - txtImageRepo.setEditable(true); - txtImageRepo.setToolTipText( - I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText")); - tmpGrid.add(lblImageRepo); - tmpGrid.add(txtImageRepo).fill(true, false).expand(true, false); - tmpGrid.finish(false); - - JPanel p1 = new JPanel(); - p1.setVisible(false); - GridManager g1 = new GridManager(p1, 3, true, new Insets(0, 5, 0, 5)); + // #################################### + // Dockerfile Input Panel -- UNUSED -- + JPanel pnlDockerfileInput = new ContainerTabPanel(ContainerBuildContextMethod.FILE); + pnlDockerfileInput.setVisible(false); + tmpGrid = new GridManager(pnlDockerfileInput, 3, true, new Insets(0, 5, 0, 5)); QLabel imageFileCaption = new QLabel( I18n.PAGE_LAYOUT.getString("ContainerUploadPage.DockerFile.label")); txtImageFile = new JTextField(); txtImageFile.setEditable(false); btnBrowseForImage = new JButton(I18n.PAGE_LAYOUT.getString("ImageUpload.Button.browseForImage.text")); btnBrowseForImage.setMnemonic(KeyEvent.VK_B); - g1.add(imageFileCaption); - g1.add(txtImageFile).fill(true, false).expand(true, false); - g1.add(btnBrowseForImage); - g1.finish(false); - - JPanel p2 = new JPanel(); - p2.setVisible(false); - GridManager g2 = new GridManager(p2, 2, true, new Insets(0, 5, 0, 5)); + tmpGrid.add(imageFileCaption); + tmpGrid.add(txtImageFile).fill(true, false).expand(true, false); + tmpGrid.add(btnBrowseForImage); + tmpGrid.finish(false); + + // #################################### + // Git Repository Input Panel -- UNUSED -- + JPanel pnlGitRepositoryInput = new ContainerTabPanel(ContainerBuildContextMethod.GIT_REPOSITORY); + pnlGitRepositoryInput.setVisible(false); + tmpGrid = new GridManager(pnlGitRepositoryInput, 2, true, new Insets(0, 5, 0, 5)); QLabel lblGitRepo = new QLabel(I18n.PAGE_LAYOUT.getString("ContainerUploadPage.GitRepository.label")); lblGitRepo.setToolTipText( I18n.PAGE_LAYOUT.getString("ContainerUploadPage.GitRepository.toolTipText")); txtGitRepo = new JTextField(); txtGitRepo.setToolTipText( I18n.PAGE_LAYOUT.getString("ContainerUploadPage.GitRepository.toolTipText")); - g2.add(lblGitRepo); - g2.add(txtGitRepo).fill(true, false).expand(true, false); - g2.finish(false); + tmpGrid.add(lblGitRepo); + tmpGrid.add(txtGitRepo).fill(true, false).expand(true, false); + tmpGrid.finish(false); + + // #################################### + // Container Archive Input Panel + JPanel pnlContainerImage = new ContainerTabPanel(ContainerBuildContextMethod.DOCKER_TAR); + pnlGitRepositoryInput.setVisible(false); + tmpGrid = new GridManager(pnlContainerImage, 3, true, new Insets(0, 5, 0, 5)); + + QLabel lblImageTarFile = new QLabel( + I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ContainerImageFile.label")); + lblImageTarFile.setToolTipText( + I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ContainerImageFile.ToolTipText")); + + txtImageTar = new JTextField(); + txtImageTar.setEnabled(false); + btnBrowseImageTar = new JButton(I18n.PAGE_LAYOUT.getString("ImageUpload.Button.browseForImage.text")); + btnBrowseImageTar.setMnemonic(KeyEvent.VK_B); + tmpGrid.add(lblImageTarFile); + tmpGrid.add(txtImageTar).fill(true, false).expand(true, false); + tmpGrid.add(btnBrowseImageTar); + tmpGrid.finish(false); + // #################################### + // Image Repository Input Panel + JPanel pnlImageRepo = new ContainerTabPanel(ContainerBuildContextMethod.IMAGE_REPO); + pnlImageRepo.setVisible(true); + tmpGrid = new GridManager(pnlImageRepo, 2, true, new Insets(0, 5, 0, 5)); + QLabel lblImageRepo = new QLabel(I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepo.label")); + lblImageRepo.setToolTipText( + I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText")); + txtImageRepo = new JTextField(); + txtImageRepo.setEditable(true); + txtImageRepo.setToolTipText( + I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText")); + tmpGrid.add(lblImageRepo); + tmpGrid.add(txtImageRepo).fill(true, false).expand(true, false); + tmpGrid.finish(false); + + // #################################### + // Tabbed Input Pane tpInput = new JTabbedPane(); - tpInput.addTab("Dockerfile", p1); - tpInput.addTab("Git-Repository", p2); - tpInput.addTab("Image-Repository", imageRepoPanel); + // TODO we need first a proper backend in bwlp-sat to build container + //tpInput.addTab("Dockerfile", pnlDockerfileInput); + //tpInput.addTab("Git-Repository", pnlGitRepositoryInput); + tpInput.addTab("Docker-Archive", pnlContainerImage); + tpInput.addTab("Image-Repository", pnlImageRepo); tpInput.setSelectedIndex(0); - grid.add(tpInput, 3).fill(true, false); grid.nextRow(); + // #################################### + // Image Name lblImageName = new QLabel(I18n.PANEL.getString("ContainerPanel.Label.ImageName.text")); txtImageName = new JTextField(); grid.add(lblImageName); grid.add(txtImageName, 2, 1).fill(true, false).expand(true, false); grid.nextRow(); - QLabel lblContainerImageFile = new QLabel( - I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ContainerImageFile.label")); - lblContainerImageFile.setToolTipText( - I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ContainerImageFile.ToolTipText")); - txtContainerImageFile = new JTextField(); - txtContainerImageFile.setEnabled(false); - txtContainerImageFile.setToolTipText( - I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ContainerImageFile.ToolTipText")); - grid.add(lblContainerImageFile); - grid.add(txtContainerImageFile, 2).fill(true, false).expand(true, false); - grid.nextRow(); - + // #################################### + // Info Box grid.add(Box.createVerticalGlue(), 3).expand(true, true); txtInfoText = new JTextArea(); txtInfoText.setBorder(BorderFactory.createTitledBorder( @@ -126,6 +149,14 @@ public class ContainerUploadPageLayout extends WizardPage { } protected ContainerBuildContextMethod getBuildContextMethod() { - return ContainerBuildContextMethod.fromInt(tpInput.getSelectedIndex()); + return ((ContainerTabPanel) tpInput.getSelectedComponent()).containerImageContext; + } + + static class ContainerTabPanel extends JPanel { + public final ContainerBuildContextMethod containerImageContext; + + ContainerTabPanel(ContainerBuildContextMethod imageContext) { + this.containerImageContext = imageContext; + } } } diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java index b21564a6..c7e85d22 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java @@ -13,9 +13,8 @@ import org.openslx.dozmod.model.ContainerDefinition; import org.openslx.dozmod.state.UploadWizardState; import org.openslx.dozmod.thrift.ThriftActions; import org.openslx.dozmod.thrift.cache.MetaDataCache; +import org.openslx.dozmod.util.ContainerUtils; import org.openslx.virtualization.configuration.VirtualizationConfigurationDocker; -import org.openslx.virtualization.configuration.container.ContainerBuildContextMethod; -import org.openslx.virtualization.configuration.container.ContainerMeta; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -23,8 +22,6 @@ 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; @@ -65,8 +62,8 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { 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. + // This constructor is used in case the user wants do upload a new version. + // TODO currently unused public ContainerUploadPage(Wizard wizard, UploadWizardState uploadWizardState, ImageDetailsRead imageDetailsRead) { super(wizard); @@ -129,45 +126,37 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { } }); - 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) { - + this.btnBrowseImageTar.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + browseImageTarFile(); } + }); - @Override public void mouseExited(MouseEvent e) { - + this.txtImageTar.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + browseImageTarFile(); } }); btnBrowseForImage.requestFocus(); } - private void browseContainerImageFile() { + 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) { - txtContainerImageFile.setText(""); + + if (action != JFileChooser.APPROVE_OPTION || file == null || !ContainerUtils.isValidTar(file)) { + txtImageTar.setText(""); return; } - txtContainerImageFile.setText(file.getAbsolutePath()); + txtImageTar.setText(file.getAbsolutePath()); + Config.setUploadPath(file.getParent()); + reactOnUserInput(); LOGGER.info("Tar File selected"); } @@ -191,13 +180,6 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { 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(); @@ -230,9 +212,8 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { } private boolean checkUserInput() { - ContainerBuildContextMethod method = getBuildContextMethod(); - switch (method) { + switch (getBuildContextMethod()) { case IMAGE_REPO: if (txtImageRepo.getText() == null || txtImageRepo.getText().isEmpty()) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoImageRepo")); @@ -244,7 +225,7 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoReceipt")); return false; } - if (! ContainerDefinition.isValidDockerfile(txtImageFile.getText())) { + if (!ContainerDefinition.isValidDockerfile(txtImageFile.getText())) { setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoValidDockerfiler")); return false; } @@ -255,6 +236,16 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { return false; } break; + case DOCKER_TAR: + if (txtImageTar.getText() == null || txtImageTar.getText().isEmpty()) { + setWarningMessage("No Image provided"); + return false; + } + break; + + default: + // The case is not provided + return false; } if (txtImageName.getText() == null || txtImageName.getText().isEmpty()) { @@ -266,27 +257,26 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { private VirtualizationConfigurationDocker createVirtualizationConfig() { - 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(); + containerDefinition.getContainerMeta().setBuildContextMethod(getBuildContextMethod().ordinal()); + containerDefinition.getContainerMeta().setImageName(txtImageName.getText()); switch (containerDefinition.getBuildContextMethod()) { case FILE: containerDefinition.setContainerRecipe(state.descriptionFile); + state.diskFile = getDummyFile(); break; case IMAGE_REPO: - containerMeta.setImageRepo(txtImageRepo.getText()); + containerDefinition.getContainerMeta().setImageRepo(txtImageRepo.getText()); + state.diskFile = getDummyFile(); state.descriptionFile = getDummyFile(); case GIT_REPOSITORY: - containerMeta.setBuildContextUrl(txtGitRepo.getText()); + containerDefinition.getContainerMeta().setBuildContextUrl(txtGitRepo.getText()); + state.diskFile = getDummyFile(); state.descriptionFile = getDummyFile(); break; + case DOCKER_TAR: + state.diskFile = new File(txtImageTar.getText()); + state.descriptionFile = getDummyFile(); } return containerDefinition.createVirtualizationConfig(); } @@ -331,10 +321,7 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { 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; + return (f.isFile() && m.matches()) || f.isDirectory(); } @Override public String getDescription() { @@ -346,10 +333,7 @@ public class ContainerUploadPage extends ContainerUploadPageLayout { @Override public boolean accept(File f) { - boolean accept = false; - if ((f.isFile() && f.toString().endsWith(".tar")) || f.isDirectory()) - accept = true; - return accept; + return (f.isFile() && f.toString().endsWith(".tar")) || f.isDirectory(); } @Override public String getDescription() { diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java index 0e5a1d15..62e5dd0d 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java @@ -1,6 +1,11 @@ package org.openslx.dozmod.util; +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; import org.apache.log4j.Logger; +import org.kamranzafar.jtar.TarEntry; +import org.kamranzafar.jtar.TarHeader; +import org.kamranzafar.jtar.TarInputStream; import org.openslx.bwlp.thrift.iface.ImageSummaryRead; import org.openslx.bwlp.thrift.iface.LectureSummary; import org.openslx.dozmod.gui.Gui; @@ -10,6 +15,9 @@ import org.openslx.dozmod.thrift.cache.LectureCache; import org.openslx.thrifthelper.TConst; import java.awt.*; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; /** @@ -17,6 +25,7 @@ import java.util.List; */ public class ContainerUtils { + private static final Logger LOGGER = Logger.getLogger(ContainerUtils.class); /** * Checks ImageBaseId of image if already linked with existing Lectures in LectureCache. * @@ -40,4 +49,78 @@ public class ContainerUtils { Gui.showMessageBox(c, I18n.WINDOW.getString("LectureDetails.Message.error.containerLinkedWithLecture"), MessageType.WARNING, logger, null); } + + /** + * Check if a provided tar file contains information about a single docker image. + * To check the validity of the file, the existence of two JSON files is checked + * and one of the files must only contain information for one image. + * + * The tar file have to be created by the 'docker save ...' command. + * + * @param tarFile the user selected tar file. + * @return true if the images imageBaseId is already linked with a lecture. + */ + public static boolean isValidTar(File tarFile) { + + boolean isValid = false; + boolean containsManifest = false; + boolean containsRepositories = false; + JsonArray manifestJson = null; + + try { + TarInputStream tis = new TarInputStream(new FileInputStream(tarFile)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] rawData = new byte[1024]; + TarEntry entry; + + // setDefaultSkip seems to fasten things up while processing the file, + // because we want only to check manifest.json and repositories file + tis.setDefaultSkip(true); + while ((entry = tis.getNextEntry()) != null) { + if(!TarHeader.USTAR_MAGIC.equals(entry.getHeader().magic.toString())) + break; + Arrays.fill(rawData, (byte) 0); + output.reset(); + int count = 0; + + if (entry.getName().equals("manifest.json")) { + containsManifest = true; + while ((count = tis.read(rawData)) != -1) { + output.write(rawData, 0, count); + } + manifestJson = new JsonParser().parse( + new String(output.toByteArray(), StandardCharsets.UTF_8)).getAsJsonArray(); + } + + if (entry.getName().equals("repositories")) { + containsRepositories = true; + // dont read the file, no checks for the Content + } + + if (containsManifest && containsRepositories) + break; + } + tis.close(); + // check the json files inside the tar file + if (containsManifest && containsRepositories && manifestJson.isJsonArray() + && manifestJson.size() == 1) { + isValid = true; + String repoTag = manifestJson.get(0).getAsJsonObject().get("RepoTags").getAsString(); + LOGGER.info(String.format("Tar File contains Docker Image with repoTag=%s", repoTag)); + } else if (containsManifest && containsRepositories && manifestJson.isJsonArray() + && manifestJson.size() > 1) { + Gui.showMessageBox("Tar File container more then one Images!", MessageType.ERROR, + LOGGER, + null); + } else { + Gui.showMessageBox("No valid Tar File with Images provided!", MessageType.ERROR, + LOGGER, + null); + } + + } catch (IOException e) { + LOGGER.error("IOError while processing tar file", e); + } + return isValid; + } } -- cgit v1.2.3-55-g7522