Skip to content

Commit

Permalink
Merge pull request #630 from BIX-Digital/bugfix/preflight-check-opens…
Browse files Browse the repository at this point in the history
…hift-project-key-is-missing

adds openshift project key preflight check
  • Loading branch information
stitakis authored Nov 17, 2020
2 parents 2fb2661 + a69c439 commit ce173ef
Show file tree
Hide file tree
Showing 16 changed files with 627 additions and 4 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ dependencies {
implementation 'org.springframework.security:spring-security-jwt:1.1.0.RELEASE'
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.4.0.RELEASE'

implementation(group: 'com.openshift', name: 'openshift-restclient-java', version: '9.0.0.Final') {
exclude(group: 'org.slf4j', module: 'slf4j-api:')
exclude(group: 'org.slf4j', module: 'slf4j-log4j12')
}

// azure ad
implementation('com.microsoft.azure:azure-active-directory-spring-boot-starter:2.2.2')

Expand Down
15 changes: 15 additions & 0 deletions docs/modules/provisioning-app/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,21 @@ jira.admin_user=jira_admin
Note: if the pair of properties is not defined for a third party tool, the logged in user's credentials are used to authenticate against the application.
The credentials are read by caling the method _getUserName_ and _getUserPassword_ from https://github.com/opendevstack/ods-provisioning-app/blob/master/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter[IODSAuthnzAdapter]]. See also implementation of _org.opendevstack.provision.services.BaseServiceAdapter#authenticatedCall()_

=== Other configuration

To adapt the provisioning app to your infrastructure following properties will help you to disable some adapters/services.

To disable the confluence adapter you can add this property to the application properties:
```
adapters.confluence.enabled=false
```

The Openshift Service currently is used to verify that a project key does not exists in the cluster before provisioning a project.
If you need to disable it, you can add this property to the application properties:
```
services.openshift.enabled=false
```

== FAQ

. Where is the provision app deployed? +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.opendevstack.provision.config;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.opendevstack.provision.services.openshift.OpenshiftClient;
import org.opendevstack.provision.services.openshift.OpenshiftService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;

/** @author Sebastian Titakis */
@Configuration
@ConditionalOnProperty(
name = "services.openshift.enabled",
havingValue = "true",
matchIfMissing = true)
public class OpenshiftServiceConfig {

private static final Logger logger = LoggerFactory.getLogger(OpenshiftServiceConfig.class);

public static final String DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE =
"file:///var/run/secrets/kubernetes.io/serviceaccount/token";

@Value(
"${openshift.provisioning-app.service-account.file:"
+ DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE
+ "}")
private String ocTokenResourceFile;

@Value("${openshift.provisioning-app.service-account.token:}")
private String ocToken;

@Value("${openshift.api.uri}")
private String openshiftApiUri;

@Autowired private ResourceLoader resourceLoader;

@Bean
public OpenshiftService openshiftService(OpenshiftClient openshiftClient) {
return new OpenshiftService(openshiftClient);
}

@Bean
public OpenshiftClient openshiftClient() {

if (null != ocToken && !ocToken.isEmpty()) {
logger.info(
"Found oc token configured in property 'openshift.provisioning-app.service-account.token'. Using it to log into to openshift! [openshift.api.uri={}]",
openshiftApiUri);

return create(openshiftApiUri, ocToken);

} else if (null != ocTokenResourceFile) {

if (!ocTokenResourceFile.equals(DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE)) {
logger.info(
"Found oc service account file configured in property 'openshift.provisioning-app.service-account.file'. Using it to log into to openshift! [openshift.api.uri={}, serviceAccountTokenFile={}]",
openshiftApiUri,
ocTokenResourceFile);
} else {
logger.info(
"Using default service account file to connect to openshift! [openshift.api.uri={}, serviceAccountTokenFile={}]",
openshiftApiUri,
DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE);
}

Resource resource = resourceLoader.getResource(ocTokenResourceFile);

if (!resource.exists()) {
throw new RuntimeException(
"Cannot load oc token from file because file does not exists! [file="
+ ocTokenResourceFile
+ "]");
}

return create(openshiftApiUri, resource);

} else {
throw new RuntimeException("This should never happens! Ask developers to take a look!");
}
}

private OpenshiftClient create(String openshiftApiUri, Resource token) {
Assert.notNull(token, "Parameter 'token' is null!");

try {
if (!token.exists()) {
throw new RuntimeException(
String.format(
"File with oc token does not exists! [file=%s]!", token.getURI().toString()));
}

String ocToken = new String(Files.readAllBytes(Path.of(token.getURI())));

return create(openshiftApiUri, ocToken);

} catch (IOException ex) {
logger.error("Failed to create openshift service!", ex);
throw new RuntimeException(ex);
}
}

private OpenshiftClient create(String openshiftApiUri, String token) {
Assert.notNull(openshiftApiUri, "Parameter 'openshiftApiUri' is null!");
Assert.notNull(token, "Parameter 'token' is null!");

return new OpenshiftClient(openshiftApiUri, token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.opendevstack.provision.model.jenkins.Job;
import org.opendevstack.provision.services.MailAdapter;
import org.opendevstack.provision.services.StorageAdapter;
import org.opendevstack.provision.services.openshift.OpenshiftService;
import org.opendevstack.provision.storage.IStorage;
import org.opendevstack.provision.util.exception.ProjectAlreadyExistsException;
import org.slf4j.Logger;
Expand Down Expand Up @@ -87,6 +88,9 @@ public class ProjectApiController {
@Autowired(required = false)
private ICollaborationAdapter confluenceAdapter;

@Autowired(required = false)
private OpenshiftService openshiftService;

@Autowired private ISCMAdapter bitbucketAdapter;
@Autowired private IJobExecutionAdapter jenkinsPipelineAdapter;
@Autowired private MailAdapter mailAdapter;
Expand Down Expand Up @@ -117,6 +121,9 @@ public class ProjectApiController {
@Value("${adapters.confluence.enabled:true}")
private boolean confluenceAdapterEnable;

@Value("${services.openshift.enabled:true}")
private boolean openshiftServiceEnable;

@PostConstruct
public void postConstruct() {
logger.info(
Expand Down Expand Up @@ -338,6 +345,10 @@ private List<CheckPreconditionFailure> checkPreconditions(OpenProjectData newPro

if (newProject.platformRuntime) {
results.addAll(bitbucketAdapter.checkCreateProjectPreconditions(newProject));

if (isOpenshiftServiceEnable() && null != openshiftService) {
results.addAll(openshiftService.checkCreateProjectPreconditions(newProject));
}
}

if (results.isEmpty()) {
Expand Down Expand Up @@ -958,6 +969,14 @@ public IStorage getDirectStorage() {
return directStorage;
}

public boolean isOpenshiftServiceEnable() {
return openshiftServiceEnable;
}

public void setOpenshiftServiceEnable(boolean openshiftServiceEnable) {
this.openshiftServiceEnable = openshiftServiceEnable;
}

public boolean isConfluenceAdapterEnable() {
return confluenceAdapterEnable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.opendevstack.provision.services.openshift;

import com.openshift.restclient.ClientBuilder;
import com.openshift.restclient.IClient;
import com.openshift.restclient.model.IResource;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/** @author Sebastian Titakis */
public class OpenshiftClient {

private final IClient iClient;

private final String url;

public OpenshiftClient(String url, String token) {
this(url, new ClientBuilder(url).usingToken(token).build());
}

public OpenshiftClient(String url, IClient ocClient) {
this.url = url;
this.iClient = ocClient;
}

public Set<String> projects() {

List<IResource> projectResource = iClient.list("Project");

return projectResource.stream().map(project -> project.getName()).collect(Collectors.toSet());
}

public String getUrl() {
return url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.opendevstack.provision.services.openshift;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.opendevstack.provision.adapter.exception.AdapterException;
import org.opendevstack.provision.adapter.exception.CreateProjectPreconditionException;
import org.opendevstack.provision.controller.CheckPreconditionFailure;
import org.opendevstack.provision.model.OpenProjectData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/** @author Sebastian Titakis */
public class OpenshiftService {

private static final Logger logger = LoggerFactory.getLogger(OpenshiftService.class);

public static final String SERVICE_NAME = "openshiftService";

private OpenshiftClient openshiftClient;

public OpenshiftService(OpenshiftClient openshiftClient) {
this.openshiftClient = openshiftClient;
}

public List<CheckPreconditionFailure> checkCreateProjectPreconditions(OpenProjectData newProject)
throws CreateProjectPreconditionException {

try {
Assert.notNull(newProject, "Parameter 'newProject' is null!");
Assert.notNull(
newProject.projectKey, "Properties 'projectKey' of parameter 'newProject' is null!");

logger.info("checking create project preconditions for project '{}'!", newProject.projectKey);

List<CheckPreconditionFailure> preconditionFailures =
createProjectKeyExistsCheck(newProject.projectKey);

logger.info(
"done with check create project preconditions for project '{}'!", newProject.projectKey);

return preconditionFailures;

} catch (AdapterException e) {
throw new CreateProjectPreconditionException(SERVICE_NAME, newProject.projectKey, e);
} catch (Exception e) {
String message =
String.format(
"Unexpected error when checking precondition for creation of project '%s'",
newProject.projectKey);
logger.error(message, e);
throw new CreateProjectPreconditionException(SERVICE_NAME, newProject.projectKey, message);
}
}

public List<CheckPreconditionFailure> createProjectKeyExistsCheck(String projectKey) {

try {
logger.info("Checking if ODS project '{}-*' exists in openshift!", projectKey);

List<CheckPreconditionFailure> preconditionFailures = new ArrayList<>();

Set<String> projects = openshiftClient.projects();
List<String> existingProjects =
projects.stream()
.filter(s -> s.toLowerCase().startsWith(projectKey.toLowerCase() + "-"))
.collect(Collectors.toList());

if (existingProjects.size() > 0) {
String message =
String.format(
"Project name (namespace) with prefix '%s' already exists in '%s'! [existingProjects=%s]",
projectKey, SERVICE_NAME, Arrays.asList(existingProjects.toArray()));
preconditionFailures.add(CheckPreconditionFailure.getProjectExistsInstance(message));
}

return preconditionFailures;

} catch (Exception ex) {
throw new AdapterException(ex);
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application-odsbox.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ bitbucket.opendevstack.project=opendevstack

# openshift properties
openshift.apps.basedomain=.ocp.odsbox.lan
openshift.provisioning-app.service-account-file-path=file:///var/run/secrets/kubernetes.io/serviceaccount/token
openshift.api.uri=https://api.odsbox.lan:8443
openshift.console.uri=https://ocp.odsbox.lan:8443/console/project/

# webhook proxy used to run create project jenkinsfile
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ ods.git-ref=master
openshift.project.upgrade=false
openshift.apps.basedomain=.192.168.56.101.nip.io
openshift.console.uri=https://192.168.56.101:8443/console
openshift.api.uri=https://192.168.56.101:8443

#openshift project name patterns
openshift.test.project.name.pattern=%s/project/%s-test
openshift.dev.project.name.pattern=%s/project/%s-dev
Expand Down
Loading

0 comments on commit ce173ef

Please sign in to comment.