diff --git a/CloudronManifest.json b/CloudronManifest.json index b4f5896..d5c69b7 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -5,11 +5,19 @@ "description": "file://DESCRIPTION.md", "changelog": "file://CHANGELOG", "tagline": "Distributed object storage", - "version": "1.165.1", + "version": "2.0.0", "healthCheckPath": "/minio/login", "httpPort": 8000, + "tcpPorts": { + "API_PORT": { + "title": "API PORT", + "description": "API PORT", + "defaultValue": 9000 + } + }, "addons": { - "localstorage": {} + "localstorage": {}, + "ldap": {} }, "manifestVersion": 2, "website": "http://www.minio.io", diff --git a/DESCRIPTION.md b/DESCRIPTION.md index e2ff5b2..f03e6d1 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -1,4 +1,4 @@ -This app packages Minio 2021-07-08T01-15-01Z. +This app packages Minio 2021-07-15T22-27-34Z. Minio is a distributed object storage server built for cloud applications and devops. diff --git a/Dockerfile b/Dockerfile index d79a82f..865677f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,11 @@ FROM cloudron/base:3.0.0@sha256:455c70428723e3a823198c57472785437eb6eab082e79b3f RUN mkdir -p /app/code WORKDIR /app/code -ARG VERSION=RELEASE.2021-07-08T01-15-01Z +ARG VERSION=RELEASE.2021-07-15T22-27-34Z +ARG MC_VERSION=RELEASE.2021-06-13T17-48-22Z -RUN wget https://dl.min.io/server/minio/release/linux-amd64/minio.${VERSION} -O /app/code/minio && chmod +x /app/code/minio +RUN wget https://dl.min.io/server/minio/release/linux-amd64/minio.${VERSION} -O /app/code/minio && chmod +x /app/code/minio && \ + wget https://dl.min.io/client/mc/release/linux-amd64/mc.${MC_VERSION} -O /app/code/mc && chmod +x /app/code/mc ADD start.sh /app/code/start.sh ADD minio-credentials /app/code/minio-credentials diff --git a/start.sh b/start.sh index 611cf2a..d3ea368 100755 --- a/start.sh +++ b/start.sh @@ -9,11 +9,38 @@ mkdir -p /app/data/data /run/minio/config /run/minio/certs echo "==> Changing ownership" [[ $(stat --format '%U' /app/data/data) != "cloudron" ]] && chown -R cloudron:cloudron /app/data -[[ ! -f /app/data/env.sh ]] && echo -e "# Add custom minio configuration to this file. Restart the app for changes to take effect.\n\nexport CLOUDRON_MINIO_STARTUP_ARGS='server /app/data/data'" > /app/data/env.sh +if [[ ! -f /app/data/env.sh ]]; then + echo -e "# Add custom minio configuration to this file. Restart the app for changes to take effect.\n\nexport CLOUDRON_MINIO_STARTUP_ARGS='server /app/data/data'" > /app/data/env.sh + # https://github.com/minio/minio#things-to-consider + echo -e "export MINIO_BROWSER_REDIRECT_URI=$(echo $CLOUDRON_APP_ORIGIN)" >> /app/data/env.sh + # ###### ! WARNING ! LDAP IS DISABLED FOR NOW ###### + # https://github.com/minio/minio/blob/master/docs/sts/ldap.md + # https://docs.min.io/minio/baremetal/security/ad-ldap-external-identity-management/configure-ad-ldap-external-identity-management.html#minio-authenticate-using-ad-ldap-generic + # (address) AD/LDAP server address e.g. "myldapserver.com:636" + # echo -e "export MINIO_IDENTITY_LDAP_SERVER_ADDR='$(echo $CLOUDRON_LDAP_SERVER):$(echo $CLOUDRON_LDAP_PORT)'" >> /app/data/env.sh + # (string) DN for LDAP read-only service account used to perform DN and group lookups + # echo -e "export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN='$(echo $CLOUDRON_LDAP_BIND_DN)'" >> /app/data/env.sh + # (string) Password for LDAP read-only service account used to perform DN and group lookups + # echo -e "export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD='$(echo $CLOUDRON_LDAP_BIND_PASSWORD)'" >> /app/data/env.sh + # (string) Base LDAP DN to search for user DN + # echo -e "export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN='$(echo $CLOUDRON_LDAP_USERS_BASE_DN)'" >> /app/data/env.sh + # (string) Search filter to lookup user DN + # echo -e "export MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER='(&(objectclass=user)(|(username=%uid)(mail=%uid)))'" >> /app/data/env.sh + # https://docs.min.io/minio/baremetal/reference/minio-server/minio-server.html#envvar.MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY + # echo -e "export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY='on'" >> /app/data/env.sh + # https://docs.min.io/minio/baremetal/reference/minio-server/minio-server.html#envvar.MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY + # echo -e "export MINIO_IDENTITY_LDAP_SERVER_INSECURE='on'" >> /app/data/env.sh + # ###### ! WARNING ! LDAP IS DISABLED FOR NOW ###### +fi + +if [[ ! -d /app/data/mc_config ]]; then + echo "==> Set /app/data/mc default config dir" + mkdir -p /app/data/mc_config + /app/code/mc --config-dir /app/data/mc_config &> /dev/null || true +fi source /app/data/env.sh # the --config-dir is deprecated and not used. but without it, minio will try to create $HOME/.minio :/ same for --certs-dir echo "==> Starting minio" -exec /usr/local/bin/gosu cloudron:cloudron /app/code/minio --certs-dir /run/minio/certs --config-dir /run/minio/config --quiet ${CLOUDRON_MINIO_STARTUP_ARGS} --address :8000 - +exec /usr/local/bin/gosu cloudron:cloudron /app/code/minio --certs-dir /run/minio/certs --config-dir /run/minio/config --quiet ${CLOUDRON_MINIO_STARTUP_ARGS} --address :$API_PORT --console-address :8000 diff --git a/test/test.js b/test/test.js index 67ec4ec..4d3bd95 100644 --- a/test/test.js +++ b/test/test.js @@ -1,9 +1,11 @@ #!/usr/bin/env node +/* jshint esversion: 8 */ /* global describe */ /* global before */ /* global after */ /* global it */ +/* global xit */ 'use strict'; @@ -15,7 +17,6 @@ var execSync = require('child_process').execSync, { Builder, By, Key, until } = require('selenium-webdriver'), { Options } = require('selenium-webdriver/chrome'); - describe('Application life cycle test', function () { this.timeout(0); @@ -23,130 +24,121 @@ describe('Application life cycle test', function () { const TEST_TIMEOUT = 10000; const EXEC_ARGS = { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' }; const BUCKET = 'cloudrontestbucket'; + const username = process.env.USERNAME; + const password = process.env.PASSWORD; - var app; - var browser; + let browser, app; + + before(function (done) { + if (!process.env.PASSWORD) return done(new Error('PASSWORD env var not set')); + if (!process.env.USERNAME) return done(new Error('USERNAME env var not set')); - before(function () { browser = new Builder().forBrowser('chrome').setChromeOptions(new Options().windowSize({ width: 1280, height: 1024 })).build(); + done(); }); after(function () { browser.quit(); }); + function sleep(millis) { + return new Promise(resolve => setTimeout(resolve, millis)); + } + + async function waitForElement(elem) { + await browser.wait(until.elementLocated(elem), TEST_TIMEOUT); + await browser.wait(until.elementIsVisible(browser.findElement(elem)), TEST_TIMEOUT); + } + function getAppInfo() { var inspect = JSON.parse(execSync('cloudron inspect')); app = inspect.apps.filter(function (a) { return a.location.indexOf(LOCATION) === 0; })[0]; expect(app).to.be.an('object'); } - function pageLoaded() { - return browser.wait(until.elementLocated(By.className('page-load pl-0 pl-1')), TEST_TIMEOUT); + async function login(accessKey='minioadmin', secretKey='minioadmin') { + await browser.get(`https://${app.fqdn}/login`); + await waitForElement(By.id('accessKey')); + await browser.findElement(By.id('accessKey')).sendKeys(accessKey); + await browser.findElement(By.id('secretKey')).sendKeys(secretKey); + await browser.findElement(By.xpath('//button[@type="submit"]/span[text()="Login"]')).click(); + await waitForElement(By.xpath(`//div/span[contains(text(), "Dashboard")]`)); } - function visible(selector) { - return browser.wait(until.elementLocated(selector), TEST_TIMEOUT).then(function () { - return browser.wait(until.elementIsVisible(browser.findElement(selector)), TEST_TIMEOUT); - }); + async function old_login(accessKey='minioadmin', secretKey='minioadmin') { + await browser.get(`https://${app.fqdn}/minio/login`); + await waitForElement(By.id('accessKey')); + await browser.findElement(By.id('accessKey')).sendKeys(accessKey); + await browser.findElement(By.id('secretKey')).sendKeys(secretKey); + await browser.findElement(By.xpath('//button[@type="submit"]')).click(); + await waitForElement(By.xpath(`//input[@placeholder="Search Buckets..."]`)); } - function login(accessKey, secretKey, callback) { - browser.manage().deleteAllCookies(); - browser.get('https://' + app.fqdn).then(function () { - return visible(By.id('accessKey')); - }).then(function () { - return browser.findElement(By.id('accessKey')).sendKeys(accessKey); - }).then(function () { - return browser.findElement(By.id('secretKey')).sendKeys(secretKey); - }).then(function () { - // return browser.findElement(By.className('lw-btn')).click(); - return browser.findElement(By.tagName('form')).submit(); - }).then(function () { - return browser.wait(until.elementLocated(By.id('top-right-menu')), TEST_TIMEOUT); - }).then(function () { - callback(); - }); + async function logout() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//div/span[contains(text(), "Dashboard")]`)); + await browser.findElement(By.xpath('//div/span[contains(text(), "Logout")]')).click(); + await waitForElement(By.id('accessKey')); } - function logout(callback) { - browser.get('https://' + app.fqdn); - - pageLoaded().then(function () { - return visible(By.id('top-right-menu')); - }).then(function () { - return browser.findElement(By.id('top-right-menu')).click(); - }).then(function () { - if (app.manifest.version === '1.137.0') { - return visible(By.xpath('//*[text()="Sign Out "]')); - } else { - return visible(By.xpath('//*[contains(text(), "Logout")]')); - } - }).then(function () { - if (app.manifest.version === '1.137.0') { - return browser.findElement(By.xpath('//*[text()="Sign Out "]')).click(); - } else { - return browser.findElement(By.xpath('//*[contains(text(),"Logout")]')).click(); - } - }).then(function () { - return browser.wait(until.elementLocated(By.id('accessKey')), TEST_TIMEOUT); - }).then(function () { - callback(); - }); + async function old_logout() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//input[@placeholder="Search Buckets..."]`)); + await browser.findElement(By.xpath('//div/button[@id="top-right-menu"]')).click(); + await browser.findElement(By.xpath('//ul/li/a[@id="logout"]')).click(); + await waitForElement(By.id('accessKey')); } - function addBucket(callback) { - browser.get('https://' + app.fqdn); - - pageLoaded().then(function () { - return visible(By.className('fa-plus')); - }).then(function () { - return browser.findElement(By.className('fa-plus')).click(); - }).then(function () { - const c = 'fa-hdd'; - return visible(By.className(c)); - }).then(function () { - const c = 'fa-hdd'; - return browser.findElement(By.className(c)).click(); - }).then(function () { - return visible(By.xpath('//*[@class="modal-body"]/form/div/input')); - }).then(function () { - return browser.findElement(By.xpath('//*[@class="modal-body"]/form/div/input')).sendKeys(BUCKET); - }).then(function () { - return browser.findElement(By.xpath('//*[@class="modal-body"]/form')).submit(); - }).then(function () { - return visible(By.xpath('//*[@class="main"]/a[text()="' + BUCKET + '"]')); - }).then(function () { - callback(); - }); + async function addBucket() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath('//div/span[contains(text(), "Bucket")]')); + await browser.findElement(By.xpath('//div/span[contains(text(), "Bucket")]')).click(); + await waitForElement(By.xpath('//span[text()="Create Bucket"]')); + await browser.findElement(By.xpath('//span[text()="Create Bucket"]')).click(); + await browser.findElement(By.xpath('//input[@id="bucket-name"]')).sendKeys(BUCKET); + await browser.findElement(By.xpath('//button[@type="submit"]/span[text()="Save"]')).click(); + await waitForElement(By.xpath(`//div/span[contains(text(), "${BUCKET}")]`)); + await browser.findElement(By.xpath(`//div/span[contains(text(), "${BUCKET}")]`)); } - function checkBucket(callback) { - browser.get('https://' + app.fqdn); - - pageLoaded().then(function () { - return browser.findElement(By.xpath(`//a[contains(text(), ${BUCKET})]`)); - }).then(function () { - callback(); - }); + async function old_addBucket() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//input[@placeholder="Search Buckets..."]`)); + await browser.findElement(By.xpath('//div/button[@id="fe-action-toggle"]')).click(); + await browser.findElement(By.xpath('//ul/a[@id="show-make-bucket"]')).click(); + await browser.findElement(By.xpath('//input[@placeholder="Bucket Name"]')).sendKeys(BUCKET); + await browser.findElement(By.xpath('//*[@class="modal-body"]/form')).submit(); + await waitForElement(By.xpath(`//*[@class="main"]/a[text()="${BUCKET}"]`)); } - function openSettings(callback) { - browser.get('https://' + app.fqdn); + async function checkBucket() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//div/span[contains(text(), "Dashboard")]`)); + await browser.findElement(By.xpath('//div/span[contains(text(), "Bucket")]')).click(); + await waitForElement(By.xpath(`//div/span[contains(text(), "${BUCKET}")]`)); + await browser.findElement(By.xpath(`//div/span[contains(text(), "${BUCKET}")]`)); + } - pageLoaded().then(function () { - return visible(By.id('top-right-menu')); - }).then(function () { - return browser.findElement(By.id('top-right-menu')).click(); - }).then(function () { - return visible(By.xpath('//*[contains(text(), "Change Password")]')); - }).then(function () { - return browser.findElement(By.xpath('//*[contains(text(),"Change Password")]')).click(); - }).then(function () { - return browser.wait(until.elementLocated(By.xpath('//*[contains(text(), "Change Password")]')), TEST_TIMEOUT); - }).then(function () { - callback(); - }); + async function old_checkBucket() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//input[@placeholder="Search Buckets..."]`)); + await waitForElement(By.xpath(`//*[@class="main"]/a[text()="${BUCKET}"]`)); + } + + async function openSettings() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//div/span[contains(text(), "Dashboard")]`)); + await browser.findElement(By.xpath('//div/span[contains(text(), "Account")]')).click(); + await waitForElement(By.xpath(`//button/span[text()="Change Password"]`)); + await browser.findElement(By.xpath('//button/span[text()="Change Password"]')); + } + + async function old_openSettings() { + await browser.get(`https://${app.fqdn}/`); + await waitForElement(By.xpath(`//input[@placeholder="Search Buckets..."]`)); + await browser.findElement(By.xpath('//div/button[@id="top-right-menu"]')).click(); + await waitForElement(By.xpath('//ul/li/a[contains(text(), "Change Password")]')); + await browser.findElement(By.xpath('//ul/li/a[contains(text(), "Change Password")]')); } xit('build app', function () { execSync('cloudron build', EXEC_ARGS); }); @@ -199,10 +191,11 @@ describe('Application life cycle test', function () { it('can install app', function () { execSync('cloudron install --appstore-id io.minio.cloudronapp --location ' + LOCATION, EXEC_ARGS); }); it('can get app information', getAppInfo); - it('can login', login.bind(null, 'minioadmin', 'minioadmin')); - it('can add buckets', addBucket); - it('can logout', logout); - it('can update', function () { execSync('cloudron update --app ' + LOCATION, EXEC_ARGS); }); + it('can login', old_login.bind(null, 'minioadmin', 'minioadmin')); + it('can add buckets', old_addBucket); + it('can logout', old_logout); + it('can update', function () { execSync(`cloudron update --app ${LOCATION} --no-wait`, EXEC_ARGS); }); + it('can enable API Port', function () { execSync(`cloudron configure --app ${LOCATION} -p API_PORT=9000 -l ${LOCATION} `, EXEC_ARGS); }); it('can get app information', getAppInfo); it('can login', login.bind(null, 'minioadmin', 'minioadmin'));