Skip to content

Commit

Permalink
Ensure dashboard is enabled on k8s bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
wallyworld committed Jun 11, 2020
1 parent db826b5 commit 001db17
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 7 deletions.
1 change: 1 addition & 0 deletions caas/jujud-operator-dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN apt-get update \
python3-distutils \
# below apt dependencies are required by controller pod.
iproute2 \
curl \
&& pip3 install --upgrade pip setuptools \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /root/.cache
Expand Down
68 changes: 68 additions & 0 deletions caas/kubernetes/provider/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package provider

import (
"encoding/json"
"fmt"
"net/url"
"path/filepath"
"strings"
"time"
Expand All @@ -13,14 +15,17 @@ import (
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/names/v4"
"github.com/juju/proxy"
"github.com/juju/retry"
"github.com/juju/utils"
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/juju/juju/agent"
agenttools "github.com/juju/juju/agent/tools"
"github.com/juju/juju/caas"
"github.com/juju/juju/cloud"
"github.com/juju/juju/cloudconfig"
Expand Down Expand Up @@ -1005,6 +1010,13 @@ func (c *controllerStack) buildContainerSpecForController(statefulset *apps.Stat
)
var jujudCmd string
if c.pcfg.ControllerId == agent.BootstrapControllerId {
guiCmd, err := c.setUpGUICommand()
if err != nil {
return errors.Trace(err)
}
if guiCmd != "" {
jujudCmd += "\n" + guiCmd
}
// only do bootstrap-state on the bootstrap controller - controller-0.
jujudCmd += "\n" + fmt.Sprintf(
"test -e $JUJU_DATA_DIR/%s || $JUJU_TOOLS_DIR/jujud bootstrap-state $JUJU_DATA_DIR/%s --data-dir $JUJU_DATA_DIR %s --timeout %s",
Expand All @@ -1022,3 +1034,59 @@ func (c *controllerStack) buildContainerSpecForController(statefulset *apps.Stat
statefulset.Spec.Template.Spec.Containers = generateContainerSpecs(jujudCmd)
return nil
}

func (c *controllerStack) setUpGUICommand() (string, error) {
if c.pcfg.Bootstrap.GUI == nil {
return "", nil
}
var guiCmds []string
u, err := url.Parse(c.pcfg.Bootstrap.GUI.URL)
if err != nil {
return "", errors.Annotate(err, "cannot parse Juju GUI URL")
}
guiJson, err := json.Marshal(c.pcfg.Bootstrap.GUI)
if err != nil {
return "", errors.Trace(err)
}
guiDir := agenttools.SharedGUIDir(c.pcfg.DataDir)
guiCmds = append(guiCmds,
"echo Installing Dashboard...",
"export gui="+utils.ShQuote(guiDir),
"mkdir -p $gui",
)
// Download the GUI from simplestreams.
command := "curl -sSf -o $gui/gui.tar.bz2 --retry 10"
if c.pcfg.DisableSSLHostnameVerification {
command += " --insecure"
}

curlProxyArgs := formatCurlProxyArguments(u.String(), c.pcfg.ProxySettings)
command += curlProxyArgs
command += " " + utils.ShQuote(u.String())
// A failure in fetching the Juju GUI archive should not prevent the
// model to be bootstrapped. Better no GUI than no Juju at all.
command += " || echo Unable to retrieve Juju Dashboard"
guiCmds = append(guiCmds, command)
guiCmds = append(guiCmds,
"[ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256",
fmt.Sprintf(
`[ -f $gui/jujugui.sha256 ] && (grep '%s' $gui/jujugui.sha256 && printf %%s %s > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch)`,
c.pcfg.Bootstrap.GUI.SHA256, utils.ShQuote(string(guiJson))),
)
return strings.Join(guiCmds, "\n"), nil
}

func formatCurlProxyArguments(guiURL string, proxySettings proxy.Settings) (proxyArgs string) {
if strings.HasPrefix(guiURL, "http://") && proxySettings.Http != "" {
proxyUrl := proxySettings.Http
proxyArgs += fmt.Sprintf(" --proxy %s", proxyUrl)
} else if strings.HasPrefix(guiURL, "https://") && proxySettings.Https != "" {
proxyUrl := proxySettings.Https
// curl automatically uses HTTP CONNECT for URLs containing HTTPS
proxyArgs += fmt.Sprintf(" --proxy %s", proxyUrl)
}
if proxySettings.NoProxy != "" {
proxyArgs += fmt.Sprintf(" --noproxy %s", proxySettings.NoProxy)
}
return
}
14 changes: 14 additions & 0 deletions caas/kubernetes/provider/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
jujuclock "github.com/juju/clock"
"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
"github.com/juju/version"
"github.com/juju/worker/v2/workertest"
gc "gopkg.in/check.v1"
apps "k8s.io/api/apps/v1"
Expand All @@ -31,6 +32,7 @@ import (
envtesting "github.com/juju/juju/environs/testing"
"github.com/juju/juju/mongo"
"github.com/juju/juju/testing"
"github.com/juju/juju/tools"
jujuversion "github.com/juju/juju/version"
)

Expand Down Expand Up @@ -206,6 +208,12 @@ func (s *bootstrapSuite) TestBootstrap(c *gc.C) {

s.pcfg.Bootstrap.Timeout = 10 * time.Minute
s.pcfg.Bootstrap.ControllerExternalIPs = []string{"10.0.0.1"}
s.pcfg.Bootstrap.GUI = &tools.GUIArchive{
URL: "http://gui-url",
Version: version.MustParse("6.6.6"),
SHA256: "deadbeef",
Size: 999,
}

controllerStacker := s.controllerStackerGetter()

Expand Down Expand Up @@ -560,6 +568,12 @@ export JUJU_TOOLS_DIR=$JUJU_DATA_DIR/tools
mkdir -p $JUJU_TOOLS_DIR
cp /opt/jujud $JUJU_TOOLS_DIR/jujud
echo Installing Dashboard...
export gui='/var/lib/juju/gui'
mkdir -p $gui
curl -sSf -o $gui/gui.tar.bz2 --retry 10 'http://gui-url' || echo Unable to retrieve Juju Dashboard
[ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256
[ -f $gui/jujugui.sha256 ] && (grep 'deadbeef' $gui/jujugui.sha256 && printf %s '{"version":"6.6.6","url":"http://gui-url","sha256":"deadbeef","size":999}' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch)
test -e $JUJU_DATA_DIR/agents/controller-0/agent.conf || $JUJU_TOOLS_DIR/jujud bootstrap-state $JUJU_DATA_DIR/bootstrap-params --data-dir $JUJU_DATA_DIR --debug --timeout 10m0s
$JUJU_TOOLS_DIR/jujud machine --data-dir $JUJU_DATA_DIR --controller-id 0 --log-to-stderr --debug
`[1:],
Expand Down
11 changes: 11 additions & 0 deletions cloudconfig/podcfg/podcfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/names/v4"
"github.com/juju/proxy"
"github.com/juju/version"

"github.com/juju/juju/agent"
Expand Down Expand Up @@ -40,6 +41,14 @@ type ControllerPodConfig struct {
// Controller must also be set.
Bootstrap *BootstrapConfig

// DisableSSLHostnameVerification can be set to true to tell cloud-init
// that it shouldn't verify SSL certificates
DisableSSLHostnameVerification bool

// ProxySettings encapsulates all proxy-related settings used to access
// an outside network.
ProxySettings proxy.Settings

// Controller contains controller-specific configuration. If this is
// set, then the instance will be configured as a controller pod.
Controller *ControllerConfig
Expand Down Expand Up @@ -356,6 +365,8 @@ func NewBootstrapControllerPodConfig(
// Bootstrap func, and that has set an agent-version (via finding the tools to,
// use for bootstrap, or otherwise).
func FinishControllerPodConfig(pcfg *ControllerPodConfig, cfg *config.Config, agentEnvironment map[string]string) {
pcfg.DisableSSLHostnameVerification = !cfg.SSLHostnameVerification()
pcfg.ProxySettings = cfg.JujuProxySettings()
if pcfg.AgentEnvironment == nil {
pcfg.AgentEnvironment = make(map[string]string)
}
Expand Down
8 changes: 6 additions & 2 deletions cloudconfig/podcfg/podcfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,24 @@ func (*podcfgSuite) TestBootstrapConstraints(c *gc.C) {

func (*podcfgSuite) TestFinishControllerPodConfig(c *gc.C) {
cfg := testing.CustomModelConfig(c, testing.Attrs{
"type": "kubernetes",
"type": "kubernetes",
"ssl-hostname-verification": false,
"juju-https-proxy": "https-proxy",
})
podConfig, err := podcfg.NewBootstrapControllerPodConfig(
testing.FakeControllerConfig(),
"controller-1",
"kubernetes",
constraints.Value{},
)
c.Assert(err, jc.ErrorIsNil)
podcfg.FinishControllerPodConfig(
podConfig,
cfg,
map[string]string{"foo": "bar"},
)
c.Assert(err, jc.ErrorIsNil)
c.Assert(podConfig.DisableSSLHostnameVerification, jc.IsTrue)
c.Assert(podConfig.ProxySettings.Https, gc.Equals, "https-proxy")
c.Assert(podConfig.AgentEnvironment, jc.DeepEquals, map[string]string{
"PROVIDER_TYPE": "kubernetes",
"foo": "bar",
Expand Down
21 changes: 17 additions & 4 deletions environs/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func bootstrapCAAS(
}
podConfig.JujuVersion = jujuVersion
podConfig.OfficialBuild = jujuversion.OfficialBuild
if err := finalizePodBootstrapConfig(podConfig, args, environ.Config()); err != nil {
if err := finalizePodBootstrapConfig(ctx, podConfig, args, environ.Config()); err != nil {
return errors.Annotate(err, "finalizing bootstrap instance config")
}
if err := result.CaasBootstrapFinalizer(ctx, podConfig, args.DialOpts); err != nil {
Expand Down Expand Up @@ -686,7 +686,7 @@ func finalizeInstanceBootstrapConfig(
icfg.Bootstrap.RegionInheritedConfig = args.Cloud.RegionConfig
icfg.Bootstrap.HostedModelConfig = args.HostedModelConfig
icfg.Bootstrap.Timeout = args.DialOpts.Timeout
icfg.Bootstrap.GUI = guiArchive(args.GUIDataSourceBaseURL, cfg.GUIStream(), vers.Major, vers.Minor, func(msg string) {
icfg.Bootstrap.GUI = guiArchive(args.GUIDataSourceBaseURL, cfg.GUIStream(), vers.Major, vers.Minor, true, func(msg string) {
ctx.Infof(msg)
})
icfg.Bootstrap.JujuDbSnapPath = args.JujuDbSnapPath
Expand All @@ -695,6 +695,7 @@ func finalizeInstanceBootstrapConfig(
}

func finalizePodBootstrapConfig(
ctx environs.BootstrapContext,
pcfg *podcfg.ControllerPodConfig,
args BootstrapParams,
cfg *config.Config,
Expand Down Expand Up @@ -756,6 +757,11 @@ func finalizePodBootstrapConfig(
pcfg.AgentEnvironment[k] = v
}

vers, ok := cfg.AgentVersion()
if !ok {
return errors.New("controller model configuration has no agent-version")
}

pcfg.Bootstrap.ControllerModelConfig = cfg
pcfg.Bootstrap.ControllerCloud = args.Cloud
pcfg.Bootstrap.ControllerCloudRegion = args.CloudRegion
Expand All @@ -768,6 +774,9 @@ func finalizePodBootstrapConfig(
pcfg.Bootstrap.ControllerServiceType = args.ControllerServiceType
pcfg.Bootstrap.ControllerExternalName = args.ControllerExternalName
pcfg.Bootstrap.ControllerExternalIPs = append([]string(nil), args.ControllerExternalIPs...)
pcfg.Bootstrap.GUI = guiArchive(args.GUIDataSourceBaseURL, cfg.GUIStream(), vers.Major, vers.Minor, false, func(msg string) {
ctx.Infof(msg)
})
return nil
}

Expand Down Expand Up @@ -1035,10 +1044,14 @@ func setPrivateMetadataSources(metadataDir string) ([]*imagemetadata.ImageMetada
// non-empty, remote GUI archive info is retrieved from simplestreams using it
// as the base URL. The given logProgress function is used to inform users
// about errors or progress in setting up the Juju GUI.
func guiArchive(dataSourceBaseURL, stream string, major, minor int, logProgress func(string)) *coretools.GUIArchive {
func guiArchive(dataSourceBaseURL, stream string, major, minor int, allowLocal bool, logProgress func(string)) *coretools.GUIArchive {
// The environment variable is only used for development purposes.
path := os.Getenv("JUJU_GUI")
if path != "" {
if path != "" && !allowLocal {
// TODO(wallyworld) - support local archive on k8s controllers at bootstrap
// It can't be passed the same way as on IAAS as it's too large.
logProgress("Dashboard from local archive on bootstrap not supported")
} else if path != "" {
vers, err := guiVersion(path)
if err != nil {
logProgress(fmt.Sprintf("Cannot use Juju Dashboard at %q: %s", path, err))
Expand Down
3 changes: 2 additions & 1 deletion environs/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func (s *bootstrapSuite) SetUpTest(c *gc.C) {
s.BaseSuite.SetUpTest(c)
s.ToolsFixture.SetUpTest(c)

s.PatchEnvironment("JUJU_GUI", "")
s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
storageDir := c.MkDir()
s.PatchValue(&envtools.DefaultBaseURL, storageDir)
Expand Down Expand Up @@ -325,7 +326,7 @@ func (s *bootstrapSuite) assertFinalizePodBootstrapConfig(c *gc.C, serviceType,
ControllerExternalIPs: externalIps,
ExtraAgentValuesForTesting: map[string]string{"foo": "bar"},
}
err = bootstrap.FinalizePodBootstrapConfig(podConfig, params, modelCfg)
err = bootstrap.FinalizePodBootstrapConfig(envtesting.BootstrapContext(c), podConfig, params, modelCfg)
c.Assert(err, jc.ErrorIsNil)
c.Assert(podConfig.Bootstrap.ControllerModelConfig, jc.DeepEquals, modelCfg)
c.Assert(podConfig.Bootstrap.ControllerServiceType, gc.Equals, serviceType)
Expand Down

0 comments on commit 001db17

Please sign in to comment.