grim/convey
Clone
Summary
Browse
Changes
Graph
ChangeLog the fixes from pr #25. Fixes #125, #126
2017-10-03, Gary Kramlich
674d936a6785
ChangeLog the fixes from pr #25. Fixes #125, #126
/*
* Convey
* Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package
state
import
(
"fmt"
"os"
"strings"
"sync"
"time"
"bitbucket.org/rw_grim/convey/environment"
"bitbucket.org/rw_grim/convey/network"
"bitbucket.org/rw_grim/convey/workspace"
)
type
State
struct
{
Network
network
.
Network
Workspace
workspace
.
Workspace
KeepWorkspace
bool
ForceSequential
bool
EnableSSHAgent
bool
TaskTimeout
time
.
Duration
Environment
[]
string
DockerConfig
string
CPUShares
string
Memory
string
// States have the ability to "wrap" another one without
// changing the underlying state. This is used by the
// extends intrinsic in order to modify the stat without
// requiring unique access to the state object during the
// execution of the extended task. A past implementation
// had modified the stack directly, but that causes an
// extended task to clobber other tasks in concurrent mode.
parent
*
State
expandables
[]
string
expandableDelimiter
string
// This list is a stash of container names which are run
// in detached mode. Appending to this may happen from
// multiple goroutines, so this needs to be guarded via
// mutex.
detachedContainers
map
[
string
]
struct
{}
mutex
*
sync
.
RWMutex
}
func
New
()
*
State
{
return
&
State
{
detachedContainers
:
map
[
string
]
struct
{}{},
mutex
:
&
sync
.
RWMutex
{},
}
}
func
(
st
*
State
)
Valid
()
error
{
if
st
.
parent
==
nil
&&
(
st
.
detachedContainers
==
nil
||
st
.
mutex
==
nil
)
{
return
fmt
.
Errorf
(
"state must be constructed via New"
)
}
if
st
.
EnableSSHAgent
{
if
val
:=
os
.
Getenv
(
"SSH_AUTH_SOCK"
);
val
==
""
{
return
fmt
.
Errorf
(
"ssh-agent forwarding requested, but agent not running"
)
}
}
return
nil
}
// MapSlice calls SliceMapper on the given environment, but also checks to
// see if the variable could be expanded into a list.
func
(
st
*
State
)
MapSlice
(
env
,
fullEnv
[]
string
)
([]
string
,
error
)
{
if
len
(
env
)
==
1
{
// If we only have one thing and it looks like $VAR or ${VAR}, then
// see if we have it in the list of things we can expand on the stack.
if
delimiter
,
ok
:=
st
.
getDelimiter
(
getName
(
env
[
0
]));
ok
{
mapped
,
err
:=
environment
.
Mapper
(
env
[
0
],
fullEnv
)
if
err
!=
nil
{
return
nil
,
err
}
// Split it!
return
strings
.
SplitN
(
mapped
,
delimiter
,
-
1
),
nil
}
}
return
environment
.
SliceMapper
(
env
,
fullEnv
)
}
// GetDelimiter returns the highest (outermost extend task) delimiter registered
// with a given expandable environment variable. Returns true if found by name.
func
(
st
*
State
)
getDelimiter
(
name
string
)
(
string
,
bool
)
{
if
st
.
parent
==
nil
{
return
""
,
false
}
for
_
,
expandable
:=
range
st
.
expandables
{
if
expandable
==
name
{
return
st
.
expandableDelimiter
,
true
}
}
return
st
.
parent
.
getDelimiter
(
name
)
}
// WrapWithExpandableEnv will create a shallow clone of the state with a reference
// to the current state as "parent" with a modified environment. This creates a local
// stack of states which do not interfere with other goroutines. A pop operation is
// the same as ignoring the wrapped values and using the underlying state. This stack
// is used to map a slice within an extended task.
func
(
st
*
State
)
WrapWithExpandableEnv
(
env
,
expandable
[]
string
,
delimiter
string
)
*
State
{
// Merge the environment into this map, but do NOT override anything that
// is currently in the state's environment - this has higher precedence.
env
=
environment
.
Merge
(
env
,
st
.
Environment
)
return
&
State
{
Network
:
st
.
Network
,
Workspace
:
st
.
Workspace
,
KeepWorkspace
:
st
.
KeepWorkspace
,
ForceSequential
:
st
.
ForceSequential
,
EnableSSHAgent
:
st
.
EnableSSHAgent
,
TaskTimeout
:
st
.
TaskTimeout
,
Environment
:
env
,
DockerConfig
:
st
.
DockerConfig
,
CPUShares
:
st
.
CPUShares
,
Memory
:
st
.
Memory
,
parent
:
st
,
expandables
:
expandable
,
expandableDelimiter
:
delimiter
,
}
}
// MarkDetached will add the given container name into the list
// of containers running in detached mode which must be shut down
// at the end of the plan. This falls through directly to the root
// state so that states wrapping the global one do not have to sync
// additional detached container names.
func
(
st
*
State
)
MarkDetached
(
name
string
)
{
if
st
.
parent
!=
nil
{
st
.
parent
.
MarkDetached
(
name
)
return
}
st
.
mutex
.
Lock
()
defer
st
.
mutex
.
Unlock
()
st
.
detachedContainers
[
name
]
=
struct
{}{}
}
// GetDetached returns a list of all detached containers.
func
(
st
*
State
)
GetDetached
()
[]
string
{
if
st
.
parent
!=
nil
{
return
st
.
parent
.
GetDetached
()
}
st
.
mutex
.
RLock
()
defer
st
.
mutex
.
RUnlock
()
names
:=
[]
string
{}
for
key
:=
range
st
.
detachedContainers
{
names
=
append
(
names
,
key
)
}
return
names
}
// getName returns the name of the variable `var` if the string is of the
// form $var or ${var}.
func
getName
(
env
string
)
string
{
env
=
strings
.
TrimSpace
(
env
)
if
strings
.
HasPrefix
(
env
,
"$"
)
{
return
strings
.
Trim
(
env
[
1
:],
"{}"
)
}
return
""
}