Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
ccc5f473
Commit
ccc5f473
authored
Jun 06, 2018
by
Tiago Botelho
Committed by
Rémy Coutable
Jun 06, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adds Kubernetes Pod Logs to GitLab
parent
d1057461
Changes
39
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
709 additions
and
142 deletions
+709
-142
app/assets/javascripts/environments/components/environments_table.vue
...avascripts/environments/components/environments_table.vue
+1
-0
app/assets/javascripts/job.js
app/assets/javascripts/job.js
+9
-77
app/assets/javascripts/lib/utils/logoutput_behaviours.js
app/assets/javascripts/lib/utils/logoutput_behaviours.js
+46
-0
app/assets/javascripts/lib/utils/scroll_utils.js
app/assets/javascripts/lib/utils/scroll_utils.js
+29
-0
app/assets/stylesheets/pages/builds.scss
app/assets/stylesheets/pages/builds.scss
+18
-1
app/assets/stylesheets/pages/environments.scss
app/assets/stylesheets/pages/environments.scss
+7
-0
app/controllers/projects/environments_controller.rb
app/controllers/projects/environments_controller.rb
+2
-0
app/serializers/environment_entity.rb
app/serializers/environment_entity.rb
+2
-0
app/views/projects/jobs/show.html.haml
app/views/projects/jobs/show.html.haml
+1
-3
app/views/shared/builds/_build_output.html.haml
app/views/shared/builds/_build_output.html.haml
+3
-0
config/routes/project.rb
config/routes/project.rb
+3
-0
ee/app/assets/javascripts/environments/components/deploy_board_component.vue
...cripts/environments/components/deploy_board_component.vue
+7
-0
ee/app/assets/javascripts/environments/components/deploy_board_instance_component.vue
...vironments/components/deploy_board_instance_component.vue
+18
-2
ee/app/assets/javascripts/kubernetes_logs.js
ee/app/assets/javascripts/kubernetes_logs.js
+82
-0
ee/app/assets/javascripts/pages/projects/environments/logs/index.js
...ets/javascripts/pages/projects/environments/logs/index.js
+8
-0
ee/app/controllers/ee/projects/environments_controller.rb
ee/app/controllers/ee/projects/environments_controller.rb
+35
-0
ee/app/models/concerns/ee/kubernetes_service.rb
ee/app/models/concerns/ee/kubernetes_service.rb
+12
-0
ee/app/models/license.rb
ee/app/models/license.rb
+1
-0
ee/app/policies/ee/project_policy.rb
ee/app/policies/ee/project_policy.rb
+7
-0
ee/app/serializers/ee/environment_entity.rb
ee/app/serializers/ee/environment_entity.rb
+15
-0
ee/app/views/projects/environments/logs.html.haml
ee/app/views/projects/environments/logs.html.haml
+18
-0
ee/changelogs/unreleased/4752-kubernetes-pod-logs.yml
ee/changelogs/unreleased/4752-kubernetes-pod-logs.yml
+5
-0
ee/lib/gitlab/kubernetes/deployment.rb
ee/lib/gitlab/kubernetes/deployment.rb
+1
-0
ee/spec/controllers/projects/environments_controller_spec.rb
ee/spec/controllers/projects/environments_controller_spec.rb
+44
-0
ee/spec/fixtures/api/schemas/environment.json
ee/spec/fixtures/api/schemas/environment.json
+3
-0
ee/spec/fixtures/api/schemas/rollout_status.json
ee/spec/fixtures/api/schemas/rollout_status.json
+4
-0
ee/spec/lib/gitlab/kubernetes/deployment_spec.rb
ee/spec/lib/gitlab/kubernetes/deployment_spec.rb
+17
-17
ee/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
ee/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
+12
-12
ee/spec/models/ee/clusters/platforms/kubernetes_spec.rb
ee/spec/models/ee/clusters/platforms/kubernetes_spec.rb
+34
-0
qa/qa/page/project/job/show.rb
qa/qa/page/project/job/show.rb
+1
-1
spec/javascripts/ee/kubernetes_logs_spec.js
spec/javascripts/ee/kubernetes_logs_spec.js
+0
-0
spec/javascripts/ee/kubernetes_mock_data.js
spec/javascripts/ee/kubernetes_mock_data.js
+107
-0
spec/javascripts/environments/deploy_board_component_spec.js
spec/javascripts/environments/deploy_board_component_spec.js
+5
-2
spec/javascripts/environments/deploy_board_instance_component_spec.js
...ipts/environments/deploy_board_instance_component_spec.js
+17
-0
spec/javascripts/environments/environment_item_spec.js
spec/javascripts/environments/environment_item_spec.js
+2
-0
spec/javascripts/environments/mock_data.js
spec/javascripts/environments/mock_data.js
+31
-27
spec/javascripts/fixtures/environments_logs.html.haml
spec/javascripts/fixtures/environments_logs.html.haml
+18
-0
spec/javascripts/kubernetes_logs_spec.js
spec/javascripts/kubernetes_logs_spec.js
+69
-0
spec/support/helpers/kubernetes_helpers.rb
spec/support/helpers/kubernetes_helpers.rb
+15
-0
No files found.
app/assets/javascripts/environments/components/environments_table.vue
View file @
ccc5f473
...
...
@@ -107,6 +107,7 @@ export default {
:deploy-board-data=
"model.deployBoardData"
:is-loading=
"model.isLoadingDeployBoard"
:is-empty=
"model.isEmptyDeployBoard"
:logs-path=
"model.logs_path"
/>
</div>
</div>
...
...
app/assets/javascripts/job.js
View file @
ccc5f473
...
...
@@ -6,9 +6,12 @@ import { visitUrl } from './lib/utils/url_utility';
import
bp
from
'
./breakpoints
'
;
import
{
numberToHumanSize
}
from
'
./lib/utils/number_utils
'
;
import
{
setCiStatusFavicon
}
from
'
./lib/utils/common_utils
'
;
import
{
isScrolledToBottom
,
scrollDown
}
from
'
./lib/utils/scroll_utils
'
;
import
LogOutputBehaviours
from
'
./lib/utils/logoutput_behaviours
'
;
export
default
class
Job
{
export
default
class
Job
extends
LogOutputBehaviours
{
constructor
(
options
)
{
super
();
this
.
timeout
=
null
;
this
.
state
=
null
;
this
.
fetchingStatusFavicon
=
false
;
...
...
@@ -29,10 +32,6 @@ export default class Job {
this
.
$buildTraceOutput
=
$
(
'
.js-build-output
'
);
this
.
$topBar
=
$
(
'
.js-top-bar
'
);
// Scroll controllers
this
.
$scrollTopBtn
=
$
(
'
.js-scroll-up
'
);
this
.
$scrollBottomBtn
=
$
(
'
.js-scroll-down
'
);
clearTimeout
(
this
.
timeout
);
this
.
initSidebar
();
...
...
@@ -48,23 +47,14 @@ export default class Job {
.
off
(
'
click
'
,
'
.stage-item
'
)
.
on
(
'
click
'
,
'
.stage-item
'
,
this
.
updateDropdown
);
// add event listeners to the scroll buttons
this
.
$scrollTopBtn
.
off
(
'
click
'
)
.
on
(
'
click
'
,
this
.
scrollToTop
.
bind
(
this
));
this
.
$scrollBottomBtn
.
off
(
'
click
'
)
.
on
(
'
click
'
,
this
.
scrollToBottom
.
bind
(
this
));
this
.
scrollThrottled
=
_
.
throttle
(
this
.
toggleScroll
.
bind
(
this
),
100
);
this
.
$window
.
off
(
'
scroll
'
)
.
on
(
'
scroll
'
,
()
=>
{
if
(
!
this
.
isScrolledToBottom
())
{
if
(
!
isScrolledToBottom
())
{
this
.
toggleScrollAnimation
(
false
);
}
else
if
(
this
.
isScrolledToBottom
()
&&
!
this
.
isLogComplete
)
{
}
else
if
(
isScrolledToBottom
()
&&
!
this
.
isLogComplete
)
{
this
.
toggleScrollAnimation
(
true
);
}
this
.
scrollThrottled
();
...
...
@@ -90,60 +80,8 @@ export default class Job {
StickyFill
.
add
(
this
.
$topBar
);
}
// eslint-disable-next-line class-methods-use-this
canScroll
()
{
return
$
(
document
).
height
()
>
$
(
window
).
height
();
}
toggleScroll
()
{
const
$document
=
$
(
document
);
const
currentPosition
=
$document
.
scrollTop
();
const
scrollHeight
=
$document
.
height
();
const
windowHeight
=
$
(
window
).
height
();
if
(
this
.
canScroll
())
{
if
(
currentPosition
>
0
&&
(
scrollHeight
-
currentPosition
!==
windowHeight
))
{
// User is in the middle of the log
this
.
toggleDisableButton
(
this
.
$scrollTopBtn
,
false
);
this
.
toggleDisableButton
(
this
.
$scrollBottomBtn
,
false
);
}
else
if
(
currentPosition
===
0
)
{
// User is at Top of Log
this
.
toggleDisableButton
(
this
.
$scrollTopBtn
,
true
);
this
.
toggleDisableButton
(
this
.
$scrollBottomBtn
,
false
);
}
else
if
(
this
.
isScrolledToBottom
())
{
// User is at the bottom of the build log.
this
.
toggleDisableButton
(
this
.
$scrollTopBtn
,
false
);
this
.
toggleDisableButton
(
this
.
$scrollBottomBtn
,
true
);
}
}
else
{
this
.
toggleDisableButton
(
this
.
$scrollTopBtn
,
true
);
this
.
toggleDisableButton
(
this
.
$scrollBottomBtn
,
true
);
}
}
// eslint-disable-next-line class-methods-use-this
isScrolledToBottom
()
{
const
$document
=
$
(
document
);
const
currentPosition
=
$document
.
scrollTop
();
const
scrollHeight
=
$document
.
height
();
const
windowHeight
=
$
(
window
).
height
();
return
scrollHeight
-
currentPosition
===
windowHeight
;
}
// eslint-disable-next-line class-methods-use-this
scrollDown
()
{
const
$document
=
$
(
document
);
$document
.
scrollTop
(
$document
.
height
());
}
scrollToBottom
()
{
this
.
scrollDown
();
scrollDown
();
this
.
hasBeenScrolled
=
true
;
this
.
toggleScroll
();
}
...
...
@@ -154,12 +92,6 @@ export default class Job {
this
.
toggleScroll
();
}
// eslint-disable-next-line class-methods-use-this
toggleDisableButton
(
$button
,
disable
)
{
if
(
disable
&&
$button
.
prop
(
'
disabled
'
))
return
;
$button
.
prop
(
'
disabled
'
,
disable
);
}
toggleScrollAnimation
(
toggle
)
{
this
.
$scrollBottomBtn
.
toggleClass
(
'
animate
'
,
toggle
);
}
...
...
@@ -191,7 +123,7 @@ export default class Job {
this
.
state
=
log
.
state
;
}
this
.
isScrollInBottom
=
this
.
isScrolledToBottom
();
this
.
isScrollInBottom
=
isScrolledToBottom
();
if
(
log
.
append
)
{
this
.
$buildTraceOutput
.
append
(
log
.
html
);
...
...
@@ -231,7 +163,7 @@ export default class Job {
})
.
then
(()
=>
{
if
(
this
.
isScrollInBottom
)
{
this
.
scrollDown
();
scrollDown
();
}
})
.
then
(()
=>
this
.
toggleScroll
());
...
...
app/assets/javascripts/lib/utils/logoutput_behaviours.js
0 → 100644
View file @
ccc5f473
import
$
from
'
jquery
'
;
import
{
canScroll
,
isScrolledToBottom
,
toggleDisableButton
}
from
'
./scroll_utils
'
;
export
default
class
LogOutputBehaviours
{
constructor
()
{
// Scroll buttons
this
.
$scrollTopBtn
=
$
(
'
.js-scroll-up
'
);
this
.
$scrollBottomBtn
=
$
(
'
.js-scroll-down
'
);
this
.
$scrollTopBtn
.
off
(
'
click
'
).
on
(
'
click
'
,
this
.
scrollToTop
.
bind
(
this
));
this
.
$scrollBottomBtn
.
off
(
'
click
'
).
on
(
'
click
'
,
this
.
scrollToBottom
.
bind
(
this
));
}
toggleScroll
()
{
const
$document
=
$
(
document
);
const
currentPosition
=
$document
.
scrollTop
();
const
scrollHeight
=
$document
.
height
();
const
windowHeight
=
$
(
window
).
height
();
if
(
canScroll
())
{
if
(
currentPosition
>
0
&&
scrollHeight
-
currentPosition
!==
windowHeight
)
{
// User is in the middle of the log
toggleDisableButton
(
this
.
$scrollTopBtn
,
false
);
toggleDisableButton
(
this
.
$scrollBottomBtn
,
false
);
}
else
if
(
currentPosition
===
0
)
{
// User is at Top of Log
toggleDisableButton
(
this
.
$scrollTopBtn
,
true
);
toggleDisableButton
(
this
.
$scrollBottomBtn
,
false
);
}
else
if
(
isScrolledToBottom
())
{
// User is at the bottom of the build log.
toggleDisableButton
(
this
.
$scrollTopBtn
,
false
);
toggleDisableButton
(
this
.
$scrollBottomBtn
,
true
);
}
}
else
{
toggleDisableButton
(
this
.
$scrollTopBtn
,
true
);
toggleDisableButton
(
this
.
$scrollBottomBtn
,
true
);
}
}
toggleScrollAnimation
(
toggle
)
{
this
.
$scrollBottomBtn
.
toggleClass
(
'
animate
'
,
toggle
);
}
}
app/assets/javascripts/lib/utils/scroll_utils.js
0 → 100644
View file @
ccc5f473
import
$
from
'
jquery
'
;
export
const
canScroll
=
()
=>
$
(
document
).
height
()
>
$
(
window
).
height
();
/**
* Checks if the entire page is scrolled down all the way to the bottom
*/
export
const
isScrolledToBottom
=
()
=>
{
const
$document
=
$
(
document
);
const
currentPosition
=
$document
.
scrollTop
();
const
scrollHeight
=
$document
.
height
();
const
windowHeight
=
$
(
window
).
height
();
return
scrollHeight
-
currentPosition
===
windowHeight
;
};
export
const
scrollDown
=
()
=>
{
const
$document
=
$
(
document
);
$document
.
scrollTop
(
$document
.
height
());
};
export
const
toggleDisableButton
=
(
$button
,
disable
)
=>
{
if
(
disable
&&
$button
.
prop
(
'
disabled
'
))
return
;
$button
.
prop
(
'
disabled
'
,
disable
);
};
export
default
{};
app/assets/stylesheets/pages/builds.scss
View file @
ccc5f473
...
...
@@ -125,6 +125,7 @@
align-items
:
center
;
svg
{
width
:
15px
;
height
:
15px
;
display
:
block
;
fill
:
$gl-text-color
;
...
...
@@ -159,7 +160,12 @@
}
}
.btn-scroll
:disabled
{
.btn-refresh
{
border-radius
:
4px
;
}
.btn-scroll
:disabled
,
.btn-refresh
:disabled
{
opacity
:
0
.35
;
cursor
:
not
-
allowed
;
}
...
...
@@ -447,3 +453,14 @@
right
:
0
;
margin-top
:
-17px
;
}
@include
media-breakpoint-down
(
sm
)
{
.top-bar
{
.truncated-info
{
white-space
:
nowrap
;
overflow
:
hidden
;
max-width
:
220px
;
text-overflow
:
ellipsis
;
}
}
}
app/assets/stylesheets/pages/environments.scss
View file @
ccc5f473
...
...
@@ -232,6 +232,13 @@
&
-running
{
background-color
:
$green-100
;
border-color
:
$green-400
;
// EE-specific start
&
:hover
{
background-color
:
$green-300
;
border-color
:
$green-500
;
}
// EE-specific end
}
&
-succeeded
{
...
...
app/controllers/projects/environments_controller.rb
View file @
ccc5f473
...
...
@@ -9,6 +9,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action
:verify_api_request!
,
only: :terminal_websocket_authorize
before_action
:expire_etag_cache
,
only:
[
:index
]
prepend
::
EE
::
Projects
::
EnvironmentsController
def
index
@environments
=
project
.
environments
.
with_state
(
params
[
:scope
]
||
:available
)
...
...
app/serializers/environment_entity.rb
View file @
ccc5f473
class
EnvironmentEntity
<
Grape
::
Entity
include
RequestAwareEntity
prepend
::
EE
::
EnvironmentEntity
expose
:id
expose
:name
expose
:state
...
...
app/views/projects/jobs/show.html.haml
View file @
ccc5f473
...
...
@@ -88,9 +88,7 @@
%button
.js-scroll-down.btn-scroll.btn-transparent.btn-blank
{
type:
'button'
,
disabled:
true
}
=
custom_icon
(
'scroll_down'
)
%pre
.build-trace
#build-trace
%code
.bash.js-build-output
.build-loader-animation.js-build-refresh
=
render
'shared/builds/build_output'
-
else
=
render
"empty_states"
...
...
app/views/shared/builds/_build_output.html.haml
0 → 100644
View file @
ccc5f473
%pre
.build-trace
#build-trace
%code
.bash.js-build-output
.build-loader-animation.js-build-refresh
config/routes/project.rb
View file @
ccc5f473
...
...
@@ -265,6 +265,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get
:metrics
get
:additional_metrics
get
'/terminal.ws/authorize'
,
to:
'environments#terminal_websocket_authorize'
,
constraints:
{
format:
nil
}
# EE
get
:logs
end
collection
do
...
...
ee/app/assets/javascripts/environments/components/deploy_board_component.vue
View file @
ccc5f473
...
...
@@ -32,6 +32,11 @@
type
:
Boolean
,
required
:
true
,
},
logsPath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
canRenderDeployBoard
()
{
...
...
@@ -74,6 +79,8 @@
<instance-component
:status=
"instance.status"
:tooltip-text=
"instance.tooltip"
:pod-name=
"instance.pod_name"
:logs-path=
"logsPath"
:stable=
"instance.stable"
:key=
"i"
/>
...
...
ee/app/assets/javascripts/environments/components/deploy_board_instance_component.vue
View file @
ccc5f473
...
...
@@ -43,6 +43,17 @@
required
:
false
,
default
:
true
,
},
podName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
logsPath
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
...
...
@@ -55,16 +66,21 @@
return
cssClassName
;
},
computedLogPath
()
{
return
`
${
this
.
logsPath
}
?pod_name=
${
this
.
podName
}
`
;
},
},
};
</
script
>
<
template
>
<
div
<
a
v-tooltip
class=
"deploy-board-instance"
:class=
"cssClass"
:data-title=
"tooltipText"
data-placement=
"top"
:href=
"computedLogPath"
>
</
div
>
</
a
>
</
template
>
ee/app/assets/javascripts/kubernetes_logs.js
0 → 100644
View file @
ccc5f473
import
$
from
'
jquery
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
getParameterValues
}
from
'
~/lib/utils/url_utility
'
;
import
{
isScrolledToBottom
,
scrollDown
,
toggleDisableButton
}
from
'
~/lib/utils/scroll_utils
'
;
import
LogOutputBehaviours
from
'
~/lib/utils/logoutput_behaviours
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
_
from
'
underscore
'
;
export
default
class
KubernetesPodLogs
extends
LogOutputBehaviours
{
constructor
(
container
)
{
super
();
this
.
options
=
$
(
container
).
data
();
this
.
podNameContainer
=
$
(
container
).
find
(
'
.js-pod-name
'
);
this
.
podName
=
getParameterValues
(
'
pod_name
'
)[
0
];
this
.
$buildOutputContainer
=
$
(
container
).
find
(
'
.js-build-output
'
);
this
.
$window
=
$
(
window
);
this
.
$refreshLogBtn
=
$
(
container
).
find
(
'
.js-refresh-log
'
);
this
.
$buildRefreshAnimation
=
$
(
container
).
find
(
'
.js-build-refresh
'
);
this
.
isLogComplete
=
false
;
this
.
scrollThrottled
=
_
.
throttle
(
this
.
toggleScroll
.
bind
(
this
),
100
);
if
(
!
this
.
podName
)
{
createFlash
(
s__
(
'
Environments|No pod name has been specified
'
));
return
;
}
const
podTitle
=
sprintf
(
s__
(
'
Environments|Pod logs from %{podName}
'
),
{
podName
:
`<strong>
${
_
.
escape
(
this
.
podName
)}
</strong>`
,
},
false
,
);
this
.
podNameContainer
.
empty
();
this
.
podNameContainer
.
append
(
podTitle
);
this
.
$window
.
off
(
'
scroll
'
).
on
(
'
scroll
'
,
()
=>
{
if
(
!
isScrolledToBottom
())
{
this
.
toggleScrollAnimation
(
false
);
}
else
if
(
isScrolledToBottom
()
&&
!
this
.
isLogComplete
)
{
this
.
toggleScrollAnimation
(
true
);
}
this
.
scrollThrottled
();
});
this
.
$refreshLogBtn
.
off
(
'
click
'
).
on
(
'
click
'
,
this
.
getPodLogs
.
bind
(
this
));
}
scrollToBottom
()
{
scrollDown
();
this
.
toggleScroll
();
}
scrollToTop
()
{
$
(
document
).
scrollTop
(
0
);
this
.
toggleScroll
();
}
getPodLogs
()
{
this
.
scrollToTop
();
this
.
$buildOutputContainer
.
empty
();
this
.
$buildRefreshAnimation
.
show
();
toggleDisableButton
(
this
.
$refreshLogBtn
,
'
true
'
);
return
axios
.
get
(
this
.
options
.
logsPath
,
{
params
:
{
pod_name
:
this
.
podName
},
})
.
then
(
res
=>
{
const
logs
=
res
.
data
.
logs
;
const
formattedLogs
=
logs
.
map
(
logEntry
=>
`
${
_
.
escape
(
logEntry
)}
<br />`
);
this
.
$buildOutputContainer
.
append
(
formattedLogs
);
scrollDown
();
this
.
isLogComplete
=
true
;
this
.
$buildRefreshAnimation
.
hide
();
toggleDisableButton
(
this
.
$refreshLogBtn
,
false
);
})
.
catch
(()
=>
createFlash
(
__
(
'
Something went wrong on our end
'
)));
}
}
ee/app/assets/javascripts/pages/projects/environments/logs/index.js
0 → 100644
View file @
ccc5f473
import
KubernetesLogs
from
'
../../../../kubernetes_logs
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
const
kubernetesLogContainer
=
document
.
querySelector
(
'
.js-kubernetes-logs
'
);
const
kubernetesLog
=
new
KubernetesLogs
(
kubernetesLogContainer
);
kubernetesLog
.
getPodLogs
();
});
ee/app/controllers/ee/projects/environments_controller.rb
0 → 100644
View file @
ccc5f473
module
EE
module
Projects
module
EnvironmentsController
extend
ActiveSupport
::
Concern
prepended
do
before_action
:authorize_read_pod_logs!
,
only:
[
:logs
]
before_action
:environment_ee
,
only:
[
:logs
]
end
def
logs
respond_to
do
|
format
|
format
.
html
format
.
json
do
::
Gitlab
::
PollingInterval
.
set_header
(
response
,
interval:
3_000
)
render
json:
{
logs:
pod_logs
.
strip
.
split
(
"
\n
"
).
as_json
}
end
end
end
private
def
environment_ee
environment
end
def
pod_logs
@pod_logs
||=
environment
.
deployment_platform
.
read_pod_logs
(
params
[
:pod_name
])
end
end
end
end
ee/app/models/concerns/ee/kubernetes_service.rb
View file @
ccc5f473
module
EE
module
KubernetesService
extend
ActiveSupport
::
Concern
LOGS_LIMIT
=
500
.
freeze
def
rollout_status
(
environment
)
result
=
with_reactive_cache
do
|
data
|
deployments
=
filter_by_label
(
data
[
:deployments
],
app:
environment
.
slug
)
...
...
@@ -35,5 +39,13 @@ module EE
[]
end
def
read_pod_logs
(
pod_name
,
container:
nil
)
kubeclient
.
get_pod_log
(
pod_name
,
actual_namespace
,
container:
container
,
tail_lines:
LOGS_LIMIT
).
as_json
rescue
::
Kubeclient
::
HttpError
=>
err
raise
err
unless
err
.
error_code
==
404
[]
end
end
end
ee/app/models/license.rb
View file @
ccc5f473
...
...
@@ -71,6 +71,7 @@ class License < ActiveRecord::Base
epics
ide
chatops
pod_logs
]
.
freeze
# List all features available for early adopters,
...
...
ee/app/policies/ee/project_policy.rb
View file @
ccc5f473
...
...
@@ -44,6 +44,11 @@ module EE
!
PushRule
.
global
&
.
commit_committer_check
end
with_scope
:subject
condition
(
:pod_logs_enabled
)
do
@subject
.
feature_available?
(
:pod_logs
,
@user
)
end
rule
{
admin
}.
enable
:change_repository_storage
rule
{
support_bot
}.
enable
:guest_access
...
...
@@ -90,6 +95,8 @@ module EE
enable
:update_approvers
end
rule
{
pod_logs_enabled
&
can?
(
:master_access
)
}.
enable
:read_pod_logs
rule
{
auditor
}.
policy
do
enable
:public_user_access
prevent
:request_access
...
...
ee/app/serializers/ee/environment_entity.rb
0 → 100644
View file @
ccc5f473
module
EE
module
EnvironmentEntity
extend
ActiveSupport
::
Concern
prepended
do
expose
:logs_path
,
if:
->
(
*
)
{
can_read_pod_logs?
}
do
|
environment
|
logs_project_environment_path
(
environment
.
project
,
environment
)
end
end
def
can_read_pod_logs?
can?
(
current_user
,
:read_pod_logs
,
environment
.
project
)
end
end
end
ee/app/views/projects/environments/logs.html.haml
0 → 100644
View file @
ccc5f473
.js-kubernetes-logs
{
data:
{
logs_path:
logs_project_environment_path
(
@project
,
@environment
,
format: :json
)
}
}
.build-page
.build-trace-container.prepend-top-default
.top-bar.js-top-bar
.truncated-info.hidden-xs.pull-left.js-pod-name
.controllers.pull-right
.has-tooltip.controllers-buttons
{
title:
_
(
'Scroll to top'
),
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-scroll-up.btn-scroll.btn-transparent.btn-blank
{
type:
'button'
,
disabled:
true
}
=
custom_icon
(
'scroll_up'
)
.has-tooltip.controllers-buttons
{
title:
_
(
'Scroll to bottom'
),
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-scroll-down.btn-scroll.btn-transparent.btn-blank
{
type:
'button'
,
disabled:
true
}
=
custom_icon
(
'scroll_down'
)
.has-tooltip.controllers-buttons
{
title:
_
(
'Refresh'
),
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-refresh-log.btn-default.btn-refresh
{
type:
'button'
,
disabled:
true
}
=
sprite_icon
(
'retry'
)
=
render
'shared/builds/build_output'
ee/changelogs/unreleased/4752-kubernetes-pod-logs.yml
0 → 100644
View file @
ccc5f473
---
title
:
Allows the review of kubernetes pod logs within GitLab
merge_request
:
4752
author
:
type
:
added
ee/lib/gitlab/kubernetes/deployment.rb
View file @
ccc5f473
...
...
@@ -74,6 +74,7 @@ module Gitlab
def
deployment_instance
(
pod_name
:,
pod_status
:)
{
status:
pod_status
&
.
downcase
,
pod_name:
pod_name
,
tooltip:
"
#{
name
}
(
#{
pod_name
}
)
#{
pod_status
}
"
,
track:
track
,
stable:
stable?
...
...
ee/spec/controllers/projects/environments_controller_spec.rb
View file @
ccc5f473
...
...
@@ -74,6 +74,50 @@ describe Projects::EnvironmentsController do
end
end
describe
'GET logs'
do
let
(
:logs
)
{
"Log 1
\n
Log 2
\n
Log 3"
}
let
(
:pod_name
)
{
'foo'
}
before
do
stub_licensed_features
(
pod_logs:
true
)
create
(
:cluster
,
:provided_by_gcp
,
environment_scope:
'*'
,
projects:
[
project
])
allow_any_instance_of
(
EE
::
KubernetesService
).
to
receive
(
:read_pod_logs
).
with
(
pod_name
).
and_return
(
logs
)
end
context
'when unlicensed'
do
before
do
stub_licensed_features
(
pod_logs:
false
)
end
it
'renders forbidden'
do
get
:logs
,
environment_params
(
pod_name:
pod_name
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
end
context
'when using HTML format'
do
it
'renders logs template'
do
get
:logs
,
environment_params
(
pod_name:
pod_name
)
expect
(
response
).
to
be_ok
expect
(
response
).
to
render_template
'logs'
end
end
context
'when using JSON format'
do
it
'returns the logs for a specific pod'
do
get
:logs
,
environment_params
(
pod_name:
pod_name
,
format: :json
)
expect
(
response
).
to
be_ok
expect
(
json_response
[
"logs"
]).
to
match_array
([
"Log 1"
,
"Log 2"
,
"Log 3"
])
end
end
end
def
environment_params
(
opts
=
{})
opts
.
reverse_merge
(
namespace_id:
project
.
namespace
,
project_id:
project
,
...
...
ee/spec/fixtures/api/schemas/environment.json
View file @
ccc5f473
...
...
@@ -57,6 +57,9 @@
"folder_path"
:
{
"type"
:
"string"
},
"logs_path"
:
{
"type"
:
"string"
},
"created_at"
:
{
"type"
:
"date"
},
...
...
ee/spec/fixtures/api/schemas/rollout_status.json
View file @
ccc5f473
...
...
@@ -21,6 +21,7 @@
"type"
:
"object"
,
"required"
:
[
"status"
,
"pod_name"
,
"tooltip"
,
"track"
,
"stable"
...
...
@@ -29,6 +30,9 @@
"status"
:
{
"type"
:
"string"
},
"pod_name"
:
{
"type"
:
"string"
},
"tooltip"
:
{
"type"
:
"string"
},
...
...
ee/spec/lib/gitlab/kubernetes/deployment_spec.rb
View file @
ccc5f473
...
...
@@ -54,10 +54,10 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns all pods with generated names and pending'
do
expected
=
[
{
status:
'pending'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
}
{
status:
'pending'
,
pod_name:
'generated-name-with-suffix'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'generated-name-with-suffix'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'generated-name-with-suffix'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'generated-name-with-suffix'
,
tooltip:
'unknown (generated-name-with-suffix) Pending'
,
track:
'stable'
,
stable:
true
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
@@ -73,9 +73,9 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns not spawned pods as pending and unknown and running'
do
expected
=
[
{
status:
'running'
,
tooltip:
'unknown (generated-name-with-suffix) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'unknown (Not provided) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'unknown (Not provided) Pending'
,
track:
'stable'
,
stable:
true
}
{
status:
'running'
,
pod_name:
'generated-name-with-suffix'
,
tooltip:
'unknown (generated-name-with-suffix) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'Not provided'
,
tooltip:
'unknown (Not provided) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'Not provided'
,
tooltip:
'unknown (Not provided) Pending'
,
track:
'stable'
,
stable:
true
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
@@ -96,10 +96,10 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns all instances as named and waiting'
do
expected
=
[
{
status:
'pending'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'foo (kube-pod1) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'foo (kube-pod2) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'foo (kube-pod3) Pending'
,
track:
'stable'
,
stable:
true
}
{
status:
'pending'
,
pod_name:
'kube-pod'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'kube-pod1'
,
tooltip:
'foo (kube-pod1) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'kube-pod2'
,
tooltip:
'foo (kube-pod2) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'kube-pod3'
,
tooltip:
'foo (kube-pod3) Pending'
,
track:
'stable'
,
stable:
true
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
@@ -120,10 +120,10 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns all instances'
do
expected
=
[
{
status:
'succeeded'
,
tooltip:
'foo (kube-pod) Succeeded'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
tooltip:
'foo (kube-pod1) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'foo (kube-pod2) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
tooltip:
'foo (kube-pod3) Pending'
,
track:
'stable'
,
stable:
true
}
{
status:
'succeeded'
,
pod_name:
'kube-pod'
,
tooltip:
'foo (kube-pod) Succeeded'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
pod_name:
'kube-pod1'
,
tooltip:
'foo (kube-pod1) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'kube-pod2'
,
tooltip:
'foo (kube-pod2) Pending'
,
track:
'stable'
,
stable:
true
},
{
status:
'pending'
,
pod_name:
'kube-pod3'
,
tooltip:
'foo (kube-pod3) Pending'
,
track:
'stable'
,
stable:
true
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
@@ -140,7 +140,7 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns all instances'
do
expected
=
[
{
status:
'pending'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'stable'
,
stable:
true
}
{
status:
'pending'
,
pod_name:
'kube-pod'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'stable'
,
stable:
true
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
@@ -153,7 +153,7 @@ describe Gitlab::Kubernetes::Deployment do
it
'returns all instances'
do
expected
=
[
{
status:
'pending'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'canary'
,
stable:
false
}
{
status:
'pending'
,
pod_name:
'kube-pod'
,
tooltip:
'foo (kube-pod) Pending'
,
track:
'canary'
,
stable:
false
}
]
expect
(
deployment
.
instances
).
to
eq
(
expected
)
...
...
ee/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
View file @
ccc5f473
...
...
@@ -45,12 +45,12 @@ describe Gitlab::Kubernetes::RolloutStatus do
it
'stores the union of deployment instances'
do
expected
=
[
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
}
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'any'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
}
]
expect
(
rollout_status
.
instances
).
to
eq
(
expected
)
...
...
@@ -66,12 +66,12 @@ describe Gitlab::Kubernetes::RolloutStatus do
it
'stores the union of deployment instances'
do
expected
=
[
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
}
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"two"
,
tooltip:
'two (two) Running'
,
track:
'canary'
,
stable:
false
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
},
{
status:
'running'
,
pod_name:
"one"
,
tooltip:
'one (one) Running'
,
track:
'stable'
,
stable:
true
}
]
expect
(
rollout_status
.
instances
).
to
eq
(
expected
)
...
...
ee/spec/models/ee/clusters/platforms/kubernetes_spec.rb
View file @
ccc5f473
...
...
@@ -28,4 +28,38 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it
{
is_expected
.
to
eq
(
pods:
[],
deployments:
[])
}
end
end
describe
'#read_pod_logs'
do
subject
{
service
.
read_pod_logs
(
pod_name
)
}
let
(
:pod_name
)
{
'foo'
}
let!
(
:cluster
)
{
create
(
:cluster
,
:project
,
enabled:
true
,
platform_kubernetes:
service
)
}
let
(
:service
)
{
create
(
:cluster_platform_kubernetes
,
:configured
)
}
context
'when kubernetes responds with valid logs'
do
before
do
stub_kubeclient_logs
(
pod_name
)
end
it
'returns logs'
do
expect
(
subject
.
body
).
to
eq
(
"
\"
Log 1
\\
nLog 2
\\
nLog 3
\"
"
)
end
end
context
'when kubernetes response with 500s'
do
before
do
stub_kubeclient_logs
(
pod_name
,
status:
500
)
end
it
{
expect
{
subject
}.
to
raise_error
(
::
Kubeclient
::
HttpError
)
}
end
context
'when kubernetes responds with 404s'
do
before
do
stub_kubeclient_logs
(
pod_name
,
status:
404
)
end
it
{
is_expected
.
to
be_empty
}
end
end
end
qa/qa/page/project/job/show.rb
View file @
ccc5f473
...
...
@@ -4,7 +4,7 @@ module QA::Page
COMPLETED_STATUSES
=
%w[passed failed canceled blocked skipped manual]
.
freeze
# excludes created, pending, running
PASSED_STATUS
=
'passed'
.
freeze
view
'app/views/
projects/jobs/show
.html.haml'
do
view
'app/views/
shared/builds/_build_output
.html.haml'
do
element
:build_output
,
'.js-build-output'
end
...
...
spec/javascripts/ee/kubernetes_logs_spec.js
0 → 100644
View file @
ccc5f473
spec/javascripts/ee/kubernetes_mock_data.js
0 → 100644
View file @
ccc5f473
export
const
logMockData
=
[
'
[2018-05-17 16:31:10] INFO WEBrick 1.3.1
'
,
'
[2018-05-17 16:31:10] INFO ruby 2.4.1 (2017-03-22) [x86_64-linux-musl]
'
,
'
[2018-05-17 16:31:10] INFO WEBrick::HTTPServer#start: pid=5 port=5000
'
,
'
172.17.0.1 - - [17/May/2018:16:31:14 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:24 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:32 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:34 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:42 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:44 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:52 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:31:54 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:02 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:04 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:12 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:14 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:22 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:24 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:32 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:34 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:42 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:44 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:52 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:32:54 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:02 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:04 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:12 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:14 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:22 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:24 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:32 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:34 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:42 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:44 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:52 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:33:54 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:02 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:04 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:12 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:14 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:22 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:24 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:32 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:34 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:42 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:44 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:52 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:34:54 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:02 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:04 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:12 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:14 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:22 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
'
172.17.0.1 - - [17/May/2018:16:35:24 UTC]
\'
GET / HTTP/1.1
\'
200 13
'
,
'
- -> /
'
,
];
export
default
{};
spec/javascripts/environments/deploy_board_component_spec.js
View file @
ccc5f473
import
Vue
from
'
vue
'
;
import
DeployBoard
from
'
ee/environments/components/deploy_board_component.vue
'
;
import
{
deployBoardMockData
}
from
'
./mock_data
'
;
import
{
deployBoardMockData
,
environment
}
from
'
./mock_data
'
;
describe
(
'
Deploy Board
'
,
()
=>
{
let
DeployBoardComponent
;
...
...
@@ -18,6 +18,7 @@ describe('Deploy Board', () => {
deployBoardData
:
deployBoardMockData
,
isLoading
:
false
,
isEmpty
:
false
,
logsPath
:
environment
.
log_path
,
},
}).
$mount
();
});
...
...
@@ -29,7 +30,7 @@ describe('Deploy Board', () => {
});
it
(
'
should render all instances
'
,
()
=>
{
const
instances
=
component
.
$el
.
querySelectorAll
(
'
.deploy-board-instances-container
div
'
);
const
instances
=
component
.
$el
.
querySelectorAll
(
'
.deploy-board-instances-container
a
'
);
expect
(
instances
.
length
).
toEqual
(
deployBoardMockData
.
instances
.
length
);
...
...
@@ -55,6 +56,7 @@ describe('Deploy Board', () => {
deployBoardData
:
{},
isLoading
:
false
,
isEmpty
:
true
,
logsPath
:
environment
.
log_path
,
},
}).
$mount
();
});
...
...
@@ -74,6 +76,7 @@ describe('Deploy Board', () => {
deployBoardData
:
{},
isLoading
:
true
,
isEmpty
:
false
,
logsPath
:
environment
.
log_path
,
},
}).
$mount
();
});
...
...
spec/javascripts/environments/deploy_board_instance_component_spec.js
View file @
ccc5f473
import
Vue
from
'
vue
'
;
import
DeployBoardInstance
from
'
ee/environments/components/deploy_board_instance_component.vue
'
;
import
{
folder
}
from
'
./mock_data
'
;
describe
(
'
Deploy Board Instance
'
,
()
=>
{
let
DeployBoardInstanceComponent
;
...
...
@@ -13,6 +14,7 @@ describe('Deploy Board Instance', () => {
propsData
:
{
status
:
'
ready
'
,
tooltipText
:
'
This is a pod
'
,
logsPath
:
folder
.
log_path
,
},
}).
$mount
();
...
...
@@ -24,6 +26,7 @@ describe('Deploy Board Instance', () => {
const
component
=
new
DeployBoardInstanceComponent
({
propsData
:
{
status
:
'
deploying
'
,
logsPath
:
folder
.
log_path
,
},
}).
$mount
();
...
...
@@ -36,9 +39,23 @@ describe('Deploy Board Instance', () => {
propsData
:
{
status
:
'
deploying
'
,
stable
:
false
,
logsPath
:
folder
.
log_path
,
},
}).
$mount
();
expect
(
component
.
$el
.
classList
.
contains
(
'
deploy-board-instance-canary
'
)).
toBe
(
true
);
});
it
(
'
should have a log path computed with a pod name as a parameter
'
,
()
=>
{
const
component
=
new
DeployBoardInstanceComponent
({
propsData
:
{
status
:
'
deploying
'
,
stable
:
false
,
logsPath
:
folder
.
log_path
,
podName
:
'
tanuki-1
'
,
},
}).
$mount
();
expect
(
component
.
computedLogPath
).
toEqual
(
'
/root/review-app/environments/12/logs?pod_name=tanuki-1
'
);
});
});
spec/javascripts/environments/environment_item_spec.js
View file @
ccc5f473
...
...
@@ -20,6 +20,7 @@ describe('Environment item', () => {
size
:
3
,
isFolder
:
true
,
environment_path
:
'
url
'
,
log_path
:
'
url
'
,
};
component
=
new
EnvironmentItem
({
...
...
@@ -108,6 +109,7 @@ describe('Environment item', () => {
},
'
stop_action?
'
:
true
,
environment_path
:
'
root/ci-folders/environments/31
'
,
log_path
:
'
root/ci-folders/environments/31/logs
'
,
created_at
:
'
2016-11-07T11:11:16.525Z
'
,
updated_at
:
'
2016-11-10T15:55:58.778Z
'
,
};
...
...
spec/javascripts/environments/mock_data.js
View file @
ccc5f473
...
...
@@ -12,6 +12,7 @@ export const environmentsList = [
stop_path
:
'
/root/review-app/environments/7/stop
'
,
created_at
:
'
2017-01-31T10:53:46.894Z
'
,
updated_at
:
'
2017-01-31T10:53:46.894Z
'
,
log_path
:
'
/root/review-app/environments/7/logs
'
,
rollout_status
:
{},
},
{
...
...
@@ -28,6 +29,7 @@ export const environmentsList = [
stop_path
:
'
/root/review-app/environments/12/stop
'
,
created_at
:
'
2017-02-01T19:42:18.400Z
'
,
updated_at
:
'
2017-02-01T19:42:18.400Z
'
,
log_path
:
'
/root/review-app/environments/12/logs
'
,
rollout_status
:
{},
},
];
...
...
@@ -120,37 +122,39 @@ export const folder = {
stop_path
:
'
/root/review-app/environments/12/stop
'
,
created_at
:
'
2017-02-01T19:42:18.400Z
'
,
updated_at
:
'
2017-02-01T19:42:18.400Z
'
,
rollout_status
:
{},
log_path
:
'
/root/review-app/environments/12/logs
'
,
};
export
const
deployBoardMockData
=
{
instances
:
[
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2334 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2335 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2336 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2337 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2338 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2339 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2340 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2334 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2335 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2336 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2337 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2338 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2339 Finished
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2340 Finished
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2341 Deploying
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2342 Deploying
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2343 Deploying
'
},
{
status
:
'
failed
'
,
tooltip
:
'
tanuki-2344 Failed
'
},
{
status
:
'
ready
'
,
tooltip
:
'
tanuki-2345 Ready
'
},
{
status
:
'
ready
'
,
tooltip
:
'
tanuki-2346 Ready
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2348 Preparing
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2349 Preparing
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2350 Preparing
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2353 Preparing
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2354 Waiting
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2355 Waiting
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2356 Waiting
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2334 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2335 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2336 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2337 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2338 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2339 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2340 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2334 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2335 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2336 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2337 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2338 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2339 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
finished
'
,
tooltip
:
'
tanuki-2340 Finished
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2341 Deploying
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2342 Deploying
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
deploying
'
,
tooltip
:
'
tanuki-2343 Deploying
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
failed
'
,
tooltip
:
'
tanuki-2344 Failed
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
ready
'
,
tooltip
:
'
tanuki-2345 Ready
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
ready
'
,
tooltip
:
'
tanuki-2346 Ready
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2348 Preparing
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2349 Preparing
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2350 Preparing
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
preparing
'
,
tooltip
:
'
tanuki-2353 Preparing
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2354 Waiting
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2355 Waiting
'
,
pod_name
:
'
production-tanuki-1
'
},
{
status
:
'
waiting
'
,
tooltip
:
'
tanuki-2356 Waiting
'
,
pod_name
:
'
production-tanuki-1
'
},
],
abort_url
:
'
url
'
,
rollback_url
:
'
url
'
,
...
...
spec/javascripts/fixtures/environments_logs.html.haml
0 → 100644
View file @
ccc5f473
.js-kubernetes-logs
{
data:
{
logs_path:
'/root/kubernetes-app/environments/1/logs'
}
}
.build-page
.build-trace-container.prepend-top-default
.top-bar.js-top-bar
.truncated-info.hidden-xs.pull-left.js-pod-name
Pod logs from pod name
.controllers.pull-right
.has-tooltip.controllers-buttons
{
title:
'Scroll to top'
,
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-scroll-up.btn-scroll.btn-transparent.btn-blank
{
type:
'button'
,
disabled:
true
}
.has-tooltip.controllers-buttons
{
title:
'Scroll to bottom'
,
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-scroll-down.btn-scroll.btn-transparent.btn-blank
{
type:
'button'
,
disabled:
true
}
.has-tooltip.controllers-buttons
{
title:
'Refresh'
,
data:
{
placement:
'top'
,
container:
'body'
}
}
%button
.js-refresh-log.btn-default.btn-refresh
{
type:
'button'
,
disabled:
true
}
%pre
.build-trace
#build-trace
%code
.bash.js-build-output
.build-loader-animation.js-build-refresh
spec/javascripts/kubernetes_logs_spec.js
0 → 100644
View file @
ccc5f473
import
KubernetesLogs
from
'
ee/kubernetes_logs
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
logMockData
}
from
'
./ee/kubernetes_mock_data
'
;
describe
(
'
Kubernetes Logs
'
,
()
=>
{
const
fixtureTemplate
=
'
static/environments_logs.html.raw
'
;
const
mockPodName
=
'
production-tanuki-1
'
;
const
logMockPath
=
'
/root/kubernetes-app/environments/1/logs
'
;
let
kubernetesLogContainer
;
let
kubernetesLog
;
let
mock
;
preloadFixtures
(
fixtureTemplate
);
describe
(
'
When data is requested correctly
'
,
()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
fixtureTemplate
);
spyOnDependency
(
KubernetesLogs
,
'
getParameterValues
'
).
and
.
callFake
(()
=>
[
mockPodName
]);
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
(
logMockPath
).
reply
(
200
,
{
logs
:
logMockData
});
kubernetesLogContainer
=
document
.
querySelector
(
'
.js-kubernetes-logs
'
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
has the pod name placed on the top bar
'
,
()
=>
{
kubernetesLog
=
new
KubernetesLogs
(
kubernetesLogContainer
);
const
topBar
=
document
.
querySelector
(
'
.js-pod-name
'
);
expect
(
topBar
.
textContent
).
toContain
(
kubernetesLog
.
podName
);
});
it
(
'
queries the pod log data and sets the dom elements
'
,
(
done
)
=>
{
const
scrollSpy
=
spyOnDependency
(
KubernetesLogs
,
'
scrollDown
'
).
and
.
callThrough
();
const
toggleDisableSpy
=
spyOnDependency
(
KubernetesLogs
,
'
toggleDisableButton
'
).
and
.
stub
();
kubernetesLog
=
new
KubernetesLogs
(
kubernetesLogContainer
);
kubernetesLog
.
getPodLogs
();
setTimeout
(()
=>
{
expect
(
kubernetesLog
.
isLogComplete
).
toEqual
(
true
);
expect
(
kubernetesLog
.
$buildOutputContainer
.
text
()).
toContain
(
logMockData
[
0
].
trim
());
expect
(
scrollSpy
).
toHaveBeenCalled
();
expect
(
toggleDisableSpy
).
toHaveBeenCalled
();
done
();
},
0
);
});
});
describe
(
'
When no pod name is available
'
,
()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
fixtureTemplate
);
kubernetesLogContainer
=
document
.
querySelector
(
'
.js-kubernetes-logs
'
);
});
it
(
'
shows up a flash message when no pod name is specified
'
,
()
=>
{
const
createFlashSpy
=
spyOnDependency
(
KubernetesLogs
,
'
createFlash
'
).
and
.
stub
();
kubernetesLog
=
new
KubernetesLogs
(
kubernetesLogContainer
);
expect
(
createFlashSpy
).
toHaveBeenCalled
();
});
});
});
spec/support/helpers/kubernetes_helpers.rb
View file @
ccc5f473
...
...
@@ -9,6 +9,10 @@ module KubernetesHelpers
kube_response
(
kube_pods_body
)
end
def
kube_logs_response
kube_response
(
kube_logs_body
)
end
def
kube_deployments_response
kube_response
(
kube_deployments_body
)
end
...
...
@@ -25,6 +29,13 @@ module KubernetesHelpers
WebMock
.
stub_request
(
:get
,
pods_url
).
to_return
(
response
||
kube_pods_response
)
end
def
stub_kubeclient_logs
(
pod_name
,
response
=
nil
)
stub_kubeclient_discover
(
service
.
api_url
)
logs_url
=
service
.
api_url
+
"/api/v1/namespaces/
#{
service
.
actual_namespace
}
/pods/
#{
pod_name
}
/log?tailLines=
#{
Clusters
::
Platforms
::
Kubernetes
::
LOGS_LIMIT
}
"
WebMock
.
stub_request
(
:get
,
logs_url
).
to_return
(
response
||
kube_logs_response
)
end
def
stub_kubeclient_deployments
(
response
=
nil
)
stub_kubeclient_discover
(
service
.
api_url
)
deployments_url
=
service
.
api_url
+
"/apis/extensions/v1beta1/namespaces/
#{
service
.
actual_namespace
}
/deployments"
...
...
@@ -89,6 +100,10 @@ module KubernetesHelpers
}
end
def
kube_logs_body
"Log 1
\n
Log 2
\n
Log 3"
end
def
kube_deployments_body
{
"kind"
=>
"DeploymentList"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment