chore: add multi-org flag to develop.sh (#13923)

This commit is contained in:
Garrett Delfosse
2024-07-18 14:43:07 -05:00
committed by GitHub
parent f975701b34
commit 6f20a64f9d
6 changed files with 269 additions and 33 deletions
+18
View File
@@ -290,6 +290,24 @@ func (c *Client) ProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, e
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
}
func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerDaemon, error) {
res, err := c.Request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organizationID.String()),
nil,
)
if err != nil {
return nil, xerrors.Errorf("execute request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, ReadBodyAsError(res)
}
var daemons []ProvisionerDaemon
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
}
// CreateTemplateVersion processes source-code and optionally associates the version with a template.
// Executing without a template is useful for validating source-code.
func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid.UUID, req CreateTemplateVersionRequest) (TemplateVersion, error) {
+9
View File
@@ -135,3 +135,12 @@ Serve prometheus metrics on the address defined by prometheus address.
| Default | <code>127.0.0.1:2112</code> |
The bind address to serve prometheus metrics.
### -O, --org
| | |
| ----------- | -------------------------------- |
| Type | <code>string</code> |
| Environment | <code>$CODER_ORGANIZATION</code> |
Select which organization (uuid or name) to use.
+35 -2
View File
@@ -4,7 +4,9 @@ package cli
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"regexp"
"time"
@@ -76,6 +78,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
prometheusEnable bool
prometheusAddress string
)
orgContext := agpl.NewOrganizationContext()
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "start",
@@ -95,6 +98,35 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
interruptCtx, interruptCancel := inv.SignalNotifyContext(ctx, agpl.InterruptSignals...)
defer interruptCancel()
// This can fail to get the current organization
// if the client is not authenticated as a user,
// like when only PSK is provided.
// This will be cleaner once PSK is replaced
// with org scoped authentication tokens.
org, err := orgContext.Selected(inv, client)
if err != nil {
var cErr *codersdk.Error
if !errors.As(err, &cErr) || cErr.StatusCode() != http.StatusUnauthorized {
return xerrors.Errorf("current organization: %w", err)
}
if preSharedKey == "" {
return xerrors.New("must provide a pre-shared key when not authenticated as a user")
}
org = codersdk.Organization{ID: uuid.Nil}
if orgContext.FlagSelect != "" {
// If we are using PSK, we can't fetch the organization
// to validate org name so we need the user to provide
// a valid organization ID.
orgID, err := uuid.Parse(orgContext.FlagSelect)
if err != nil {
return xerrors.New("must provide an org ID when not authenticated as a user and organization is specified")
}
org = codersdk.Organization{ID: orgID}
}
}
tags, err := agpl.ParseProvisionerTags(rawTags)
if err != nil {
return err
@@ -198,16 +230,16 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
connector := provisionerd.LocalProvisioners{
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient),
}
id := uuid.New()
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
ID: id,
ID: uuid.New(),
Name: name,
Provisioners: []codersdk.ProvisionerType{
codersdk.ProvisionerTypeTerraform,
},
Tags: tags,
PreSharedKey: preSharedKey,
Organization: org.ID,
})
}, &provisionerd.Options{
Logger: logger,
@@ -348,6 +380,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
Default: "127.0.0.1:2112",
},
}
orgContext.AttachOptions(cmd)
return cmd
}
+163 -29
View File
@@ -27,36 +27,134 @@ import (
func TestProvisionerDaemon_PSK(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name=matt-daemon")
err := conf.URL().Write(client.URL.String())
require.NoError(t, err)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
clitest.Start(t, inv)
pty.ExpectNoMatchBefore(ctx, "check entitlement", "starting provisioner daemon")
pty.ExpectMatchContext(ctx, "matt-daemon")
t.Run("OK", func(t *testing.T) {
t.Parallel()
var daemons []codersdk.ProvisionerDaemon
require.Eventually(t, func() bool {
daemons, err = client.ProvisionerDaemons(ctx)
if err != nil {
return false
}
return len(daemons) == 1
}, testutil.WaitShort, testutil.IntervalSlow)
require.Equal(t, "matt-daemon", daemons[0].Name)
require.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
require.Equal(t, buildinfo.Version(), daemons[0].Version)
require.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name=matt-daemon")
err := conf.URL().Write(client.URL.String())
require.NoError(t, err)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
clitest.Start(t, inv)
pty.ExpectNoMatchBefore(ctx, "check entitlement", "starting provisioner daemon")
pty.ExpectMatchContext(ctx, "matt-daemon")
var daemons []codersdk.ProvisionerDaemon
require.Eventually(t, func() bool {
daemons, err = client.ProvisionerDaemons(ctx)
if err != nil {
return false
}
return len(daemons) == 1
}, testutil.WaitShort, testutil.IntervalSlow)
require.Equal(t, "matt-daemon", daemons[0].Name)
require.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
require.Equal(t, buildinfo.Version(), daemons[0].Version)
require.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
})
t.Run("AnotherOrg", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.ID.String())
err := conf.URL().Write(client.URL.String())
require.NoError(t, err)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
clitest.Start(t, inv)
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
var daemons []codersdk.ProvisionerDaemon
require.Eventually(t, func() bool {
daemons, err = client.OrganizationProvisionerDaemons(ctx, anotherOrg.ID)
if err != nil {
return false
}
return len(daemons) == 1
}, testutil.WaitShort, testutil.IntervalSlow)
assert.Equal(t, "org-daemon", daemons[0].Name)
assert.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
})
t.Run("AnotherOrgByNameWithUser", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
anotherClient, _ := coderdtest.CreateAnotherUser(t, client, anotherOrg.ID, rbac.RoleTemplateAdmin())
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.Name)
clitest.SetupConfig(t, anotherClient, conf)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
clitest.Start(t, inv)
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
})
t.Run("AnotherOrgByNameNoUser", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.Name)
err := conf.URL().Write(client.URL.String())
require.NoError(t, err)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
err = inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "must provide an org ID when not authenticated as a user and organization is specified")
})
t.Run("NoUserNoPSK", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
inv, conf := newCLI(t, "provisionerd", "start", "--name", "org-daemon")
err := conf.URL().Write(client.URL.String())
require.NoError(t, err)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
err = inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "must provide a pre-shared key when not authenticated as a user")
})
}
func TestProvisionerDaemon_SessionToken(t *testing.T) {
@@ -166,6 +264,42 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) {
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
})
t.Run("ScopeUserAnotherOrg", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{
ProvisionerDaemonPSK: "provisionersftw",
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
anotherClient, anotherUser := coderdtest.CreateAnotherUser(t, client, anotherOrg.ID, rbac.RoleTemplateAdmin())
inv, conf := newCLI(t, "provisionerd", "start", "--tag", "scope=user", "--name", "org-daemon", "--org", anotherOrg.ID.String())
clitest.SetupConfig(t, anotherClient, conf)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
clitest.Start(t, inv)
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
var daemons []codersdk.ProvisionerDaemon
var err error
require.Eventually(t, func() bool {
daemons, err = client.OrganizationProvisionerDaemons(ctx, anotherOrg.ID)
if err != nil {
return false
}
return len(daemons) == 1
}, testutil.WaitShort, testutil.IntervalSlow)
assert.Equal(t, "org-daemon", daemons[0].Name)
assert.Equal(t, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope])
assert.Equal(t, anotherUser.ID.String(), daemons[0].Tags[provisionersdk.TagOwner])
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
})
t.Run("PrometheusEnabled", func(t *testing.T) {
t.Parallel()
@@ -6,6 +6,9 @@ USAGE:
Run a provisioner daemon
OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
-c, --cache-dir string, $CODER_CACHE_DIRECTORY (default: [cache dir])
Directory to store cached data.
+41 -2
View File
@@ -18,8 +18,9 @@ debug=0
DEFAULT_PASSWORD="SomeSecurePassword!"
password="${CODER_DEV_ADMIN_PASSWORD:-${DEFAULT_PASSWORD}}"
use_proxy=0
multi_org=0
args="$(getopt -o "" -l access-url:,use-proxy,agpl,debug,password: -- "$@")"
args="$(getopt -o "" -l access-url:,use-proxy,agpl,debug,password:,multi-organization -- "$@")"
eval set -- "$args"
while true; do
case "$1" in
@@ -39,6 +40,10 @@ while true; do
use_proxy=1
shift
;;
--multi-organization)
multi_org=1
shift
;;
--debug)
debug=1
shift
@@ -57,6 +62,10 @@ if [ "${CODER_BUILD_AGPL:-0}" -gt "0" ] && [ "${use_proxy}" -gt "0" ]; then
echo '== ERROR: cannot use both external proxies and APGL build.' && exit 1
fi
if [ "${CODER_BUILD_AGPL:-0}" -gt "0" ] && [ "${multi_org}" -gt "0" ]; then
echo '== ERROR: cannot use both multi-organizations and APGL build.' && exit 1
fi
# Preflight checks: ensure we have our required dependencies, and make sure nothing is listening on port 3000 or 8080
dependencies curl git go make pnpm
curl --fail http://127.0.0.1:3000 >/dev/null 2>&1 && echo '== ERROR: something is listening on port 3000. Kill it and re-run this script.' && exit 1
@@ -168,21 +177,51 @@ fatal() {
echo 'Failed to create regular user. To troubleshoot, try running this command manually.'
fi
# Create a new organization and add the member user to it.
if [ "${multi_org}" -gt "0" ]; then
another_org="second-organization"
if ! "${CODER_DEV_SHIM}" organizations show selected --org "${another_org}" >/dev/null 2>&1; then
echo "Creating organization '${another_org}'..."
(
"${CODER_DEV_SHIM}" organizations create -y "${another_org}"
) || echo "Failed to create organization '${another_org}'"
fi
if ! "${CODER_DEV_SHIM}" org members list --org ${another_org} | grep "^member" >/dev/null 2>&1; then
echo "Adding member user to organization '${another_org}'..."
(
"${CODER_DEV_SHIM}" organizations members add member --org "${another_org}"
) || echo "Failed to add member user to organization '${another_org}'"
fi
echo "Starting external provisioner for '${another_org}'..."
(
start_cmd EXT_PROVISIONER "" "${CODER_DEV_SHIM}" provisionerd start --tag "scope=organization" --name second-org-daemon --org "${another_org}"
) || echo "Failed to start external provisioner. No external provisioner started."
fi
# If we have docker available and the "docker" template doesn't already
# exist, then let's try to create a template!
template_name="docker"
if docker info >/dev/null 2>&1 && ! "${CODER_DEV_SHIM}" templates versions list "${template_name}" >/dev/null 2>&1; then
# sometimes terraform isn't installed yet when we go to create the
# template
echo "Waiting for terraform to be installed..."
sleep 5
echo "Initializing docker template..."
temp_template_dir="$(mktemp -d)"
"${CODER_DEV_SHIM}" templates init --id "${template_name}" "${temp_template_dir}"
DOCKER_HOST="$(docker context inspect --format '{{ .Endpoints.docker.Host }}')"
printf 'docker_arch: "%s"\ndocker_host: "%s"\n' "${GOARCH}" "${DOCKER_HOST}" >"${temp_template_dir}/params.yaml"
(
"${CODER_DEV_SHIM}" templates push "${template_name}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes
echo "Pushing docker template to 'first-organization'..."
"${CODER_DEV_SHIM}" templates push "${template_name}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes --org first-organization
if [ "${multi_org}" -gt "0" ]; then
echo "Pushing docker template to '${another_org}'..."
"${CODER_DEV_SHIM}" templates push "${template_name}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes --org "${another_org}"
fi
rm -rfv "${temp_template_dir}" # Only delete template dir if template creation succeeds
) || echo "Failed to create a template. The template files are in ${temp_template_dir}"
fi