Skip to content

Commit

Permalink
Merge pull request coreos#1994 from kyoto/node-iam-role
Browse files Browse the repository at this point in the history
frontend: Add the ability to set IAM role for master / worker nodes
  • Loading branch information
squat authored Oct 2, 2017
2 parents 9436aec + cbaa0bc commit 97e47e6
Show file tree
Hide file tree
Showing 19 changed files with 28,136 additions and 17 deletions.
1 change: 1 addition & 0 deletions installer/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func New(config *Config) (http.Handler, error) {
mux.Handle("/aws/vpcs", logRequests(httpHandler("POST", ctx, awsGetVPCsHandler)))
mux.Handle("/aws/vpcs/subnets", logRequests(httpHandler("POST", ctx, awsGetVPCsSubnetsHandler)))
mux.Handle("/aws/ssh-key-pairs", logRequests(httpHandler("POST", ctx, awsGetKeyPairsHandler)))
mux.Handle("/aws/iam-roles", logRequests(httpHandler("POST", ctx, awsGetIamRolesHandler)))
mux.Handle("/aws/zones", logRequests(httpHandler("POST", ctx, awsGetZonesHandler)))
mux.Handle("/aws/domain", logRequests(httpHandler("POST", ctx, awsGetDomainInfoHandler)))

Expand Down
23 changes: 23 additions & 0 deletions installer/api/handlers_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/route53domains"

Expand Down Expand Up @@ -227,6 +228,28 @@ func awsGetKeyPairsHandler(w http.ResponseWriter, req *http.Request, _ *Context)
return writeJSONResponse(w, req, http.StatusOK, response)
}

// awsGetIamRolesHandler returns the list of IAM roles.
func awsGetIamRolesHandler(w http.ResponseWriter, req *http.Request, _ *Context) error {
awsSession, err := awsSessionFromRequest(req)
if err != nil {
return fromAWSErr(err)
}

iamSvc := iam.New(awsSession)
resp, err := iamSvc.ListRoles(&iam.ListRolesInput{})
if err != nil {
return fromAWSErr(err)
}

roles := make([]string, len(resp.Roles))
for i, role := range resp.Roles {
roles[i] = aws.StringValue(role.RoleName)
}
sort.Strings(roles)

return writeJSONResponse(w, req, http.StatusOK, roles)
}

// awsGetZonesHandler returns the list of Route53 Hosted Zones.
func awsGetZonesHandler(w http.ResponseWriter, req *http.Request, _ *Context) error {

Expand Down
1 change: 1 addition & 0 deletions installer/frontend/aws-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const createAction = (name, fn, shouldReject = false) => (body, creds, isNow) =>
export const getVpcs = createAction('availableVpcs', awsApis.getVpcs);
export const getVpcSubnets = createAction('availableVpcSubnets', awsApis.getVpcSubnets);
export const getSsh = createAction('availableSsh', awsApis.getSsh, true);
export const getIamRoles = createAction('availableIamRoles', awsApis.getIamRoles);
export const getRegions = createAction('availableRegions', awsApis.getRegions, true);
export const getZones = createAction('availableR53Zones', awsApis.getZones, true);
export const getDomainInfo = createAction('domainInfo', awsApis.getDomainInfo);
Expand Down
2 changes: 1 addition & 1 deletion installer/frontend/aws-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ const TFPostJSON = url => (body = {}, creds = {}, platform = 'aws') => {
);
};


export const getRegions = postJSON('/aws/regions');
export const getSsh = postJSON('/aws/ssh-key-pairs');
export const getIamRoles = postJSON('/aws/iam-roles');
export const getVpcs = postJSON('/aws/vpcs');
export const getVpcSubnets = postJSON('/aws/vpcs/subnets');
export const getZones = postJSON('/aws/zones');
Expand Down
7 changes: 7 additions & 0 deletions installer/frontend/cluster-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const ADMIN_PASSWORD2 = 'adminPassword2';
export const POD_CIDR = 'podCIDR';
export const SERVICE_CIDR = 'serviceCIDR';

export const IAM_ROLE = 'iamRole';
export const NUMBER_OF_INSTANCES = 'numberOfInstances';
export const INSTANCE_TYPE = 'instanceType';
export const STORAGE_SIZE_IN_GIB = 'storageSizeInGiB';
Expand All @@ -70,6 +71,7 @@ export const STORAGE_IOPS = 'storageIOPS';
export const RETRY = 'retry';

