Compare commits

..

7 Commits

Author SHA1 Message Date
LP B
0dd40d6d01 feat(app/k8s): update ingress scheme from v1beta1 to v1 (#5466) 2021-08-31 12:34:00 +12:00
cong meng
d4c3612334 fix(ingress): EE-1049 Ingress config is lost when deleting an application deployed with ingress (#5264)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-08-31 12:29:56 +12:00
cong meng
eae70e6be0 fix EE-1078 Too strict form validation for docker environment variables (#5278)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
2021-08-31 12:29:52 +12:00
zees-dev
b4f4ef701a feat(kubeconfig): kubeconfig download functionality EE-1202 (#5386)
* backend migration/backport

* Feat(kubeconfig): kubeconfig download button frontend EE-1202 (#5385)

* kubeconfig download button frontend

* fix kubeconfig download button

* backend migration/backport

* moved ng-if up one level

Co-authored-by: zees-dev <dev.786zshan@gmail.com>

* resolved conflicts, updated code

* - kube-config -> kube-config-download-button
- fixed kubeconfig file name (bug)

Co-authored-by: Richard Wei <54336863+WaysonWei@users.noreply.github.com>
2021-08-31 10:07:50 +12:00
Anthony Lapenna
e8a6f15210 chore(build-system): update dev-toolkit (#4887) (#5543)
* chore(build-system): update dev-toolkit

* chore(build-system): update dev-toolkit

* chore(build-system): update dev-toolkit Dockerfile

* chore(build-system): update gruntfile

* chore(build-system): gruntfile update

* chore(build-system): better support for private git repositories

* Update toolkit.Dockerfile

* merge develop into toolkit-update

* merge develop into toolkit-update
2021-08-31 10:04:31 +12:00
Dmitry Salakhov
c39c7010be Revert "fix(stacks): allow root based compose file paths (#5506)" (#5540)
This reverts commit 78c4530956.
2021-08-30 19:06:35 +12:00
Dmitry Salakhov
78c4530956 fix(stacks): allow root based compose file paths (#5506) 2021-08-30 17:14:44 +12:00
18 changed files with 189 additions and 54 deletions

View File

@@ -79,4 +79,4 @@ func (handler *Handler) proxyRequestsToKubernetesAPI(w http.ResponseWriter, r *h
http.StripPrefix(requestPrefix, proxy).ServeHTTP(w, r)
return nil
}
}

View File

@@ -158,7 +158,7 @@ func (transport *baseTransport) proxySecretDeleteOperation(request *http.Request
}
func isSecretRepresentPrivateRegistry(secret map[string]interface{}) bool {
if secret["type"] == nil || secret["type"].(string) != string(v1.SecretTypeDockerConfigJson) {
if secret["type"].(string) != string(v1.SecretTypeDockerConfigJson) {
return false
}

View File

@@ -1214,8 +1214,8 @@ type (
// KubeClient represents a service used to query a Kubernetes environment
KubeClient interface {
GetServiceAccount(tokendata *TokenData) (*v1.ServiceAccount, error)
SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error
GetServiceAccount(tokendata *TokenData) (*v1.ServiceAccount, error)
GetServiceAccountBearerToken(userID int) (string, error)
CreateUserShellPod(ctx context.Context, serviceAccountName string) (*KubernetesShellPod, error)
StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer) error

View File

@@ -0,0 +1,15 @@
export default class KubeConfigController {
/* @ngInject */
constructor($window, KubernetesConfigService) {
this.$window = $window;
this.KubernetesConfigService = KubernetesConfigService;
}
async downloadKubeconfig() {
await this.KubernetesConfigService.downloadConfig();
}
$onInit() {
this.state = { isHTTPS: this.$window.location.protocol === 'https:' };
}
}

View File

@@ -0,0 +1,11 @@
<button
ng-if="$ctrl.state.isHTTPS"
type="button"
class="btn btn-xs btn-primary"
ng-click="$ctrl.downloadKubeconfig()"
analytics-on
analytics-category="kubernetes"
analytics-event="kubernetes-kubectl-kubeconfig"
>
Kubeconfig <i class="fas fa-download space-right"></i>
</button>

View File

@@ -0,0 +1,7 @@
import angular from 'angular';
import controller from './kube-config-download-button.controller';
angular.module('portainer.kubernetes').component('kubeConfigDownloadButton', {
templateUrl: './kube-config-download-button.html',
controller,
});

View File

@@ -3,13 +3,12 @@ import * as fit from 'xterm/lib/addons/fit/fit';
export default class KubectlShellController {
/* @ngInject */
constructor(TerminalWindow, $window, $async, EndpointProvider, LocalStorage, KubernetesConfigService, Notifications) {
constructor(TerminalWindow, $window, $async, EndpointProvider, LocalStorage, Notifications) {
this.$async = $async;
this.$window = $window;
this.TerminalWindow = TerminalWindow;
this.EndpointProvider = EndpointProvider;
this.LocalStorage = LocalStorage;
this.KubernetesConfigService = KubernetesConfigService;
this.Notifications = Notifications;
}
@@ -83,7 +82,7 @@ export default class KubectlShellController {
endpointId: this.EndpointProvider.endpointID(),
};
const wsProtocol = this.state.isHTTPS ? 'wss://' : 'ws://';
const wsProtocol = this.$window.location.protocol === 'https:' ? 'wss://' : 'ws://';
const path = '/api/websocket/kubernetes-shell';
const queryParams = Object.entries(params)
.map(([k, v]) => `${k}=${v}`)
@@ -97,17 +96,12 @@ export default class KubectlShellController {
this.configureSocketAndTerminal(this.state.shell.socket, this.state.shell.term);
}
async downloadKubeconfig() {
await this.KubernetesConfigService.downloadConfig();
}
$onInit() {
return this.$async(async () => {
this.state = {
css: 'normal',
checked: false,
icon: 'fa-window-minimize',
isHTTPS: this.$window.location.protocol === 'https:',
shell: {
connected: false,
socket: null,

View File

@@ -1,14 +1,12 @@
<button type="button" class="btn btn-xs btn-primary" ng-click="$ctrl.connectConsole()" ng-disabled="$ctrl.state.shell.connected" data-cy="k8sSidebar-shellButton">
<i class="fa fa-terminal" style="margin-right: 2px;"></i>
kubectl shell
<i class="fa fa-terminal space-right"></i> kubectl shell
</button>
<kube-config-download-button></kube-config-download-button>
<div ng-if="$ctrl.state.checked" class="{{ $ctrl.state.css }}-kubectl-shell">
<div class="shell-container">
<div class="shell-item"><i class="fas fa-terminal" style="margin-right: 5px;"></i>kubectl shell</div>
<div ng-if="$ctrl.state.isHTTPS" class="shell-item-center">
<a href="" ng-click="$ctrl.downloadKubeconfig()"><i class="fas fa-file-download" style="margin-right: 5px;"></i>Download Kubeconfig</a>
</div>
<div class="shell-item-right">
<i class="fas fa-redo-alt" ng-click="$ctrl.screenClear();" data-cy="k8sShell-refreshButton"></i>
<i

View File

@@ -19,10 +19,10 @@ export class KubernetesIngressConverter {
: _.map(rule.http.paths, (path) => {
const ingRule = new KubernetesIngressRule();
ingRule.IngressName = data.metadata.name;
ingRule.ServiceName = path.backend.serviceName;
ingRule.ServiceName = path.backend.service.name;
ingRule.Host = rule.host || '';
ingRule.IP = data.status.loadBalancer.ingress ? data.status.loadBalancer.ingress[0].ip : undefined;
ingRule.Port = path.backend.servicePort;
ingRule.Port = path.backend.service.port.number;
ingRule.Path = path.path;
return ingRule;
});
@@ -151,8 +151,8 @@ export class KubernetesIngressConverter {
rule.http.paths = _.map(paths, (p) => {
const path = new KubernetesIngressRulePathCreatePayload();
path.path = p.Path;
path.backend.serviceName = p.ServiceName;
path.backend.servicePort = p.Port;
path.backend.service.name = p.ServiceName;
path.backend.service.port.number = p.Port;
return path;
});
hostsWithRules.push(host);
@@ -173,7 +173,7 @@ export class KubernetesIngressConverter {
res.spec.rules = [];
_.forEach(data.Hosts, (host) => {
if (!host.NeedsDeletion) {
res.spec.rules.push({ host: host.Host });
res.spec.rules.push({ host: host.Host || host });
}
});
} else {

View File

@@ -20,10 +20,15 @@ export function KubernetesIngressRuleCreatePayload() {
export function KubernetesIngressRulePathCreatePayload() {
return {
backend: {
serviceName: '',
servicePort: 0,
},
path: '',
pathType: 'ImplementationSpecific',
backend: {
service: {
name: '',
port: {
number: 0,
},
},
},
};
}

View File

@@ -5,7 +5,7 @@ angular.module('portainer.kubernetes').factory('KubernetesIngresses', factory);
function factory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
'use strict';
return function (namespace) {
const url = `${API_ENDPOINT_ENDPOINTS}/:endpointId/kubernetes/apis/networking.k8s.io/v1beta1${namespace ? '/namespaces/:namespace' : ''}/ingresses/:id/:action`;
const url = `${API_ENDPOINT_ENDPOINTS}/:endpointId/kubernetes/apis/networking.k8s.io/v1${namespace ? '/namespaces/:namespace' : ''}/ingresses/:id/:action`;
return $resource(
url,
{

View File

@@ -9,7 +9,6 @@ class KubernetesConfigService {
async downloadConfig() {
const response = await this.KubernetesConfig.get();
const headers = response.headers();
const contentDispositionHeader = headers['content-disposition'];
const filename = contentDispositionHeader.replace('attachment;', '').trim();

View File

@@ -1,12 +1,4 @@
import { KEY_REGEX, VALUE_REGEX } from '@/portainer/helpers/env-vars';
class EnvironmentVariablesSimpleModeItemController {
/* @ngInject */
constructor() {
this.KEY_REGEX = KEY_REGEX;
this.VALUE_REGEX = VALUE_REGEX;
}
onChangeName(name) {
const fieldIsInvalid = typeof name === 'undefined';
if (fieldIsInvalid) {

View File

@@ -9,7 +9,6 @@
placeholder="e.g. FOO"
ng-model="$ctrl.variable.name"
ng-disabled="$ctrl.variable.added"
ng-pattern="$ctrl.KEY_REGEX"
ng-change="$ctrl.onChangeName($ctrl.variable.name)"
required
/>
@@ -36,7 +35,6 @@
ng-model="$ctrl.variable.value"
placeholder="e.g. bar"
ng-trim="false"
ng-pattern="$ctrl.VALUE_REGEX"
name="value"
ng-change="$ctrl.onChangeValue($ctrl.variable.value)"
/>

View File

@@ -1,7 +1,6 @@
import _ from 'lodash-es';
export const KEY_REGEX = /[a-zA-Z]([-_a-zA-Z0-9]*[a-zA-Z0-9])?/.source;
export const KEY_REGEX = /(.+)/.source;
export const VALUE_REGEX = /(.*)?/.source;
const KEY_VALUE_REGEX = new RegExp(`^(${KEY_REGEX})\\s*=(${VALUE_REGEX})$`);
@@ -16,7 +15,7 @@ export function parseDotEnvFile(src) {
return parseArrayOfStrings(
_.compact(src.split(NEWLINES_REGEX))
.map((v) => v.trim())
.filter((v) => !v.startsWith('#'))
.filter((v) => !v.startsWith('#') && v !== '')
);
}
@@ -40,7 +39,7 @@ export function parseArrayOfStrings(array) {
const parsedKeyValArr = variableString.trim().match(KEY_VALUE_REGEX);
if (parsedKeyValArr != null && parsedKeyValArr.length > 4) {
return { name: parsedKeyValArr[1], value: parsedKeyValArr[3] || '' };
return { name: parsedKeyValArr[1].trim(), value: parsedKeyValArr[3].trim() || '' };
}
})
);

99
build/linux/dev-toolkit/run.sh Executable file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# Script used to init the Portainer development environment inside the dev-toolkit image
### COLOR OUTPUT ###
ESeq="\x1b["
RCol="$ESeq"'0m' # Text Reset
# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds
Bla="$ESeq"'0;30m'; BBla="$ESeq"'1;30m'; UBla="$ESeq"'4;30m'; IBla="$ESeq"'0;90m'; BIBla="$ESeq"'1;90m'; On_Bla="$ESeq"'40m'; On_IBla="$ESeq"'0;100m';
Red="$ESeq"'0;31m'; BRed="$ESeq"'1;31m'; URed="$ESeq"'4;31m'; IRed="$ESeq"'0;91m'; BIRed="$ESeq"'1;91m'; On_Red="$ESeq"'41m'; On_IRed="$ESeq"'0;101m';
Gre="$ESeq"'0;32m'; BGre="$ESeq"'1;32m'; UGre="$ESeq"'4;32m'; IGre="$ESeq"'0;92m'; BIGre="$ESeq"'1;92m'; On_Gre="$ESeq"'42m'; On_IGre="$ESeq"'0;102m';
Yel="$ESeq"'0;33m'; BYel="$ESeq"'1;33m'; UYel="$ESeq"'4;33m'; IYel="$ESeq"'0;93m'; BIYel="$ESeq"'1;93m'; On_Yel="$ESeq"'43m'; On_IYel="$ESeq"'0;103m';
Blu="$ESeq"'0;34m'; BBlu="$ESeq"'1;34m'; UBlu="$ESeq"'4;34m'; IBlu="$ESeq"'0;94m'; BIBlu="$ESeq"'1;94m'; On_Blu="$ESeq"'44m'; On_IBlu="$ESeq"'0;104m';
Pur="$ESeq"'0;35m'; BPur="$ESeq"'1;35m'; UPur="$ESeq"'4;35m'; IPur="$ESeq"'0;95m'; BIPur="$ESeq"'1;95m'; On_Pur="$ESeq"'45m'; On_IPur="$ESeq"'0;105m';
Cya="$ESeq"'0;36m'; BCya="$ESeq"'1;36m'; UCya="$ESeq"'4;36m'; ICya="$ESeq"'0;96m'; BICya="$ESeq"'1;96m'; On_Cya="$ESeq"'46m'; On_ICya="$ESeq"'0;106m';
Whi="$ESeq"'0;37m'; BWhi="$ESeq"'1;37m'; UWhi="$ESeq"'4;37m'; IWhi="$ESeq"'0;97m'; BIWhi="$ESeq"'1;97m'; On_Whi="$ESeq"'47m'; On_IWhi="$ESeq"'0;107m';
printSection() {
echo -e "${BIYel}>>>> ${BIWhi}${1}${RCol}"
}
info() {
echo -e "${BIWhi}${1}${RCol}"
}
success() {
echo -e "${BIGre}${1}${RCol}"
}
error() {
echo -e "${BIRed}${1}${RCol}"
}
errorAndExit() {
echo -e "${BIRed}${1}${RCol}"
exit 1
}
### !COLOR OUTPUT ###
SETUP_FILE=/setup-done
display_configuration() {
info "Portainer dev-toolkit container configuration"
info "Go version"
/usr/local/go/bin/go version
info "Node version"
node -v
info "Yarn version"
yarn -v
info "Docker version"
docker version
}
main() {
[[ -z $PUSER ]] && errorAndExit "Unable to find PUSER environment variable. Please ensure PUSER is set before running this script."
[[ -z $PUID ]] && errorAndExit "Unable to find PUID environment variable. Please ensure PUID is set before running this script."
[[ -z $PGID ]] && errorAndExit "Unable to find PGID environment variable. Please ensure PGID is set before running this script."
[[ -z $DOCKERGID ]] && errorAndExit "Unable to find DOCKERGID environment variable. Please ensure DOCKERGID is set before running this script."
if [[ -f "${SETUP_FILE}" ]]; then
info "Portainer dev-toolkit container already configured."
display_configuration
else
info "Creating user group..."
groupadd -g $PGID $PUSER
info "Creating user..."
useradd -l -u $PUID -g $PUSER $PUSER
info "Setting up home..."
install -d -m 0755 -o $PUSER -g $PUSER /home/$PUSER
info "Configuring Docker..."
groupadd -g $DOCKERGID docker
usermod -aG docker $PUSER
info "Configuring Go..."
echo "PATH=\"$PATH:/usr/local/go/bin\"" > /etc/environment
info "Configuring Git..."
su $PUSER -c "git config --global url.git@github.com:.insteadOf https://github.com/"
info "Configuring SSH..."
mkdir /home/$PUSER/.ssh
cp /host-ssh/* /home/$PUSER/.ssh/
chown -R $PUSER:$PUSER /home/$PUSER/.ssh
touch "${SETUP_FILE}"
success "Portainer dev-toolkit container successfully configured."
display_configuration
fi
}
main
su $PUSER -s "$@"

View File

@@ -1,4 +1,4 @@
FROM ubuntu
FROM ubuntu:20.04
# Expose port for the Portainer UI and Edge server
EXPOSE 9000
@@ -14,13 +14,30 @@ ARG GO_VERSION=go1.16.6.linux-amd64
# Install packages
RUN apt-get update --fix-missing && apt-get install -qq \
dialog \
apt-utils \
curl \
build-essential \
nodejs \
git \
wget
dialog \
apt-utils \
curl \
build-essential \
git \
wget \
apt-transport-https \
ca-certificates \
gnupg-agent \
software-properties-common
# Install Docker CLI
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
&& add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable" \
&& apt-get update \
&& apt-get install -y docker-ce-cli
# Install NodeJS
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y nodejs
# Install Yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
@@ -33,8 +50,8 @@ RUN cd /tmp \
&& tar -xf ${GO_VERSION}.tar.gz \
&& mv go /usr/local
# Configure Go
ENV PATH "$PATH:/usr/local/go/bin"
# Copy run script
COPY run.sh /
RUN chmod +x /run.sh
# Confirm installation
RUN go version && node -v && yarn -v
ENTRYPOINT ["/run.sh"]

View File

@@ -7,6 +7,7 @@ var arch = os.arch();
if (arch === 'x64') arch = 'amd64';
var portainer_data = '${PORTAINER_DATA:-/tmp/portainer}';
var portainer_root = process.env.PORTAINER_PROJECT ? process.env.PORTAINER_PROJECT : process.env.PWD;
module.exports = function (grunt) {
loadGruntTasks(grunt, {
@@ -174,7 +175,7 @@ function shell_build_binary_azuredevops(p, a) {
function shell_run_container() {
return [
'docker rm -f portainer',
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
'docker run -d -p 8000:8000 -p 9000:9000 -v ' + portainer_root + '/dist:/app -v ' +
portainer_data +
':/data -v /var/run/docker.sock:/var/run/docker.sock:z -v /var/run/docker.sock:/var/run/alternative.sock:z -v /tmp:/tmp --name portainer portainer/base /app/portainer',
].join(';');