// FORMS:
export const AWS_CREDS = 'AWSCreds';
export const AWS_ETCDS = 'aws_etcds';
export const AWS_VPC_FORM = 'aws_vpc';
export const AWS_CONTROLLERS = 'aws_controllers';
Expand All @@ -92,6 +94,9 @@ const EXTERNAL = 'external';
const PROVISIONED = 'provisioned';
export const ETCD_OPTIONS = { EXTERNAL, PROVISIONED };

// String that would be an invalid IAM role name
export const IAM_ROLE_CREATE_OPTION = '%create%';

export const toVPCSubnet = (region, subnets, deselected) => {
const vpcSubnets = {};
_.each(subnets, (v, availabilityZone) => {
Expand Down Expand Up @@ -229,10 +234,12 @@ export const toAWS_TF = (cc, FORMS) => {
tectonic_aws_region: cc[AWS_REGION],
tectonic_admin_email: cc[ADMIN_EMAIL],
tectonic_aws_master_ec2_type: controllers[INSTANCE_TYPE],
tectonic_aws_master_iam_role_name: controllers[IAM_ROLE] === IAM_ROLE_CREATE_OPTION ? undefined : controllers[IAM_ROLE],
tectonic_aws_master_root_volume_iops: controllers[STORAGE_TYPE] === 'io1' ? controllers[STORAGE_IOPS] : undefined,
tectonic_aws_master_root_volume_size: controllers[STORAGE_SIZE_IN_GIB],
tectonic_aws_master_root_volume_type: controllers[STORAGE_TYPE],
tectonic_aws_worker_ec2_type: workers[INSTANCE_TYPE],
tectonic_aws_worker_iam_role_name: workers[IAM_ROLE] === IAM_ROLE_CREATE_OPTION ? undefined : workers[IAM_ROLE],
tectonic_aws_worker_root_volume_iops: workers[STORAGE_TYPE] === 'io1' ? controllers[STORAGE_IOPS] : undefined,
tectonic_aws_worker_root_volume_size: workers[STORAGE_SIZE_IN_GIB],
tectonic_aws_worker_root_volume_type: workers[STORAGE_TYPE],
Expand Down
2 changes: 1 addition & 1 deletion installer/frontend/components/aws-cloud-credentials.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TectonicGA } from '../tectonic-ga';
import {
AWS_ACCESS_KEY_ID,
AWS_CONTROLLER_SUBNETS,
AWS_CREDS,
AWS_WORKER_SUBNETS,
AWS_CONTROLLER_SUBNET_IDS,
AWS_WORKER_SUBNET_IDS,
Expand Down Expand Up @@ -44,7 +45,6 @@ const REGION_NAMES = {
};

const { batchSetIn } = configActions;
const AWS_CREDS = 'AWSCreds';

const awsCredsForm = new Form(AWS_CREDS, [
new Field(STS_ENABLED, {default: false}),
Expand Down
18 changes: 17 additions & 1 deletion installer/frontend/components/aws-define-nodes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { connect } from 'react-redux';
import {
AWS_CONTROLLERS,
AWS_WORKERS,
IAM_ROLE,
IAM_ROLE_CREATE_OPTION,
INSTANCE_TYPE,
NUMBER_OF_INSTANCES,
STORAGE_IOPS,
Expand Down Expand Up @@ -40,13 +42,27 @@ const IOPs = connect(
</Row>
);

const IamRoles = connect(
({clusterConfig}) => ({roles: _.get(clusterConfig, ['extra', IAM_ROLE], [])})
)(
({roles, type}) => <Row htmlFor={`${type}--iam-role`} label="IAM Role">
<Connect field={toKey(type, IAM_ROLE)}>
<Select id={`${type}--iam-role`}>
<option value={IAM_ROLE_CREATE_OPTION}>Create an IAM role for me (default)</option>
{roles.map(r => <option value={r} key={r}>{r}</option>)}
</Select>
</Connect>
</Row>
);

const Errors = connect(
({clusterConfig}, {type}) => ({
error: _.get(clusterConfig, toError(type)) || _.get(clusterConfig, toAsyncError(type)),
})
)(props => props.error ? <div className="wiz-error-message">{props.error}</div> : <span />);

export const DefineNode = ({type, max}) => <div>
export const DefineNode = ({type, max, withIamRole = true}) => <div>
{withIamRole && <IamRoles type={type} />}
<Row htmlFor={`${type}--number`} label="Instances">
<Connect field={toKey(type, NUMBER_OF_INSTANCES)}>
<NumberInput className="wiz-super-short-input" id={`${type}--number`} min="1" max={max} />
Expand Down
4 changes: 4 additions & 0 deletions installer/frontend/components/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import { Footer } from './footer';

const downloadState = (state) => {
const toSave = savable(state);

// Extra field data doesn't need to be saved
_.unset(toSave, 'clusterConfig.extra');

const saved = JSON.stringify(toSave, null, 2);
const stateBlob = new Blob([saved], {type: 'application/json'});
saveAs(stateBlob, 'tectonic.progress');
Expand Down
4 changes: 2 additions & 2 deletions installer/frontend/components/etcd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const fields = [
new Field(ETCD_OPTION, {
default: ETCD_OPTIONS.PROVISIONED,
}),
makeNodeForm(AWS_ETCDS, value => one2Nine(value) || validate.isOdd(value), {
makeNodeForm(AWS_ETCDS, false, value => one2Nine(value) || validate.isOdd(value), {
dependencies: [ETCD_OPTION],
ignoreWhen: cc => cc[ETCD_OPTION] !== ETCD_OPTIONS.PROVISIONED,
}),
Expand Down Expand Up @@ -107,7 +107,7 @@ export const Etcd = connect(({clusterConfig}) => ({
{isAWS && etcdOption === ETCD_OPTIONS.PROVISIONED && <hr />}
{isAWS && etcdOption === ETCD_OPTIONS.PROVISIONED &&
<div className="row form-group col-xs-12">
<DefineNode type={AWS_ETCDS} max={9} />
<DefineNode type={AWS_ETCDS} max={9} withIamRole={false} />
</div>
}
</div>
Expand Down
25 changes: 24 additions & 1 deletion installer/frontend/components/make-node-form.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import _ from 'lodash';

import * as awsActions from '../aws-actions';
import {
AWS_CREDS,
IAM_ROLE,
IAM_ROLE_CREATE_OPTION,
INSTANCE_TYPE,
NUMBER_OF_INSTANCES,
STORAGE_IOPS,
Expand All @@ -13,7 +17,18 @@ import { validate } from '../validate';

const toKey = (name, field) => `${name}-${field}`;

export const makeNodeForm = (name, instanceValidator = validate.int({min: 1, max: 999}), opts) => {
// Use this single dummy form / field to trigger loading the IAM roles list. Then IAM role fields can set this as their
// dependency, which avoids triggering a separate API request for each field.
new Form('DUMMY_NODE_FORM', [
new Field(IAM_ROLE, {
default: 'DUMMY_VALUE',
name: IAM_ROLE,
dependencies: [AWS_CREDS],
getExtraStuff: (dispatch, isNow) => dispatch(awsActions.getIamRoles(null, null, isNow)),
}),
]);

export const makeNodeForm = (name, withIamRole = true, instanceValidator = validate.int({min: 1, max: 999}), opts) => {
const storageType = toKey(name, STORAGE_TYPE);

// all fields must have a unique name!
Expand Down Expand Up @@ -47,6 +62,14 @@ export const makeNodeForm = (name, instanceValidator = validate.int({min: 1, max
}),
];

if (withIamRole) {
fields.unshift(new Field(toKey(name, IAM_ROLE), {
default: IAM_ROLE_CREATE_OPTION,
name: IAM_ROLE,
dependencies: [IAM_ROLE],
}));
}

const validator = (data) => {
const type = data[STORAGE_TYPE];
const size = data[STORAGE_SIZE_IN_GIB];
Expand Down
1 change: 1 addition & 0 deletions installer/frontend/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const DEFAULT_AWS = {
availableVpcs: UNLOADED_AWS_VALUE,
availableVpcSubnets: UNLOADED_AWS_VALUE,
availableSsh: UNLOADED_AWS_VALUE,
availableIamRoles: UNLOADED_AWS_VALUE,
availableRegions: UNLOADED_AWS_VALUE,
availableKms: UNLOADED_AWS_VALUE,
availableR53Zones: UNLOADED_AWS_VALUE,
Expand Down
11 changes: 6 additions & 5 deletions installer/glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions installer/glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import:
- aws/credentials
- aws/session
- service/ec2
- service/iam
- package: github.com/go-ini/ini
version: 6e4869b434bd001f6983749881c7ead3545887d8
- package: github.com/jmespath/go-jmespath
Expand Down
Loading

0 comments on commit 97e47e6

Please sign in to comment.